/* 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"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/sidebar/sidebar-panel-header.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs", SmartAssistEngine: "moz-src:///browser/components/genai/SmartAssistEngine.sys.mjs", }); const FULL_PAGE_URL = "chrome://browser/content/genai/smartAssistPage.html"; /** * A custom element for managing the smart assistant sidebar. */ export class SmartAssist extends MozLitElement { static properties = { userPrompt: { type: String }, aiResponse: { type: String }, conversationState: { type: Array }, mode: { type: String }, // "tab" | "sidebar" overrideNewTab: { type: Boolean }, }; constructor() { super(); this.userPrompt = ""; // TODO the conversation state will evenually need to be stored in a "higher" location // then just the state of this lit component. This is a Stub to get the convo started for now this.conversationState = [ { role: "system", content: "You are a helpful assistant" }, ]; this.mode = "sidebar"; this.overrideNewTab = Services.prefs.getBoolPref( "browser.ml.smartAssist.overrideNewTab" ); } connectedCallback() { super.connectedCallback(); if (this.mode === "sidebar" && this.overrideNewTab) { this._applyNewTabOverride(true); } } /** * Adds a new message to the conversation history. * * @param {object} chatEntry - A message object to add to the conversation * @param {("system"|"user"|"assistant")} chatEntry.role - The role of the message sender * @param {string} chatEntry.content - The text content of the message */ _updateConversationState = chatEntry => { this.conversationState = [...this.conversationState, chatEntry]; }; _handlePromptInput = e => { const value = e.target.value; this.userPrompt = value; }; _handleSubmit = async () => { const formattedPrompt = (this.userPrompt || "").trim(); if (!formattedPrompt) { return; } // Push user prompt this._updateConversationState({ role: "user", content: formattedPrompt }); this.userPrompt = ""; // Create an empty assistant placeholder. this._updateConversationState({ role: "assistant", content: "" }); const latestAssistantMessageIndex = this.conversationState.length - 1; let acc = ""; try { const stream = lazy.SmartAssistEngine.fetchWithHistory( this.conversationState ); for await (const chunk of stream) { acc += chunk; this.conversationState[latestAssistantMessageIndex] = { ...this.conversationState[latestAssistantMessageIndex], content: acc, }; this.requestUpdate?.(); } } catch (e) { this.conversationState[latestAssistantMessageIndex] = { role: "assistant", content: `There was an error`, }; this.requestUpdate?.(); } }; /** * Mock Functionality to open full page UX * * @param {boolean} enable * Whether or not to override the new tab page. */ _applyNewTabOverride(enable) { try { enable ? (lazy.AboutNewTab.newTabURL = FULL_PAGE_URL) : lazy.AboutNewTab.resetNewTabURL(); } catch (e) { console.error("Failed to toggle new tab override:", e); } } _onToggleFullPage(e) { const isChecked = e.target.checked; Services.prefs.setBoolPref( "browser.ml.smartAssist.overrideNewTab", isChecked ); this.overrideNewTab = isChecked; this._applyNewTabOverride(isChecked); } render() { return html`