@yudin-s/react-chrome-ai 0.1.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/SECURITY.md +12 -0
- package/dist/index.cjs +938 -0
- package/dist/index.d.cts +431 -0
- package/dist/index.d.ts +431 -0
- package/dist/index.js +868 -0
- package/docs/ai-agents.md +155 -0
- package/docs/hooks.md +378 -0
- package/docs/publishing.md +102 -0
- package/examples/README.md +24 -0
- package/examples/basic-prompt.tsx +18 -0
- package/examples/chrome-ai-studio/README.md +22 -0
- package/examples/chrome-ai-studio/index.html +12 -0
- package/examples/chrome-ai-studio/package.json +22 -0
- package/examples/chrome-ai-studio/src/App.tsx +138 -0
- package/examples/chrome-ai-studio/src/main.tsx +10 -0
- package/examples/chrome-ai-studio/src/styles.css +219 -0
- package/examples/chrome-ai-studio/tsconfig.json +16 -0
- package/examples/chrome-ai-studio/vite.config.ts +6 -0
- package/examples/local-review-workbench/README.md +18 -0
- package/examples/local-review-workbench/index.html +12 -0
- package/examples/local-review-workbench/package.json +22 -0
- package/examples/local-review-workbench/src/App.tsx +121 -0
- package/examples/local-review-workbench/src/main.tsx +10 -0
- package/examples/local-review-workbench/src/styles.css +190 -0
- package/examples/local-review-workbench/tsconfig.json +16 -0
- package/examples/local-review-workbench/vite.config.ts +6 -0
- package/examples/local-translator/README.md +24 -0
- package/examples/local-translator/index.html +12 -0
- package/examples/local-translator/package.json +23 -0
- package/examples/local-translator/src/App.tsx +235 -0
- package/examples/local-translator/src/main.tsx +10 -0
- package/examples/local-translator/src/styles.css +161 -0
- package/examples/local-translator/tsconfig.json +16 -0
- package/examples/local-translator/vite.config.ts +6 -0
- package/examples/model-download-progress.tsx +20 -0
- package/examples/summarizer.tsx +28 -0
- package/llms.txt +97 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
// src/hooks.ts
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/runtime.ts
|
|
5
|
+
var ChromeAIError = class extends Error {
|
|
6
|
+
constructor(message, status = "error", cause) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ChromeAIError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.cause = cause;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var defaultExpectedOptions = {
|
|
14
|
+
expectedInputs: [{ type: "text", languages: ["en"] }],
|
|
15
|
+
expectedOutputs: [{ type: "text", languages: ["en"] }]
|
|
16
|
+
};
|
|
17
|
+
function getChromeLanguageModelAPI(runtime = globalThis) {
|
|
18
|
+
return runtime.LanguageModel ?? runtime.ai?.languageModel;
|
|
19
|
+
}
|
|
20
|
+
function isChromeLanguageModelSupported(runtime) {
|
|
21
|
+
return Boolean(getChromeLanguageModelAPI(runtime));
|
|
22
|
+
}
|
|
23
|
+
function defaultLanguageModelOptions() {
|
|
24
|
+
return {
|
|
25
|
+
expectedInputs: defaultExpectedOptions.expectedInputs?.map((item) => ({ ...item })),
|
|
26
|
+
expectedOutputs: defaultExpectedOptions.expectedOutputs?.map((item) => ({ ...item }))
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function normalizeDownloadProgress(event) {
|
|
30
|
+
const loaded = typeof event?.loaded === "number" ? event.loaded : void 0;
|
|
31
|
+
const total = typeof event?.total === "number" ? event.total : void 0;
|
|
32
|
+
const progress = typeof loaded === "number" && typeof total === "number" && total > 0 ? Math.max(0, Math.min(1, loaded / total)) : typeof loaded === "number" && loaded >= 0 && loaded <= 1 ? loaded : void 0;
|
|
33
|
+
return {
|
|
34
|
+
loaded,
|
|
35
|
+
total,
|
|
36
|
+
progress,
|
|
37
|
+
percent: typeof progress === "number" ? Math.round(progress * 100) : void 0,
|
|
38
|
+
indeterminate: typeof progress !== "number",
|
|
39
|
+
completed: progress === 1
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function readChromeAIAvailability(options = defaultLanguageModelOptions(), runtime) {
|
|
43
|
+
const api = getChromeLanguageModelAPI(runtime);
|
|
44
|
+
if (!api) {
|
|
45
|
+
return "unavailable";
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return await api.availability(options);
|
|
49
|
+
} catch {
|
|
50
|
+
return api.availability();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function readChromeAIParams(runtime) {
|
|
54
|
+
const api = getChromeLanguageModelAPI(runtime);
|
|
55
|
+
if (!api?.params) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
return api.params();
|
|
59
|
+
}
|
|
60
|
+
function withDownloadProgress(options = {}, onProgress) {
|
|
61
|
+
return {
|
|
62
|
+
...options,
|
|
63
|
+
monitor(monitor) {
|
|
64
|
+
options.monitor?.(monitor);
|
|
65
|
+
monitor.addEventListener("downloadprogress", (event) => {
|
|
66
|
+
const raw = {
|
|
67
|
+
loaded: typeof event.loaded === "number" ? event.loaded : void 0,
|
|
68
|
+
total: typeof event.total === "number" ? event.total : void 0
|
|
69
|
+
};
|
|
70
|
+
onProgress?.(normalizeDownloadProgress(raw), raw);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function createChromeAISession(options = defaultLanguageModelOptions(), runtime, onProgress) {
|
|
76
|
+
const api = getChromeLanguageModelAPI(runtime);
|
|
77
|
+
if (!api) {
|
|
78
|
+
throw new ChromeAIError("Chrome LanguageModel API is not exposed in this runtime.", "unsupported");
|
|
79
|
+
}
|
|
80
|
+
const availability = await readChromeAIAvailability(options, runtime);
|
|
81
|
+
if (availability === "unavailable") {
|
|
82
|
+
throw new ChromeAIError("Chrome LanguageModel is unavailable on this device, browser, origin, or profile.", "unavailable");
|
|
83
|
+
}
|
|
84
|
+
return api.create(withDownloadProgress(options, onProgress));
|
|
85
|
+
}
|
|
86
|
+
async function prepareChromeAIModel(options = defaultLanguageModelOptions(), runtime, onProgress) {
|
|
87
|
+
const supported = isChromeLanguageModelSupported(runtime);
|
|
88
|
+
if (!supported) {
|
|
89
|
+
return { status: "unsupported", supported: false };
|
|
90
|
+
}
|
|
91
|
+
const availability = await readChromeAIAvailability(options, runtime);
|
|
92
|
+
if (availability === "unavailable") {
|
|
93
|
+
return { status: "unavailable", supported, availability };
|
|
94
|
+
}
|
|
95
|
+
let session;
|
|
96
|
+
try {
|
|
97
|
+
session = await createChromeAISession(options, runtime, onProgress);
|
|
98
|
+
return {
|
|
99
|
+
status: "ready",
|
|
100
|
+
supported,
|
|
101
|
+
availability: "available",
|
|
102
|
+
contextUsage: session.contextUsage,
|
|
103
|
+
contextWindow: session.contextWindow
|
|
104
|
+
};
|
|
105
|
+
} catch (cause) {
|
|
106
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
107
|
+
return { status: "error", supported, availability, error };
|
|
108
|
+
} finally {
|
|
109
|
+
session?.destroy();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getUserActivation() {
|
|
113
|
+
if (typeof navigator === "undefined") {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
return navigator.userActivation?.isActive || navigator.userActivation?.hasBeenActive;
|
|
117
|
+
}
|
|
118
|
+
function toError(value) {
|
|
119
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/reflection.ts
|
|
123
|
+
function safeParseJSON(text) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(text);
|
|
126
|
+
} catch {
|
|
127
|
+
const match = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
128
|
+
if (!match?.[1]) {
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return JSON.parse(match[1]);
|
|
133
|
+
} catch {
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function createStructuredPrompt(input, options = {}) {
|
|
139
|
+
const suffix = [
|
|
140
|
+
options.instructions,
|
|
141
|
+
options.format === "json" ? "Return only valid JSON. Do not include Markdown fences or explanatory text." : void 0
|
|
142
|
+
].filter(Boolean).join("\n");
|
|
143
|
+
if (!suffix) {
|
|
144
|
+
return input;
|
|
145
|
+
}
|
|
146
|
+
if (typeof input === "string") {
|
|
147
|
+
return `${input}
|
|
148
|
+
|
|
149
|
+
${suffix}`;
|
|
150
|
+
}
|
|
151
|
+
return [
|
|
152
|
+
...input,
|
|
153
|
+
{
|
|
154
|
+
role: "user",
|
|
155
|
+
content: suffix
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
function defaultReflectionPrompt(draft) {
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
role: "user",
|
|
163
|
+
content: "Review the previous answer for instruction-following, unsupported claims, and formatting errors. Return the corrected final answer only."
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
role: "assistant",
|
|
167
|
+
content: draft
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
async function promptWithReflection(session, input, options = {}) {
|
|
172
|
+
const promptInput = createStructuredPrompt(input, options);
|
|
173
|
+
const promptOptions = {
|
|
174
|
+
signal: options.signal,
|
|
175
|
+
responseConstraint: options.schema
|
|
176
|
+
};
|
|
177
|
+
const draft = await session.prompt(promptInput, promptOptions);
|
|
178
|
+
const text = options.reflect ? await session.prompt((options.reflectionPrompt ?? defaultReflectionPrompt)(draft), promptOptions) : draft;
|
|
179
|
+
const data = options.parse ? options.parse(text) : options.format === "json" ? safeParseJSON(text) : void 0;
|
|
180
|
+
return { text, data, draft: options.reflect ? draft : void 0 };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/streams.ts
|
|
184
|
+
async function* readableStreamToAsyncIterable(stream) {
|
|
185
|
+
const reader = stream.getReader();
|
|
186
|
+
try {
|
|
187
|
+
while (true) {
|
|
188
|
+
const { done, value } = await reader.read();
|
|
189
|
+
if (done) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
yield value;
|
|
193
|
+
}
|
|
194
|
+
} finally {
|
|
195
|
+
reader.releaseLock();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function toAsyncIterable(source) {
|
|
199
|
+
if (typeof source[Symbol.asyncIterator] === "function") {
|
|
200
|
+
return source;
|
|
201
|
+
}
|
|
202
|
+
return readableStreamToAsyncIterable(source);
|
|
203
|
+
}
|
|
204
|
+
async function collectAsyncIterable(source) {
|
|
205
|
+
const chunks = [];
|
|
206
|
+
for await (const chunk of toAsyncIterable(source)) {
|
|
207
|
+
chunks.push(chunk);
|
|
208
|
+
}
|
|
209
|
+
return chunks;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/task-apis.ts
|
|
213
|
+
function getChromeAITaskAPI(apiName, runtime = globalThis) {
|
|
214
|
+
return runtime[apiName];
|
|
215
|
+
}
|
|
216
|
+
function isChromeAITaskSupported(apiName, runtime) {
|
|
217
|
+
return Boolean(getChromeAITaskAPI(apiName, runtime));
|
|
218
|
+
}
|
|
219
|
+
async function readChromeAITaskAvailability(apiName, options, runtime) {
|
|
220
|
+
const api = getChromeAITaskAPI(apiName, runtime);
|
|
221
|
+
if (!api) {
|
|
222
|
+
return "unavailable";
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
return await api.availability(options);
|
|
226
|
+
} catch {
|
|
227
|
+
return api.availability();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function withTaskDownloadProgress(options = {}, onProgress) {
|
|
231
|
+
return {
|
|
232
|
+
...options,
|
|
233
|
+
monitor(monitor) {
|
|
234
|
+
options.monitor?.(monitor);
|
|
235
|
+
monitor.addEventListener("downloadprogress", (event) => {
|
|
236
|
+
const raw = {
|
|
237
|
+
loaded: typeof event.loaded === "number" ? event.loaded : void 0,
|
|
238
|
+
total: typeof event.total === "number" ? event.total : void 0
|
|
239
|
+
};
|
|
240
|
+
onProgress?.(normalizeDownloadProgress(raw), raw);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function createChromeAITaskSession(apiName, options = {}, runtime, onProgress) {
|
|
246
|
+
const api = getChromeAITaskAPI(apiName, runtime);
|
|
247
|
+
if (!api) {
|
|
248
|
+
throw new Error(`${apiName} API is not exposed in this browser.`);
|
|
249
|
+
}
|
|
250
|
+
const availability = await readChromeAITaskAvailability(apiName, options, runtime);
|
|
251
|
+
if (availability === "unavailable") {
|
|
252
|
+
throw new Error(`${apiName} API is unavailable for the requested options.`);
|
|
253
|
+
}
|
|
254
|
+
return api.create(withTaskDownloadProgress(options, onProgress));
|
|
255
|
+
}
|
|
256
|
+
async function destroyChromeAITaskSession(session) {
|
|
257
|
+
const destroy = session?.destroy;
|
|
258
|
+
if (typeof destroy === "function") {
|
|
259
|
+
destroy.call(session);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function assertTaskMethod(session, methodName) {
|
|
263
|
+
const method = session?.[methodName];
|
|
264
|
+
if (typeof method !== "function") {
|
|
265
|
+
throw new Error(`Task session does not expose ${methodName}().`);
|
|
266
|
+
}
|
|
267
|
+
return method.bind(session);
|
|
268
|
+
}
|
|
269
|
+
async function callChromeAITaskMethod(session, methodName, ...args) {
|
|
270
|
+
try {
|
|
271
|
+
return await assertTaskMethod(session, methodName)(...args);
|
|
272
|
+
} catch (cause) {
|
|
273
|
+
throw toError(cause);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/hooks.ts
|
|
278
|
+
function useChromeAIAvailability({
|
|
279
|
+
options = defaultLanguageModelOptions(),
|
|
280
|
+
autoCheck = true
|
|
281
|
+
} = {}) {
|
|
282
|
+
const [status, setStatus] = useState("idle");
|
|
283
|
+
const [availability, setAvailability] = useState();
|
|
284
|
+
const [error, setError] = useState();
|
|
285
|
+
const optionsRef = useRef(options);
|
|
286
|
+
optionsRef.current = options;
|
|
287
|
+
const refresh = useCallback(async () => {
|
|
288
|
+
setStatus("checking");
|
|
289
|
+
setError(void 0);
|
|
290
|
+
try {
|
|
291
|
+
const result = await readChromeAIAvailability(optionsRef.current);
|
|
292
|
+
setAvailability(result);
|
|
293
|
+
setStatus(result === "unavailable" ? "unavailable" : "ready");
|
|
294
|
+
return result;
|
|
295
|
+
} catch (cause) {
|
|
296
|
+
const nextError = toError(cause);
|
|
297
|
+
setError(nextError);
|
|
298
|
+
setStatus("error");
|
|
299
|
+
throw nextError;
|
|
300
|
+
}
|
|
301
|
+
}, []);
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
if (!autoCheck) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
void refresh();
|
|
307
|
+
}, [autoCheck, refresh]);
|
|
308
|
+
return {
|
|
309
|
+
supported: isChromeLanguageModelSupported(),
|
|
310
|
+
status,
|
|
311
|
+
availability,
|
|
312
|
+
userActivation: getUserActivation(),
|
|
313
|
+
error,
|
|
314
|
+
refresh
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function useChromeAIParams(autoLoad = true) {
|
|
318
|
+
const [params, setParams] = useState();
|
|
319
|
+
const [error, setError] = useState();
|
|
320
|
+
const [status, setStatus] = useState("idle");
|
|
321
|
+
const refresh = useCallback(async () => {
|
|
322
|
+
setStatus("loading");
|
|
323
|
+
setError(void 0);
|
|
324
|
+
try {
|
|
325
|
+
const result = await readChromeAIParams();
|
|
326
|
+
setParams(result);
|
|
327
|
+
setStatus(result ? "ready" : "unsupported");
|
|
328
|
+
return result;
|
|
329
|
+
} catch (cause) {
|
|
330
|
+
const nextError = toError(cause);
|
|
331
|
+
setError(nextError);
|
|
332
|
+
setStatus("error");
|
|
333
|
+
throw nextError;
|
|
334
|
+
}
|
|
335
|
+
}, []);
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (autoLoad) {
|
|
338
|
+
void refresh();
|
|
339
|
+
}
|
|
340
|
+
}, [autoLoad, refresh]);
|
|
341
|
+
return { params, status, error, refresh };
|
|
342
|
+
}
|
|
343
|
+
function useChromeAISession({
|
|
344
|
+
createOptions = defaultLanguageModelOptions(),
|
|
345
|
+
autoCreate = false,
|
|
346
|
+
destroyOnUnmount = true
|
|
347
|
+
} = {}) {
|
|
348
|
+
const [state, setState] = useState({
|
|
349
|
+
status: "idle",
|
|
350
|
+
session: null
|
|
351
|
+
});
|
|
352
|
+
const sessionRef = useRef(null);
|
|
353
|
+
const createOptionsRef = useRef(createOptions);
|
|
354
|
+
createOptionsRef.current = createOptions;
|
|
355
|
+
const destroySession = useCallback(() => {
|
|
356
|
+
sessionRef.current?.destroy();
|
|
357
|
+
sessionRef.current = null;
|
|
358
|
+
setState((current) => ({ ...current, status: "idle", session: null }));
|
|
359
|
+
}, []);
|
|
360
|
+
const createSession = useCallback(async (overrideOptions) => {
|
|
361
|
+
setState((current) => ({ ...current, status: "checking", error: void 0 }));
|
|
362
|
+
const options = overrideOptions ?? createOptionsRef.current;
|
|
363
|
+
const availability = await readChromeAIAvailability(options);
|
|
364
|
+
const availabilityStatus = availability === "available" ? "ready" : availability;
|
|
365
|
+
setState((current) => ({ ...current, availability, status: availabilityStatus }));
|
|
366
|
+
try {
|
|
367
|
+
const session = await createChromeAISession(options, void 0, (progress) => {
|
|
368
|
+
setState((current) => ({
|
|
369
|
+
...current,
|
|
370
|
+
status: progress.completed ? "preparing" : "downloading",
|
|
371
|
+
progress
|
|
372
|
+
}));
|
|
373
|
+
});
|
|
374
|
+
sessionRef.current?.destroy();
|
|
375
|
+
sessionRef.current = session;
|
|
376
|
+
setState({ status: "ready", availability: "available", session });
|
|
377
|
+
return session;
|
|
378
|
+
} catch (cause) {
|
|
379
|
+
const error = toError(cause);
|
|
380
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", session: null, availability, error });
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}, []);
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
if (autoCreate) {
|
|
386
|
+
void createSession();
|
|
387
|
+
}
|
|
388
|
+
}, [autoCreate, createSession]);
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
if (!destroyOnUnmount) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
return () => {
|
|
394
|
+
sessionRef.current?.destroy();
|
|
395
|
+
sessionRef.current = null;
|
|
396
|
+
};
|
|
397
|
+
}, [destroyOnUnmount]);
|
|
398
|
+
return {
|
|
399
|
+
...state,
|
|
400
|
+
createSession,
|
|
401
|
+
destroySession
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function useChromeAIPrompt({
|
|
405
|
+
reflection,
|
|
406
|
+
reuseSession = true,
|
|
407
|
+
...sessionOptions
|
|
408
|
+
} = {}) {
|
|
409
|
+
const { session, createSession, destroySession, progress } = useChromeAISession(sessionOptions);
|
|
410
|
+
const [state, setState] = useState({
|
|
411
|
+
status: "idle",
|
|
412
|
+
text: "",
|
|
413
|
+
chunks: []
|
|
414
|
+
});
|
|
415
|
+
const ensureSession = useCallback(async () => session ?? createSession(), [createSession, session]);
|
|
416
|
+
const reset = useCallback(() => {
|
|
417
|
+
setState({ status: "idle", text: "", chunks: [] });
|
|
418
|
+
}, []);
|
|
419
|
+
const prompt = useCallback(
|
|
420
|
+
async (input, options) => {
|
|
421
|
+
setState({ status: "prompting", input, text: "", chunks: [] });
|
|
422
|
+
const activeSession = await ensureSession();
|
|
423
|
+
try {
|
|
424
|
+
const text = await activeSession.prompt(input, options);
|
|
425
|
+
setState({ status: "ready", input, text, chunks: [text] });
|
|
426
|
+
if (!reuseSession) {
|
|
427
|
+
destroySession();
|
|
428
|
+
}
|
|
429
|
+
return text;
|
|
430
|
+
} catch (cause) {
|
|
431
|
+
const error = toError(cause);
|
|
432
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", input, text: "", chunks: [], error });
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
[destroySession, ensureSession, reuseSession]
|
|
437
|
+
);
|
|
438
|
+
const promptStructured = useCallback(
|
|
439
|
+
async (input, options) => {
|
|
440
|
+
setState({ status: "prompting", input, text: "", chunks: [] });
|
|
441
|
+
const activeSession = await ensureSession();
|
|
442
|
+
try {
|
|
443
|
+
const result = await promptWithReflection(activeSession, input, {
|
|
444
|
+
...reflection,
|
|
445
|
+
...options
|
|
446
|
+
});
|
|
447
|
+
setState({ status: "ready", input, text: result.text, data: result.data, chunks: [result.text] });
|
|
448
|
+
if (!reuseSession) {
|
|
449
|
+
destroySession();
|
|
450
|
+
}
|
|
451
|
+
return result;
|
|
452
|
+
} catch (cause) {
|
|
453
|
+
const error = toError(cause);
|
|
454
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", input, text: "", chunks: [], error });
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
[destroySession, ensureSession, reflection, reuseSession]
|
|
459
|
+
);
|
|
460
|
+
return {
|
|
461
|
+
...state,
|
|
462
|
+
prompt,
|
|
463
|
+
promptStructured,
|
|
464
|
+
reset,
|
|
465
|
+
session,
|
|
466
|
+
progress
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function useChromeAIStream(session) {
|
|
470
|
+
const [state, setState] = useState({
|
|
471
|
+
status: "idle",
|
|
472
|
+
text: "",
|
|
473
|
+
chunks: []
|
|
474
|
+
});
|
|
475
|
+
const reset = useCallback(() => {
|
|
476
|
+
setState({ status: "idle", text: "", chunks: [] });
|
|
477
|
+
}, []);
|
|
478
|
+
const streamPrompt = useCallback(
|
|
479
|
+
async (input, options) => {
|
|
480
|
+
if (!session) {
|
|
481
|
+
throw new Error("Chrome AI session is not ready.");
|
|
482
|
+
}
|
|
483
|
+
setState({ status: "streaming", text: "", chunks: [] });
|
|
484
|
+
try {
|
|
485
|
+
const chunks = [];
|
|
486
|
+
for await (const chunk of toAsyncIterable(session.promptStreaming(input, options))) {
|
|
487
|
+
chunks.push(chunk);
|
|
488
|
+
setState({ status: "streaming", text: chunks.join(""), chunks: [...chunks] });
|
|
489
|
+
}
|
|
490
|
+
const text = chunks.join("");
|
|
491
|
+
setState({ status: "ready", text, chunks });
|
|
492
|
+
return text;
|
|
493
|
+
} catch (cause) {
|
|
494
|
+
const error = toError(cause);
|
|
495
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", text: "", chunks: [], error });
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
[session]
|
|
500
|
+
);
|
|
501
|
+
return useMemo(() => ({ ...state, streamPrompt, reset }), [reset, state, streamPrompt]);
|
|
502
|
+
}
|
|
503
|
+
function useChromeAIAppend(session) {
|
|
504
|
+
const [state, setState] = useState({
|
|
505
|
+
status: "idle"
|
|
506
|
+
});
|
|
507
|
+
const reset = useCallback(() => {
|
|
508
|
+
setState({ status: "idle" });
|
|
509
|
+
}, []);
|
|
510
|
+
const append = useCallback(
|
|
511
|
+
async (messages, options) => {
|
|
512
|
+
if (!session?.append) {
|
|
513
|
+
const error = new Error("Chrome AI session.append() is not supported in this browser.");
|
|
514
|
+
setState({ status: "unsupported", error });
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
setState({ status: "appending" });
|
|
518
|
+
try {
|
|
519
|
+
await session.append(messages, options);
|
|
520
|
+
setState({ status: "ready" });
|
|
521
|
+
} catch (cause) {
|
|
522
|
+
const error = toError(cause);
|
|
523
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", error });
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
[session]
|
|
528
|
+
);
|
|
529
|
+
return useMemo(() => ({ ...state, append, reset }), [append, reset, state]);
|
|
530
|
+
}
|
|
531
|
+
function useChromeAIClone(session) {
|
|
532
|
+
const cloneRef = useRef(null);
|
|
533
|
+
const [state, setState] = useState({
|
|
534
|
+
status: "idle",
|
|
535
|
+
clone: null
|
|
536
|
+
});
|
|
537
|
+
const destroyClone = useCallback(() => {
|
|
538
|
+
cloneRef.current?.destroy();
|
|
539
|
+
cloneRef.current = null;
|
|
540
|
+
setState({ status: "idle", clone: null });
|
|
541
|
+
}, []);
|
|
542
|
+
const cloneSession = useCallback(
|
|
543
|
+
async (options) => {
|
|
544
|
+
if (!session?.clone) {
|
|
545
|
+
const error = new Error("Chrome AI session.clone() is not supported in this browser.");
|
|
546
|
+
setState({ status: "unsupported", clone: null, error });
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
setState((current) => ({ ...current, status: "cloning", error: void 0 }));
|
|
550
|
+
try {
|
|
551
|
+
const nextClone = await session.clone(options);
|
|
552
|
+
cloneRef.current?.destroy();
|
|
553
|
+
cloneRef.current = nextClone;
|
|
554
|
+
setState({ status: "ready", clone: nextClone });
|
|
555
|
+
return nextClone;
|
|
556
|
+
} catch (cause) {
|
|
557
|
+
const error = toError(cause);
|
|
558
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", clone: null, error });
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
[session]
|
|
563
|
+
);
|
|
564
|
+
useEffect(() => {
|
|
565
|
+
return () => {
|
|
566
|
+
cloneRef.current?.destroy();
|
|
567
|
+
cloneRef.current = null;
|
|
568
|
+
};
|
|
569
|
+
}, []);
|
|
570
|
+
return {
|
|
571
|
+
...state,
|
|
572
|
+
cloneSession,
|
|
573
|
+
destroyClone
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function useChromeAIContext(session, options = {}) {
|
|
577
|
+
const [contextUsage, setContextUsage] = useState(session?.contextUsage);
|
|
578
|
+
const [contextWindow, setContextWindow] = useState(session?.contextWindow);
|
|
579
|
+
const [overflowed, setOverflowed] = useState(false);
|
|
580
|
+
const refresh = useCallback(() => {
|
|
581
|
+
setContextUsage(session?.contextUsage);
|
|
582
|
+
setContextWindow(session?.contextWindow);
|
|
583
|
+
}, [session]);
|
|
584
|
+
const resetOverflow = useCallback(() => {
|
|
585
|
+
setOverflowed(false);
|
|
586
|
+
}, []);
|
|
587
|
+
useEffect(() => {
|
|
588
|
+
refresh();
|
|
589
|
+
}, [refresh]);
|
|
590
|
+
useEffect(() => {
|
|
591
|
+
if (!session) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const onOverflow = () => setOverflowed(true);
|
|
595
|
+
session.addEventListener("contextoverflow", onOverflow);
|
|
596
|
+
return () => session.removeEventListener("contextoverflow", onOverflow);
|
|
597
|
+
}, [session]);
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
if (!session || !options.pollIntervalMs) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const id = window.setInterval(refresh, options.pollIntervalMs);
|
|
603
|
+
return () => window.clearInterval(id);
|
|
604
|
+
}, [options.pollIntervalMs, refresh, session]);
|
|
605
|
+
return {
|
|
606
|
+
contextUsage,
|
|
607
|
+
contextWindow,
|
|
608
|
+
overflowed,
|
|
609
|
+
refresh,
|
|
610
|
+
resetOverflow
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function useChromeAITaskAvailability({
|
|
614
|
+
apiName,
|
|
615
|
+
options,
|
|
616
|
+
autoCheck = true
|
|
617
|
+
}) {
|
|
618
|
+
const [status, setStatus] = useState("idle");
|
|
619
|
+
const [availability, setAvailability] = useState();
|
|
620
|
+
const [error, setError] = useState();
|
|
621
|
+
const optionsRef = useRef(options);
|
|
622
|
+
optionsRef.current = options;
|
|
623
|
+
const refresh = useCallback(async () => {
|
|
624
|
+
setStatus("checking");
|
|
625
|
+
setError(void 0);
|
|
626
|
+
try {
|
|
627
|
+
const result = await readChromeAITaskAvailability(apiName, optionsRef.current);
|
|
628
|
+
setAvailability(result);
|
|
629
|
+
setStatus(result === "unavailable" ? "unavailable" : "ready");
|
|
630
|
+
return result;
|
|
631
|
+
} catch (cause) {
|
|
632
|
+
const nextError = toError(cause);
|
|
633
|
+
setError(nextError);
|
|
634
|
+
setStatus("error");
|
|
635
|
+
throw nextError;
|
|
636
|
+
}
|
|
637
|
+
}, [apiName]);
|
|
638
|
+
useEffect(() => {
|
|
639
|
+
if (autoCheck) {
|
|
640
|
+
void refresh();
|
|
641
|
+
}
|
|
642
|
+
}, [autoCheck, refresh]);
|
|
643
|
+
return {
|
|
644
|
+
apiName,
|
|
645
|
+
supported: isChromeAITaskSupported(apiName),
|
|
646
|
+
status,
|
|
647
|
+
availability,
|
|
648
|
+
userActivation: getUserActivation(),
|
|
649
|
+
error,
|
|
650
|
+
refresh
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function useChromeAITaskSession({
|
|
654
|
+
apiName,
|
|
655
|
+
createOptions = {},
|
|
656
|
+
autoCreate = false,
|
|
657
|
+
destroyOnUnmount = true,
|
|
658
|
+
onSession
|
|
659
|
+
}) {
|
|
660
|
+
const [status, setStatus] = useState("idle");
|
|
661
|
+
const [session, setSession] = useState(null);
|
|
662
|
+
const [availability, setAvailability] = useState();
|
|
663
|
+
const [progress, setProgress] = useState();
|
|
664
|
+
const [error, setError] = useState();
|
|
665
|
+
const sessionRef = useRef(null);
|
|
666
|
+
const createOptionsRef = useRef(createOptions);
|
|
667
|
+
createOptionsRef.current = createOptions;
|
|
668
|
+
const destroySession = useCallback(() => {
|
|
669
|
+
void destroyChromeAITaskSession(sessionRef.current);
|
|
670
|
+
sessionRef.current = null;
|
|
671
|
+
setSession(null);
|
|
672
|
+
setStatus("idle");
|
|
673
|
+
}, []);
|
|
674
|
+
const createSession = useCallback(async (overrideOptions) => {
|
|
675
|
+
const options = overrideOptions ?? createOptionsRef.current;
|
|
676
|
+
setStatus("checking");
|
|
677
|
+
setError(void 0);
|
|
678
|
+
setProgress(void 0);
|
|
679
|
+
const nextAvailability = await readChromeAITaskAvailability(apiName, options);
|
|
680
|
+
setAvailability(nextAvailability);
|
|
681
|
+
setStatus(nextAvailability === "available" ? "ready" : nextAvailability);
|
|
682
|
+
try {
|
|
683
|
+
const nextSession = await createChromeAITaskSession(apiName, options, void 0, (nextProgress) => {
|
|
684
|
+
setProgress(nextProgress);
|
|
685
|
+
setStatus(nextProgress.completed ? "preparing" : "downloading");
|
|
686
|
+
});
|
|
687
|
+
await destroyChromeAITaskSession(sessionRef.current);
|
|
688
|
+
sessionRef.current = nextSession;
|
|
689
|
+
setSession(nextSession);
|
|
690
|
+
setStatus("ready");
|
|
691
|
+
setAvailability("available");
|
|
692
|
+
onSession?.(nextSession);
|
|
693
|
+
return nextSession;
|
|
694
|
+
} catch (cause) {
|
|
695
|
+
const nextError = toError(cause);
|
|
696
|
+
setError(nextError);
|
|
697
|
+
setStatus(nextError.name === "AbortError" ? "aborted" : "error");
|
|
698
|
+
throw nextError;
|
|
699
|
+
}
|
|
700
|
+
}, [apiName, onSession]);
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
if (autoCreate) {
|
|
703
|
+
void createSession();
|
|
704
|
+
}
|
|
705
|
+
}, [autoCreate, createSession]);
|
|
706
|
+
useEffect(() => {
|
|
707
|
+
if (!destroyOnUnmount) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
return () => {
|
|
711
|
+
void destroyChromeAITaskSession(sessionRef.current);
|
|
712
|
+
sessionRef.current = null;
|
|
713
|
+
};
|
|
714
|
+
}, [destroyOnUnmount]);
|
|
715
|
+
return {
|
|
716
|
+
apiName,
|
|
717
|
+
supported: isChromeAITaskSupported(apiName),
|
|
718
|
+
status,
|
|
719
|
+
session,
|
|
720
|
+
availability,
|
|
721
|
+
progress,
|
|
722
|
+
error,
|
|
723
|
+
createSession,
|
|
724
|
+
destroySession
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function useChromeAITaskOperation({
|
|
728
|
+
methodName,
|
|
729
|
+
streaming = false,
|
|
730
|
+
reuseSession = true,
|
|
731
|
+
...sessionOptions
|
|
732
|
+
}) {
|
|
733
|
+
const task = useChromeAITaskSession(sessionOptions);
|
|
734
|
+
const [state, setState] = useState({
|
|
735
|
+
status: "idle",
|
|
736
|
+
text: "",
|
|
737
|
+
chunks: []
|
|
738
|
+
});
|
|
739
|
+
const run = useCallback(async (...args) => {
|
|
740
|
+
setState({ status: streaming ? "streaming" : "prompting", text: "", chunks: [] });
|
|
741
|
+
const activeSession = task.session ?? await task.createSession();
|
|
742
|
+
try {
|
|
743
|
+
const method = assertTaskMethod(activeSession, methodName);
|
|
744
|
+
const result = method(...args);
|
|
745
|
+
if (streaming) {
|
|
746
|
+
const chunks = [];
|
|
747
|
+
for await (const chunk of toAsyncIterable(await result)) {
|
|
748
|
+
chunks.push(chunk);
|
|
749
|
+
setState({ status: "streaming", text: chunks.join(""), chunks: [...chunks] });
|
|
750
|
+
}
|
|
751
|
+
const text2 = chunks.join("");
|
|
752
|
+
setState({ status: "ready", result: text2, text: text2, chunks });
|
|
753
|
+
if (!reuseSession) {
|
|
754
|
+
task.destroySession();
|
|
755
|
+
}
|
|
756
|
+
return text2;
|
|
757
|
+
}
|
|
758
|
+
const awaited = await result;
|
|
759
|
+
const text = typeof awaited === "string" ? awaited : "";
|
|
760
|
+
setState({ status: "ready", result: awaited, text, chunks: text ? [text] : [] });
|
|
761
|
+
if (!reuseSession) {
|
|
762
|
+
task.destroySession();
|
|
763
|
+
}
|
|
764
|
+
return awaited;
|
|
765
|
+
} catch (cause) {
|
|
766
|
+
const error = toError(cause);
|
|
767
|
+
setState({ status: error.name === "AbortError" ? "aborted" : "error", text: "", chunks: [], error });
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
}, [methodName, reuseSession, streaming, task]);
|
|
771
|
+
const reset = useCallback(() => {
|
|
772
|
+
setState({ status: "idle", text: "", chunks: [] });
|
|
773
|
+
}, []);
|
|
774
|
+
return {
|
|
775
|
+
...task,
|
|
776
|
+
...state,
|
|
777
|
+
run,
|
|
778
|
+
reset
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function useChromeAISummarizer(options = {}) {
|
|
782
|
+
return useChromeAITaskOperation({
|
|
783
|
+
...options,
|
|
784
|
+
apiName: "Summarizer",
|
|
785
|
+
methodName: options.streaming ? "summarizeStreaming" : "summarize"
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
function useChromeAITranslator(options = {}) {
|
|
789
|
+
return useChromeAITaskOperation({
|
|
790
|
+
...options,
|
|
791
|
+
apiName: "Translator",
|
|
792
|
+
methodName: options.streaming ? "translateStreaming" : "translate"
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function useChromeAILanguageDetector(options = {}) {
|
|
796
|
+
return useChromeAITaskOperation({
|
|
797
|
+
...options,
|
|
798
|
+
apiName: "LanguageDetector",
|
|
799
|
+
methodName: "detect"
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
function useChromeAIWriter(options = {}) {
|
|
803
|
+
return useChromeAITaskOperation({
|
|
804
|
+
...options,
|
|
805
|
+
apiName: "Writer",
|
|
806
|
+
methodName: options.streaming ? "writeStreaming" : "write"
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
function useChromeAIRewriter(options = {}) {
|
|
810
|
+
return useChromeAITaskOperation({
|
|
811
|
+
...options,
|
|
812
|
+
apiName: "Rewriter",
|
|
813
|
+
methodName: options.streaming ? "rewriteStreaming" : "rewrite"
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
function useChromeAIProofreader(options = {}) {
|
|
817
|
+
return useChromeAITaskOperation({
|
|
818
|
+
...options,
|
|
819
|
+
apiName: "Proofreader",
|
|
820
|
+
methodName: "proofread"
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
export {
|
|
824
|
+
ChromeAIError,
|
|
825
|
+
assertTaskMethod,
|
|
826
|
+
callChromeAITaskMethod,
|
|
827
|
+
collectAsyncIterable,
|
|
828
|
+
createChromeAISession,
|
|
829
|
+
createChromeAITaskSession,
|
|
830
|
+
createStructuredPrompt,
|
|
831
|
+
defaultLanguageModelOptions,
|
|
832
|
+
defaultReflectionPrompt,
|
|
833
|
+
destroyChromeAITaskSession,
|
|
834
|
+
getChromeAITaskAPI,
|
|
835
|
+
getChromeLanguageModelAPI,
|
|
836
|
+
getUserActivation,
|
|
837
|
+
isChromeAITaskSupported,
|
|
838
|
+
isChromeLanguageModelSupported,
|
|
839
|
+
normalizeDownloadProgress,
|
|
840
|
+
prepareChromeAIModel,
|
|
841
|
+
promptWithReflection,
|
|
842
|
+
readChromeAIAvailability,
|
|
843
|
+
readChromeAIParams,
|
|
844
|
+
readChromeAITaskAvailability,
|
|
845
|
+
readableStreamToAsyncIterable,
|
|
846
|
+
safeParseJSON,
|
|
847
|
+
toAsyncIterable,
|
|
848
|
+
toError,
|
|
849
|
+
useChromeAIAppend,
|
|
850
|
+
useChromeAIAvailability,
|
|
851
|
+
useChromeAIClone,
|
|
852
|
+
useChromeAIContext,
|
|
853
|
+
useChromeAILanguageDetector,
|
|
854
|
+
useChromeAIParams,
|
|
855
|
+
useChromeAIPrompt,
|
|
856
|
+
useChromeAIProofreader,
|
|
857
|
+
useChromeAIRewriter,
|
|
858
|
+
useChromeAISession,
|
|
859
|
+
useChromeAIStream,
|
|
860
|
+
useChromeAISummarizer,
|
|
861
|
+
useChromeAITaskAvailability,
|
|
862
|
+
useChromeAITaskOperation,
|
|
863
|
+
useChromeAITaskSession,
|
|
864
|
+
useChromeAITranslator,
|
|
865
|
+
useChromeAIWriter,
|
|
866
|
+
withDownloadProgress,
|
|
867
|
+
withTaskDownloadProgress
|
|
868
|
+
};
|