@arraypress/extension-kit 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -2
- package/package.json +2 -2
- package/src/ai/model.d.ts +317 -0
- package/src/ai/model.js +312 -0
- package/src/build/vendor.js +1 -1
- package/src/content/slices.d.ts +63 -0
- package/src/content/slices.js +80 -0
- package/src/content/watch.d.ts +71 -0
- package/src/content/watch.js +95 -0
- package/src/index.d.ts +31 -4
- package/src/index.js +22 -4
- package/src/styles/chrome-ui.css +11 -0
- package/src/testing/e2e.d.ts +15 -0
- package/src/testing/e2e.js +26 -1
- package/src/testing/fake-model.d.ts +87 -0
- package/src/testing/fake-model.js +142 -0
- package/src/testing/fake-storage.d.ts +2 -0
- package/src/testing/fake-storage.js +15 -3
- package/src/testing/index.d.ts +11 -3
- package/src/testing/index.js +8 -3
- package/src/testing/messages.d.ts +33 -0
- package/src/testing/messages.js +51 -0
- package/src/ui/model-row.d.ts +111 -0
- package/src/ui/model-row.js +108 -0
- package/src/ui/text-dialog.d.ts +44 -7
- package/src/ui/text-dialog.js +54 -16
package/README.md
CHANGED
|
@@ -14,6 +14,11 @@ The pieces ArrayPress's Chrome extensions share, so each is written once:
|
|
|
14
14
|
("Rename folder", or several fields as "Add site search"), the toast with Undo and a settings card's site list, built on the
|
|
15
15
|
stylesheet's classes; they work in a content script's shadow root too.
|
|
16
16
|
- **Sites**: what people type into a per-site list, and whether a page is on one.
|
|
17
|
+
- **Chrome's on-device model** (Gemini Nano, the Prompt API): availability, a download from the
|
|
18
|
+
person's click with progress, one base session with a `clone()` per question, a time limit on
|
|
19
|
+
every answer, cold starts, and the settings page's “Chrome’s built-in AI” row.
|
|
20
|
+
- **Content-script helpers**: a big page worked through in slices that never hold it long, and a
|
|
21
|
+
change watcher for single-page apps and endless lists.
|
|
17
22
|
- **Build and test helpers** (Node): icon rendering, vendoring npm packages (with a staleness
|
|
18
23
|
check), test builds, store screenshots, zipping for the Chrome Web Store, a fake
|
|
19
24
|
`chrome.storage`, Playwright helpers with time limits, packaging checks, and a copied-code guard.
|
|
@@ -211,6 +216,81 @@ Registers, updates or unregisters the script `id` to match what `want()` gives a
|
|
|
211
216
|
none). Calls run one after another, so a quick run of changes can't register twice or end out of
|
|
212
217
|
order; a refused change rejects that call only.
|
|
213
218
|
|
|
219
|
+
## Chrome's on-device model
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
import { chromeLanguageModel, createModelSession } from '../vendor/extension-kit/index.js';
|
|
223
|
+
|
|
224
|
+
const ENGLISH = { expectedInputs: [{ type: 'text', languages: ['en'] }], expectedOutputs: [{ type: 'text', languages: ['en'] }] };
|
|
225
|
+
const model = createModelSession({
|
|
226
|
+
model: chromeLanguageModel, // read when used: Chrome may add it after start-up
|
|
227
|
+
options: ENGLISH, // availability() is asked with what create() gets
|
|
228
|
+
initialPrompts: [{ role: 'system', content: 'You name downloaded files.' }, ...EXAMPLE],
|
|
229
|
+
});
|
|
230
|
+
const answer = await model.ask(prompt, { timeoutMs: 2500, signal, responseConstraint: schema });
|
|
231
|
+
if (answer.text === null) useRules(answer.reason); // 'unavailable' | 'late' | 'aborted' | 'failed'
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Following https://developer.chrome.com/docs/ai/prompt-api: only an `available` model is used (a
|
|
235
|
+
question never downloads it); one base session is made when first needed and kept; each question
|
|
236
|
+
goes to a `clone()` of it, let go after; the answer's `AbortSignal` fires at the time limit. The
|
|
237
|
+
first session after Chrome starts can take ~18 s while the model loads: a question with a shorter
|
|
238
|
+
limit comes back `'late'`, and the session goes on loading for the next one. Nothing loads it ahead
|
|
239
|
+
of time (that would load the model on every start). `release()` lets the base session go.
|
|
240
|
+
|
|
241
|
+
`modelAvailability(api, options)` never throws (`'missing'` without a Prompt API).
|
|
242
|
+
`downloadModel(api, options, {onProgress})` is the only `create()` that may download: call it
|
|
243
|
+
first in a click's handler, with nothing awaited before it. The settings row:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<div class="row two-line">
|
|
247
|
+
<span class="label">
|
|
248
|
+
<span data-i18n="optionsModel">Chrome’s built-in AI</span>
|
|
249
|
+
<span class="secondary"><span id="model-status"></span> <span id="model-help" hidden></span></span>
|
|
250
|
+
<progress id="model-progress" max="1" value="0" hidden></progress>
|
|
251
|
+
</span>
|
|
252
|
+
<button id="model-download" type="button" data-i18n="optionsModelDownload" hidden>Download</button>
|
|
253
|
+
</div>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
const row = createModelRow(
|
|
258
|
+
{ status: byId('model-status'), help: byId('model-help'), download: byId('model-download', HTMLButtonElement), progress: byId('model-progress', HTMLProgressElement) },
|
|
259
|
+
{ options: ENGLISH, state: () => ask(MESSAGES.modelState), t, onShown: ({ state }) => { useAi.disabled = state === 'off'; } },
|
|
260
|
+
);
|
|
261
|
+
await row.refresh();
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Its words are the page's messages under `MODEL_MESSAGES`' keys (`optionsModel`, `optionsModelReady`,
|
|
265
|
+
`optionsModelNeedsDownload`, `optionsModelDownloadHelp`, `optionsModelDownload`,
|
|
266
|
+
`optionsModelDownloading` with `$PERCENT$`, `optionsModelDownloadFailed`, `optionsModelUnsupported`,
|
|
267
|
+
`optionsModelRequirements`, `optionsModelMissing`, `optionsModelMissingHelp`); `describeModel` maps
|
|
268
|
+
a state to them. Tests use `fakeLanguageModel` (`/testing`), the Prompt API's shape: availability,
|
|
269
|
+
download progress (or a failed download), a cold start, slow and failed answers, aborts, and a log
|
|
270
|
+
of what it was asked.
|
|
271
|
+
|
|
272
|
+
## Content scripts
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
import { inSlices } from '../vendor/extension-kit/content/slices.js';
|
|
276
|
+
import { watchChanges } from '../vendor/extension-kit/content/watch.js';
|
|
277
|
+
|
|
278
|
+
await inSlices(document.body, (block) => scanner.scan(block)); // ≤4,000 characters a block, 40 ms slices
|
|
279
|
+
const stop = watchChanges(document.body, {
|
|
280
|
+
changed: (nodes) => nodes.forEach((node) => scanner.scan(node)), // one batch, each subtree once
|
|
281
|
+
removed: (node) => scanner.forget(node), // at once
|
|
282
|
+
ignore: (node) => node instanceof Element && node.localName === 'my-note', // the extension's own
|
|
283
|
+
settleMs: 120, maxWaitMs: 600,
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`inSlices(root, work, {blockChars, sliceMs, pause})` cuts a page into blocks (`blocksOf`) and
|
|
288
|
+
hands the main thread back whenever a slice has run its time, so a 2,000-item listing never
|
|
289
|
+
freezes. `watchChanges` hands over what was added or changed a little after the page stops
|
|
290
|
+
changing (at most `maxWaitMs` while it keeps on); an element with a few children (`regroupChildren`,
|
|
291
|
+
default 6) whose children changed is handed over whole. Both are pure DOM, so a content script
|
|
292
|
+
imports them directly (list them in `web_accessible_resources`).
|
|
293
|
+
|
|
214
294
|
## Time limits
|
|
215
295
|
|
|
216
296
|
```js
|
|
@@ -273,7 +353,11 @@ const site = await askText({
|
|
|
273
353
|
});
|
|
274
354
|
```
|
|
275
355
|
|
|
276
|
-
Resolves to the text, trimmed, or `null` when cancelled.
|
|
356
|
+
Resolves to the text, trimmed, or `null` when cancelled. A field can be `multiline` (a textarea,
|
|
357
|
+
`rows` high: Enter starts a new line), carry a `hint` under it (or a function of its text, kept up
|
|
358
|
+
as it's typed: “12 words”), and take a `value` that's still being worked out (a promise, say an
|
|
359
|
+
on-device model's suggestion): the field waits, disabled, with `waiting` under it, then takes the
|
|
360
|
+
text and the focus (unless the person has typed meanwhile); a failed one leaves it empty. The action button (default "Save") stays
|
|
277
361
|
disabled while the text is blank or `validate` gives a reason, which shows under the field
|
|
278
362
|
(`checkText` is that rule, pure). Enter presses the action button; Esc and Cancel cancel; the
|
|
279
363
|
focus goes back to what had it, or to `returnTo` (an element, or a function asked at the end).
|
|
@@ -389,10 +473,13 @@ except those listed, each with a reason:
|
|
|
389
473
|
|
|
390
474
|
| Export | What it does |
|
|
391
475
|
|---|---|
|
|
392
|
-
| `
|
|
476
|
+
| `fakeLanguageModel(state)` | A fake Prompt API (`LanguageModel`): `availability`, `create` (download progress, a cold start, `failDownload`), sessions that `clone`, answer (`answerMs`, `answer(prompt)`, `failPrompt`) and stop on their signal; `log` of what it was asked, `set` to change it. Browser-safe, so a test build can put it on `globalThis`. |
|
|
477
|
+
| `fakeChromeStorage({local, sync, session, now})` | In-memory `chrome.storage`: JSON copies, `onChanged` for real changes only, sync's 8,192-byte item quota, its 102,400 bytes in all (`SYNC_QUOTA_BYTES`) and 120 writes a minute. `settled()` waits for listeners. |
|
|
393
478
|
| `launchExtension(chromium, dir, options)` | Load an unpacked extension into a fresh profile; `{context, worker, extensionId, url(page), profile, close()}`. |
|
|
394
479
|
| `launchDownloading(chromium, dir, options)`, `filesIn(dir)` | An extension in a browser that downloads for real: `{…launchExtension's, downloads}`, the profile's own Downloads folder (Playwright otherwise saves downloads under made-up names no extension sees); the files under a folder, downloads still running left out. |
|
|
395
480
|
| `extensionProfile(chromium, dir, options)` | A profile to start the browser on more than once (a restart, a first start with a changed manifest): `{dir, launch(), remove()}`. |
|
|
481
|
+
| `runChecks(runner, body, cleanUps)` | A script's checks: an error outside any check fails, the clean-ups always run (each within 30 s), then the tally. |
|
|
482
|
+
| `messageLookup(extensionDir, locale)`, `readMessages(extensionDir, locale)` | `chrome.i18n.getMessage` over the real `_locales` (a locale over en, named placeholders filled), for unit tests. |
|
|
396
483
|
| `checkRunner({timeout})` | Named checks that report and go on: `check(name, fn, {timeout})`, `fail(name)` for an error found outside a check, `finish()`. A check that runs past its limit (`CHECK_MS`, 60 s) fails, and the run goes on. |
|
|
397
484
|
| `eachLimited(items, limit, work)` | Work through a list at most `limit` at once, `work(item, slot, index)` (also in the main entry). |
|
|
398
485
|
| `realisticBrowser(options)`, `chromeUserAgent(browser)` | Launch options and a user agent for a browser that doesn't say it's automated, for live checks. |
|
|
@@ -435,6 +522,7 @@ except those listed, each with a reason:
|
|
|
435
522
|
| Manager layout | `.manager`/`.layout`, `.sidebar` with `.tree` > `.tree-item`, `.main` with `.list-card` |
|
|
436
523
|
| Menu (cr-action-menu) | `<dialog class="menu">` of `button[role="menuitem"]`, `hr` (or `createMenu`) |
|
|
437
524
|
| ⋮ icon | `<span class="icon icon-more-vert" aria-hidden="true">` (or `moreActionsButton`) |
|
|
525
|
+
| Subpage header (settings-subpage) | `<div class="subpage-head">` with a back `button.icon-button` holding `.icon.icon-arrow-back`, then `<h2>` |
|
|
438
526
|
| Dialog (cr-dialog) | `<dialog class="dialog"><form method="dialog">` `.dialog-title`, `.dialog-body`, `.dialog-buttons` (`askText`, `askFields`) |
|
|
439
527
|
| Toast (cr-toast) | `<div class="toast" popover="manual">text <button>Undo</button></div>` (or `createToast`); with `hidden` instead of `popover`, it shows over the page but not the top layer |
|
|
440
528
|
| Drawer (cr-drawer) | `<dialog class="drawer">` with `.drawer-head` and a `.nav` |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arraypress/extension-kit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Shared pieces for Chrome extensions: Chrome's own page styles, a Chrome Sync settings store, the light/dark toolbar glyph, About links, and build and test helpers.",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Shared pieces for Chrome extensions: Chrome's own page styles and components, a Chrome Sync settings store, the light/dark toolbar glyph, Chrome's on-device model, content-script helpers, About links, and build and test helpers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"types": "src/index.d.ts",
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome's on-device model (Gemini Nano, through the Prompt API), the way
|
|
3
|
+
* every extension here uses it, following
|
|
4
|
+
* https://developer.chrome.com/docs/ai/prompt-api:
|
|
5
|
+
*
|
|
6
|
+
* - `availability()` is asked with the options `create()` gets, and only an
|
|
7
|
+
* 'available' model is used: nothing here downloads it, except
|
|
8
|
+
* {@link downloadModel}, which a page calls from the person's click (Chrome
|
|
9
|
+
* only downloads from a user gesture) and which reports progress;
|
|
10
|
+
* - one base session, with the instructions and examples, is made once and
|
|
11
|
+
* kept; each question goes to a `clone()` of it, so answers don't leak into
|
|
12
|
+
* each other;
|
|
13
|
+
* - each answer has a time limit: an `AbortSignal` stops the answer once
|
|
14
|
+
* it's late, and the caller gets a reason instead of a hang;
|
|
15
|
+
* - the first session after Chrome starts can take ~18 s while the model
|
|
16
|
+
* loads (a cold start, M3 Max). A question with a shorter limit gets
|
|
17
|
+
* 'late', and the session goes on loading for the next one. Nothing loads
|
|
18
|
+
* it ahead of time: that would load the model on every start for work that
|
|
19
|
+
* may never come.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here touches `chrome.*` or the global itself: the Prompt API comes
|
|
22
|
+
* in as `model` (read when used, since Chrome may add it after start-up), so
|
|
23
|
+
* tests hand it the kit's fake (`fakeLanguageModel`, `/testing`) at exactly
|
|
24
|
+
* this edge. TypeScript's DOM library doesn't declare the Prompt API yet, so
|
|
25
|
+
* the parts used are typed here.
|
|
26
|
+
*
|
|
27
|
+
* @module ai/model
|
|
28
|
+
*/
|
|
29
|
+
export type Availability = 'available' | 'downloadable' | 'downloading' | 'unavailable' | 'missing';
|
|
30
|
+
export type PromptOptions = {
|
|
31
|
+
/**
|
|
32
|
+
* Stops the answer.
|
|
33
|
+
*/
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* A JSON Schema (or RegExp) the answer must follow.
|
|
37
|
+
*/
|
|
38
|
+
responseConstraint?: object;
|
|
39
|
+
};
|
|
40
|
+
export type LanguageModelSession = {
|
|
41
|
+
/**
|
|
42
|
+
* The whole answer.
|
|
43
|
+
*/
|
|
44
|
+
prompt: (prompt: string, options?: PromptOptions) => Promise<string>;
|
|
45
|
+
/**
|
|
46
|
+
* A copy with the same history.
|
|
47
|
+
*/
|
|
48
|
+
clone: (options?: {
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}) => Promise<LanguageModelSession>;
|
|
51
|
+
/**
|
|
52
|
+
* Let it go.
|
|
53
|
+
*/
|
|
54
|
+
destroy: () => void;
|
|
55
|
+
};
|
|
56
|
+
export type InitialPrompt = {
|
|
57
|
+
role: 'system' | 'user' | 'assistant';
|
|
58
|
+
content: string;
|
|
59
|
+
};
|
|
60
|
+
export type LanguageModelOptions = {
|
|
61
|
+
/**
|
|
62
|
+
* What it's given.
|
|
63
|
+
*/
|
|
64
|
+
expectedInputs?: {
|
|
65
|
+
type: string;
|
|
66
|
+
languages: string[];
|
|
67
|
+
}[];
|
|
68
|
+
/**
|
|
69
|
+
* What it answers in.
|
|
70
|
+
*/
|
|
71
|
+
expectedOutputs?: {
|
|
72
|
+
type: string;
|
|
73
|
+
languages: string[];
|
|
74
|
+
}[];
|
|
75
|
+
/**
|
|
76
|
+
* Its instructions and examples.
|
|
77
|
+
*/
|
|
78
|
+
initialPrompts?: InitialPrompt[];
|
|
79
|
+
/**
|
|
80
|
+
* Hears `downloadprogress` (`loaded`, 0–1) while the model downloads.
|
|
81
|
+
*/
|
|
82
|
+
monitor?: (monitor: EventTarget) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Stops creating it.
|
|
85
|
+
*/
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
};
|
|
88
|
+
export type LanguageModelStatic = {
|
|
89
|
+
/**
|
|
90
|
+
* Whether it can run here.
|
|
91
|
+
*/
|
|
92
|
+
availability: (options?: LanguageModelOptions) => Promise<Availability>;
|
|
93
|
+
/**
|
|
94
|
+
* A session (downloads the model first, from a click).
|
|
95
|
+
*/
|
|
96
|
+
create: (options?: LanguageModelOptions) => Promise<LanguageModelSession>;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* The model's answer to `availability()`: 'available', 'downloadable',
|
|
100
|
+
* 'downloading' or 'unavailable'; and, from {@link modelAvailability},
|
|
101
|
+
* 'missing' when this Chrome has no Prompt API.
|
|
102
|
+
*
|
|
103
|
+
* @typedef {'available' | 'downloadable' | 'downloading' | 'unavailable' | 'missing'} Availability
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* @typedef {object} PromptOptions
|
|
107
|
+
* @property {AbortSignal} [signal] Stops the answer.
|
|
108
|
+
* @property {object} [responseConstraint] A JSON Schema (or RegExp) the answer must follow.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* @typedef {object} LanguageModelSession
|
|
112
|
+
* @property {(prompt: string, options?: PromptOptions) => Promise<string>} prompt The whole answer.
|
|
113
|
+
* @property {(options?: {signal?: AbortSignal}) => Promise<LanguageModelSession>} clone A copy with the same history.
|
|
114
|
+
* @property {() => void} destroy Let it go.
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* @typedef {{role: 'system' | 'user' | 'assistant', content: string}} InitialPrompt
|
|
118
|
+
*/
|
|
119
|
+
/**
|
|
120
|
+
* @typedef {object} LanguageModelOptions
|
|
121
|
+
* @property {{type: string, languages: string[]}[]} [expectedInputs] What it's given.
|
|
122
|
+
* @property {{type: string, languages: string[]}[]} [expectedOutputs] What it answers in.
|
|
123
|
+
* @property {InitialPrompt[]} [initialPrompts] Its instructions and examples.
|
|
124
|
+
* @property {(monitor: EventTarget) => void} [monitor] Hears `downloadprogress` (`loaded`, 0–1) while the model downloads.
|
|
125
|
+
* @property {AbortSignal} [signal] Stops creating it.
|
|
126
|
+
*/
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {object} LanguageModelStatic
|
|
129
|
+
* @property {(options?: LanguageModelOptions) => Promise<Availability>} availability Whether it can run here.
|
|
130
|
+
* @property {(options?: LanguageModelOptions) => Promise<LanguageModelSession>} create A session (downloads the model first, from a click).
|
|
131
|
+
*/
|
|
132
|
+
/**
|
|
133
|
+
* Chrome's `LanguageModel`, or undefined when this Chrome has none.
|
|
134
|
+
*
|
|
135
|
+
* @param {object} [scope] - Where to look. Default `globalThis` (a page, or the service worker).
|
|
136
|
+
* @returns {LanguageModelStatic | undefined} The Prompt API.
|
|
137
|
+
* @example
|
|
138
|
+
* const sessions = createModelSession({ model: chromeLanguageModel, options: ENGLISH });
|
|
139
|
+
*/
|
|
140
|
+
export declare function chromeLanguageModel(scope?: object): LanguageModelStatic | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* The model's state, never throwing: 'missing' without a Prompt API, and
|
|
143
|
+
* 'unavailable' when asking fails.
|
|
144
|
+
*
|
|
145
|
+
* @param {LanguageModelStatic | undefined} model - The Prompt API.
|
|
146
|
+
* @param {LanguageModelOptions} options - The options sessions are created with.
|
|
147
|
+
* @returns {Promise<Availability>} Its state.
|
|
148
|
+
* @example
|
|
149
|
+
* await modelAvailability(chromeLanguageModel(), ENGLISH); // 'available'
|
|
150
|
+
*/
|
|
151
|
+
export declare function modelAvailability(model: LanguageModelStatic | undefined, options: LanguageModelOptions): Promise<Availability>;
|
|
152
|
+
/**
|
|
153
|
+
* Download the model, from the person's click: `create()` with a monitor,
|
|
154
|
+
* the session let go once made. Call it first in the click's handler, with
|
|
155
|
+
* nothing awaited before it, or Chrome refuses (no user activation).
|
|
156
|
+
*
|
|
157
|
+
* @param {LanguageModelStatic | undefined} model - The Prompt API.
|
|
158
|
+
* @param {LanguageModelOptions} options - The options sessions are created with.
|
|
159
|
+
* @param {{onProgress?: (loaded: number) => void, signal?: AbortSignal}} [handlers] - `onProgress`: how much is
|
|
160
|
+
* downloaded, 0–1, as Chrome reports it; `signal`: stops it.
|
|
161
|
+
* @returns {Promise<Availability>} The state afterwards ('available' once it's here).
|
|
162
|
+
* @throws {Error} When there's no Prompt API, or Chrome couldn't download it.
|
|
163
|
+
* @example
|
|
164
|
+
* button.addEventListener('click', () => {
|
|
165
|
+
* downloadModel(chromeLanguageModel(), ENGLISH, { onProgress: (loaded) => { bar.value = loaded; } })
|
|
166
|
+
* .then(show, () => show('downloadable', 'The download didn’t finish. Try again.'));
|
|
167
|
+
* });
|
|
168
|
+
*/
|
|
169
|
+
export declare function downloadModel(model: LanguageModelStatic | undefined, options: LanguageModelOptions, { onProgress, signal }?: {
|
|
170
|
+
onProgress?: (loaded: number) => void;
|
|
171
|
+
signal?: AbortSignal;
|
|
172
|
+
}): Promise<Availability>;
|
|
173
|
+
/**
|
|
174
|
+
* The settings page's words for a state: standard message keys (the
|
|
175
|
+
* extension's `_locales` names them alike), as `SITE_LIST_MESSAGES` does
|
|
176
|
+
* for a site list.
|
|
177
|
+
*/
|
|
178
|
+
export declare const MODEL_MESSAGES: Readonly<{
|
|
179
|
+
/** “Chrome’s built-in AI”: the row's name. */
|
|
180
|
+
name: "optionsModel";
|
|
181
|
+
/** “Checking…” before the state is known. */
|
|
182
|
+
checking: "optionsModelChecking";
|
|
183
|
+
/** “Ready”. */
|
|
184
|
+
ready: "optionsModelReady";
|
|
185
|
+
/** “Needs download”. */
|
|
186
|
+
needsDownload: "optionsModelNeedsDownload";
|
|
187
|
+
/** “Chrome downloads its model once and shares it with every site and extension that uses it”. */
|
|
188
|
+
downloadHelp: "optionsModelDownloadHelp";
|
|
189
|
+
/** “Download”: the button. */
|
|
190
|
+
download: "optionsModelDownload";
|
|
191
|
+
/** “Downloading… $PERCENT$%” (content `$1`). */
|
|
192
|
+
downloading: "optionsModelDownloading";
|
|
193
|
+
/** “The download didn’t finish. Try again.” */
|
|
194
|
+
downloadFailed: "optionsModelDownloadFailed";
|
|
195
|
+
/** “Not supported on this computer”. */
|
|
196
|
+
unsupported: "optionsModelUnsupported";
|
|
197
|
+
/** What a computer needs to run it. */
|
|
198
|
+
requirements: "optionsModelRequirements";
|
|
199
|
+
/** “This Chrome doesn’t have it”. */
|
|
200
|
+
missing: "optionsModelMissing";
|
|
201
|
+
/** What to do about that, and that the extension works without it. */
|
|
202
|
+
missingHelp: "optionsModelMissingHelp";
|
|
203
|
+
}>;
|
|
204
|
+
export type ModelStatus = {
|
|
205
|
+
/**
|
|
206
|
+
* Ready to use, can be downloaded, or can't run here.
|
|
207
|
+
*/
|
|
208
|
+
state: 'ready' | 'pending' | 'off';
|
|
209
|
+
/**
|
|
210
|
+
* The status's message key ({@link MODEL_MESSAGES}).
|
|
211
|
+
*/
|
|
212
|
+
status: string;
|
|
213
|
+
/**
|
|
214
|
+
* The help's message key ('' for none).
|
|
215
|
+
*/
|
|
216
|
+
help: string;
|
|
217
|
+
/**
|
|
218
|
+
* Whether to offer Download.
|
|
219
|
+
*/
|
|
220
|
+
canDownload: boolean;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* The model's state, as a settings page shows it.
|
|
224
|
+
*
|
|
225
|
+
* @typedef {object} ModelStatus
|
|
226
|
+
* @property {'ready' | 'pending' | 'off'} state Ready to use, can be downloaded, or can't run here.
|
|
227
|
+
* @property {string} status The status's message key ({@link MODEL_MESSAGES}).
|
|
228
|
+
* @property {string} help The help's message key ('' for none).
|
|
229
|
+
* @property {boolean} canDownload Whether to offer Download.
|
|
230
|
+
*/
|
|
231
|
+
/**
|
|
232
|
+
* Describe an availability for a settings page. Pure.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} availability - 'available', 'downloadable', 'downloading', 'unavailable' or 'missing'.
|
|
235
|
+
* @returns {ModelStatus} How to show it.
|
|
236
|
+
* @example
|
|
237
|
+
* describeModel('downloadable'); // { state: 'pending', status: 'optionsModelNeedsDownload', help: 'optionsModelDownloadHelp', canDownload: true }
|
|
238
|
+
*/
|
|
239
|
+
export declare function describeModel(availability: string): ModelStatus;
|
|
240
|
+
export type ModelAnswer = {
|
|
241
|
+
text: string;
|
|
242
|
+
reason: 'answered';
|
|
243
|
+
} | {
|
|
244
|
+
text: null;
|
|
245
|
+
reason: 'unavailable' | 'late' | 'aborted' | 'failed';
|
|
246
|
+
};
|
|
247
|
+
export type AskOptions = {
|
|
248
|
+
/**
|
|
249
|
+
* The most the answer may take, from the moment it's asked: the base
|
|
250
|
+
* session (if it must be made) and the answer.
|
|
251
|
+
*/
|
|
252
|
+
timeoutMs: number;
|
|
253
|
+
/**
|
|
254
|
+
* Stops it sooner (the person cancelled).
|
|
255
|
+
*/
|
|
256
|
+
signal?: AbortSignal;
|
|
257
|
+
/**
|
|
258
|
+
* A JSON Schema the answer must follow.
|
|
259
|
+
*/
|
|
260
|
+
responseConstraint?: object;
|
|
261
|
+
};
|
|
262
|
+
export type ModelSession = {
|
|
263
|
+
/**
|
|
264
|
+
* The model's state, or 'missing'.
|
|
265
|
+
*/
|
|
266
|
+
availability: () => Promise<Availability>;
|
|
267
|
+
/**
|
|
268
|
+
* Ask a copy of the base session.
|
|
269
|
+
*/
|
|
270
|
+
ask: (prompt: string, options: AskOptions) => Promise<ModelAnswer>;
|
|
271
|
+
/**
|
|
272
|
+
* Let the base session go (the next question makes it again).
|
|
273
|
+
*/
|
|
274
|
+
release: () => void;
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* What a question got: the answer, or why there's none: the model isn't
|
|
278
|
+
* on this computer ('unavailable'), the time ran out first ('late', a cold
|
|
279
|
+
* start among them), the caller's signal stopped it ('aborted'), or the model
|
|
280
|
+
* failed ('failed').
|
|
281
|
+
*
|
|
282
|
+
* @typedef {{text: string, reason: 'answered'} | {text: null, reason: 'unavailable' | 'late' | 'aborted' | 'failed'}} ModelAnswer
|
|
283
|
+
*/
|
|
284
|
+
/**
|
|
285
|
+
* @typedef {object} AskOptions
|
|
286
|
+
* @property {number} timeoutMs The most the answer may take, from the moment it's asked: the base
|
|
287
|
+
* session (if it must be made) and the answer.
|
|
288
|
+
* @property {AbortSignal} [signal] Stops it sooner (the person cancelled).
|
|
289
|
+
* @property {object} [responseConstraint] A JSON Schema the answer must follow.
|
|
290
|
+
*/
|
|
291
|
+
/**
|
|
292
|
+
* @typedef {object} ModelSession
|
|
293
|
+
* @property {() => Promise<Availability>} availability The model's state, or 'missing'.
|
|
294
|
+
* @property {(prompt: string, options: AskOptions) => Promise<ModelAnswer>} ask Ask a copy of the base session.
|
|
295
|
+
* @property {() => void} release Let the base session go (the next question makes it again).
|
|
296
|
+
*/
|
|
297
|
+
/**
|
|
298
|
+
* A base session over a Prompt API, made when first needed and kept, each
|
|
299
|
+
* question asked of a `clone()` within its time limit.
|
|
300
|
+
*
|
|
301
|
+
* @param {{model: () => LanguageModelStatic | undefined, options: LanguageModelOptions, initialPrompts?: InitialPrompt[]}} setup -
|
|
302
|
+
* `model`: the Prompt API, read when used; `options`: what sessions are made (and availability asked)
|
|
303
|
+
* with; `initialPrompts`: the instructions and examples every question starts from.
|
|
304
|
+
* @returns {ModelSession} The session.
|
|
305
|
+
* @example
|
|
306
|
+
* const sessions = createModelSession({
|
|
307
|
+
* model: chromeLanguageModel,
|
|
308
|
+
* options: { expectedInputs: [{ type: 'text', languages: ['en'] }], expectedOutputs: [{ type: 'text', languages: ['en'] }] },
|
|
309
|
+
* initialPrompts: [{ role: 'system', content: 'You name downloaded files.' }],
|
|
310
|
+
* });
|
|
311
|
+
* const { text } = await sessions.ask('Site: acme.com', { timeoutMs: 2500 }); // null when late
|
|
312
|
+
*/
|
|
313
|
+
export declare function createModelSession({ model, options, initialPrompts }: {
|
|
314
|
+
model: () => LanguageModelStatic | undefined;
|
|
315
|
+
options: LanguageModelOptions;
|
|
316
|
+
initialPrompts?: InitialPrompt[];
|
|
317
|
+
}): ModelSession;
|