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:
@@ -24,6 +24,8 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const LinkPreview = {
|
export const LinkPreview = {
|
||||||
|
// Shared downloading state to use across multiple previews
|
||||||
|
downloadingModel: false,
|
||||||
keyboardComboActive: false,
|
keyboardComboActive: false,
|
||||||
_windowStates: new Map(),
|
_windowStates: new Map(),
|
||||||
linkPreviewPanelId: "link-preview-panel",
|
linkPreviewPanelId: "link-preview-panel",
|
||||||
@@ -189,6 +191,8 @@ export const LinkPreview = {
|
|||||||
createOGCard(doc, pageData) {
|
createOGCard(doc, pageData) {
|
||||||
const ogCard = doc.createElement("link-preview-card");
|
const ogCard = doc.createElement("link-preview-card");
|
||||||
ogCard.pageData = pageData;
|
ogCard.pageData = pageData;
|
||||||
|
// Assume we need to wait if another generate is downloading.
|
||||||
|
ogCard.showWait = this.downloadingModel;
|
||||||
|
|
||||||
if (pageData.article.textContent) {
|
if (pageData.article.textContent) {
|
||||||
this.generateKeyPoints(ogCard);
|
this.generateKeyPoints(ogCard);
|
||||||
@@ -222,8 +226,14 @@ export const LinkPreview = {
|
|||||||
await lazy.LinkPreviewModel.generateTextAI(
|
await lazy.LinkPreviewModel.generateTextAI(
|
||||||
ogCard.pageData.article.textContent,
|
ogCard.pageData.article.textContent,
|
||||||
{
|
{
|
||||||
|
onDownload: state => {
|
||||||
|
ogCard.showWait = state;
|
||||||
|
this.downloadingModel = state;
|
||||||
|
},
|
||||||
onError: console.error,
|
onError: console.error,
|
||||||
onText(text) {
|
onText: text => {
|
||||||
|
// Clear waiting in case a different generate handled download.
|
||||||
|
ogCard.showWait = false;
|
||||||
ogCard.addKeyPoint(text);
|
ogCard.addKeyPoint(text);
|
||||||
if (--expected == 0) {
|
if (--expected == 0) {
|
||||||
ogCard.generating = false;
|
ogCard.generating = false;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const MIN_WORD_COUNT = 5;
|
|||||||
const lazy = {};
|
const lazy = {};
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
createEngine: "chrome://global/content/ml/EngineProcess.sys.mjs",
|
createEngine: "chrome://global/content/ml/EngineProcess.sys.mjs",
|
||||||
|
Progress: "chrome://global/content/ml/Utils.sys.mjs",
|
||||||
});
|
});
|
||||||
XPCOMUtils.defineLazyPreferenceGetter(
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
lazy,
|
lazy,
|
||||||
@@ -233,10 +234,11 @@ export const LinkPreviewModel = {
|
|||||||
*
|
*
|
||||||
* @param {string} inputText
|
* @param {string} inputText
|
||||||
* @param {object} callbacks for progress and error
|
* @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.onText optional for text chunks
|
||||||
* @param {Function} callbacks.onError optional for error
|
* @param {Function} callbacks.onError optional for error
|
||||||
*/
|
*/
|
||||||
async generateTextAI(inputText, { onText, onError } = {}) {
|
async generateTextAI(inputText, { onDownload, onText, onError } = {}) {
|
||||||
const processedInput = this.preprocessText(inputText);
|
const processedInput = this.preprocessText(inputText);
|
||||||
// Asssume generated text is approximately the same length as the input.
|
// Asssume generated text is approximately the same length as the input.
|
||||||
const nPredict = Math.ceil(processedInput.length / CHARACTERS_PER_TOKEN);
|
const nPredict = Math.ceil(processedInput.length / CHARACTERS_PER_TOKEN);
|
||||||
@@ -251,7 +253,8 @@ export const LinkPreviewModel = {
|
|||||||
|
|
||||||
let engine;
|
let engine;
|
||||||
try {
|
try {
|
||||||
engine = await lazy.createEngine({
|
engine = await lazy.createEngine(
|
||||||
|
{
|
||||||
backend: "wllama",
|
backend: "wllama",
|
||||||
engineId: "wllamapreview",
|
engineId: "wllamapreview",
|
||||||
kvCacheDtype: "q8_0",
|
kvCacheDtype: "q8_0",
|
||||||
@@ -269,7 +272,15 @@ export const LinkPreviewModel = {
|
|||||||
useMlock: false,
|
useMlock: false,
|
||||||
useMmap: true,
|
useMmap: true,
|
||||||
...JSON.parse(lazy.config),
|
...JSON.parse(lazy.config),
|
||||||
});
|
},
|
||||||
|
data => {
|
||||||
|
if (data.type == lazy.Progress.ProgressType.DOWNLOAD) {
|
||||||
|
onDownload?.(
|
||||||
|
data.statusText != lazy.Progress.ProgressStatusText.DONE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const postProcessor = new SentencePostProcessor();
|
const postProcessor = new SentencePostProcessor();
|
||||||
for await (const val of engine.runWithGenerator({
|
for await (const val of engine.runWithGenerator({
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||||||
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
|
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.
|
* Class representing a link preview element.
|
||||||
*
|
*
|
||||||
@@ -19,9 +23,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||||||
*/
|
*/
|
||||||
class LinkPreviewCard extends MozLitElement {
|
class LinkPreviewCard extends MozLitElement {
|
||||||
static properties = {
|
static properties = {
|
||||||
generating: { type: Number },
|
generating: { type: Number }, // 0 = off, 1-4 = generating & dots state
|
||||||
keyPoints: { type: Array },
|
keyPoints: { type: Array },
|
||||||
pageData: { type: Object },
|
pageData: { type: Object },
|
||||||
|
showWait: { type: Boolean },
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -61,11 +66,13 @@ class LinkPreviewCard extends MozLitElement {
|
|||||||
updated(properties) {
|
updated(properties) {
|
||||||
if (properties.has("generating")) {
|
if (properties.has("generating")) {
|
||||||
if (this.generating > 0) {
|
if (this.generating > 0) {
|
||||||
|
// Count up to 4 so that we can show 0 to 3 dots.
|
||||||
this.dotsTimeout = setTimeout(
|
this.dotsTimeout = setTimeout(
|
||||||
() => (this.generating = (this.generating % 3) + 1),
|
() => (this.generating = (this.generating % 4) + 1),
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Setting to false or 0 means we're done generating.
|
||||||
clearTimeout(this.dotsTimeout);
|
clearTimeout(this.dotsTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,15 +94,14 @@ class LinkPreviewCard extends MozLitElement {
|
|||||||
metaData["og:title"] ||
|
metaData["og:title"] ||
|
||||||
metaData["twitter:title"] ||
|
metaData["twitter:title"] ||
|
||||||
metaData["html:title"] ||
|
metaData["html:title"] ||
|
||||||
pageUrl ||
|
"This link can’t be previewed";
|
||||||
"";
|
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
articleData.excerpt ||
|
articleData.excerpt ||
|
||||||
metaData["og:description"] ||
|
metaData["og:description"] ||
|
||||||
metaData["twitter:description"] ||
|
metaData["twitter:description"] ||
|
||||||
metaData.description ||
|
metaData.description ||
|
||||||
"";
|
"No Reason. Just ’cause. (better error handling incoming)";
|
||||||
|
|
||||||
const imageUrl =
|
const imageUrl =
|
||||||
metaData["og:image"] || metaData["twitter:image:src"] || "";
|
metaData["og:image"] || metaData["twitter:image:src"] || "";
|
||||||
@@ -136,14 +142,31 @@ class LinkPreviewCard extends MozLitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="ai-content">
|
<div class="ai-content">
|
||||||
<h3>
|
<h3>
|
||||||
Generat${this.generating ? "ing" : "ed"} key
|
${this.generating
|
||||||
points${".".repeat(this.generating)}
|
? "Generating key points" + ".".repeat(this.generating - 1)
|
||||||
|
: "Key points"}
|
||||||
</h3>
|
</h3>
|
||||||
<ul>
|
<ul>
|
||||||
${this.keyPoints.map(item => html`<li>${item}</li>`)}
|
${this.keyPoints.map(item => html`<li>${item}</li>`)}
|
||||||
</ul>
|
</ul>
|
||||||
<hr />
|
<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>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
const { LinkPreview } = ChromeUtils.importESModule(
|
const { LinkPreview } = ChromeUtils.importESModule(
|
||||||
"moz-src:///browser/components/genai/LinkPreview.sys.mjs"
|
"moz-src:///browser/components/genai/LinkPreview.sys.mjs"
|
||||||
);
|
);
|
||||||
|
const { LinkPreviewModel } = ChromeUtils.importESModule(
|
||||||
|
"moz-src:///browser/components/genai/LinkPreviewModel.sys.mjs"
|
||||||
|
);
|
||||||
const { sinon } = ChromeUtils.importESModule(
|
const { sinon } = ChromeUtils.importESModule(
|
||||||
"resource://testing-common/Sinon.sys.mjs"
|
"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]],
|
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(
|
window.dispatchEvent(
|
||||||
new KeyboardEvent("keydown", {
|
new KeyboardEvent("keydown", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -129,6 +140,28 @@ add_task(async function test_link_preview_panel_shown() {
|
|||||||
|
|
||||||
is(stub.callCount, 1, "would have generated key points");
|
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();
|
panel.remove();
|
||||||
stub.restore();
|
stub.restore();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user