/* 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 http://mozilla.org/MPL/2.0/. */ import { html } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; import { Preferences } from "chrome://global/content/preferences/Preferences.mjs"; import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs"; import { OnDeviceModelManager } from "chrome://browser/content/preferences/OnDeviceModelManager.mjs"; /** * @import { OnDeviceModelFeaturesEnum } from "chrome://browser/content/preferences/OnDeviceModelManager.mjs" * @typedef {typeof AiControlGlobalStates[keyof typeof AiControlGlobalStates]} AiControlGlobalStatesEnum */ const { CommonDialog } = ChromeUtils.importESModule( "resource://gre/modules/CommonDialog.sys.mjs" ); const XPCOMUtils = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ).XPCOMUtils; const lazy = XPCOMUtils.declareLazy({ AIWindow: "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs", GenAI: "resource:///modules/GenAI.sys.mjs", MemoryStore: "moz-src:///browser/components/aiwindow/services/MemoryStore.sys.mjs", }); Preferences.addAll([ // browser.ai.control.* prefs defined in main.js { id: "browser.ml.chat.provider", type: "string" }, { id: "browser.smartwindow.apiKey", type: "string" }, { id: "browser.smartwindow.enabled", type: "bool" }, { id: "browser.smartwindow.endpoint", type: "string" }, { id: "browser.smartwindow.firstrun.modelChoice", type: "string" }, { id: "browser.smartwindow.memories", type: "bool" }, { id: "browser.smartwindow.model", type: "string" }, { id: "browser.smartwindow.preferences.endpoint", type: "string" }, { id: "browser.smartwindow.tos.consentTime", type: "int" }, { id: "browser.preferences.aiControls.showUnavailable", type: "bool" }, ]); Preferences.addSetting({ id: "aiControlsShowUnavailable", pref: "browser.preferences.aiControls.showUnavailable", }); Preferences.addSetting({ id: "aiControlsDescription" }); Preferences.addSetting({ id: "blockAiGroup" }); Preferences.addSetting({ id: "blockAiDescription" }); Preferences.addSetting({ id: "onDeviceFieldset" }); Preferences.addSetting({ id: "onDeviceGroup", deps: [ "aiControlTranslationsSelect", "aiControlPdfjsAltTextSelect", "aiControlSmartTabGroupsSelect", "aiControlLinkPreviewKeyPointsSelect", ], getControlConfig(config, deps) { for (let option of config.options) { let control = option.items[0]; if (control.id in deps) { option.controlAttrs = option.controlAttrs || {}; option.controlAttrs.class = deps[control.id].visible ? "" : "setting-hidden"; } } return config; }, }); Preferences.addSetting({ id: "aiStatesDescription" }); Preferences.addSetting({ id: "sidebarChatbotFieldset" }); Preferences.addSetting({ id: "aiBlockedMessage", deps: ["aiControlDefaultToggle"], visible: deps => deps.aiControlDefaultToggle.value, }); const AiControlStates = Object.freeze({ default: "default", enabled: "enabled", blocked: "blocked", available: "available", }); const AiControlGlobalStates = Object.freeze({ available: "available", blocked: "blocked", }); /** * @param {AiControlGlobalStatesEnum} state */ function updateAiControlDefault(state) { let isBlocked = state == AiControlGlobalStates.blocked; for (let feature of Object.values(OnDeviceModelManager.features)) { if (isBlocked) { // Reset to default (blocked) state unless it was already blocked. OnDeviceModelManager.disable(feature); } else if (!isBlocked && !OnDeviceModelManager.isEnabled(feature)) { // Reset to default (available) state unless it was manually enabled. OnDeviceModelManager.reset(feature); } } if (isBlocked) { Services.prefs.setStringPref( "browser.ai.control.default", AiControlGlobalStates.blocked ); } // There's no feature-specific dropdown for extensions since it's still a // trial feature, so just turn it off/on based on the global switch. Services.prefs.setBoolPref("extensions.ml.enabled", !isBlocked); Glean.browser.globalAiControlToggled.record({ blocked: isBlocked }); } class BlockAiConfirmationDialog extends MozLitElement { get dialog() { return this.renderRoot.querySelector("dialog"); } get confirmButton() { return this.renderRoot.querySelector('moz-button[type="primary"]'); } get cancelButton() { return this.renderRoot.querySelector('moz-button:not([type="primary"])'); } async showModal() { await this.updateComplete; this.dialog.showModal(); } handleCancel() { this.dialog.close(); } handleConfirm() { this.dialog.close(); updateAiControlDefault(AiControlGlobalStates.blocked); } render() { return html`

