Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AIChat refactor to support standalone and persistent conversations #24921

Merged
merged 32 commits into from
Sep 20, 2024

Conversation

petemill
Copy link
Member

@petemill petemill commented Jul 30, 2024

This PR refactors the ai_chat component on the browser-side and in the UI to allow for

  • Multiple conversations
  • Conversations to be independent of content (WebContents/Tab)

This is achieved with the following architecture (more details at brave/brave-browser#31623):

  • AIChatService owns all the conversation metadata
  • ConversationHandler owns the data for conversations (chat messages, suggested questions) and performs all the operations via the EngineConsumer (which is unchanged).
  • Content (title, url, page content) is attached to a conversation via an AssociatedContentDelegate. This is implemented by AssociatedContentDriver (and derives AIChatTabHelper for WebUI and an iOS equivalent). When a conversation is related to some content, one of these is attached. Standalone conversations do not have associated content and therefore this delegate is null.

AIChatService -> ConversationHandler -> AssociatedContentDelegate [AssociatedContentDriver [AIChatTabHelper]]

  • Conversations which had associated content, but the content is not live anymore (i.e. the Tab was navigated away or closed), receive an AssociatedArchiveContent, which keeps the url and last title and page content so that the conversation can be continued.

Future TODO (other PRs)

Resolves brave/brave-browser#31623

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

@petemill petemill force-pushed the aichat-services branch 2 times, most recently from 904d9c5 to 90e5b70 Compare July 30, 2024 22:23
@petemill petemill self-assigned this Aug 4, 2024
@petemill petemill force-pushed the aichat-services branch 4 times, most recently from 4f4d6a2 to d7f1476 Compare August 6, 2024 22:34
std::unique_ptr<KeyedService>
AIChatServiceFactory::BuildServiceInstanceForBrowserContext(
content::BrowserContext* context) const {
auto skus_service_getter = base::BindRepeating(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whys is this passing a getter instead of the skus service itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is just moved. We should fix that, but not here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we create follow-up issues to track this stuff?


content::BrowserContext* AIChatServiceFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
if (!Profile::FromBrowserContext(context)->IsRegularProfile() ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use ProfileSelections::Builder().WithRegular(ProfileSelection::kOwnInstance)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just merged another PR which adds https://github.com/brave/brave-core/blob/master/browser/ai_chat/ai_chat_utils.cc#L15-L20:

bool IsAllowedForContext(content::BrowserContext* context,
                         bool check_policy /*=true*/) {
  Profile* profile = Profile::FromBrowserContext(context);
  return profile->IsRegularProfile() && ai_chat::features::IsAIChatEnabled() &&
         (!check_policy || IsAIChatEnabled(profile->GetPrefs()));
}

That's fine to stay that way? I was going to update here to use this new util, but we'd rather use the ProfileSelections path instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think use ProfileSelections in factory constructor to exclude specifically checking regular profile here and then we still need to use/update ai_chat::IsAllowedForContext for other checks so we would have consistent checking among different places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be .WithRegular(ProfileSelection::kOriginalOnly) to make sure the service isn't built for incognito? Don't we in-fact want the default behavior of ProfileKeyedServiceFactory?

Example of a service that does not exist in OTR (default behavior)

return std::make_unique<AIChatService>(
ModelServiceFactory::GetForBrowserContext(context),
std::move(credential_manager), user_prefs::UserPrefs::Get(context),
(g_brave_browser_process->process_misc_metrics())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the parens around this? @DJAndries have you had a change to do anything with this yet to make it less clunky to access?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the ternary? formatter isn't complaining, but happy to change...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't be needed here

@github-actions github-actions bot added the CI/storybook-url Deploy storybook and provide a unique URL for each build label Aug 27, 2024
@petemill petemill force-pushed the aichat-services branch 2 times, most recently from 1e5ceda to a8143f6 Compare August 31, 2024 04:42
void ConversationHandler::OnGetStagedEntriesFromContent(
const std::optional<SearchQuerySummary>& search_query_summary) {
// Check if all requirements are still met.
if (!search_query_summary || !chat_history_.empty() ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also check user opt-in here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's being checked in MaybeFetchOrClearContentStagedConversation and there's no way currently to opt-out whilst that's happening, but I'll add the check anyway.

@petemill petemill force-pushed the aichat-services branch 7 times, most recently from c041d30 to e8ce0d2 Compare September 11, 2024 08:14
@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@petemill petemill marked this pull request as ready for review September 11, 2024 16:23
@petemill petemill requested a review from a team as a code owner September 11, 2024 16:23
@petemill
Copy link
Member Author

Everything passed CI except iOS due to unrelated test runner fix in #25669. Rebased to get that passing.

@@ -60,10 +62,16 @@ void ChromeAutocompleteProviderClient::OpenLeo(const std::u16string& query) {
browser->tab_strip_model()->GetActiveWebContents());
DCHECK(chat_tab_helper);

auto* conversation_handler =
ai_chat::AIChatServiceFactory::GetForBrowserContext(profile_)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we need a null check here before calling GetOrCreateConversationHandlerForContent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was covered by

  CHECK(profile_->IsRegularProfile());

and the feature not being added to the omnibox suggestions in incognito profiles, but I agree it's better to check the service - added in 5f89085

} else {
auto* browser = GetBrowser();
if (!browser) {
VLOG(1) << "Can't get browser";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this kind of logging should be removed before merge

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not new

Copy link
Member

@darkdh darkdh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai-chat cpp side looks good to me. Unit tests for new codes and moved codes are nicely done with great coverage!

conversation_observations_{this};
};

class MockAIChatCredentialManager : public AIChatCredentialManager {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, we will do this in followup.

Comment on lines +264 to +289
class ConversationHandlerUnitTest_OptedOut
: public ConversationHandlerUnitTest {
public:
ConversationHandlerUnitTest_OptedOut() { is_opted_in_ = false; }
};

class ConversationHandlerUnitTest_NoAssociatedContent
: public ConversationHandlerUnitTest {
public:
ConversationHandlerUnitTest_NoAssociatedContent() {
has_associated_content_ = false;
}
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, we will do this in followup.

auto selected_text = base::UTF16ToUTF8(params_.selection_text);

if (rewrite_in_place) {
source_web_contents_->SetUserData(kAIChatRewriteDataKey,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this would be better handled inside the tab helper so it gets cleared when the page navigates? I think we have way too much logic for aichat embedded in RenderViewContextMenu here that should be encapsulated within aichat

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also are there tests for this somewhere? I'm not seeing anything, but searching github PRs sucks so I could be missing something

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, I found them

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure but this is not added in this PR cc @yrliou

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we create follow-up issues to track this stuff?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

Copy link
Member

@yrliou yrliou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only outstanding comment is the issue of putting ai_chat_service_factory in browser target, I'll do an immediate follow-up to fix it.

@petemill
Copy link
Member Author

I made this issue to track findings of future code improvements brave/brave-browser#41171

@petemill petemill enabled auto-merge (squash) September 20, 2024 18:55
@mkarolin mkarolin merged commit a0424cf into master Sep 20, 2024
16 of 17 checks passed
@mkarolin mkarolin deleted the aichat-services branch September 20, 2024 19:34
@github-actions github-actions bot added this to the 1.72.x - Nightly milestone Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/storybook-url Deploy storybook and provide a unique URL for each build
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

AI Chat service refactor to support multiple-conversation, persistence, and standalone page