[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:
@@ -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"><a target="_blank" href="$1"></ph>Learn more<ph name="END_LINK"></a></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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -269,19 +269,19 @@ Sure, here is the rewritten version of the excerpt: <response>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user