From b8e2daec67ecc393396517fc97506e79e2eec555 Mon Sep 17 00:00:00 2001 From: Pete Miller Date: Mon, 20 Nov 2023 23:29:04 -0800 Subject: [PATCH] AI Chat: Suggested questions becomes on-demand instead of automatic There is no longer a need for the related preference. --- .../settings/BraveLeoResetPreference.java | 2 - .../api/settings_private/brave_prefs_util.cc | 2 - .../brave_pref_service_incognito_allowlist.cc | 1 - .../brave_leo_assistant_page.html | 4 - .../webui/ai_chat/ai_chat_ui_page_handler.cc | 24 ++-- .../webui/ai_chat/ai_chat_ui_page_handler.h | 4 +- .../brave_settings_leo_assistant_handler.cc | 2 - ...ave_settings_localized_strings_provider.cc | 2 - components/ai_chat/core/browser/constants.cc | 7 +- .../core/browser/conversation_driver.cc | 109 +++++++----------- .../core/browser/conversation_driver.h | 17 +-- .../ai_chat/core/common/mojom/ai_chat.mojom | 23 ++-- components/ai_chat/core/common/pref_names.cc | 18 ++- components/ai_chat/core/common/pref_names.h | 6 +- .../components/conversation_list/index.tsx | 43 ++++++- .../components/feature_button_menu/index.tsx | 27 +---- .../resources/page/components/main/index.tsx | 7 -- .../prompt_auto_suggestion/index.tsx | 43 ------- .../prompt_auto_suggestion/style.module.scss | 37 ------ .../ai_chat/resources/page/state/context.ts | 8 +- .../page/state/data-context-provider.tsx | 21 +--- .../page/stories/components_panel.tsx | 11 +- .../ai_chat/resources/page/stories/locale.ts | 7 +- components/resources/ai_chat_ui_strings.grdp | 19 +-- 24 files changed, 147 insertions(+), 297 deletions(-) delete mode 100644 components/ai_chat/resources/page/components/prompt_auto_suggestion/index.tsx delete mode 100644 components/ai_chat/resources/page/components/prompt_auto_suggestion/style.module.scss diff --git a/android/java/org/chromium/chrome/browser/settings/BraveLeoResetPreference.java b/android/java/org/chromium/chrome/browser/settings/BraveLeoResetPreference.java index ff188961359..6bb5a7dbf3a 100644 --- a/android/java/org/chromium/chrome/browser/settings/BraveLeoResetPreference.java +++ b/android/java/org/chromium/chrome/browser/settings/BraveLeoResetPreference.java @@ -74,8 +74,6 @@ public class BraveLeoResetPreference } UserPrefs.get(profile) .clearPref(BravePref.LAST_ACCEPTED_DISCLAIMER); - UserPrefs.get(profile) - .setBoolean(BravePref.BRAVE_CHAT_AUTO_GENERATE_QUESTIONS, false); } else { dialog.dismiss(); } diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index 2e9e91df14e..22f874976a8 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -284,8 +284,6 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { // Leo Assistant pref #if BUILDFLAG(ENABLE_AI_CHAT) - (*s_brave_allowlist)[ai_chat::prefs::kBraveChatAutoGenerateQuestions] = - settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_brave_allowlist)[ai_chat::prefs::kBraveChatAutocompleteProviderEnabled] = settings_api::PrefType::PREF_TYPE_BOOLEAN; (*s_brave_allowlist)[ai_chat::prefs::kDefaultModelKey] = diff --git a/browser/prefs/brave_pref_service_incognito_allowlist.cc b/browser/prefs/brave_pref_service_incognito_allowlist.cc index 975930f8a04..a2e505780b9 100644 --- a/browser/prefs/brave_pref_service_incognito_allowlist.cc +++ b/browser/prefs/brave_pref_service_incognito_allowlist.cc @@ -39,7 +39,6 @@ const std::vector& GetBravePersistentPrefNames() { #endif #if BUILDFLAG(ENABLE_AI_CHAT) ai_chat::prefs::kLastAcceptedDisclaimer, - ai_chat::prefs::kBraveChatAutoGenerateQuestions, ai_chat::prefs::kBraveChatAutocompleteProviderEnabled, #endif // BUILDFLAG(ENABLE_AI_CHAT) }); diff --git a/browser/resources/settings/brave_leo_assistant_page/brave_leo_assistant_page.html b/browser/resources/settings/brave_leo_assistant_page/brave_leo_assistant_page.html index f5858f1fde8..b01ed44372b 100644 --- a/browser/resources/settings/brave_leo_assistant_page/brave_leo_assistant_page.html +++ b/browser/resources/settings/brave_leo_assistant_page/brave_leo_assistant_page.html @@ -47,10 +47,6 @@ You can obtain one at https://mozilla.org/MPL/2.0/. --> label="$i18n{braveLeoAssistantShowIconOnToolbarLabel}" on-settings-boolean-control-change="onLeoAssistantShowOnToolbarChange_"> - -
$i18n{braveLeoAssistantModelSelectionLabel}
diff --git a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc index 9a90c776132..6647ab11483 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc +++ b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc @@ -142,15 +142,13 @@ void AIChatUIPageHandler::GetConversationHistory( void AIChatUIPageHandler::GetSuggestedQuestions( GetSuggestedQuestionsCallback callback) { if (!active_chat_tab_helper_) { - std::move(callback).Run({}, false, - mojom::AutoGenerateQuestionsPref::Disabled); + std::move(callback).Run({}, mojom::SuggestionGenerationStatus::None); return; } - bool can_generate; - mojom::AutoGenerateQuestionsPref auto_generate; - std::move(callback).Run(active_chat_tab_helper_->GetSuggestedQuestions( - can_generate, auto_generate), - can_generate, auto_generate); + mojom::SuggestionGenerationStatus suggestion_status; + std::move(callback).Run( + active_chat_tab_helper_->GetSuggestedQuestions(suggestion_status), + suggestion_status); } void AIChatUIPageHandler::GenerateQuestions() { @@ -159,11 +157,6 @@ void AIChatUIPageHandler::GenerateQuestions() { } } -void AIChatUIPageHandler::SetAutoGenerateQuestions(bool value) { - profile_->GetPrefs()->SetBoolean( - ai_chat::prefs::kBraveChatAutoGenerateQuestions, value); -} - void AIChatUIPageHandler::GetSiteInfo(GetSiteInfoCallback callback) { auto site_info = BuildSiteInfo(); const bool is_fetching_content = active_chat_tab_helper_->HasPageContent() == @@ -357,11 +350,10 @@ void AIChatUIPageHandler::OnModelChanged(const std::string& model_key) { void AIChatUIPageHandler::OnSuggestedQuestionsChanged( std::vector questions, - bool has_generated, - mojom::AutoGenerateQuestionsPref auto_generate) { + mojom::SuggestionGenerationStatus suggestion_generation_status) { if (page_.is_bound()) { - page_->OnSuggestedQuestionsChanged(std::move(questions), has_generated, - auto_generate); + page_->OnSuggestedQuestionsChanged(std::move(questions), + suggestion_generation_status); } } diff --git a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h index 346d3f66144..244b0231476 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h +++ b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h @@ -58,7 +58,6 @@ class AIChatUIPageHandler : public ai_chat::mojom::PageHandler, void MarkAgreementAccepted() override; void GetSuggestedQuestions(GetSuggestedQuestionsCallback callback) override; void GenerateQuestions() override; - void SetAutoGenerateQuestions(bool can_auto_generate_questions) override; void GetSiteInfo(GetSiteInfoCallback callback) override; void OpenBraveLeoSettings() override; void OpenURL(const GURL& url) override; @@ -88,8 +87,7 @@ class AIChatUIPageHandler : public ai_chat::mojom::PageHandler, void OnModelChanged(const std::string& model_key) override; void OnSuggestedQuestionsChanged( std::vector questions, - bool has_generated, - mojom::AutoGenerateQuestionsPref auto_generate) override; + mojom::SuggestionGenerationStatus suggestion_generation_status) override; void OnFaviconImageDataChanged() override; void OnPageHasContent(bool page_contents_is_truncated) override; diff --git a/browser/ui/webui/settings/brave_settings_leo_assistant_handler.cc b/browser/ui/webui/settings/brave_settings_leo_assistant_handler.cc index 16afed70e59..fa1725c5f9e 100644 --- a/browser/ui/webui/settings/brave_settings_leo_assistant_handler.cc +++ b/browser/ui/webui/settings/brave_settings_leo_assistant_handler.cc @@ -142,8 +142,6 @@ void BraveLeoAssistantHandler::HandleResetLeoData( ShowLeoAssistantIconVisibleIfNot(service); profile_->GetPrefs()->ClearPref(ai_chat::prefs::kLastAcceptedDisclaimer); - profile_->GetPrefs()->SetBoolean( - ai_chat::prefs::kBraveChatAutoGenerateQuestions, false); AllowJavascript(); } diff --git a/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc b/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc index bb1198fa640..94a14dbc706 100644 --- a/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc +++ b/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc @@ -383,8 +383,6 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source, {"leoAssistant", IDS_SETTINGS_LEO_ASSISTANT}, {"braveLeoAssistantShowIconOnToolbarLabel", IDS_SETTINGS_LEO_ASSISTANT_SHOW_ICON_ON_TOOLBAR_LABEL}, - {"braveLeoAssistantShowSuggestedPromptsLabel", - IDS_SETTINGS_LEO_ASSISTANT_SHOW_SUGGESTED_PROMPTS_LABEL}, {"braveLeoAssistantResetAndClearDataLabel", IDS_SETTINGS_LEO_ASSISTANT_RESET_AND_CLEAR_DATA_LABEL}, {"braveLeoAssistantResetAndClearDataConfirmationText", diff --git a/components/ai_chat/core/browser/constants.cc b/components/ai_chat/core/browser/constants.cc index 75f4cd8849e..a3ad6295f15 100644 --- a/components/ai_chat/core/browser/constants.cc +++ b/components/ai_chat/core/browser/constants.cc @@ -19,10 +19,6 @@ base::span GetLocalizedStrings() { {"aboutDescription_2", IDS_CHAT_UI_ABOUT_DESCRIPTION_2}, {"aboutDescription_3", IDS_CHAT_UI_ABOUT_DESCRIPTION_3}, {"placeholderLabel", IDS_CHAT_UI_PLACEHOLDER_LABEL}, - {"enableQuestionsTitle", IDS_CHAT_UI_ENABLE_QUESTIONS_TITLE}, - {"enableQuestionsDesc", IDS_CHAT_UI_ENABLE_QUESTIONS_DESC}, - {"enableQuestionsButtonLabel", IDS_CHAT_UI_ENABLE_QUESTIONS_BUTTON_LABEL}, - {"noThanksButtonLabel", IDS_CHAT_UI_NO_THANKS_BUTTON_LABEL}, {"pageContentWarning", IDS_CHAT_UI_PAGE_CONTENT_WARNING}, {"errorNetworkLabel", IDS_CHAT_UI_ERROR_NETWORK}, {"errorRateLimit", IDS_CHAT_UI_ERROR_RATE_LIMIT}, @@ -35,11 +31,12 @@ base::span GetLocalizedStrings() { {"modelNameSyntax", IDS_CHAT_UI_MODEL_NAME_SYNTAX}, {"modelCategory-chat", IDS_CHAT_UI_MODEL_CATEGORY_CHAT}, {"menuNewChat", IDS_CHAT_UI_MENU_NEW_CHAT}, - {"menuSuggestedQuestions", IDS_CHAT_UI_MENU_SUGGESTED_QUESTIONS}, {"menuGoPremium", IDS_CHAT_UI_MENU_GO_PREMIUM}, {"menuManageSubscription", IDS_CHAT_UI_MENU_MANAGE_SUBSCRIPTION}, {"menuSettings", IDS_CHAT_UI_MENU_SETTINGS}, {"menuTitleModels", IDS_CHAT_UI_MENU_TITLE_MODELS}, + {"suggestionsTitle", IDS_CHAT_UI_SUGGESTIONS_TITLE}, + {"suggestQuestionsLabel", IDS_CHAT_UI_SUGGEST_QUESTIONS_LABEL}, {"premiumButtonLabel", IDS_CHAT_UI_PREMIUM_BUTTON_LABEL}, {"rateLimitReachedTitle", IDS_CHAT_UI_RATE_LIMIT_REACHED_TITLE}, {"rateLimitReachedDesc", IDS_CHAT_UI_RATE_LIMIT_REACHED_DESC}, diff --git a/components/ai_chat/core/browser/conversation_driver.cc b/components/ai_chat/core/browser/conversation_driver.cc index c055d98bcab..3038eb533e4 100644 --- a/components/ai_chat/core/browser/conversation_driver.cc +++ b/components/ai_chat/core/browser/conversation_driver.cc @@ -57,11 +57,6 @@ ConversationDriver::ConversationDriver(raw_ptr pref_service, prefs::kLastAcceptedDisclaimer, base::BindRepeating(&ConversationDriver::OnUserOptedIn, weak_ptr_factory_.GetWeakPtr())); - pref_change_registrar_.Add( - prefs::kBraveChatAutoGenerateQuestions, - base::BindRepeating( - &ConversationDriver::OnPermissionChangedAutoGenerateQuestions, - weak_ptr_factory_.GetWeakPtr())); // Engines and model names are selectable per conversation, not static. // Start with default from pref value but only if user set. We can't rely on @@ -145,7 +140,6 @@ void ConversationDriver::OnConversationActiveChanged(bool is_conversation_active return; } MaybeGeneratePageText(); - MaybeGenerateQuestions(); } void ConversationDriver::InitEngine() { @@ -209,10 +203,6 @@ void ConversationDriver::OnUserOptedIn() { } } -void ConversationDriver::OnPermissionChangedAutoGenerateQuestions() { - MaybeGenerateQuestions(); -} - void ConversationDriver::AddToConversationHistory(mojom::ConversationTurn turn) { chat_history_.push_back(std::move(turn)); @@ -322,18 +312,6 @@ void ConversationDriver::MaybeGeneratePageText() { } } -void ConversationDriver::MaybeGenerateQuestions() { - // Automatically fetch questions related to page content, if allowed - bool can_auto_fetch_questions = - HasUserOptedIn() && is_conversation_active_ && - pref_service_->GetBoolean( - ai_chat::prefs::kBraveChatAutoGenerateQuestions) && - !article_text_.empty() && (suggested_questions_.size() <= 1); - if (can_auto_fetch_questions) { - GenerateQuestions(); - } -} - void ConversationDriver::OnPageContentRetrieved(int64_t navigation_id, std::string contents_text, bool is_video) { @@ -356,30 +334,30 @@ void ConversationDriver::OnPageContentRetrieved(int64_t navigation_id, OnPageHasContentChanged(IsPageContentsTruncated()); // Now that we have article text, we can suggest to summarize it - DCHECK(suggested_questions_.empty()) + DCHECK(suggestions_.empty()) << "Expected suggested questions to be clear when there has been no" - << " previous text content but there were " << suggested_questions_.size() - << " suggested questions: " - << base::JoinString(suggested_questions_, ", "); + << " previous text content but there were " << suggestions_.size() + << " suggested questions: " << base::JoinString(suggestions_, ", "); // Now that we have content, we can provide a summary on-demand. Add that to // suggested questions. - suggested_questions_.emplace_back( + suggestions_.emplace_back( is_video_ ? l10n_util::GetStringUTF8(IDS_CHAT_UI_SUMMARIZE_VIDEO) : l10n_util::GetStringUTF8(IDS_CHAT_UI_SUMMARIZE_PAGE)); + suggestion_generation_status_ = + mojom::SuggestionGenerationStatus::CanGenerate; OnSuggestedQuestionsChanged(); - MaybeGenerateQuestions(); } void ConversationDriver::CleanUp() { chat_history_.clear(); article_text_.clear(); - suggested_questions_.clear(); + suggestions_.clear(); pending_request_.reset(); is_same_document_navigation_ = false; is_page_text_fetch_in_progress_ = false; is_request_in_progress_ = false; - has_generated_questions_ = false; + suggestion_generation_status_ = mojom::SuggestionGenerationStatus::None; should_page_content_be_disconnected_ = false; OnSuggestedQuestionsChanged(); SetAPIError(mojom::APIError::None); @@ -410,13 +388,10 @@ void ConversationDriver::SetSameDocumentNavigation(bool same_document_navigation } std::vector ConversationDriver::GetSuggestedQuestions( - bool& can_generate, - mojom::AutoGenerateQuestionsPref& auto_generate) { + mojom::SuggestionGenerationStatus& suggestion_status) { // Can we get suggested questions - can_generate = !has_generated_questions_ && !article_text_.empty(); - // Are we allowed to auto-generate - auto_generate = GetAutoGeneratePref(); - return suggested_questions_; + suggestion_status = suggestion_generation_status_; + return suggestions_; } PageContentAssociation ConversationDriver::HasPageContent() { @@ -455,23 +430,33 @@ void ConversationDriver::GenerateQuestions() { DVLOG(1) << __func__; // This function should not be presented in the UI if the user has not // opted-in yet. - DCHECK(HasUserOptedIn()); + if (!HasUserOptedIn()) { + NOTREACHED() << "GenerateQuestions should not be called before user is " + << "opted in to AI Chat"; + return; + } + // We're not expecting to call this if the UI is not active for this + // conversation. DCHECK(is_conversation_active_); + // We're not expecting to already have generated suggestions + DCHECK_LE(suggestions_.size(), 1u); + // Can't operate if we don't have an article text if (article_text_.empty()) { return; } - // Don't perform the operation more than once - if (suggested_questions_.size() > 1u) { + + if (suggestion_generation_status_ == + mojom::SuggestionGenerationStatus::IsGenerating || + suggestion_generation_status_ == + mojom::SuggestionGenerationStatus::HasGenerated) { + NOTREACHED() << "UI should not allow GenerateQuestions to be called more " + << "than once"; return; } - // Don't generate suggested questions if there's already on-going conversions - if (!chat_history_.empty()) { - return; - } - - has_generated_questions_ = true; + suggestion_generation_status_ = + mojom::SuggestionGenerationStatus::IsGenerating; OnSuggestedQuestionsChanged(); // Make API request for questions. // Do not call SetRequestInProgress, this progress @@ -494,11 +479,12 @@ void ConversationDriver::OnSuggestedQuestionsResponse( return; } - suggested_questions_.insert(suggested_questions_.end(), result.begin(), - result.end()); + suggestions_.insert(suggestions_.end(), result.begin(), result.end()); + suggestion_generation_status_ = + mojom::SuggestionGenerationStatus::HasGenerated; // Notify observers OnSuggestedQuestionsChanged(); - DVLOG(2) << "Got questions:" << base::JoinString(suggested_questions_, "\n"); + DVLOG(2) << "Got questions:" << base::JoinString(suggestions_, "\n"); } void ConversationDriver::MakeAPIRequestWithConversationHistoryUpdate( @@ -516,11 +502,10 @@ void ConversationDriver::MakeAPIRequestWithConversationHistoryUpdate( bool is_suggested_question = false; // If it's a suggested question, remove it - auto found_question_iter = - base::ranges::find(suggested_questions_, turn.text); - if (found_question_iter != suggested_questions_.end()) { + auto found_question_iter = base::ranges::find(suggestions_, turn.text); + if (found_question_iter != suggestions_.end()) { is_suggested_question = true; - suggested_questions_.erase(found_question_iter); + suggestions_.erase(found_question_iter); OnSuggestedQuestionsChanged(); } @@ -630,8 +615,8 @@ void ConversationDriver::OnEngineCompletionComplete( void ConversationDriver::OnSuggestedQuestionsChanged() { for (auto& obs : observers_) { - obs.OnSuggestedQuestionsChanged( - suggested_questions_, has_generated_questions_, GetAutoGeneratePref()); + obs.OnSuggestedQuestionsChanged(suggestions_, + suggestion_generation_status_); } } @@ -641,22 +626,6 @@ void ConversationDriver::OnPageHasContentChanged(bool page_contents_is_truncated } } -mojom::AutoGenerateQuestionsPref ConversationDriver::GetAutoGeneratePref() { - mojom::AutoGenerateQuestionsPref pref = - mojom::AutoGenerateQuestionsPref::Unset; - - const base::Value* auto_generate_value = pref_service_->GetUserPrefValue( - ai_chat::prefs::kBraveChatAutoGenerateQuestions); - - if (auto_generate_value) { - pref = (auto_generate_value->GetBool() - ? mojom::AutoGenerateQuestionsPref::Enabled - : mojom::AutoGenerateQuestionsPref::Disabled); - } - - return pref; -} - void ConversationDriver::SetAPIError(const mojom::APIError& error) { current_error_ = error; diff --git a/components/ai_chat/core/browser/conversation_driver.h b/components/ai_chat/core/browser/conversation_driver.h index 407d031997c..f0d132070cd 100644 --- a/components/ai_chat/core/browser/conversation_driver.h +++ b/components/ai_chat/core/browser/conversation_driver.h @@ -43,8 +43,7 @@ class ConversationDriver { virtual void OnModelChanged(const std::string& model_key) {} virtual void OnSuggestedQuestionsChanged( std::vector questions, - bool has_generated, - mojom::AutoGenerateQuestionsPref auto_generate) {} + mojom::SuggestionGenerationStatus suggestion_generation_status) {} virtual void OnFaviconImageDataChanged() {} virtual void OnPageHasContent(bool page_contents_is_truncated) {} }; @@ -78,8 +77,7 @@ class ConversationDriver { // are already generated, nothing will happen. void GenerateQuestions(); std::vector GetSuggestedQuestions( - bool& can_generate, - mojom::AutoGenerateQuestionsPref& auto_generate); + mojom::SuggestionGenerationStatus& suggestion_status); PageContentAssociation HasPageContent(); void DisconnectPageContents(); void ClearConversationHistory(); @@ -110,7 +108,6 @@ class ConversationDriver { void InitEngine(); bool HasUserOptedIn(); void OnUserOptedIn(); - void OnPermissionChangedAutoGenerateQuestions(); bool MaybePopPendingRequests(); void MaybeGenerateQuestions(); @@ -129,7 +126,6 @@ class ConversationDriver { mojom::PageHandler::GetPremiumStatusCallback parent_callback, mojom::PremiumStatus premium_status); - mojom::AutoGenerateQuestionsPref GetAutoGeneratePref(); void SetAPIError(const mojom::APIError& error); raw_ptr pref_service_; @@ -148,8 +144,13 @@ class ConversationDriver { bool is_conversation_active_ = false; bool is_page_text_fetch_in_progress_ = false; bool is_request_in_progress_ = false; - std::vector suggested_questions_; - bool has_generated_questions_ = false; + std::vector suggestions_; + // Keep track of whether we've generated suggested questions for the current + // context. We cannot rely on counting the questions in |suggested_questions_| + // since they get removed when used, or we might not have received any + // successfully. + mojom::SuggestionGenerationStatus suggestion_generation_status_ = + mojom::SuggestionGenerationStatus::None; bool is_video_ = false; bool should_page_content_be_disconnected_ = false; // Store the unique ID for each navigation so that diff --git a/components/ai_chat/core/common/mojom/ai_chat.mojom b/components/ai_chat/core/common/mojom/ai_chat.mojom index 89918a0e56e..7078c786e45 100644 --- a/components/ai_chat/core/common/mojom/ai_chat.mojom +++ b/components/ai_chat/core/common/mojom/ai_chat.mojom @@ -17,12 +17,6 @@ enum ConversationTurnVisibility { HIDDEN // does not get added to chat history, used as a flag internally }; -enum AutoGenerateQuestionsPref { - Unset, // User has not made a choice yet - Enabled, // User has chosen to enable auto-generation - Disabled // User has chosen not to enable auto-generation -}; - enum APIError { None, ConnectionIssue, @@ -47,6 +41,13 @@ enum PremiumStatus { ActiveDisconnected, }; +enum SuggestionGenerationStatus { + None, + CanGenerate, + IsGenerating, + HasGenerated, +}; + struct SiteInfo { string title; bool is_content_truncated; @@ -96,17 +97,17 @@ interface PageHandler { // Command to change the model for the Conversation to |model_name|. ChangeModel(string model_key); GetConversationHistory() => (array conversation_history); + // List of all suggested questions for the active tab in the page's Browser // window. GetSuggestedQuestions() => ( - array questions, bool can_generate, - AutoGenerateQuestionsPref auto_generate); + array questions, SuggestionGenerationStatus suggestion_status); // The browser should generate some questions and fire an event when they // are ready. GenerateQuestions(); + SubmitHumanConversationEntry(string input); MarkAgreementAccepted(); - SetAutoGenerateQuestions(bool can_auto_generate_questions); // Get associated page information. If there is none then |site_info| will be // |null|. If the reason for that is it's still being fetched, then // |is_fetching| will be |true|. @@ -141,9 +142,7 @@ interface ChatUIPage { // Provides the full list of questions for the active tab in the page's // Browser window. OnSuggestedQuestionsChanged( - array questions, - bool has_generated, - AutoGenerateQuestionsPref auto_generate); + array questions, SuggestionGenerationStatus status); // Associated page information has changed. If there is none then |site_info| // will be |null|. If the reason for that is it's still being fetched, then // |is_fetching| will be |true|. diff --git a/components/ai_chat/core/common/pref_names.cc b/components/ai_chat/core/common/pref_names.cc index 8b505b5ae1a..e67b052dc98 100644 --- a/components/ai_chat/core/common/pref_names.cc +++ b/components/ai_chat/core/common/pref_names.cc @@ -6,18 +6,34 @@ #include "brave/components/ai_chat/core/common/pref_names.h" #include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" namespace ai_chat::prefs { +namespace { + +inline constexpr char kObseleteBraveChatAutoGenerateQuestions[] = + "brave.ai_chat.auto_generate_questions"; + +} // namespace + void RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterTimePref(kLastAcceptedDisclaimer, {}); - registry->RegisterBooleanPref(kBraveChatAutoGenerateQuestions, false); registry->RegisterBooleanPref(kBraveChatAutocompleteProviderEnabled, true); registry->RegisterBooleanPref(kUserDismissedPremiumPrompt, false); registry->RegisterStringPref(kDefaultModelKey, "chat-default"); } +void RegisterProfilePrefsForMigration(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(kObseleteBraveChatAutoGenerateQuestions, false); +} + +void MigrateProfilePrefs(PrefService* profile_prefs) { + profile_prefs->ClearPref(kObseleteBraveChatAutoGenerateQuestions); +} + void RegisterLocalStatePrefs(PrefRegistrySimple* registry) { + // Added 11/2023 registry->RegisterDictionaryPref(kBraveChatPremiumCredentialCache); } diff --git a/components/ai_chat/core/common/pref_names.h b/components/ai_chat/core/common/pref_names.h index 9a7ae1a4d05..d161ffbe768 100644 --- a/components/ai_chat/core/common/pref_names.h +++ b/components/ai_chat/core/common/pref_names.h @@ -7,13 +7,12 @@ #define BRAVE_COMPONENTS_AI_CHAT_CORE_COMMON_PREF_NAMES_H_ class PrefRegistrySimple; +class PrefService; namespace ai_chat::prefs { inline constexpr char kLastAcceptedDisclaimer[] = "brave.ai_chat.last_accepted_disclaimer"; -inline constexpr char kBraveChatAutoGenerateQuestions[] = - "brave.ai_chat.auto_generate_questions"; inline constexpr char kBraveChatAutocompleteProviderEnabled[] = "brave.ai_chat.autocomplete_provider_enabled"; inline constexpr char kBraveChatP3AChatCountWeeklyStorage[] = @@ -34,6 +33,9 @@ inline constexpr char kBraveChatP3AOmniboxAutocompleteWeeklyStorage[] = void RegisterProfilePrefs(PrefRegistrySimple* registry); +void RegisterProfilePrefsForMigration(PrefRegistrySimple* registry); +void MigrateProfilePrefs(PrefService* profile_prefs); + void RegisterLocalStatePrefs(PrefRegistrySimple* registry); } // namespace ai_chat::prefs diff --git a/components/ai_chat/resources/page/components/conversation_list/index.tsx b/components/ai_chat/resources/page/components/conversation_list/index.tsx index 42e3718d0d9..5ee1e38e442 100644 --- a/components/ai_chat/resources/page/components/conversation_list/index.tsx +++ b/components/ai_chat/resources/page/components/conversation_list/index.tsx @@ -9,21 +9,34 @@ import Button from '@brave/leo/react/button' import Icon from '@brave/leo/react/icon' import styles from './style.module.scss' -import getPageHandlerInstance, { CharacterType } from '../../api/page_handler' +import getPageHandlerInstance, * as mojom from '../../api/page_handler' import DataContext from '../../state/context' import ContextMenuAssistant from '../context_menu_assistant' +import { getLocale } from '$web-common/locale' + +const SUGGESTION_STATUS_SHOW_BUTTON: mojom.SuggestionGenerationStatus[] = [ + mojom.SuggestionGenerationStatus.CanGenerate, + mojom.SuggestionGenerationStatus.IsGenerating +] function ConversationList() { // Scroll the last conversation item in to view when entries are added. const lastConversationEntryElementRef = React.useRef(null) + + const context = React.useContext(DataContext) const { isGenerating, conversationHistory, suggestedQuestions, shouldDisableUserInput - } = React.useContext(DataContext) + } = context + const portalRefs = React.useRef>(new Map()) + const showSuggestions: boolean = + suggestedQuestions.length > 0 || + SUGGESTION_STATUS_SHOW_BUTTON.includes(context.suggestionStatus) + React.useEffect(() => { if (!conversationHistory.length && !isGenerating) { return @@ -50,8 +63,9 @@ function ConversationList() { {conversationHistory.map((turn, id) => { const isLastEntry = id === conversationHistory.length - 1 const isLoading = isLastEntry && isGenerating - const isHuman = turn.characterType === CharacterType.HUMAN - const isAIAssistant = turn.characterType === CharacterType.ASSISTANT + const isHuman = turn.characterType === mojom.CharacterType.HUMAN + const isAIAssistant = + turn.characterType === mojom.CharacterType.ASSISTANT const turnClass = classnames({ [styles.turn]: true, @@ -97,10 +111,10 @@ function ConversationList() { ) })}
- {suggestedQuestions.length > 0 && ( + {showSuggestions && (
- Suggested follow-ups + {getLocale('suggestionsTitle')}
{suggestedQuestions.map((question, id) => ( @@ -113,6 +127,23 @@ function ConversationList() { {question} ))} + {SUGGESTION_STATUS_SHOW_BUTTON.includes( + context.suggestionStatus + ) && ( + + )}
)} diff --git a/components/ai_chat/resources/page/components/feature_button_menu/index.tsx b/components/ai_chat/resources/page/components/feature_button_menu/index.tsx index d95d35b0f3e..71adf4c8c64 100644 --- a/components/ai_chat/resources/page/components/feature_button_menu/index.tsx +++ b/components/ai_chat/resources/page/components/feature_button_menu/index.tsx @@ -7,9 +7,8 @@ import * as React from 'react' import ButtonMenu from '@brave/leo/react/buttonMenu' import Button from '@brave/leo/react/button' import Icon from '@brave/leo/react/icon' -import Checkbox from '@brave/leo/react/checkbox' import { getLocale } from '$web-common/locale' -import getPageHandlerInstance, * as mojom from '../../api/page_handler' +import getPageHandlerInstance from '../../api/page_handler' import DataContext from '../../state/context' import styles from './style.module.scss' import classnames from '$web-common/classnames' @@ -21,15 +20,6 @@ export default function FeatureMenu() { getPageHandlerInstance().pageHandler.openBraveLeoSettings() } - // TODO(petemill): Whilst this may be accurate, if the default (i.e. Unset) - // value changes to True, this will be wrong. - const isAutoGeneratedQuestionsEnabled = - (context.userAutoGeneratePref === mojom.AutoGenerateQuestionsPref.Enabled) - - const handleAutoGenerateQuestionsClick = () => { - context.setUserAllowsAutoGenerating(!isAutoGeneratedQuestionsEnabled) - } - const handleNewConversationClick = () => { getPageHandlerInstance().pageHandler.clearConversationHistory() } @@ -86,21 +76,6 @@ export default function FeatureMenu() {
- -
- - {getLocale('menuSuggestedQuestions')} - -
-
- {!context.isPremiumUser &&
diff --git a/components/ai_chat/resources/page/components/main/index.tsx b/components/ai_chat/resources/page/components/main/index.tsx index 8b8e9a318fc..32a2b438b7a 100644 --- a/components/ai_chat/resources/page/components/main/index.tsx +++ b/components/ai_chat/resources/page/components/main/index.tsx @@ -13,7 +13,6 @@ import DataContext from '../../state/context' import ConversationList from '../conversation_list' import PrivacyMessage from '../privacy_message' import SiteTitle from '../site_title' -import PromptAutoSuggestion from '../prompt_auto_suggestion' import ErrorConnection from '../alerts/error_connection' import ErrorRateLimit from '../alerts/error_rate_limit' import InputBox from '../input_box' @@ -30,7 +29,6 @@ function Main() { const context = React.useContext(DataContext) const { siteInfo, - userAutoGeneratePref, hasAcceptedAgreement, currentError, apiHasError @@ -40,7 +38,6 @@ function Main() { getPageHandlerInstance().pageHandler.clearConversationHistory() } - const shouldPromptSuggestQuestions = hasAcceptedAgreement && userAutoGeneratePref === mojom.AutoGenerateQuestionsPref.Unset const shouldShowPremiumSuggestionForModel = hasAcceptedAgreement && @@ -52,7 +49,6 @@ function Main() { hasAcceptedAgreement && !context.isPremiumStatusFetching && // Avoid flash of content !shouldShowPremiumSuggestionForModel && // Don't show 2 premium prompts - !shouldPromptSuggestQuestions && // Don't show premium prompt and question prompt !apiHasError && // Don't show premium prompt and errors (rate limit error has its own premium prompt suggestion) context.canShowPremiumPrompt && siteInfo === null && // SiteInfo request has finished and this is a standalone conversation @@ -183,9 +179,6 @@ function Main() {
}
- {shouldPromptSuggestQuestions && - - }
diff --git a/components/ai_chat/resources/page/components/prompt_auto_suggestion/index.tsx b/components/ai_chat/resources/page/components/prompt_auto_suggestion/index.tsx deleted file mode 100644 index 7c381254da2..00000000000 --- a/components/ai_chat/resources/page/components/prompt_auto_suggestion/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright (c) 2023 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import * as React from 'react' -import Icon from '@brave/leo/react/icon' -import Button from '@brave/leo/react/button' -import { getLocale } from '$web-common/locale' - -import styles from './style.module.scss' -import DataContext from '../../state/context' - -function PromptAutoSuggestion () { - const { setUserAllowsAutoGenerating, generateSuggestedQuestions } = React.useContext(DataContext) - - const handleOnEnableAutoGenerateQuestion = () => { - setUserAllowsAutoGenerating(true) - generateSuggestedQuestions() - } - - const handleOnDismiss = () => { - setUserAllowsAutoGenerating(false) - } - - return ( -
- -

{getLocale('enableQuestionsTitle')}

-

{getLocale('enableQuestionsDesc')}

-
- - -
-
- ) -} - -export default PromptAutoSuggestion diff --git a/components/ai_chat/resources/page/components/prompt_auto_suggestion/style.module.scss b/components/ai_chat/resources/page/components/prompt_auto_suggestion/style.module.scss deleted file mode 100644 index f0089a2f000..00000000000 --- a/components/ai_chat/resources/page/components/prompt_auto_suggestion/style.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2023 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// you can obtain one at https://mozilla.org/MPL/2.0/. - -.box { - width: 100%; - max-width: 460px; - background: var(--leo-color-page-background); - border-radius: 16px; - padding: 24px 16px 24px 24px; - margin-bottom: 16px; - position: relative; - - h1 { - margin: 0; - padding: 0; - font: var(--leo-font-primary-default-semibold); - } - - p { - font: var(--leo-font-primary-small-regular); - } -} - -.icon { - --leo-icon-size: 18px; - position: absolute; - right: 16px; - top: 24px; - color: var(--leo-color-icon-interactive); -} - -.actionsBox { - display: inline-flex; - gap: 10px; -} diff --git a/components/ai_chat/resources/page/state/context.ts b/components/ai_chat/resources/page/state/context.ts index a55d47ed251..2e2ad4f545f 100644 --- a/components/ai_chat/resources/page/state/context.ts +++ b/components/ai_chat/resources/page/state/context.ts @@ -13,9 +13,8 @@ export interface AIChatContext { conversationHistory: mojom.ConversationTurn[] suggestedQuestions: string[] isGenerating: boolean - canGenerateQuestions: boolean + suggestionStatus: mojom.SuggestionGenerationStatus hasAcceptedAgreement: boolean - userAutoGeneratePref: mojom.AutoGenerateQuestionsPref | undefined siteInfo?: mojom.SiteInfo | null favIconUrl: string | undefined currentError: mojom.APIError | undefined @@ -30,7 +29,6 @@ export interface AIChatContext { setCurrentModel: (model: mojom.Model) => void, switchToDefaultModel: () => void, generateSuggestedQuestions: () => void - setUserAllowsAutoGenerating: (value: boolean) => void goPremium: () => void managePremium: () => void handleAgreeClick: () => void @@ -47,14 +45,13 @@ export const defaultContext: AIChatContext = { conversationHistory: [], suggestedQuestions: [], isGenerating: false, - canGenerateQuestions: false, + suggestionStatus: mojom.SuggestionGenerationStatus.None, hasAcceptedAgreement: false, apiHasError: false, shouldDisableUserInput: false, isPremiumStatusFetching: false, isPremiumUser: false, isPremiumUserDisconnected: false, - userAutoGeneratePref: undefined, siteInfo: undefined, favIconUrl: undefined, currentError: mojom.APIError.None, @@ -64,7 +61,6 @@ export const defaultContext: AIChatContext = { setCurrentModel: () => {}, switchToDefaultModel: () => {}, generateSuggestedQuestions: () => {}, - setUserAllowsAutoGenerating: () => {}, goPremium: () => {}, managePremium: () => {}, handleAgreeClick: () => {}, diff --git a/components/ai_chat/resources/page/state/data-context-provider.tsx b/components/ai_chat/resources/page/state/data-context-provider.tsx index d060a851339..5f5877ca22d 100644 --- a/components/ai_chat/resources/page/state/data-context-provider.tsx +++ b/components/ai_chat/resources/page/state/data-context-provider.tsx @@ -32,8 +32,7 @@ function DataContextProvider (props: DataContextProviderProps) { const [conversationHistory, setConversationHistory] = React.useState([]) const [suggestedQuestions, setSuggestedQuestions] = React.useState([]) const [isGenerating, setIsGenerating] = React.useState(false) - const [canGenerateQuestions, setCanGenerateQuestions] = React.useState(false) - const [userAutoGeneratePref, setUserAutoGeneratePref] = React.useState() + const [suggestionStatus, setSuggestionStatus] = React.useState(mojom.SuggestionGenerationStatus.None) // undefined for nothing received yet // null for no site info // mojom.SiteInfo for valid site info @@ -83,18 +82,12 @@ function DataContextProvider (props: DataContextProviderProps) { .then((res) => setConversationHistory(res.conversationHistory)) } - const setUserAllowsAutoGenerating = (value: boolean) => { - getPageHandlerInstance().pageHandler.setAutoGenerateQuestions(value) - setUserAutoGeneratePref(value ? mojom.AutoGenerateQuestionsPref.Enabled : mojom.AutoGenerateQuestionsPref.Disabled) - } - const getSuggestedQuestions = () => { getPageHandlerInstance() .pageHandler.getSuggestedQuestions() .then((r) => { setSuggestedQuestions(r.questions) - setCanGenerateQuestions(r.canGenerate) - setUserAutoGeneratePref(r.autoGenerate) + setSuggestionStatus(r.suggestionStatus) }) } @@ -249,14 +242,12 @@ function DataContextProvider (props: DataContextProviderProps) { // Setup data event handlers getPageHandlerInstance().callbackRouter.onConversationHistoryUpdate.addListener(() => { getConversationHistory() - setCanGenerateQuestions(false) }) getPageHandlerInstance().callbackRouter.onAPIRequestInProgress.addListener(setIsGenerating) getPageHandlerInstance().callbackRouter.onSuggestedQuestionsChanged - .addListener((questions: string[], hasGenerated: boolean, autoGenerate: mojom.AutoGenerateQuestionsPref) => { + .addListener((questions: string[], suggestionStatus: mojom.SuggestionGenerationStatus) => { setSuggestedQuestions(questions) - setCanGenerateQuestions(!hasGenerated) - setUserAutoGeneratePref(autoGenerate) + setSuggestionStatus(suggestionStatus) } ) getPageHandlerInstance().callbackRouter.onFaviconImageDataChanged.addListener((faviconImageData: number[]) => setFavIconUrl(toBlobURL(faviconImageData))) @@ -290,8 +281,7 @@ function DataContextProvider (props: DataContextProviderProps) { conversationHistory, isGenerating, suggestedQuestions, - canGenerateQuestions, - userAutoGeneratePref, + suggestionStatus, siteInfo: siteInfo, favIconUrl, currentError, @@ -309,7 +299,6 @@ function DataContextProvider (props: DataContextProviderProps) { goPremium, managePremium, generateSuggestedQuestions, - setUserAllowsAutoGenerating, handleAgreeClick, dismissPremiumPrompt, getCanShowPremiumPrompt, diff --git a/components/ai_chat/resources/page/stories/components_panel.tsx b/components/ai_chat/resources/page/stories/components_panel.tsx index b8590bec59a..1c97225ca7b 100644 --- a/components/ai_chat/resources/page/stories/components_panel.tsx +++ b/components/ai_chat/resources/page/stories/components_panel.tsx @@ -88,8 +88,8 @@ export default { options: getKeysForMojomEnum(mojom.APIError), control: { type: 'select' } }, - suggestedQuestionsPref: { - options: getKeysForMojomEnum(mojom.AutoGenerateQuestionsPref), + suggestionStatus: { + options: getKeysForMojomEnum(mojom.SuggestionGenerationStatus), control: { type: 'select' } }, model: { @@ -108,14 +108,12 @@ export default { isPremiumUser: true, isPremiumUserDisconnected: false, currentErrorState: 'ConnectionIssue' satisfies keyof typeof mojom.APIError, - suggestedQuestionsPref: 'Unset' satisfies keyof typeof mojom.AutoGenerateQuestionsPref, + suggestionStatus: 'None' satisfies keyof typeof mojom.SuggestionGenerationStatus, model: MODELS[0].name }, decorators: [ (Story: any, options: any) => { const [isGenerating] = React.useState(false) - const [canGenerateQuestions] = React.useState(false) - const userAutoGeneratePref: mojom.AutoGenerateQuestionsPref = mojom.AutoGenerateQuestionsPref[options.args.suggestedQuestionsPref] const [favIconUrl] = React.useState() const hasAcceptedAgreement = options.args.hasAcceptedAgreement @@ -140,8 +138,7 @@ export default { isGenerating, isPremiumStatusFetching: false, suggestedQuestions, - canGenerateQuestions, - userAutoGeneratePref, + suggestionStatus: mojom.SuggestionGenerationStatus[options.args.suggestionStatus], canShowPremiumPrompt: options.args.canShowPremiumPrompt, siteInfo, favIconUrl, diff --git a/components/ai_chat/resources/page/stories/locale.ts b/components/ai_chat/resources/page/stories/locale.ts index 27a8ee6e388..298e9dcb385 100644 --- a/components/ai_chat/resources/page/stories/locale.ts +++ b/components/ai_chat/resources/page/stories/locale.ts @@ -8,10 +8,6 @@ import { provideStrings } from '../../../../../.storybook/locale' provideStrings({ summarizeFailedLabel: 'The summarizer feature is currently available only for select articles and other long-form web pages.', placeholderLabel: 'Enter a prompt here', - enableQuestionsTitle: 'Enable suggested questions?', - enableQuestionsDesc: 'Brave AI can automatically suggest questions based on the content of the site. You can disable it later in Settings.', - enableQuestionsButtonLabel: 'Enable', - noThanksButtonLabel: 'No Thanks', aboutTitle: 'About Leo', aboutDescription: 'Brave Leo is an AI smart assistant that can summarize web pages, transcribe videos, and answer questions. Brave Leo Premium uses advanced $1AI models$2 for even more nuanced replies, and gives early access to new features.', aboutDescription_2: `The accuracy of responses is not guaranteed, and may include inaccurate, misleading, or false information. Don't submit sensitive or private info, and use caution with any answers related to health, finance, personal safety, or similar.`, @@ -27,9 +23,10 @@ provideStrings({ modelNameSyntax: '$1 by $2', 'modelCategory-chat': 'Chat', menuNewChat: 'New chat', - menuSuggestedQuestions: 'Suggested questions', menuSettings: 'Settings', menuTitleModels: 'Available language models', + suggestionsTitle: 'Suggested follow-ups', + suggestQuestionsTitle: 'Suggest questions…', premiumButtonLabel: 'Go Premium', rateLimitReachedTitle: 'Response rate limit reached', rateLimitReachedDesc: "Unlock a higher response rate by subscribing to Premium, or try again later", diff --git a/components/resources/ai_chat_ui_strings.grdp b/components/resources/ai_chat_ui_strings.grdp index ef9cae250e5..768cd4ddb65 100644 --- a/components/resources/ai_chat_ui_strings.grdp +++ b/components/resources/ai_chat_ui_strings.grdp @@ -27,8 +27,11 @@ Enter a prompt here - - Enable suggested questions? + + Suggested follow-ups + + + Suggest questions… Summarize this video @@ -36,15 +39,6 @@ Summarize this page - - Leo can automatically suggest questions based on the content of the site. To do this, the page contents of your navigations will be sent to Leo to generate these suggestions while Leo is open. You can disable it later in Settings. - - - Enable - - - No Thanks - Disconnect to stop sending this page content to Leo, and start a new conversation @@ -75,9 +69,6 @@ New chat - - Suggested questions - Go Premium