From 4c62bb6510d7f66f20014919a3261cec4578e469 Mon Sep 17 00:00:00 2001 From: Ed Lee Date: Sat, 3 Aug 2024 04:19:46 +0000 Subject: [PATCH] Bug 1910005 - Expose chat shortcuts pref as part of experimental subitem r=tarek,fluent-reviewers,settings-reviewers,desktop-theme-reviewers,dao,mstriemer Switch default shortcuts enabled with labs checkbox to disable and labs visibility. Add metric and event telemetry for shortcuts pref and usage. Bump pip labs ordering up one. Differential Revision: https://phabricator.services.mozilla.com/D217782 --- browser/app/profile/firefox.js | 6 +- browser/components/genai/GenAI.sys.mjs | 104 +++++++++++++-- browser/components/genai/metrics.yaml | 120 ++++++++++++++++++ .../tests/browser/browser_chat_shortcuts.js | 16 ++- .../tests/browser/browser_chat_telemetry.js | 11 ++ .../preferences/experimental.inc.xhtml | 4 + browser/locales-preview/genai.ftl | 10 +- toolkit/components/featuregates/Features.toml | 24 ++-- 8 files changed, 266 insertions(+), 29 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 9116e0b9351d..b04f84ae6b78 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1962,8 +1962,12 @@ pref("browser.ml.chat.prompts.0", '{"label":"Summarize","value":"Please summariz pref("browser.ml.chat.prompts.1", '{"label":"Simplify Language","value":"Please rewrite the selection in plain, clear language suitable for a general audience without specialized knowledge. Use all of the following tactics: simple vocabulary; short sentences; active voice; headers and bulleted lists for scannability. Maintain meaning and factual accuracy.","id":"simplify"}'); pref("browser.ml.chat.prompts.2", '{"label":"Quiz Me","value":"Please quiz me on this selection. Ask me a variety of types of questions, for example multiple choice, true or false, and short answer. Wait for my response before moving on to the next question.","id":"quiz","targeting":"!provider|regExpMatch(\'gemini\')"}'); pref("browser.ml.chat.provider", ""); -pref("browser.ml.chat.shortcuts", false); +pref("browser.ml.chat.shortcuts", true); +#ifdef NIGHTLY_BUILD +pref("browser.ml.chat.shortcuts.custom", true); +#else pref("browser.ml.chat.shortcuts.custom", false); +#endif pref("browser.ml.chat.sidebar", true); pref("security.protectionspopup.recordEventTelemetry", true); diff --git a/browser/components/genai/GenAI.sys.mjs b/browser/components/genai/GenAI.sys.mjs index 163e7782aef4..f5a360b47522 100644 --- a/browser/components/genai/GenAI.sys.mjs +++ b/browser/components/genai/GenAI.sys.mjs @@ -21,6 +21,18 @@ XPCOMUtils.defineLazyPreferenceGetter( "chatEnabled", "browser.ml.chat.enabled" ); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "chatHideFromLabs", + "browser.ml.chat.hideFromLabs", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "chatHideLabsShortcuts", + "browser.ml.chat.hideLabsShortcuts", + false +); XPCOMUtils.defineLazyPreferenceGetter( lazy, "chatHideLocalhost", @@ -61,7 +73,9 @@ XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter( lazy, "chatShortcuts", - "browser.ml.chat.shortcuts" + "browser.ml.chat.shortcuts", + null, + (_pref, _old, val) => onChatShortcutsChange(val) ); XPCOMUtils.defineLazyPreferenceGetter( lazy, @@ -175,14 +189,11 @@ export const GenAI = { * Handle startup tasks like telemetry, adding listeners. */ init() { - Glean.genaiChatbot.enabled.set(lazy.chatEnabled); - Glean.genaiChatbot.provider.set(this.getProviderId()); - Glean.genaiChatbot.sidebar.set(lazy.chatSidebar); - // Access getters for side effects of observing pref changes lazy.chatHideLocalhost; lazy.chatProvider; lazy.chatProviders; + lazy.chatShortcuts; // Apply initial ordering of providers reorderChatProviders(); @@ -221,6 +232,13 @@ export const GenAI = { this.buildPreferences(content); } }); + + // Record glean metrics after applying nimbus prefs + Glean.genaiChatbot.enabled.set(lazy.chatEnabled); + Glean.genaiChatbot.provider.set(this.getProviderId()); + Glean.genaiChatbot.shortcuts.set(lazy.chatShortcuts); + Glean.genaiChatbot.shortcutsCustom.set(lazy.chatShortcutsCustom); + Glean.genaiChatbot.sidebar.set(lazy.chatSidebar); }, /** @@ -240,15 +258,17 @@ export const GenAI = { * @param {MozBrowser} browser providing context * @param {string} selection text * @param {Function} itemAdder creates and returns the item + * @param {string} entry name * @param {Function} cleanup optional on item activation * @returns {object} context used for selecting prompts */ - async addAskChatItems(browser, selection, itemAdder, cleanup) { + async addAskChatItems(browser, selection, itemAdder, entry, cleanup) { // Prepare context used for both targeting and handling prompts const window = browser.ownerGlobal; const tab = window.gBrowser.getTabForBrowser(browser); const uri = browser.currentURI; const context = { + entry, provider: lazy.chatProvider, selection, tabTitle: (tab._labelIsContentTitle && tab.label) || "", @@ -321,6 +341,7 @@ export const GenAI = { button.textContent = promptObj.label; return button; }, + "shortcuts", hide ); @@ -346,6 +367,9 @@ export const GenAI = { () => shortcuts.removeAttribute("active"), { once: true } ); + Glean.genaiChatbot.shortcutsExpanded.record({ + selection: shortcuts.selection.length, + }); } }); } @@ -355,10 +379,18 @@ export const GenAI = { if (shortcuts.timeout) { lazy.clearTimeout(shortcuts.timeout); } + // Save the latest selection so it can be used by timeout and popup + shortcuts.selection = data.selection; shortcuts.timeout = lazy.setTimeout(() => { - // Save the latest selection so it can be used by the popup - shortcuts.selection = data.selection; + // Pref might have changed since the timeout started + if (!lazy.chatShortcuts || shortcuts.hasAttribute("shown")) { + return; + } + shortcuts.toggleAttribute("shown"); + Glean.genaiChatbot.shortcutsDisplayed.record({ + selection: shortcuts.selection.length, + }); // Position the shortcuts relative to the browser's top-left corner const rect = browser.getBoundingClientRect(); @@ -393,7 +425,8 @@ export const GenAI = { await this.addAskChatItems( nsContextMenu.browser, nsContextMenu.selectionInfo.fullText ?? "", - promptObj => menu.appendItem(promptObj.label) + promptObj => menu.appendItem(promptObj.label), + "menu" ); nsContextMenu.showItem(menu, menu.itemCount > 0); }, @@ -462,7 +495,11 @@ export const GenAI = { * @param {object} context of how the prompt should be handled */ async handleAskChat(promptObj, context) { - Glean.genaiChatbot.contextmenuPromptClick.record({ + Glean.genaiChatbot[ + context.entry == "menu" + ? "contextmenuPromptClick" + : "shortcutsPromptClick" + ].record({ prompt: promptObj.id ?? "custom", provider: this.getProviderId(), selection: context.selection?.length ?? 0, @@ -507,14 +544,29 @@ export const GenAI = { * @param {Window} window for about:preferences */ buildPreferences({ document, Preferences }) { + // Section can be hidden by featuregate targeting const providerEl = document.getElementById("genai-chat-provider"); if (!providerEl) { return; } + // Some experiments might want to hide shortcuts + const shortcutsEl = document.getElementById("genai-chat-shortcuts"); + if (lazy.chatHideLabsShortcuts || lazy.chatHideFromLabs) { + shortcutsEl.remove(); + } + + // Page can load (restore at startup) just before default prefs apply + if (lazy.chatHideFromLabs) { + providerEl.parentNode.remove(); + document.getElementById("genai-chat").remove(); + return; + } + const enabled = Preferences.get("browser.ml.chat.enabled"); const onEnabledChange = () => { providerEl.disabled = !enabled.value; + shortcutsEl.disabled = !enabled.value; // Update enabled telemetry Glean.genaiChatbot.enabled.set(enabled.value); @@ -587,6 +639,23 @@ export const GenAI = { }; onProviderChange(); provider.on("change", onProviderChange); + + const shortcuts = Preferences.add({ + id: "browser.ml.chat.shortcuts", + type: "bool", + }); + const onShortcutsChange = () => { + // Update shortcuts telemetry + Glean.genaiChatbot.shortcuts.set(shortcuts.value); + if (onShortcutsChange.canChange) { + Glean.genaiChatbot.shortcutsCheckboxClick.record({ + enabled: shortcuts.value, + }); + } + onShortcutsChange.canChange = true; + }; + onShortcutsChange(); + shortcuts.on("change", onShortcutsChange); }, // nsIObserver @@ -608,6 +677,21 @@ function onChatProviderChange(value) { } } +/** + * Ensure the chat shortcuts get hidden. + * + * @param {bool} value New pref value + */ +function onChatShortcutsChange(value) { + if (!value) { + lazy.EveryWindow.readyWindows.forEach(window => + window.document + .querySelectorAll(".content-shortcuts") + .forEach(shortcuts => shortcuts.removeAttribute("shown")) + ); + } +} + /** * Update the ordering of chat providers Map. */ diff --git a/browser/components/genai/metrics.yaml b/browser/components/genai/metrics.yaml index 4db66b8048cc..d254358101e3 100644 --- a/browser/components/genai/metrics.yaml +++ b/browser/components/genai/metrics.yaml @@ -45,6 +45,40 @@ genai.chatbot: send_in_pings: - metrics + shortcuts: + type: boolean + lifetime: application + description: > + Indicates if the chatbot feature would show shortcuts on selection. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - metrics + + shortcuts_custom: + type: boolean + lifetime: application + description: > + Indicates if the chatbot feature would show shortcuts custom input. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - metrics + sidebar: type: boolean lifetime: application @@ -108,6 +142,26 @@ genai.chatbot: type: string description: Which UI surface. + shortcuts_checkbox_click: + type: event + description: > + Chatbot shortcuts checkbox was clicked to enable/disable. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - events + extra_keys: + enabled: + type: boolean + description: New/current state is enabled. + contextmenu_prompt_click: type: event description: > @@ -134,6 +188,72 @@ genai.chatbot: type: quantity description: Selected text length. + shortcuts_displayed: + type: event + description: > + Shortcuts displayed on text selection. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - events + extra_keys: + selection: + type: quantity + description: Selected text length. + + shortcuts_expanded: + type: event + description: > + Shortcuts expanded to show prompts. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - events + extra_keys: + selection: + type: quantity + description: Selected text length. + + shortcuts_prompt_click: + type: event + description: > + Chatbot prompt was clicked from the shortcuts. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1910005 + data_reviews: + - https://phabricator.services.mozilla.com/D217782 + data_sensitivity: + - interaction + expires: 134 + notification_emails: + - elee@mozilla.com + send_in_pings: + - events + extra_keys: + prompt: + type: string + description: Selected prompt id. + provider: + type: string + description: Selected provider id. + selection: + type: quantity + description: Selected text length. + sidebar_toggle: type: event description: > diff --git a/browser/components/genai/tests/browser/browser_chat_shortcuts.js b/browser/components/genai/tests/browser/browser_chat_shortcuts.js index d038159860d1..041515f28f24 100644 --- a/browser/components/genai/tests/browser/browser_chat_shortcuts.js +++ b/browser/components/genai/tests/browser/browser_chat_shortcuts.js @@ -22,6 +22,7 @@ add_task(async function test_no_shortcuts() { * Check that shortcuts get shown on selection and open popup and sidebar */ add_task(async function test_show_shortcuts() { + Services.fog.testResetFOG(); await SpecialPowers.pushPrefEnv({ set: [ ["browser.ml.chat.shortcuts", true], @@ -32,20 +33,33 @@ add_task(async function test_show_shortcuts() { await SimpleTest.promiseFocus(browser); goDoCommand("cmd_selectAll"); const shortcuts = await TestUtils.waitForCondition(() => - document.querySelector(".content-shortcuts") + document.querySelector(".content-shortcuts[shown]") ); Assert.ok(shortcuts, "Shortcuts added on select"); + let events = Glean.genaiChatbot.shortcutsDisplayed.testGetValue(); + Assert.ok(events.length, "Shortcuts shown"); + Assert.equal(events[0].extra.selection, 2, "Selected hi"); const popup = document.getElementById("ask-chat-shortcuts"); Assert.equal(popup.state, "closed", "Popup is closed"); EventUtils.sendMouseEvent({ type: "mouseover" }, shortcuts); await BrowserTestUtils.waitForEvent(popup, "popupshown"); + Assert.equal(popup.state, "open", "Popup is open"); + events = Glean.genaiChatbot.shortcutsExpanded.testGetValue(); + Assert.equal(events.length, 1, "One shortcuts opened"); + Assert.equal(events[0].extra.selection, 2, "Selected hi"); Assert.ok(!SidebarController.isOpen, "Sidebar is closed"); popup.querySelector("toolbarbutton").click(); + Assert.ok(SidebarController.isOpen, "Chat opened sidebar"); + events = Glean.genaiChatbot.shortcutsPromptClick.testGetValue(); + Assert.equal(events.length, 1, "One shortcut clicked"); + Assert.equal(events[0].extra.prompt, "summarize", "Picked summarize"); + Assert.equal(events[0].extra.provider, "localhost", "With localhost"); + Assert.equal(events[0].extra.selection, 2, "Selected hi"); SidebarController.hide(); }); diff --git a/browser/components/genai/tests/browser/browser_chat_telemetry.js b/browser/components/genai/tests/browser/browser_chat_telemetry.js index d4dd4015228b..cd21722a39ff 100644 --- a/browser/components/genai/tests/browser/browser_chat_telemetry.js +++ b/browser/components/genai/tests/browser/browser_chat_telemetry.js @@ -16,6 +16,17 @@ add_task(async function test_default_telemetry() { "none", "Default no provider for test" ); + Assert.equal( + Glean.genaiChatbot.shortcuts.testGetValue() ?? true, + true, + "Default shortcuts for test" + ); + Assert.equal( + Glean.genaiChatbot.shortcutsCustom.testGetValue() ?? + AppConstants.NIGHTLY_BUILD, + AppConstants.NIGHTLY_BUILD, + "Default shortcuts_custom for test" + ); Assert.equal( Glean.genaiChatbot.sidebar.testGetValue() ?? true, true, diff --git a/browser/components/preferences/experimental.inc.xhtml b/browser/components/preferences/experimental.inc.xhtml index 44d9925cda75..cd3c3be8af56 100644 --- a/browser/components/preferences/experimental.inc.xhtml +++ b/browser/components/preferences/experimental.inc.xhtml @@ -51,4 +51,8 @@ + diff --git a/browser/locales-preview/genai.ftl b/browser/locales-preview/genai.ftl index 3123045ffb2b..5acca85bcaf0 100644 --- a/browser/locales-preview/genai.ftl +++ b/browser/locales-preview/genai.ftl @@ -5,11 +5,8 @@ ## Generative AI (GenAI) Settings section genai-settings-chat-title = - .label = AI Chatbot Integration -genai-settings-chat-description = - Select text on a webpage and right-click to see relevant prompts like summarize, simplify, and more. { -brand-short-name } sends the selected text, page title, and prompt to the chatbot of your choice. If you try this feature, share your thoughts on Connect. - - We plan to add more chatbots as we continue to experiment. + .label = AI chatbot +genai-settings-chat-description = Adds the chatbot of your choice to the sidebar, for quick access as you browse. Share feedback genai-settings-chat-choose = Choose a chatbot genai-settings-chat-choose-one-menuitem = .label = Choose one @@ -21,3 +18,6 @@ genai-settings-chat-gemini-links = By choosing Google Gemini, you agree to the < genai-settings-chat-huggingchat-links = By choosing HuggingChat, you agree to the HuggingChat Privacy Notice and Hugging Face Privacy Policy. genai-settings-chat-lechat-links = By choosing Le Chat Mistral, you agree to the Mistral AI Terms of Service and Privacy Policy. genai-settings-chat-localhost-links = Bring your own private local chatbot such as llamafile from { -vendor-short-name }’s Innovation group. +genai-settings-chat-shortcuts = + .description = Displays a shortcut to prompts when you select text. { -brand-short-name } sends the text, page title, and prompt to the chatbot. + .label = Show prompts on text select diff --git a/toolkit/components/featuregates/Features.toml b/toolkit/components/featuregates/Features.toml index 5693b9f2bd08..fa7e575d5be5 100644 --- a/toolkit/components/featuregates/Features.toml +++ b/toolkit/components/featuregates/Features.toml @@ -23,18 +23,7 @@ restart-required = false preference = "browser.ml.chat.enabled" type = "boolean" bug-numbers = [1894999] -is-public-jexl = "nightly_build" -default-value-jexl = "false" - -[url-bar-ime-search] -group = "experimental-features-group-browsing" -title = "experimental-features-ime-search" -description = "experimental-features-ime-search-description" -restart-required = false -preference = "browser.urlbar.keepPanelOpenDuringImeComposition" -type = "boolean" -bug-numbers = [1673971] -is-public-jexl = "true" +is-public-jexl = "!'browser.ml.chat.hideFromLabs'|preferenceValue" default-value-jexl = "false" [auto-pip] @@ -48,6 +37,17 @@ bug-numbers = [1647800] is-public-jexl = "true" default-value-jexl = "false" +[url-bar-ime-search] +group = "experimental-features-group-browsing" +title = "experimental-features-ime-search" +description = "experimental-features-ime-search-description" +restart-required = false +preference = "browser.urlbar.keepPanelOpenDuringImeComposition" +type = "boolean" +bug-numbers = [1673971] +is-public-jexl = "true" +default-value-jexl = "false" + [webrtc-global-mute-toggles] group = "experimental-features-group-browsing" title = "experimental-features-webrtc-global-mute-toggles"