Bug 1894998 - Add prefs to control chat, provider, prompts r=tarek

Differential Revision: https://phabricator.services.mozilla.com/D209638
This commit is contained in:
Ed Lee 2024-06-06 14:21:15 +00:00
Родитель 3198e67a52
Коммит bc3442f25c
6 изменённых файлов: 223 добавлений и 2 удалений

Просмотреть файл

@ -1897,6 +1897,13 @@ pref("pdfjs.handleOctetStream", true);
pref("sidebar.position_start", true);
pref("sidebar.revamp", false);
pref("browser.ml.chat.enabled", false);
pref("browser.ml.chat.prompt.prefix", 'Im on page "%currentTabTitle%" with "%selection|12000%" selected. ');
pref("browser.ml.chat.prompts.0", '{"label":"Summarize","value":"Please summarize the selection using precise and concise language. Highlight the main themes and conclusions. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning of the selection."}');
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; examples where applicable to make explanations clearer; explanations for jargon and technical terms; headers and bulleted lists for scannability. Maintain factual accuracy while simplifying."}');
pref("browser.ml.chat.prompts.2", '{"label":"Quiz me","value":"Please create questions related to the selection. Ask the questions one by one. Wait for my response before moving on to the next question. Evaluate each response. Ask a variety of types of questions, like multiple choice, true or false and short answer."}');
pref("browser.ml.chat.provider", "");
pref("security.protectionspopup.recordEventTelemetry", true);
pref("security.app_menu.recordEventTelemetry", true);

Просмотреть файл

@ -4,6 +4,25 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"chatEnabled",
"browser.ml.chat.enabled"
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"chatPromptPrefix",
"browser.ml.chat.prompt.prefix"
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"chatProvider",
"browser.ml.chat.provider"
);
export const GenAI = {
/**
* Build prompts menu to ask chat for context menu or popup.
@ -12,11 +31,65 @@ export const GenAI = {
* @param {nsContextMenu} context Additional menu context
*/
buildAskChatMenu(menu, context) {
if (!lazy.chatEnabled || lazy.chatProvider == "") {
context.showItem(menu, false);
return;
}
menu.context = context;
menu.label = "Ask chatbot";
menu.menupopup?.remove();
Services.prefs.getChildList("browser.ml.chat.prompts.").forEach(pref => {
try {
let prompt = Services.prefs.getStringPref(pref);
try {
prompt = JSON.parse(prompt);
} catch (ex) {}
menu.appendItem(prompt.label ?? prompt, prompt.value ?? "");
} catch (ex) {
console.error("Failed to add menu item for " + pref, ex);
}
});
context.showItem(menu, menu.itemCount > 0);
},
/**
* Handle selected prompt by opening tab or sidebar.
* Build a prompt with context.
*
* @param {MozMenuItem} item Use value falling back to label
* @param {object} context Placeholder keys with values to replace
* @returns {string} Prompt with placeholders replaced
*/
handleAskChat() {},
buildChatPrompt(item, context = {}) {
// Combine prompt prefix with the item then replace placeholders from the
// original prompt (and not from context)
return (lazy.chatPromptPrefix + (item.value || item.label)).replace(
// Handle %placeholder% as key|options
/\%(\w+)(?:\|([^%]+))?\%/g,
(placeholder, key, options) =>
// Currently only supporting numeric options for slice with `undefined`
// resulting in whole string
context[key]?.slice(0, options) ?? placeholder
);
},
/**
* Handle selected prompt by opening tab or sidebar.
*
* @param {Event} event from menu command
*/
handleAskChat({ target }) {
const win = target.ownerGlobal;
const { selectedTab } = win.gBrowser;
const url = new URL(lazy.chatProvider);
url.searchParams.set(
"q",
this.buildChatPrompt(target, {
currentTabTitle:
(selectedTab._labelIsContentTitle && selectedTab.label) || "",
selection: target.closest("menu").context.selectionInfo.fullText ?? "",
})
);
win.openWebLinkIn(url + "", "tab", { relatedToCurrent: true });
},
};

Просмотреть файл

@ -10,3 +10,5 @@ with Files("**"):
EXTRA_JS_MODULES += [
"GenAI.sys.mjs",
]
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"]

Просмотреть файл

@ -35,3 +35,23 @@ add_task(async function test_hidden_menu() {
await hideContextMenu();
});
});
/**
* Check that chat context menu is shown with appropriate prefs set
*/
add_task(async function test_menu_enabled() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.ml.chat.enabled", true],
["browser.ml.chat.provider", "http://localhost:8080"],
],
});
await BrowserTestUtils.withNewTab("about:blank", async () => {
await openContextMenu();
Assert.ok(
!document.getElementById("context-ask-chat").hidden,
"Ask chat menu is shown"
);
await hideContextMenu();
});
});

Просмотреть файл

@ -0,0 +1,115 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const { GenAI } = ChromeUtils.importESModule(
"resource:///modules/GenAI.sys.mjs"
);
add_setup(() => {
Services.prefs.setStringPref("browser.ml.chat.prompt.prefix", "");
registerCleanupFunction(() =>
Services.prefs.clearUserPref("browser.ml.chat.prompt.prefix")
);
});
/**
* Check that prompts come from label or value
*/
add_task(function test_basic_prompt() {
Assert.equal(
GenAI.buildChatPrompt({ label: "a" }),
"a",
"Uses label for prompt"
);
Assert.equal(
GenAI.buildChatPrompt({ value: "b" }),
"b",
"Uses value for prompt"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "a", value: "b" }),
"b",
"Prefers value for prompt"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "a", value: "" }),
"a",
"Falls back to label for prompt"
);
});
/**
* Check that placeholders can use context
*/
add_task(function test_prompt_placeholders() {
Assert.equal(
GenAI.buildChatPrompt({ label: "%a%" }),
"%a%",
"Placeholder kept without context"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a%" }, { a: "z" }),
"z",
"Placeholder replaced with context"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a%%a%%a%" }, { a: "z" }),
"zzz",
"Repeat placeholders replaced with context"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a% %b%" }, { a: "z" }),
"z %b%",
"Missing placeholder context not replaced"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a% %b%" }, { a: "z", b: "y" }),
"z y",
"Multiple placeholders replaced with context"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a% %b%" }, { a: "%b%", b: "y" }),
"%b% y",
"Placeholders from original prompt replaced with context"
);
});
/**
* Check that placeholder options are used
*/
add_task(function test_prompt_placeholder_options() {
Assert.equal(
GenAI.buildChatPrompt({ label: "%a|1%" }, { a: "xyz" }),
"x",
"Context reduced to 1"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a|2%" }, { a: "xyz" }),
"xy",
"Context reduced to 2"
);
Assert.equal(
GenAI.buildChatPrompt({ label: "%a|3%" }, { a: "xyz" }),
"xyz",
"Context kept to 3"
);
});
/**
* Check that prefix pref is added to prompt
*/
add_task(function test_prompt_prefix() {
Services.prefs.setStringPref("browser.ml.chat.prompt.prefix", "hello ");
Assert.equal(
GenAI.buildChatPrompt({ label: "world" }),
"hello world",
"Prefix and prompt combined"
);
Services.prefs.setStringPref("browser.ml.chat.prompt.prefix", "%a% ");
Assert.equal(
GenAI.buildChatPrompt({ label: "%a%" }, { a: "hi" }),
"hi hi",
"Context used for prefix and prompt"
);
});

Просмотреть файл

@ -0,0 +1,4 @@
[DEFAULT]
firefox-appdir = "browser"
["test_build_chat_prompt.js"]