[AI Chat] Allow users to select Leo or custom models for tab focus (#34333)

* Add pref and backend support for tab organization model key

Introduce kBraveAIChatTabOrganizationModelKey pref (default:
"chat-automatic") to allow users to choose which model is used
for tab organization. Update CreateTabOrganizationEngineIfNeeded
to read from this pref with fallback to "chat-automatic". Add
unit test for the invalid key fallback path.

* Add tab organization model selector in settings UI

Extract shared LeoModelSelector Lit component from personalization
page. Add tab organization model dropdown that appears when the tab
organization feature flag and pref are both enabled. Gate the tab
organization toggle behind the feature flag. Add localized string
for the model selector label.

* Improve tab focus prompts

When testing local model, found gemma tends to reply topics like
"['📰 News', '💻 Web Development', ... ]" which is failed to be parsed
because of the usage of single quotes on strings.
Improve tab focus prompts to mention valid JSON array and examples for
model to follow.
This commit is contained in:
Jocelyn Liu
2026-03-04 00:59:09 +01:00
committed by GitHub
parent 1aff3a28ed
commit acf20dfedb
17 changed files with 292 additions and 125 deletions
+3
View File
@@ -1605,6 +1605,9 @@
<message name="IDS_SETTINGS_LEO_ASSISTANT_TAB_ORGANIZATION_DESC" desc="The description for settings option to enable tab focus mode">
Enable tab organization in Tab Search page. When opened, this will automatically send the titles and origins of all non-private tabs to Leo to classify them. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
</message>
<message name="IDS_SETTINGS_LEO_ASSISTANT_TAB_ORGANIZATION_MODEL_LABEL" desc="The label for the tab organization model selection dropdown">
Default model for tab focus
</message>
<message name="IDS_SETTINGS_LEO_ASSISTANT_SHOW_SUGGESTED_PROMPTS_LABEL" desc="The text for settings option">
Show suggested prompts in the conversation
</message>
@@ -281,6 +281,8 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() {
settings_api::PrefType::kBoolean;
(*s_brave_allowlist)[ai_chat::prefs::kBraveAIChatTabOrganizationEnabled] =
settings_api::PrefType::kBoolean;
(*s_brave_allowlist)[ai_chat::prefs::kBraveAIChatTabOrganizationModelKey] =
settings_api::PrefType::kString;
(*s_brave_allowlist)[ai_chat::prefs::kBraveAIChatUserCustomizationEnabled] =
settings_api::PrefType::kBoolean;
(*s_brave_allowlist)[ai_chat::prefs::kBraveAIChatUserMemoryEnabled] =
@@ -42,12 +42,15 @@ You can obtain one at https://mozilla.org/MPL/2.0/. -->
label="$i18n{braveLeoAssistantShowInContextMenuLabel}"
sub-label="$i18n{braveLeoAssistantShowInContextMenuDesc}">
</settings-toggle-button>
<settings-toggle-button class="cr-row hr"
pref="{{prefs.brave.ai_chat.tab_organization_enabled}}"
label="$i18n{braveLeoAssistantTabOrganizationLabel}"
sub-label-with-link="$i18n{braveLeoAssistantTabOrganizationDesc}"
on-sub-label-link-clicked="openTabOrganizationLearnMore_">
</settings-toggle-button>
<template is="dom-if"
if="[[isTabOrganizationFeatureEnabled_]]">
<settings-toggle-button class="cr-row hr"
pref="{{prefs.brave.ai_chat.tab_organization_enabled}}"
label="$i18n{braveLeoAssistantTabOrganizationLabel}"
sub-label-with-link="$i18n{braveLeoAssistantTabOrganizationDesc}"
on-sub-label-link-clicked="openTabOrganizationLearnMore_">
</settings-toggle-button>
</template>
<cr-link-row class="hr"
label="$i18n{braveLeoAssistantAutocompleteLink}"
on-click="openAutocompleteSetting_">
@@ -13,7 +13,9 @@ import {Router} from '../router.js'
import {loadTimeData} from '../i18n_setup.js'
import {routes} from '../route.js';
import {getTemplate} from './brave_leo_assistant_page.html.js'
import {BraveLeoAssistantBrowserProxy, BraveLeoAssistantBrowserProxyImpl, PremiumStatus, ModelWithSubtitle, PremiumInfo, ModelAccess, Model}
import {BraveLeoAssistantBrowserProxy,
BraveLeoAssistantBrowserProxyImpl, PremiumStatus,
PremiumInfo}
from './brave_leo_assistant_browser_proxy.js'
const BraveLeoAssistantPageBase =
@@ -48,12 +50,18 @@ class BraveLeoAssistantPageElement extends BraveLeoAssistantPageBase {
type: Boolean,
value: () => loadTimeData.getBoolean('isLeoAssistantHistoryAllowed')
},
isTabOrganizationFeatureEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean(
'isTabOrganizationFeatureEnabled')
},
}
}
private declare isPremiumUser_: boolean
declare isHistoryFeatureEnabled_: boolean
declare isTabOrganizationFeatureEnabled_: boolean
declare leoAssistantShowOnToolbarPref_: boolean
premiumStatus_: PremiumStatus = PremiumStatus.Unknown
browserProxy_: BraveLeoAssistantBrowserProxy =
@@ -0,0 +1,40 @@
/* Copyright (c) 2026 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/. */
/* #css_wrapper_metadata_start
* #type=style-lit
* #css_wrapper_metadata_end */
.menu-item-with-icon {
--leo-icon-size: 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 22px;
}
.model-subtitle {
font: var(--leo-font-small-regular);
color: var(--leo-color-text-tertiary);
padding: 0;
margin: 0;
}
.menu-section-title {
background-color: var(--leo-color-page-background);
padding: var(--leo-spacing-l) var(--leo-spacing-xl);
color: var(--leo-color-text-tertiary);
font: var(--leo-font-components-label);
text-transform: uppercase;
}
leo-dropdown {
min-width: 240px;
}
leo-option {
padding: var(--leo-spacing-m);
}
@@ -0,0 +1,51 @@
// Copyright (c) 2026 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 {html, nothing} from
'//resources/lit/v3_0/lit.rollup.js'
import type {LeoModelSelectorElement} from
'./model_selector.js'
export function getHtml(this: LeoModelSelectorElement) {
return html`<!--_html_template_start_-->
<leo-dropdown
value="${this.selectedKey}"
@change="${this.onSelectionChange_}"
>
<div slot="value">${this.selectedDisplayName}</div>
<div class="menu-section-title">
<span>$i18n{braveLeoModelSectionTitle}</span>
</div>
${this.models.map((entry) => html`
<leo-option value="${entry.model.key}">
<div class="menu-item-with-icon">
<div>
<div>${entry.model.displayName}</div>
${entry.model.options.leoModelOptions
!== undefined ? html`
<p class="model-subtitle">
${entry.subtitle}
</p>
` : html`
<p class="model-subtitle">
${entry.model.options
.customModelOptions
?.modelRequestName}
</p>
`}
</div>
${this.shouldShowPremiumLabel_(entry)
? html`
<leo-label mode="outline" color="blue">
$i18n{braveLeoPremiumLabelNonPremium}
</leo-label>
` : nothing}
</div>
</leo-option>
`)}
</leo-dropdown>
<!--_html_template_end_-->`
}
@@ -0,0 +1,74 @@
/* Copyright (c) 2026 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 'chrome://resources/brave/leo.bundle.js'
import {CrLitElement} from
'//resources/lit/v3_0/lit.rollup.js'
import {
ModelWithSubtitle,
ModelAccess,
} from './brave_leo_assistant_browser_proxy.js'
import {getCss} from './model_selector.css.js'
import {getHtml} from './model_selector.html.js'
export class LeoModelSelectorElement extends CrLitElement {
static get is() {
return 'leo-model-selector'
}
static override get properties() {
return {
selectedKey: {type: String},
models: {type: Array},
isPremiumUser: {type: Boolean},
}
}
static override get styles() {
return getCss()
}
override render() {
return getHtml.bind(this)()
}
accessor selectedKey: string = ''
accessor models: ModelWithSubtitle[] = []
accessor isPremiumUser: boolean = false
get selectedDisplayName(): string {
return this.models?.find(
(entry) => entry.model.key === this.selectedKey
)?.model.displayName ?? ''
}
onSelectionChange_(e: any) {
this.dispatchEvent(new CustomEvent('model-changed', {
detail: {value: e.value},
bubbles: true,
composed: true,
}))
}
shouldShowPremiumLabel_(entry: ModelWithSubtitle):
boolean {
if (!entry.model.options.leoModelOptions) {
return false
}
return entry.model.options.leoModelOptions.access
=== ModelAccess.PREMIUM && !this.isPremiumUser
}
}
declare global {
interface HTMLElementTagNameMap {
'leo-model-selector': LeoModelSelectorElement
}
}
customElements.define(
LeoModelSelectorElement.is, LeoModelSelectorElement)
@@ -11,37 +11,6 @@ You can obtain one at https://mozilla.org/MPL/2.0/. -->
.main-header {
font: var(--leo-font-heading-h4);
}
.menu-item-with-icon {
--leo-icon-size: 20px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 22px;
}
.model-subtitle {
font: var(--leo-font-small-regular);
color: var(--leo-color-text-tertiary);
padding: 0;
margin: 0;
}
.menu-section-title {
background-color: var(--leo-color-page-background);
padding: var(--leo-spacing-l) var(--leo-spacing-xl);
color: var(--leo-color-text-tertiary);
font: var(--leo-font-components-label);
text-transform: uppercase;
}
leo-dropdown {
min-width: 240px;
}
leo-option {
padding: var(--leo-spacing-m);
}
</style>
<settings-section
@@ -52,58 +21,31 @@ You can obtain one at https://mozilla.org/MPL/2.0/. -->
<div class="flex cr-padded-text">
<div>$i18n{braveLeoAssistantModelSelectionLabel}</div>
</div>
<leo-dropdown
<leo-model-selector
class="cr-padded-text"
value="[[defaultModelKeyPrefValue_]]"
on-change="onModelSelectionChange_"
>
<div slot="value">[[selectedModelDisplayName_]]</div>
<div class="menu-section-title">
<span>$i18n{braveLeoModelSectionTitle}</span>
</div>
<template
is="dom-repeat"
items="[[models_]]"
>
<leo-option value$="[[item.model.key]]">
<div class="menu-item-with-icon">
<div>
<div>[[item.model.displayName]]</div>
<template
is="dom-if"
if="[[isLeoModel_(item.model)]]"
>
<p class="model-subtitle">[[item.subtitle]]</p>
</template>
<template
is="dom-if"
if="[[!isLeoModel_(item.model)]]"
>
<p class="model-subtitle">
[[item.model.options.customModelOptions.modelRequestName]]
</p>
</template>
</div>
<template
is="dom-if"
if="[[isLeoModel_(item.model)]]"
>
<template
is="dom-if"
if="[[shouldShowModelPremiumLabel_(item.model.options.leoModelOptions.access)]]"
>
<leo-label
mode="outline"
color="blue"
>$i18n{braveLeoPremiumLabelNonPremium}</leo-label
>
</template>
</template>
</div>
</leo-option>
</template>
</leo-dropdown>
selected-key="[[defaultModelKeyPrefValue_]]"
models="[[models_]]"
is-premium-user="[[isPremiumUser_]]"
on-model-changed="onModelSelectionChange_">
</leo-model-selector>
</div>
<template is="dom-if"
if="[[isTabOrganizationDropdownVisible_(
isTabOrganizationFeatureEnabled_,
prefs.brave.ai_chat.tab_organization_enabled.value)]]">
<div class="settings-box">
<div class="flex cr-padded-text">
<div>$i18n{braveLeoAssistantTabOrganizationModelLabel}</div>
</div>
<leo-model-selector
class="cr-padded-text"
selected-key="[[prefs.brave.ai_chat.tab_organization_model_key.value]]"
models="[[models_]]"
is-premium-user="[[isPremiumUser_]]"
on-model-changed="onTabOrganizationModelChange_">
</leo-model-selector>
</div>
</template>
<template is="dom-if" if="[[isHistoryFeatureEnabled_]]">
<settings-toggle-button class="cr-row hr"
pref="{{prefs.brave.ai_chat.storage_enabled}}"
@@ -26,10 +26,9 @@ import {
PremiumStatus,
ModelWithSubtitle,
PremiumInfo,
ModelAccess,
Model
} from './brave_leo_assistant_browser_proxy.js'
import './customization_subpage.js'
import './model_selector.js'
import { getTemplate } from './personalization.html.js'
import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'
@@ -47,11 +46,6 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
static get properties() {
return {
selectedModelDisplayName_: {
type: String,
computed: 'computeDisplayName_(models_, ' +
'defaultModelKeyPrefValue_)'
},
isPremiumUser_: {
type: Boolean,
value: false,
@@ -59,7 +53,6 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
},
defaultModelKeyPrefValue_: String,
models_: {
readOnly: true,
type: Array,
},
isHistoryFeatureEnabled_: {
@@ -67,13 +60,18 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
value: () => loadTimeData.getBoolean(
'isLeoAssistantHistoryAllowed')
},
isTabOrganizationFeatureEnabled_: {
type: Boolean,
value: () => loadTimeData.getBoolean(
'isTabOrganizationFeatureEnabled')
},
}
}
private declare isPremiumUser_: boolean
declare isHistoryFeatureEnabled_: boolean
declare selectedModelDisplayName_: string
declare isTabOrganizationFeatureEnabled_: boolean
declare defaultModelKeyPrefValue_: string
declare models_: ModelWithSubtitle[]
premiumStatus_: PremiumStatus = PremiumStatus.Unknown
@@ -130,21 +128,10 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
})
}
computeDisplayName_() {
const foundEntry = this.models_?.find(
(entry) => {
return entry.model.key === this.defaultModelKeyPrefValue_
}
)
return foundEntry?.model.displayName
}
onModelSelectionChange_(e: any) {
this.browserProxy_.getSettingsHelper().setDefaultModelKey(e.value)
this.browserProxy_.getSettingsHelper().setDefaultModelKey(e.detail.value)
}
private updateCurrentPremiumStatus() {
this.browserProxy_.getSettingsHelper().getPremiumStatus()
.then((value: { status: PremiumStatus; info: PremiumInfo | null; }) => {
@@ -152,10 +139,6 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
})
}
private isLeoModel_(model: Model) {
return model.options.leoModelOptions !== undefined
}
computeIsPremiumUser_() {
if (this.premiumStatus_ === PremiumStatus.Active ||
this.premiumStatus_ === PremiumStatus.ActiveDisconnected) {
@@ -165,10 +148,6 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
return false
}
shouldShowModelPremiumLabel_(modelAccess: ModelAccess) {
return (modelAccess === ModelAccess.PREMIUM && !this.isPremiumUser_)
}
private onStorageEnabledChange_(event: Event) {
const target = event.target
assert(target instanceof SettingsToggleButtonElement);
@@ -185,6 +164,18 @@ class BraveLeoPersonalization extends BraveLeoPersonalizationBase {
const router = Router.getInstance();
router.navigateTo(router.getRoutes().BRAVE_LEO_CUSTOMIZATION);
}
private onTabOrganizationModelChange_(e: any) {
this.setPrefValue(
'brave.ai_chat.tab_organization_model_key',
e.detail.value)
}
private isTabOrganizationDropdownVisible_(
featureEnabled: boolean,
prefEnabled: boolean) {
return featureEnabled && prefEnabled
}
}
customElements.define(BraveLeoPersonalization.is, BraveLeoPersonalization)
+7 -2
View File
@@ -195,8 +195,13 @@ if (enable_ai_chat) {
"brave_leo_assistant_page/model_config_ui.ts",
"brave_leo_assistant_page/personalization.ts",
]
brave_settings_ts_files +=
[ "brave_leo_assistant_page/brave_leo_assistant_browser_proxy.ts" ]
brave_settings_ts_files += [
"brave_leo_assistant_page/brave_leo_assistant_browser_proxy.ts",
"brave_leo_assistant_page/model_selector.html.ts",
"brave_leo_assistant_page/model_selector.ts",
]
brave_settings_local_css_files +=
[ "brave_leo_assistant_page/model_selector.css" ]
}
brave_settings_ts_definitions =
@@ -54,6 +54,7 @@
#if BUILDFLAG(ENABLE_AI_CHAT)
#include "brave/components/ai_chat/core/browser/model_validator.h"
#include "brave/components/ai_chat/core/common/features.h"
#endif
#if BUILDFLAG(ENABLE_TOR)
@@ -564,6 +565,8 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source,
IDS_SETTINGS_LEO_ASSISTANT_SHOW_IN_CONTEXT_MENU_DESC},
{"braveLeoAssistantTabOrganizationLabel",
IDS_SETTINGS_LEO_ASSISTANT_TAB_ORGANIZATION_LABEL},
{"braveLeoAssistantTabOrganizationModelLabel",
IDS_SETTINGS_LEO_ASSISTANT_TAB_ORGANIZATION_MODEL_LABEL},
{"braveLeoAssistantHistoryPreferenceLabel",
IDS_SETTINGS_LEO_ASSISTANT_HISTORY_PREFERENCE_LABEL},
{"braveLeoAssistantHistoryPreferenceConfirm",
@@ -1100,6 +1103,9 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source,
html_source->AddString("braveLeoAssistantTabOrganizationLearnMoreURL",
kTabOrganizationLearnMoreURL);
html_source->AddBoolean("isTabOrganizationFeatureEnabled",
ai_chat::features::IsTabOrganizationEnabled());
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -158,6 +158,10 @@ AIChatService::AIChatService(
prefs::kBraveAIChatSkills,
base::BindRepeating(&AIChatService::OnSkillsChanged,
weak_ptr_factory_.GetWeakPtr()));
pref_change_registrar_.Add(
prefs::kBraveAIChatTabOrganizationModelKey,
base::BindRepeating(&AIChatService::OnTabOrganizationModelPrefChanged,
weak_ptr_factory_.GetWeakPtr()));
MaybeInitStorage();
@@ -1314,9 +1318,20 @@ AIChatService::CreateToolProvidersForNewConversation() {
}
void AIChatService::CreateTabOrganizationEngineIfNeeded() {
if (!tab_organization_engine_) {
tab_organization_engine_ = GetEngineForModel(kChatAutomaticModelKey);
if (tab_organization_engine_) {
return;
}
std::string target_key =
profile_prefs_->GetString(prefs::kBraveAIChatTabOrganizationModelKey);
if (!model_service_->GetModel(target_key)) {
target_key = kChatAutomaticModelKey;
}
tab_organization_engine_ = GetEngineForModel(target_key);
}
void AIChatService::OnTabOrganizationModelPrefChanged() {
tab_organization_engine_.reset();
}
void AIChatService::OnSuggestedTopicsReceived(
@@ -18,6 +18,7 @@
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
@@ -248,6 +249,8 @@ class AIChatService : public KeyedService,
private:
friend class AIChatServiceUnitTest;
FRIEND_TEST_ALL_PREFIXES(AIChatServiceUnitTest,
CreateTabOrganizationEngineIfNeeded_InvalidModelKey);
// Key is uuid
using ConversationMap =
@@ -318,6 +321,7 @@ class AIChatService : public KeyedService,
void InitializeTools();
void CreateTabOrganizationEngineIfNeeded();
void OnTabOrganizationModelPrefChanged();
void OnSuggestedTopicsReceived(
GetSuggestedTopicsCallback callback,
@@ -47,6 +47,7 @@
#include "brave/components/ai_chat/core/browser/test_utils.h"
#include "brave/components/ai_chat/core/browser/tools/memory_storage_tool.h"
#include "brave/components/ai_chat/core/browser/tools/tool.h"
#include "brave/components/ai_chat/core/browser/types.h"
#include "brave/components/ai_chat/core/browser/utils.h"
#include "brave/components/ai_chat/core/common/features.h"
#include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h"
@@ -67,6 +68,7 @@
#include "third_party/abseil-cpp/absl/strings/str_format.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#include "url/origin.h"
using ::testing::_;
using ::testing::Eq;
@@ -1374,6 +1376,22 @@ TEST_P(AIChatServiceUnitTest, GetFocusTabs_EmptyTabs) {
run_loop.Run();
}
TEST_P(AIChatServiceUnitTest,
CreateTabOrganizationEngineIfNeeded_InvalidModelKey) {
// Set the pref to a key that doesn't resolve to any valid model.
prefs_.SetString(prefs::kBraveAIChatTabOrganizationModelKey,
"nonexistent-model");
// Call CreateTabOrganizationEngineIfNeeded directly to verify the fallback
// logic without triggering the full request flow.
ai_chat_service_->CreateTabOrganizationEngineIfNeeded();
auto* engine = ai_chat_service_->GetTabOrganizationEngineForTesting();
ASSERT_NE(engine, nullptr);
EXPECT_EQ(engine->GetModelName(), "automatic");
}
TEST_P(AIChatServiceUnitTest, TemporaryConversation_NoDatabaseInteraction) {
// We create mock DB object regardless of whether history is enabled.
// In real case, there's no DB object at all if history is disabled, this
@@ -8,6 +8,7 @@
#include <string_view>
#include "base/time/time.h"
#include "brave/components/ai_chat/core/common/constants.h"
#include "brave/components/ai_chat/core/common/features.h"
#include "components/prefs/pref_registry_simple.h"
@@ -39,6 +40,8 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(kBraveAIChatToolbarButtonOpensFullPage,
false);
registry->RegisterBooleanPref(kBraveAIChatTabOrganizationEnabled, true);
registry->RegisterStringPref(kBraveAIChatTabOrganizationModelKey,
kChatAutomaticModelKey);
registry->RegisterBooleanPref(kBraveAIChatUserCustomizationEnabled, true);
registry->RegisterBooleanPref(kBraveAIChatUserMemoryEnabled, true);
registry->RegisterDictionaryPref(kBraveAIChatUserCustomizations);
@@ -117,6 +117,8 @@ inline constexpr char kObseleteBraveChatAutoGenerateQuestions[] =
inline constexpr char kBraveAIChatTabOrganizationEnabled[] =
"brave.ai_chat.tab_organization_enabled";
inline constexpr char kBraveAIChatTabOrganizationModelKey[] =
"brave.ai_chat.tab_organization_model_key";
inline constexpr char kBraveAIChatUserCustomizationEnabled[] =
"brave.ai_chat.user_customization_enabled";
+4 -4
View File
@@ -269,19 +269,19 @@ Sure, here is the rewritten version of the excerpt: &lt;response&gt;
</message>
<message name="IDS_AI_CHAT_TAB_FOCUS_REDUCE_TOPICS" translateable="false">
Here is a list of browser topics: <ph name="TOPICS">$1</ph>. Please return an array, delimited by "[" and "]" containing the 5 most unusual or interesting topics. Prepend each topic string with a single relevant emoji. Try to choose unrelated topics. Make sure to return only an array of strings enclosed in quotes.
Here is a list of browser topics: <ph name="TOPICS">$1</ph>. Please return a valid JSON array containing the 5 most unusual or interesting topics. Prepend each topic string with a single relevant emoji. Try to choose unrelated topics. Return only a JSON array of strings using double quotes, for example: ["🔬 Science", "🎵 Music"].
</message>
<message name="IDS_AI_CHAT_TAB_FOCUS_SUGGEST_TOPICS_WITH_EMOJI" translateable="false">
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return an array, delimited by "[" and "]" of up to 5 topics that the user might be working on or researching. Ensure that the topics are diverse and unrelated. Prepend each topic string with a single relevant emoji. Make sure to return only an array of strings enclosed in quotes.
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return a valid JSON array of up to 5 topics that the user might be working on or researching. Ensure that the topics are diverse and unrelated. Prepend each topic string with a single relevant emoji. Return only a JSON array of strings using double quotes, for example: ["🔬 Science", "🎵 Music"].
</message>
<message name="IDS_AI_CHAT_TAB_FOCUS_SUGGEST_TOPICS" translateable="false">
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return an array, delimited by "[" and "]" of up to 5 topics that the user might be working on or researching. Try to make the topics more detailed. Make sure to return only an array of strings enclosed in quotes.
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return a valid JSON array of up to 5 topics that the user might be working on or researching. Try to make the topics more detailed. Return only a JSON array of strings using double quotes, for example: ["Web Development", "Machine Learning"].
</message>
<message name="IDS_AI_CHAT_TAB_FOCUS_FILTER_TABS" translateable="false">
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return an array, delimited by "[" and "]" of the tabIds for tabs that are a close match for the following topic: <ph name="TOPIC">$2</ph>. Make sure to return only an array of strings enclosed in quotes.
Here is a list of browser tabs with their titles and domains: <ph name="TABS">$1</ph>. Please return a valid JSON array of the tabIds for tabs that are a close match for the following topic: <ph name="TOPIC">$2</ph>. Return only a JSON array of strings using double quotes, for example: ["1", "5", "12"].
</message>
</grit-part>