`; } } customElements.define( "block-ai-confirmation-dialog", BlockAiConfirmationDialog ); const AI_CONTROL_OPTIONS = [ { value: AiControlStates.available, l10nId: "preferences-ai-controls-state-available", }, { value: AiControlStates.enabled, l10nId: "preferences-ai-controls-state-enabled", }, { value: AiControlStates.blocked, l10nId: "preferences-ai-controls-state-blocked", }, ]; /** * Validates that a URL is trustworthy (HTTPS or localhost). * * @param {string} url - The URL to validate. * @returns {boolean} True if URL is HTTPS or localhost, otherwise false. */ function validateEndpointUrl(url) { if (!url) { return false; } try { const uri = Services.io.newURI(url); const principal = Services.scriptSecurityManager.createContentPrincipal( uri, {} ); return principal.isOriginPotentiallyTrustworthy; } catch { return false; } } Preferences.addSetting({ id: "aiControlDefaultToggle", pref: "browser.ai.control.default", setup() { document.body.append( document.createElement("block-ai-confirmation-dialog") ); }, get: prefVal => prefVal in AiControlGlobalStates ? prefVal == AiControlGlobalStates.blocked : AiControlGlobalStates.available, set(inputVal, _, setting) { if (inputVal) { // Restore the toggle to not pressed, we're opening a dialog setting.onChange(); let dialog = /** @type {BlockAiConfirmationDialog} */ ( document.querySelector("block-ai-confirmation-dialog") ); dialog.showModal(); } else { updateAiControlDefault(AiControlGlobalStates.available); } return AiControlGlobalStates.available; }, }); /** * @param {object} options * @param {string} options.id Setting id to create * @param {string} options.pref Pref id for the state * @param {OnDeviceModelFeaturesEnum} options.feature Feature id for removing models * @param {boolean} [options.supportsEnabled] If the feature supports the "enabled" state * @param {SettingConfig['getControlConfig']} [options.getControlConfig] A getControlConfig implementation. */ function makeAiControlSetting({ id, pref, feature, supportsEnabled = true, getControlConfig, }) { Preferences.addSetting({ id, pref, deps: ["aiControlDefault", "aiControlsShowUnavailable"], setup(emitChange) { /** * @param {nsISupports} _ * @param {string} __ * @param {string} changedFeature */ const featureChange = (_, __, changedFeature) => { if (changedFeature == feature) { emitChange(); } }; Services.obs.addObserver(featureChange, "OnDeviceModelManagerChange"); return () => Services.obs.removeObserver( featureChange, "OnDeviceModelManagerChange" ); }, get(prefVal, deps) { if ( prefVal == AiControlStates.blocked || (prefVal == AiControlStates.default && deps.aiControlDefault.value == AiControlGlobalStates.blocked) || OnDeviceModelManager.isBlocked(feature) ) { return AiControlStates.blocked; } if ( supportsEnabled && (prefVal == AiControlStates.enabled || OnDeviceModelManager.isEnabled(feature)) ) { return AiControlStates.enabled; } return AiControlStates.available; }, set(prefVal) { if (prefVal == AiControlStates.available) { OnDeviceModelManager.reset(feature); } else if (prefVal == AiControlStates.enabled) { OnDeviceModelManager.enable(feature); } else if (prefVal == AiControlStates.blocked) { OnDeviceModelManager.disable(feature); } return prefVal; }, disabled() { return OnDeviceModelManager.isManagedByPolicy(feature); }, visible(deps) { return ( OnDeviceModelManager.isAllowed(feature) || deps.aiControlsShowUnavailable.value ); }, getControlConfig, }); } makeAiControlSetting({ id: "aiControlTranslationsSelect", pref: "browser.ai.control.translations", feature: OnDeviceModelManager.features.Translations, supportsEnabled: false, getControlConfig(config, _, setting) { let isBlocked = setting.value == AiControlStates.blocked; let moreSettingsLink = config.options.at(-1); moreSettingsLink.hidden = isBlocked; config.supportPage = isBlocked ? "website-translation" : null; return config; }, }); makeAiControlSetting({ id: "aiControlPdfjsAltTextSelect", pref: "browser.ai.control.pdfjsAltText", feature: OnDeviceModelManager.features.PdfAltText, }); makeAiControlSetting({ id: "aiControlSmartTabGroupsSelect", pref: "browser.ai.control.smartTabGroups", feature: OnDeviceModelManager.features.TabGroups, }); makeAiControlSetting({ id: "aiControlLinkPreviewKeyPointsSelect", pref: "browser.ai.control.linkPreviewKeyPoints", feature: OnDeviceModelManager.features.KeyPoints, }); // sidebar chatbot Preferences.addSetting({ id: "chatbotProviderItem" }); Preferences.addSetting({ id: "chatbotProvider", pref: "browser.ml.chat.provider", }); Preferences.addSetting( /** @type {{ feature: OnDeviceModelFeaturesEnum } & SettingConfig } */ ({ id: "aiControlSidebarChatbotSelect", pref: "browser.ai.control.sidebarChatbot", deps: ["aiControlDefault", "chatbotProvider", "aiControlsShowUnavailable"], feature: OnDeviceModelManager.features.SidebarChatbot, setup(emitChange) { lazy.GenAI.init(); /** * @param {nsISupports} _ * @param {string} __ * @param {string} changedFeature */ const featureChange = (_, __, changedFeature) => { if (changedFeature == this.feature) { emitChange(); } }; Services.obs.addObserver(featureChange, "OnDeviceModelManagerChange"); return () => Services.obs.removeObserver( featureChange, "OnDeviceModelManagerChange" ); }, get(prefVal, deps) { if ( prefVal == AiControlStates.blocked || (prefVal == AiControlStates.default && deps.aiControlDefault.value == AiControlGlobalStates.blocked) || OnDeviceModelManager.isBlocked(this.feature) ) { return AiControlStates.blocked; } return deps.chatbotProvider.value || AiControlStates.available; }, set(inputVal, deps) { if (inputVal == AiControlStates.blocked) { OnDeviceModelManager.disable(this.feature); return inputVal; } if (inputVal == AiControlStates.available) { OnDeviceModelManager.reset(this.feature); return inputVal; } if (inputVal) { // Enable the chatbot sidebar so it can be used with this provider. OnDeviceModelManager.enable(this.feature); deps.chatbotProvider.value = inputVal; } return AiControlStates.enabled; }, disabled() { return OnDeviceModelManager.isManagedByPolicy(this.feature); }, visible(deps) { return ( OnDeviceModelManager.isAllowed(this.feature) || deps.aiControlsShowUnavailable.value ); }, getControlConfig(config, _, setting) { let providerUrl = setting.value; let options = config.options.slice(0, 3); lazy.GenAI.chatProviders.forEach((provider, url) => { let isSelected = url == providerUrl; // @ts-expect-error provider.hidden isn't in the typing if (!isSelected && provider.hidden) { return; } options.push({ value: url, controlAttrs: { label: provider.name }, }); }); if (!options.some(opt => opt.value == providerUrl)) { options.push({ value: providerUrl, controlAttrs: { label: providerUrl }, }); } return { ...config, options, }; }, }) ); Preferences.addSetting({ id: "smartWindowEnabled", pref: "browser.smartwindow.enabled", }); Preferences.addSetting({ id: "smartWindowFieldset", deps: ["smartWindowEnabled"], visible: deps => { return deps.smartWindowEnabled.value; }, }); Preferences.addSetting({ id: "aiFeaturesSmartWindowGroup", }); Preferences.addSetting({ id: "smartWindowToConsentTime", pref: "browser.smartwindow.tos.consentTime", }); Preferences.addSetting({ id: "activateSmartWindowLink", deps: ["smartWindowEnabled", "smartWindowToConsentTime"], visible: deps => { return ( deps.smartWindowEnabled.value && !deps.smartWindowToConsentTime.value ); }, onUserClick(e) { e.preventDefault(); const browser = window.browsingContext.embedderElement; lazy.AIWindow.launchWindow(browser, true); }, }); Preferences.addSetting({ id: "personalizeSmartWindowButton", deps: ["smartWindowEnabled", "smartWindowToConsentTime"], visible: deps => { return deps.smartWindowEnabled.value && deps.smartWindowToConsentTime.value; }, onUserClick(e) { e.preventDefault(); window.gotoPref("panePersonalizeSmartWindow"); }, }); Preferences.addSetting({ id: "smartWindowEndpoint", pref: "browser.smartwindow.endpoint", }); Preferences.addSetting({ id: "smartWindowModel", pref: "browser.smartwindow.model", }); Preferences.addSetting({ id: "smartWindowApiKey", pref: "browser.smartwindow.apiKey", }); Preferences.addSetting({ id: "smartWindowPreferencesEndpoint", pref: "browser.smartwindow.preferences.endpoint", }); Preferences.addSetting({ id: "smartWindowFirstRunModelChoice", pref: "browser.smartwindow.firstrun.modelChoice", }); Preferences.addSetting({ id: "modelSelection", deps: [ "smartWindowModel", "smartWindowFirstRunModelChoice", "smartWindowEndpoint", "smartWindowPreferencesEndpoint", ], get(_, deps) { const modelChoice = deps.smartWindowFirstRunModelChoice.value; if (modelChoice) { return modelChoice; } // Fall back to no selection return null; }, set(value, deps) { // Save model selection // Preset models save pref immediately, "Custom" waits for clicking the Save button if (value !== "0") { // Switching to preset const endpointEl = document.getElementById("customModelEndpoint"); const currentEndpoint = endpointEl?.value?.trim(); if (currentEndpoint) { deps.smartWindowPreferencesEndpoint.value = currentEndpoint; } Services.prefs.clearUserPref("browser.smartwindow.endpoint"); } // Write index to firstrun.modelChoice deps.smartWindowFirstRunModelChoice.value = value; }, }); Preferences.addSetting({ id: "customModelName", deps: [ "smartWindowFirstRunModelChoice", "smartWindowModel", "smartWindowEndpoint", "smartWindowPreferencesEndpoint", ], visible: deps => deps.smartWindowFirstRunModelChoice.value === "0", get(_, deps) { return deps.smartWindowModel.value || ""; }, }); Preferences.addSetting({ id: "customModelEndpoint", deps: [ "smartWindowFirstRunModelChoice", "smartWindowEndpoint", "smartWindowPreferencesEndpoint", ], visible: deps => deps.smartWindowFirstRunModelChoice.value === "0", get(_, deps) { const defaultEndpoint = Services.prefs .getDefaultBranch("") .getStringPref("browser.smartwindow.endpoint", ""); // Show saved endpoint if user has set a custom value if its different from default if ( deps.smartWindowEndpoint.value && deps.smartWindowEndpoint.value !== defaultEndpoint ) { return deps.smartWindowEndpoint.value; } // Show backup endpoint when switching back to custom if (deps.smartWindowPreferencesEndpoint.value) { return deps.smartWindowPreferencesEndpoint.value; } return ""; }, onUserChange(value) { const saveButton = document.getElementById("customModelSaveButton"); if (saveButton) { saveButton.disabled = !validateEndpointUrl(value?.trim()); } }, }); Preferences.addSetting({ id: "customModelAuthToken", deps: ["smartWindowFirstRunModelChoice", "smartWindowApiKey"], visible: deps => deps.smartWindowFirstRunModelChoice.value === "0", get(_, deps) { if (deps.smartWindowApiKey.value) { return deps.smartWindowApiKey.value; } return ""; }, }); Preferences.addSetting({ id: "customModelHelpLink", deps: ["smartWindowFirstRunModelChoice"], visible: deps => deps.smartWindowFirstRunModelChoice.value === "0", }); Preferences.addSetting({ id: "customModelSaveButton", deps: [ "smartWindowFirstRunModelChoice", "smartWindowModel", "smartWindowEndpoint", "smartWindowApiKey", "smartWindowPreferencesEndpoint", ], visible: deps => deps.smartWindowFirstRunModelChoice.value === "0", disabled() { // Read from input element since setting only updates on Save button const endpoint = document .getElementById("customModelEndpoint") ?.value?.trim(); return !validateEndpointUrl(endpoint); }, onUserClick(e, deps) { const doc = e.target.ownerDocument; // TODO: (bug 2014287) Utilize ways of handling the input changes instead of using document.getElementById() const modelName = doc.getElementById("customModelName")?.value?.trim() || ""; const modelEndpoint = doc.getElementById("customModelEndpoint")?.value?.trim() || ""; const modelAuthToken = doc.getElementById("customModelAuthToken")?.value?.trim() || ""; if (!validateEndpointUrl(modelEndpoint)) { console.warn("For custom setting URL must be HTTPS or localhost"); e.target.disabled = true; return; } // custom uses .model pref deps.smartWindowModel.value = modelName; deps.smartWindowEndpoint.value = modelEndpoint; deps.smartWindowApiKey.value = modelAuthToken; // Update backup custom endpoint when saving deps.smartWindowPreferencesEndpoint.value = modelEndpoint; }, }); Preferences.addSetting({ id: "learnFromActivityWrapper" }); Preferences.addSetting({ id: "learnFromActivity", pref: "browser.smartwindow.memories", }); Preferences.addSetting({ id: "manageMemoriesButton", onUserClick(e) { e.preventDefault(); window.gotoPref("manageMemories"); }, }); Preferences.addSetting({ id: "memories" }); Preferences.addSetting({ id: "memory-item", onUserClick(e) { const action = e.target.getAttribute("action"); const memoryId = e.target.getAttribute("memoryId"); if (action === "delete") { lazy.MemoryStore.hardDeleteMemory(memoryId); } }, }); Preferences.addSetting({ id: "deleteAllMemoriesButton", async onUserClick() { const memories = await lazy.MemoryStore.getMemories(); if (!memories.length) { return; } const [title, message, deleteButton, cancelButton] = await document.l10n.formatValues([ { id: "ai-window-delete-all-memories-title" }, { id: "ai-window-delete-all-memories-message" }, { id: "ai-window-delete-all-memories-confirm" }, { id: "ai-window-delete-all-memories-cancel" }, ]); const buttonFlags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING + Services.prompt.BUTTON_POS_0_DEFAULT; const result = await Services.prompt.asyncConfirmEx( window.browsingContext, Services.prompt.MODAL_TYPE_CONTENT, title, message, buttonFlags, deleteButton, cancelButton, null, null, false, { useTitle: true, headerIconCSSValue: CommonDialog.DEFAULT_APP_ICON_CSS, } ); if (result.get("buttonNumClicked") === 0) { for (const memory of memories) { try { await lazy.MemoryStore.hardDeleteMemory(memory.id); } catch (err) { console.error("Failed to delete memory:", memory.id, err); } } } }, }); Preferences.addSetting({ id: "no-memories-stored" }); Preferences.addSetting({ id: "memories-list-header" }); Preferences.addSetting( class extends Preferences.AsyncSetting { static id = "memoriesList"; setup() { Services.obs.addObserver(this.emitChange, "memory-store-changed"); Services.prefs.addObserver( "browser.smartwindow.memories", this.emitChange ); return () => { Services.obs.removeObserver(this.emitChange, "memory-store-changed"); Services.prefs.removeObserver( "browser.smartwindow.memories", this.emitChange ); }; } async getMemories() { return lazy.MemoryStore.getMemories(); } async getControlConfig() { const memories = await this.getMemories(); const isLearningEnabled = Services.prefs.getBoolPref( "browser.smartwindow.memories", false ); if (!memories.length) { return { items: [ { id: "no-memories-stored", l10nId: isLearningEnabled ? "ai-window-no-memories" : "ai-window-no-memories-learning-off", control: "placeholder-message", }, ], }; } return { items: [ { id: "memories-list-header", control: "moz-box-item", items: [ { id: "deleteAllMemoriesButton", control: "moz-button", l10nId: "ai-window-delete-all-memories-button", iconSrc: "chrome://global/skin/icons/delete.svg", }, ], }, ...memories.map((memory, index) => ({ id: `memory-item`, key: `memory-${index}`, control: "moz-box-item", controlAttrs: { ".label": memory.memory_summary, }, options: [ { control: "moz-button", iconSrc: "chrome://global/skin/icons/delete.svg", l10nId: "ai-window-memory-delete-button", l10nArgs: { label: memory.memory_summary }, controlAttrs: { slot: "actions-start", action: "delete", memoryId: memory.id, }, }, ], })), ], }; } } ); SettingGroupManager.registerGroups({ aiControlsDescription: { card: "never", items: [ { id: "aiControlsDescription", control: "moz-card", controlAttrs: { class: "ai-controls-description", }, options: [ { control: "p", options: [ { control: "span", l10nId: "preferences-ai-controls-description", }, { control: "span", controlAttrs: { ".textContent": " ", }, }, { control: "a", controlAttrs: { is: "moz-support-link", "support-page": "firefox-ai-controls", }, }, ], }, { control: "img", controlAttrs: { src: "chrome://browser/skin/preferences/fox-ai.svg", }, }, ], }, ], }, aiStatesDescription: { card: "never", items: [ { id: "aiStatesDescription", control: "footer", controlAttrs: { class: "text-deemphasized", }, options: [ { control: "span", l10nId: "preferences-ai-controls-state-description-before", }, { control: "ul", options: [ { control: "li", l10nId: "preferences-ai-controls-state-description-available", }, { control: "li", l10nId: "preferences-ai-controls-state-description-enabled", }, { control: "li", l10nId: "preferences-ai-controls-state-description-blocked", }, ], }, ], }, ], }, aiFeatures: { card: "always", items: [ { id: "blockAiGroup", control: "moz-box-item", items: [ { id: "aiControlDefaultToggle", l10nId: "preferences-ai-controls-block-ai", control: "moz-toggle", controlAttrs: { headinglevel: 2, inputlayout: "inline-end", }, options: [ { l10nId: "preferences-ai-controls-block-ai-description", control: "span", slot: "description", options: [ { control: "a", controlAttrs: { "data-l10n-name": "link", "support-page": "firefox-ai-controls", is: "moz-support-link", }, }, ], }, ], }, { id: "aiBlockedMessage", control: "moz-message-bar", l10nId: "preferences-ai-controls-blocked-message", }, ], }, { id: "onDeviceFieldset", l10nId: "preferences-ai-controls-on-device-group", supportPage: "on-device-models", control: "moz-fieldset", controlAttrs: { headinglevel: 2, iconsrc: "chrome://browser/skin/device-desktop.svg", }, items: [ { id: "onDeviceGroup", control: "moz-box-group", options: [ { control: "moz-box-item", items: [ { id: "aiControlTranslationsSelect", l10nId: "preferences-ai-controls-translations-control", control: "moz-select", controlAttrs: { inputlayout: "inline-end", }, options: [ ...AI_CONTROL_OPTIONS.filter( opt => opt.value != AiControlStates.enabled ), { control: "a", l10nId: "preferences-ai-controls-translations-more-link", slot: "support-link", controlAttrs: { href: "#general-translations", }, }, ], }, ], }, { control: "moz-box-item", items: [ { id: "aiControlPdfjsAltTextSelect", l10nId: "preferences-ai-controls-pdfjs-control", control: "moz-select", controlAttrs: { inputlayout: "inline-end", }, supportPage: "pdf-alt-text", options: [...AI_CONTROL_OPTIONS], }, ], }, { control: "moz-box-item", items: [ { id: "aiControlSmartTabGroupsSelect", l10nId: "preferences-ai-controls-tab-group-suggestions-control", control: "moz-select", controlAttrs: { inputlayout: "inline-end", }, supportPage: "how-use-ai-enhanced-tab-groups", options: [...AI_CONTROL_OPTIONS], }, ], }, { control: "moz-box-item", items: [ { id: "aiControlLinkPreviewKeyPointsSelect", l10nId: "preferences-ai-controls-key-points-control", control: "moz-select", controlAttrs: { inputlayout: "inline-end", }, supportPage: "use-link-previews-firefox", options: [...AI_CONTROL_OPTIONS], }, ], }, ], }, ], }, { id: "smartWindowFieldset", l10nId: "ai-window-features-group", control: "moz-fieldset", controlAttrs: { headinglevel: 2, }, items: [ { id: "aiFeaturesSmartWindowGroup", control: "moz-box-group", items: [ { id: "activateSmartWindowLink", l10nId: "ai-window-activate-link", control: "moz-box-link", }, { id: "personalizeSmartWindowButton", l10nId: "ai-window-personalize-button", control: "moz-box-button", }, ], }, ], }, { id: "sidebarChatbotFieldset", control: "moz-fieldset", l10nId: "preferences-ai-controls-sidebar-chatbot-group", supportPage: "ai-chatbot", controlAttrs: { headinglevel: 2, iconsrc: "chrome://browser/skin/sidebars.svg", }, items: [ { id: "chatbotProviderItem", control: "moz-box-item", items: [ { id: "aiControlSidebarChatbotSelect", l10nId: "preferences-ai-controls-sidebar-chatbot-control", control: "moz-select", controlAttrs: { inputlayout: "inline-end", }, options: [ { l10nId: "preferences-ai-controls-state-available", value: AiControlStates.available, }, { l10nId: "preferences-ai-controls-state-blocked", value: AiControlStates.blocked, }, { control: "hr" }, ], }, ], }, ], }, ], }, assistantModelGroup: { l10nId: "smart-window-model-section", headingLevel: 2, supportPage: "smart-window-model", items: [ { id: "modelSelection", control: "moz-radio-group", options: [ { value: "1", l10nId: "smart-window-model-fast", l10nArgs: { modelName: "gemini-flash-lite" }, }, { value: "2", l10nId: "smart-window-model-flexible", l10nArgs: { modelName: "Qwen3-235B-A22B-throughput" }, }, { value: "3", l10nId: "smart-window-model-personal", l10nArgs: { modelName: "gpt-oss-120b" }, }, { value: "0", l10nId: "smart-window-model-custom", items: [ { id: "customModelName", l10nId: "smart-window-model-custom-name", control: "moz-input-text", }, { id: "customModelEndpoint", l10nId: "smart-window-model-custom-url", control: "moz-input-url", }, { id: "customModelAuthToken", l10nId: "smart-window-model-custom-token", control: "moz-input-password", }, { id: "customModelHelpLink", control: "moz-message-bar", l10nId: "smart-window-model-custom-help", controlAttrs: { type: "info", }, options: [ { control: "a", l10nId: "smart-window-model-custom-more-link", slot: "support-link", controlAttrs: { href: "", }, }, ], }, { id: "customModelSaveButton", control: "moz-button", l10nId: "smart-window-model-custom-save", controlAttrs: { type: "primary", }, }, ], }, ], }, ], }, memoriesGroup: { l10nId: "ai-window-memories-section", headingLevel: 2, // TODO: Finalize SUMO support page slug (GENAI-3016) supportPage: "smart-window-memories", items: [ { id: "memories", control: "moz-box-group", items: [ { id: "learnFromActivityWrapper", control: "moz-box-item", items: [ { id: "learnFromActivity", l10nId: "ai-window-learn-from-activity", control: "moz-checkbox", }, ], }, { id: "manageMemoriesButton", l10nId: "ai-window-manage-memories-button", control: "moz-box-button", }, ], }, ], }, manageMemories: { items: [ { id: "memoriesList", control: "moz-box-group", controlAttrs: { type: "list", }, }, ], }, });