Bug 1954815 - add first usage message, feedback link, visit page link r=txia,firefox-ai-ml-reviewers,atossou

Hardcode some text that gets shown on first time and additional links

Differential Revision: https://phabricator.services.mozilla.com/D242045
This commit is contained in:
Ed Lee
2025-03-20 00:05:38 +00:00
parent 57cbc274ad
commit 1e6628313b
4 changed files with 107 additions and 30 deletions

View File

@@ -24,6 +24,8 @@ XPCOMUtils.defineLazyPreferenceGetter(
);
export const LinkPreview = {
// Shared downloading state to use across multiple previews
downloadingModel: false,
keyboardComboActive: false,
_windowStates: new Map(),
linkPreviewPanelId: "link-preview-panel",
@@ -189,6 +191,8 @@ export const LinkPreview = {
createOGCard(doc, pageData) {
const ogCard = doc.createElement("link-preview-card");
ogCard.pageData = pageData;
// Assume we need to wait if another generate is downloading.
ogCard.showWait = this.downloadingModel;
if (pageData.article.textContent) {
this.generateKeyPoints(ogCard);
@@ -222,8 +226,14 @@ export const LinkPreview = {
await lazy.LinkPreviewModel.generateTextAI(
ogCard.pageData.article.textContent,
{
onDownload: state => {
ogCard.showWait = state;
this.downloadingModel = state;
},
onError: console.error,
onText(text) {
onText: text => {
// Clear waiting in case a different generate handled download.
ogCard.showWait = false;
ogCard.addKeyPoint(text);
if (--expected == 0) {
ogCard.generating = false;

View File

@@ -19,6 +19,7 @@ const MIN_WORD_COUNT = 5;
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
createEngine: "chrome://global/content/ml/EngineProcess.sys.mjs",
Progress: "chrome://global/content/ml/Utils.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
@@ -233,10 +234,11 @@ export const LinkPreviewModel = {
*
* @param {string} inputText
* @param {object} callbacks for progress and error
* @param {Function} callbacks.onDownload optional for download active
* @param {Function} callbacks.onText optional for text chunks
* @param {Function} callbacks.onError optional for error
*/
async generateTextAI(inputText, { onText, onError } = {}) {
async generateTextAI(inputText, { onDownload, onText, onError } = {}) {
const processedInput = this.preprocessText(inputText);
// Asssume generated text is approximately the same length as the input.
const nPredict = Math.ceil(processedInput.length / CHARACTERS_PER_TOKEN);
@@ -251,25 +253,34 @@ export const LinkPreviewModel = {
let engine;
try {
engine = await lazy.createEngine({
backend: "wllama",
engineId: "wllamapreview",
kvCacheDtype: "q8_0",
modelFile: "smollm2-360m-instruct-q8_0.gguf",
modelHubRootUrl: "https://model-hub.mozilla.org",
modelHubUrlTemplate: "{model}/{revision}",
modelId: "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF",
modelRevision: "main",
numBatch: numContext,
numContext,
numUbatch: numContext,
runtimeFilename: "wllama.wasm",
taskName: "wllama-text-generation",
timeoutMS: -1,
useMlock: false,
useMmap: true,
...JSON.parse(lazy.config),
});
engine = await lazy.createEngine(
{
backend: "wllama",
engineId: "wllamapreview",
kvCacheDtype: "q8_0",
modelFile: "smollm2-360m-instruct-q8_0.gguf",
modelHubRootUrl: "https://model-hub.mozilla.org",
modelHubUrlTemplate: "{model}/{revision}",
modelId: "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF",
modelRevision: "main",
numBatch: numContext,
numContext,
numUbatch: numContext,
runtimeFilename: "wllama.wasm",
taskName: "wllama-text-generation",
timeoutMS: -1,
useMlock: false,
useMmap: true,
...JSON.parse(lazy.config),
},
data => {
if (data.type == lazy.Progress.ProgressType.DOWNLOAD) {
onDownload?.(
data.statusText != lazy.Progress.ProgressStatusText.DONE
);
}
}
);
const postProcessor = new SentencePostProcessor();
for await (const val of engine.runWithGenerator({

View File

@@ -12,6 +12,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
});
// TODO put in actual link probably same as labs bug 1951144
const FEEDBACK_LINK =
"https://docs.google.com/spreadsheets/d/1hsG7UXGJRN8D4ViaETICDyA0gbBArzmib1qTylmIu8M";
/**
* Class representing a link preview element.
*
@@ -19,9 +23,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
*/
class LinkPreviewCard extends MozLitElement {
static properties = {
generating: { type: Number },
generating: { type: Number }, // 0 = off, 1-4 = generating & dots state
keyPoints: { type: Array },
pageData: { type: Object },
showWait: { type: Boolean },
};
constructor() {
@@ -61,11 +66,13 @@ class LinkPreviewCard extends MozLitElement {
updated(properties) {
if (properties.has("generating")) {
if (this.generating > 0) {
// Count up to 4 so that we can show 0 to 3 dots.
this.dotsTimeout = setTimeout(
() => (this.generating = (this.generating % 3) + 1),
() => (this.generating = (this.generating % 4) + 1),
500
);
} else {
// Setting to false or 0 means we're done generating.
clearTimeout(this.dotsTimeout);
}
}
@@ -87,15 +94,14 @@ class LinkPreviewCard extends MozLitElement {
metaData["og:title"] ||
metaData["twitter:title"] ||
metaData["html:title"] ||
pageUrl ||
"";
"This link cant be previewed";
const description =
articleData.excerpt ||
metaData["og:description"] ||
metaData["twitter:description"] ||
metaData.description ||
"";
"No Reason. Just cause. (better error handling incoming)";
const imageUrl =
metaData["og:image"] || metaData["twitter:image:src"] || "";
@@ -136,14 +142,31 @@ class LinkPreviewCard extends MozLitElement {
? html`
<div class="ai-content">
<h3>
Generat${this.generating ? "ing" : "ed"} key
points${".".repeat(this.generating)}
${this.generating
? "Generating key points" + ".".repeat(this.generating - 1)
: "Key points"}
</h3>
<ul>
${this.keyPoints.map(item => html`<li>${item}</li>`)}
</ul>
<hr />
<p>AI-generated content may be inaccurate</p>
${this.showWait
? html`<p>
This may take a moment the first time you preview a link.
Key points should appear more quickly next time.
</p>`
: ""}
<p>
Key points are AI-generated and may be wrong.
<a @click=${this.handleLink} href=${FEEDBACK_LINK}>
Foxfooding feedback
</a>
</p>
<p>
<a @click=${this.handleLink} href=${pageUrl}>
Visit original page
</a>
</p>
</div>
`
: ""}

View File

@@ -4,6 +4,9 @@
const { LinkPreview } = ChromeUtils.importESModule(
"moz-src:///browser/components/genai/LinkPreview.sys.mjs"
);
const { LinkPreviewModel } = ChromeUtils.importESModule(
"moz-src:///browser/components/genai/LinkPreviewModel.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs"
);
@@ -110,7 +113,15 @@ add_task(async function test_link_preview_panel_shown() {
set: [["browser.ml.linkPreview.enabled", true]],
});
const stub = sinon.stub(LinkPreview, "generateKeyPoints");
let onDownload, toResolve;
const stub = sinon
.stub(LinkPreviewModel, "generateTextAI")
.callsFake(async (text, options) => {
onDownload = options.onDownload;
toResolve = Promise.withResolvers();
return toResolve.promise;
});
window.dispatchEvent(
new KeyboardEvent("keydown", {
bubbles: true,
@@ -129,6 +140,28 @@ add_task(async function test_link_preview_panel_shown() {
is(stub.callCount, 1, "would have generated key points");
const card = panel.querySelector("link-preview-card");
ok(card, "card created for link preview");
ok(card.generating, "initially marked as generating");
ok(!card.showWait, "initially assume not waiting");
ok(!LinkPreview.downloadingModel, "initially assume not downloading");
onDownload(true);
ok(card.showWait, "switched to waiting when download initiates");
ok(LinkPreview.downloadingModel, "shared waiting for download");
onDownload(false);
ok(!card.showWait, "no longer waiting after download complete");
ok(!LinkPreview.downloadingModel, "downloading updated");
ok(card.generating, "still generating");
toResolve.resolve();
await LinkPreview.lastRequest;
ok(!card.generating, "done generating");
panel.remove();
stub.restore();
});