@automattic/jetpack-ai-client 0.27.10 → 0.28.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 +5 -0
- package/build/chrome-ai/factory.js +51 -20
- package/build/chrome-ai/suggestions.js +10 -8
- package/build/hooks/use-ai-suggestions/index.d.ts +4 -3
- package/build/hooks/use-ai-suggestions/index.js +12 -3
- package/build/types.d.ts +32 -32
- package/build/types.js +2 -0
- package/package.json +5 -5
- package/src/chrome-ai/factory.ts +64 -21
- package/src/chrome-ai/suggestions.ts +11 -8
- package/src/hooks/use-ai-suggestions/index.ts +26 -5
- package/src/types.ts +38 -32
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.28.0] - 2025-05-12
|
|
9
|
+
### Changed
|
|
10
|
+
- AI Assistant: Propagate the AI model used in the AI requests. [#43390]
|
|
11
|
+
|
|
8
12
|
## [0.27.10] - 2025-05-05
|
|
9
13
|
### Changed
|
|
10
14
|
- Update package dependencies. [#43326]
|
|
@@ -604,6 +608,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
604
608
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
605
609
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
606
610
|
|
|
611
|
+
[0.28.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.27.10...v0.28.0
|
|
607
612
|
[0.27.10]: https://github.com/Automattic/jetpack-ai-client/compare/v0.27.9...v0.27.10
|
|
608
613
|
[0.27.9]: https://github.com/Automattic/jetpack-ai-client/compare/v0.27.8...v0.27.9
|
|
609
614
|
[0.27.8]: https://github.com/Automattic/jetpack-ai-client/compare/v0.27.7...v0.27.8
|
|
@@ -53,34 +53,50 @@ export default async function ChromeAIFactory(promptArg) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
// Early return if the prompt type is not supported.
|
|
57
|
+
if (!promptType.startsWith('ai-assistant-change-language') &&
|
|
58
|
+
!promptType.startsWith('ai-content-lens')) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// If the languageDetector is not available, we can't use the translation or summary features—it's safer to fall back
|
|
62
|
+
// to the default AI model than to risk an unexpected error.
|
|
63
|
+
if (!('LanguageDetector' in self) ||
|
|
64
|
+
!self.LanguageDetector.create ||
|
|
65
|
+
!self.LanguageDetector.availability) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const languageDetectorAvailability = await self.LanguageDetector.availability();
|
|
69
|
+
if (languageDetectorAvailability === 'unavailable') {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const detector = await self.LanguageDetector.create();
|
|
73
|
+
if (languageDetectorAvailability !== 'available') {
|
|
74
|
+
await detector.ready;
|
|
75
|
+
}
|
|
56
76
|
if (promptType.startsWith('ai-assistant-change-language')) {
|
|
57
77
|
const [language] = context.language.split(' ');
|
|
58
|
-
if (!('
|
|
59
|
-
!self.
|
|
60
|
-
!self.
|
|
78
|
+
if (!('Translator' in self) ||
|
|
79
|
+
!self.Translator.create ||
|
|
80
|
+
!self.Translator.availability) {
|
|
61
81
|
return false;
|
|
62
82
|
}
|
|
63
83
|
const languageOpts = {
|
|
64
84
|
sourceLanguage: 'en',
|
|
65
85
|
targetLanguage: language,
|
|
66
86
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (confidence.confidence > 0.75) {
|
|
77
|
-
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
87
|
+
const confidences = await detector.detect(context.content);
|
|
88
|
+
for (const confidence of confidences) {
|
|
89
|
+
// 75% confidence is just a value that was picked. Generally
|
|
90
|
+
// 80% of higher is pretty safe, but the source language is
|
|
91
|
+
// required for the translator to work at all, which is also
|
|
92
|
+
// why en is the default language.
|
|
93
|
+
if (confidence.confidence > 0.75) {
|
|
94
|
+
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
95
|
+
break;
|
|
80
96
|
}
|
|
81
97
|
}
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
98
|
+
const translationAvailability = await self.Translator.availability(languageOpts);
|
|
99
|
+
if (translationAvailability === 'unavailable') {
|
|
84
100
|
return false;
|
|
85
101
|
}
|
|
86
102
|
const chromeAI = new ChromeAISuggestionsEventSource({
|
|
@@ -90,13 +106,28 @@ export default async function ChromeAIFactory(promptArg) {
|
|
|
90
106
|
});
|
|
91
107
|
return chromeAI;
|
|
92
108
|
}
|
|
93
|
-
// TODO: consider also using ChromeAI for ai-assistant-summarize
|
|
94
109
|
if (promptType.startsWith('ai-content-lens')) {
|
|
110
|
+
if (!('Summarizer' in self)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (context.language && context.language !== 'en (English)') {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const confidences = await detector.detect(context.content);
|
|
117
|
+
// if it doesn't look like the content is in English, we can't use the summary feature
|
|
118
|
+
for (const confidence of confidences) {
|
|
119
|
+
// 75% confidence is just a value that was picked. Generally
|
|
120
|
+
// 80% of higher is pretty safe, but the source language is
|
|
121
|
+
// required for the translator to work at all, which is also
|
|
122
|
+
// why en is the default language.
|
|
123
|
+
if (confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en') {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
95
127
|
const summaryOpts = {
|
|
96
128
|
tone: tone,
|
|
97
129
|
wordCount: wordCount,
|
|
98
130
|
};
|
|
99
|
-
// TODO: detect if the content is in English and fallback if it's not
|
|
100
131
|
return new ChromeAISuggestionsEventSource({
|
|
101
132
|
content: context.content,
|
|
102
133
|
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
@@ -55,10 +55,10 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
55
55
|
}
|
|
56
56
|
// use the Chrome AI translator
|
|
57
57
|
async translate(text, target, source = '') {
|
|
58
|
-
if (!('
|
|
58
|
+
if (!('Translator' in self)) {
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
const translator = await self.
|
|
61
|
+
const translator = await self.Translator.create({
|
|
62
62
|
sourceLanguage: source,
|
|
63
63
|
targetLanguage: target,
|
|
64
64
|
});
|
|
@@ -96,16 +96,18 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
96
96
|
}
|
|
97
97
|
// use the Chrome AI summarizer
|
|
98
98
|
async summarize(text, tone, wordCount) {
|
|
99
|
-
if (!('
|
|
99
|
+
if (!('Summarizer' in self)) {
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.log('Summarizer is available');
|
|
104
|
+
const availability = await self.Summarizer.availability();
|
|
105
|
+
if (availability === 'unavailable') {
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
|
-
const
|
|
107
|
-
const summarizer = await self.
|
|
108
|
-
if (
|
|
108
|
+
const summarizerOptions = this.getSummarizerOptions(tone, wordCount);
|
|
109
|
+
const summarizer = await self.Summarizer.create(summarizerOptions);
|
|
110
|
+
if (availability !== 'available') {
|
|
109
111
|
await summarizer.ready;
|
|
110
112
|
}
|
|
111
113
|
try {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { AskQuestionOptionsArgProps } from '../../ask-question/index.ts';
|
|
5
5
|
import type SuggestionsEventSource from '../../suggestions-event-source/index.ts';
|
|
6
|
-
import type { PromptProp, SuggestionErrorCode, RequestingStateProp } from '../../types.ts';
|
|
6
|
+
import type { PromptProp, SuggestionErrorCode, RequestingStateProp, AiModelTypeProp } from '../../types.ts';
|
|
7
7
|
export type RequestingErrorProps = {
|
|
8
8
|
code: SuggestionErrorCode;
|
|
9
9
|
message: string;
|
|
@@ -18,13 +18,14 @@ type useAiSuggestionsOptions = {
|
|
|
18
18
|
askQuestionOptions?: AskQuestionOptionsArgProps;
|
|
19
19
|
initialRequestingState?: RequestingStateProp;
|
|
20
20
|
onSuggestion?: (suggestion: string) => void;
|
|
21
|
-
onDone?: (content: string, skipRequestCount?: boolean) => void;
|
|
21
|
+
onDone?: (content: string, skipRequestCount?: boolean, modelUsed?: AiModelTypeProp) => void;
|
|
22
22
|
onStop?: () => void;
|
|
23
23
|
onError?: (error: RequestingErrorProps) => void;
|
|
24
|
-
onAllErrors?: (error: RequestingErrorProps) => void;
|
|
24
|
+
onAllErrors?: (error: RequestingErrorProps, skipRequestCount?: boolean) => void;
|
|
25
25
|
};
|
|
26
26
|
type useAiSuggestionsProps = {
|
|
27
27
|
suggestion: string;
|
|
28
|
+
model: AiModelTypeProp;
|
|
28
29
|
error: RequestingErrorProps | undefined;
|
|
29
30
|
requestingState: RequestingStateProp;
|
|
30
31
|
eventSource: SuggestionsEventSource | undefined;
|
|
@@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
8
8
|
*/
|
|
9
9
|
import askQuestion from "../../ask-question/index.js";
|
|
10
10
|
import ChromeAIFactory from "../../chrome-ai/factory.js";
|
|
11
|
-
import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, } from "../../types.js";
|
|
11
|
+
import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, AI_MODEL_DEFAULT, AI_MODEL_GEMINI_NANO, } from "../../types.js";
|
|
12
12
|
/**
|
|
13
13
|
* Get the error data for a given error code.
|
|
14
14
|
*
|
|
@@ -75,6 +75,12 @@ export function removeLlamaArtifact(suggestion) {
|
|
|
75
75
|
export default function useAiSuggestions({ prompt, autoRequest = false, askQuestionOptions = {}, initialRequestingState = 'init', onSuggestion, onDone, onStop, onError, onAllErrors, } = {}) {
|
|
76
76
|
const [requestingState, setRequestingState] = useState(initialRequestingState);
|
|
77
77
|
const [suggestion, setSuggestion] = useState('');
|
|
78
|
+
const [model, setModel] = useState(AI_MODEL_DEFAULT);
|
|
79
|
+
const modelRef = useRef(AI_MODEL_DEFAULT);
|
|
80
|
+
const setModelAndRef = (value) => {
|
|
81
|
+
setModel(value);
|
|
82
|
+
modelRef.current = value;
|
|
83
|
+
};
|
|
78
84
|
const [error, setError] = useState();
|
|
79
85
|
// Store the event source in a ref, so we can handle it if needed.
|
|
80
86
|
const eventSourceRef = useRef(undefined);
|
|
@@ -101,11 +107,11 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
101
107
|
const handleDone = useCallback((event) => {
|
|
102
108
|
closeEventSource();
|
|
103
109
|
const fullSuggestion = removeLlamaArtifact(event?.detail?.message ?? event?.detail);
|
|
104
|
-
onDone?.(fullSuggestion, event?.detail?.source === 'chromeAI');
|
|
110
|
+
onDone?.(fullSuggestion, event?.detail?.source === 'chromeAI', modelRef.current);
|
|
105
111
|
setRequestingState('done');
|
|
106
112
|
}, [onDone]);
|
|
107
113
|
const handleAnyError = useCallback((event) => {
|
|
108
|
-
onAllErrors?.(event?.detail);
|
|
114
|
+
onAllErrors?.(event?.detail, event?.detail?.source === 'chromeAI');
|
|
109
115
|
}, [onAllErrors]);
|
|
110
116
|
const handleError = useCallback((errorCode) => {
|
|
111
117
|
eventSourceRef?.current?.close();
|
|
@@ -133,9 +139,11 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
133
139
|
// check if we can (or should) use Chrome AI
|
|
134
140
|
const chromeAI = await ChromeAIFactory(promptArg);
|
|
135
141
|
if (chromeAI !== false) {
|
|
142
|
+
setModelAndRef(AI_MODEL_GEMINI_NANO);
|
|
136
143
|
eventSourceRef.current = chromeAI;
|
|
137
144
|
}
|
|
138
145
|
else {
|
|
146
|
+
setModelAndRef(AI_MODEL_DEFAULT);
|
|
139
147
|
eventSourceRef.current = await askQuestion(promptArg, options);
|
|
140
148
|
}
|
|
141
149
|
if (!eventSourceRef?.current) {
|
|
@@ -231,6 +239,7 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
231
239
|
return {
|
|
232
240
|
// Data
|
|
233
241
|
suggestion,
|
|
242
|
+
model,
|
|
234
243
|
error,
|
|
235
244
|
requestingState,
|
|
236
245
|
// Requests handlers
|
package/build/types.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export declare const REQUESTING_STATES: readonly ["init", "requesting", "suggest
|
|
|
29
29
|
export type RequestingStateProp = (typeof REQUESTING_STATES)[number];
|
|
30
30
|
export declare const AI_MODEL_GPT_3_5_Turbo_16K: "gpt-3.5-turbo-16k";
|
|
31
31
|
export declare const AI_MODEL_GPT_4: "gpt-4";
|
|
32
|
-
export
|
|
32
|
+
export declare const AI_MODEL_DEFAULT: "default";
|
|
33
|
+
export declare const AI_MODEL_GEMINI_NANO: "gemini-nano";
|
|
34
|
+
export type AiModelTypeProp = typeof AI_MODEL_GPT_3_5_Turbo_16K | typeof AI_MODEL_GPT_4 | typeof AI_MODEL_GEMINI_NANO | typeof AI_MODEL_DEFAULT;
|
|
33
35
|
export type { RecordingState } from './hooks/use-media-recording/index.ts';
|
|
34
36
|
export type CancelablePromise<T = void> = Promise<T> & {
|
|
35
37
|
canceled?: boolean;
|
|
@@ -53,43 +55,41 @@ export interface BlockEditorStore {
|
|
|
53
55
|
}
|
|
54
56
|
declare global {
|
|
55
57
|
interface Window {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
LanguageDetector?: {
|
|
59
|
+
create: () => Promise<{
|
|
60
|
+
detect: (text: string) => Promise<{
|
|
61
|
+
detectedLanguage: string;
|
|
62
|
+
confidence: number;
|
|
63
|
+
}[]>;
|
|
64
|
+
ready: Promise<void>;
|
|
65
|
+
}>;
|
|
66
|
+
availability: () => Promise<'unavailable' | 'available' | 'downloadable' | 'downloading' | string>;
|
|
67
|
+
};
|
|
68
|
+
Translator?: {
|
|
69
|
+
create: (options: {
|
|
62
70
|
sourceLanguage: string;
|
|
63
71
|
targetLanguage: string;
|
|
64
72
|
}) => Promise<{
|
|
65
73
|
translate: (text: string) => Promise<string>;
|
|
66
74
|
}>;
|
|
75
|
+
availability: (options: {
|
|
76
|
+
sourceLanguage: string;
|
|
77
|
+
targetLanguage: string;
|
|
78
|
+
}) => Promise<'unavailable' | 'available' | 'downloadable' | 'downloading' | string>;
|
|
67
79
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
create: (options: {
|
|
82
|
-
sharedContext?: string;
|
|
83
|
-
type?: string;
|
|
84
|
-
format?: string;
|
|
85
|
-
length?: string;
|
|
86
|
-
}) => Promise<{
|
|
87
|
-
ready: Promise<void>;
|
|
88
|
-
summarize: (text: string, summarizeOptions?: {
|
|
89
|
-
context?: string;
|
|
90
|
-
}) => Promise<string>;
|
|
91
|
-
}>;
|
|
92
|
-
};
|
|
80
|
+
Summarizer?: {
|
|
81
|
+
availability: () => Promise<'unavailable' | 'available' | 'downloadable' | 'downloading' | string>;
|
|
82
|
+
create: (options: {
|
|
83
|
+
sharedContext?: string;
|
|
84
|
+
type?: string;
|
|
85
|
+
format?: string;
|
|
86
|
+
length?: string;
|
|
87
|
+
}) => Promise<{
|
|
88
|
+
ready: Promise<void>;
|
|
89
|
+
summarize: (text: string, summarizeOptions?: {
|
|
90
|
+
context?: string;
|
|
91
|
+
}) => Promise<string>;
|
|
92
|
+
}>;
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
}
|
package/build/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.28.0",
|
|
5
5
|
"description": "A JS client for consuming Jetpack AI services",
|
|
6
6
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"main": "./build/index.js",
|
|
46
46
|
"types": "./build/index.d.ts",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@automattic/jetpack-base-styles": "^0.7.
|
|
49
|
-
"@automattic/jetpack-components": "^0.
|
|
50
|
-
"@automattic/jetpack-connection": "^0.39.
|
|
51
|
-
"@automattic/jetpack-shared-extension-utils": "^0.19.
|
|
48
|
+
"@automattic/jetpack-base-styles": "^0.7.4",
|
|
49
|
+
"@automattic/jetpack-components": "^0.73.0",
|
|
50
|
+
"@automattic/jetpack-connection": "^0.39.14",
|
|
51
|
+
"@automattic/jetpack-shared-extension-utils": "^0.19.3",
|
|
52
52
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
53
53
|
"@types/jest": "29.5.14",
|
|
54
54
|
"@types/react": "18.3.18",
|
package/src/chrome-ai/factory.ts
CHANGED
|
@@ -75,13 +75,41 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// Early return if the prompt type is not supported.
|
|
79
|
+
if (
|
|
80
|
+
! promptType.startsWith( 'ai-assistant-change-language' ) &&
|
|
81
|
+
! promptType.startsWith( 'ai-content-lens' )
|
|
82
|
+
) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If the languageDetector is not available, we can't use the translation or summary features—it's safer to fall back
|
|
87
|
+
// to the default AI model than to risk an unexpected error.
|
|
88
|
+
if (
|
|
89
|
+
! ( 'LanguageDetector' in self ) ||
|
|
90
|
+
! self.LanguageDetector.create ||
|
|
91
|
+
! self.LanguageDetector.availability
|
|
92
|
+
) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const languageDetectorAvailability = await self.LanguageDetector.availability();
|
|
97
|
+
if ( languageDetectorAvailability === 'unavailable' ) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const detector = await self.LanguageDetector.create();
|
|
102
|
+
if ( languageDetectorAvailability !== 'available' ) {
|
|
103
|
+
await detector.ready;
|
|
104
|
+
}
|
|
105
|
+
|
|
78
106
|
if ( promptType.startsWith( 'ai-assistant-change-language' ) ) {
|
|
79
107
|
const [ language ] = context.language.split( ' ' );
|
|
80
108
|
|
|
81
109
|
if (
|
|
82
|
-
! ( '
|
|
83
|
-
! self.
|
|
84
|
-
! self.
|
|
110
|
+
! ( 'Translator' in self ) ||
|
|
111
|
+
! self.Translator.create ||
|
|
112
|
+
! self.Translator.availability
|
|
85
113
|
) {
|
|
86
114
|
return false;
|
|
87
115
|
}
|
|
@@ -91,26 +119,22 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
91
119
|
targetLanguage: language,
|
|
92
120
|
};
|
|
93
121
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if ( confidence.confidence > 0.75 ) {
|
|
105
|
-
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
122
|
+
const confidences = await detector.detect( context.content );
|
|
123
|
+
|
|
124
|
+
for ( const confidence of confidences ) {
|
|
125
|
+
// 75% confidence is just a value that was picked. Generally
|
|
126
|
+
// 80% of higher is pretty safe, but the source language is
|
|
127
|
+
// required for the translator to work at all, which is also
|
|
128
|
+
// why en is the default language.
|
|
129
|
+
if ( confidence.confidence > 0.75 ) {
|
|
130
|
+
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
131
|
+
break;
|
|
108
132
|
}
|
|
109
133
|
}
|
|
110
134
|
|
|
111
|
-
const
|
|
135
|
+
const translationAvailability = await self.Translator.availability( languageOpts );
|
|
112
136
|
|
|
113
|
-
if (
|
|
137
|
+
if ( translationAvailability === 'unavailable' ) {
|
|
114
138
|
return false;
|
|
115
139
|
}
|
|
116
140
|
|
|
@@ -123,14 +147,33 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
|
123
147
|
return chromeAI;
|
|
124
148
|
}
|
|
125
149
|
|
|
126
|
-
// TODO: consider also using ChromeAI for ai-assistant-summarize
|
|
127
150
|
if ( promptType.startsWith( 'ai-content-lens' ) ) {
|
|
151
|
+
if ( ! ( 'Summarizer' in self ) ) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if ( context.language && context.language !== 'en (English)' ) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const confidences = await detector.detect( context.content );
|
|
160
|
+
|
|
161
|
+
// if it doesn't look like the content is in English, we can't use the summary feature
|
|
162
|
+
for ( const confidence of confidences ) {
|
|
163
|
+
// 75% confidence is just a value that was picked. Generally
|
|
164
|
+
// 80% of higher is pretty safe, but the source language is
|
|
165
|
+
// required for the translator to work at all, which is also
|
|
166
|
+
// why en is the default language.
|
|
167
|
+
if ( confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en' ) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
128
172
|
const summaryOpts = {
|
|
129
173
|
tone: tone,
|
|
130
174
|
wordCount: wordCount,
|
|
131
175
|
};
|
|
132
176
|
|
|
133
|
-
// TODO: detect if the content is in English and fallback if it's not
|
|
134
177
|
return new ChromeAISuggestionsEventSource( {
|
|
135
178
|
content: context.content,
|
|
136
179
|
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
@@ -110,11 +110,11 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
110
110
|
|
|
111
111
|
// use the Chrome AI translator
|
|
112
112
|
async translate( text: string, target: string, source: string = '' ) {
|
|
113
|
-
if ( ! ( '
|
|
113
|
+
if ( ! ( 'Translator' in self ) ) {
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const translator = await self.
|
|
117
|
+
const translator = await self.Translator.create( {
|
|
118
118
|
sourceLanguage: source,
|
|
119
119
|
targetLanguage: target,
|
|
120
120
|
} );
|
|
@@ -125,6 +125,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
127
|
const translation = await translator.translate( renderHTMLFromMarkdown( { content: text } ) );
|
|
128
|
+
|
|
128
129
|
this.processEvent( {
|
|
129
130
|
id: '',
|
|
130
131
|
event: 'translation',
|
|
@@ -160,20 +161,22 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
|
160
161
|
|
|
161
162
|
// use the Chrome AI summarizer
|
|
162
163
|
async summarize( text: string, tone?: string, wordCount?: number ) {
|
|
163
|
-
if ( ! ( '
|
|
164
|
+
if ( ! ( 'Summarizer' in self ) ) {
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
166
|
-
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.log( 'Summarizer is available' );
|
|
169
|
+
const availability = await self.Summarizer.availability();
|
|
167
170
|
|
|
168
|
-
if (
|
|
171
|
+
if ( availability === 'unavailable' ) {
|
|
169
172
|
return;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
const
|
|
175
|
+
const summarizerOptions = this.getSummarizerOptions( tone, wordCount );
|
|
173
176
|
|
|
174
|
-
const summarizer = await self.
|
|
177
|
+
const summarizer = await self.Summarizer.create( summarizerOptions );
|
|
175
178
|
|
|
176
|
-
if (
|
|
179
|
+
if ( availability !== 'available' ) {
|
|
177
180
|
await summarizer.ready;
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -16,13 +16,20 @@ import {
|
|
|
16
16
|
ERROR_SERVICE_UNAVAILABLE,
|
|
17
17
|
ERROR_UNCLEAR_PROMPT,
|
|
18
18
|
ERROR_RESPONSE,
|
|
19
|
+
AI_MODEL_DEFAULT,
|
|
20
|
+
AI_MODEL_GEMINI_NANO,
|
|
19
21
|
} from '../../types.ts';
|
|
20
22
|
/**
|
|
21
23
|
* Types & constants
|
|
22
24
|
*/
|
|
23
25
|
import type { AskQuestionOptionsArgProps } from '../../ask-question/index.ts';
|
|
24
26
|
import type SuggestionsEventSource from '../../suggestions-event-source/index.ts';
|
|
25
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
PromptProp,
|
|
29
|
+
SuggestionErrorCode,
|
|
30
|
+
RequestingStateProp,
|
|
31
|
+
AiModelTypeProp,
|
|
32
|
+
} from '../../types.ts';
|
|
26
33
|
|
|
27
34
|
export type RequestingErrorProps = {
|
|
28
35
|
/*
|
|
@@ -70,7 +77,7 @@ type useAiSuggestionsOptions = {
|
|
|
70
77
|
/*
|
|
71
78
|
* onDone callback.
|
|
72
79
|
*/
|
|
73
|
-
onDone?: ( content: string, skipRequestCount?: boolean ) => void;
|
|
80
|
+
onDone?: ( content: string, skipRequestCount?: boolean, modelUsed?: AiModelTypeProp ) => void;
|
|
74
81
|
|
|
75
82
|
/*
|
|
76
83
|
* onStop callback.
|
|
@@ -85,7 +92,7 @@ type useAiSuggestionsOptions = {
|
|
|
85
92
|
/*
|
|
86
93
|
* Error callback common for all errors.
|
|
87
94
|
*/
|
|
88
|
-
onAllErrors?: ( error: RequestingErrorProps ) => void;
|
|
95
|
+
onAllErrors?: ( error: RequestingErrorProps, skipRequestCount?: boolean ) => void;
|
|
89
96
|
};
|
|
90
97
|
|
|
91
98
|
type useAiSuggestionsProps = {
|
|
@@ -94,6 +101,11 @@ type useAiSuggestionsProps = {
|
|
|
94
101
|
*/
|
|
95
102
|
suggestion: string;
|
|
96
103
|
|
|
104
|
+
/*
|
|
105
|
+
* The model.
|
|
106
|
+
*/
|
|
107
|
+
model: AiModelTypeProp;
|
|
108
|
+
|
|
97
109
|
/*
|
|
98
110
|
* The error.
|
|
99
111
|
*/
|
|
@@ -221,6 +233,12 @@ export default function useAiSuggestions( {
|
|
|
221
233
|
const [ requestingState, setRequestingState ] =
|
|
222
234
|
useState< RequestingStateProp >( initialRequestingState );
|
|
223
235
|
const [ suggestion, setSuggestion ] = useState< string >( '' );
|
|
236
|
+
const [ model, setModel ] = useState< AiModelTypeProp >( AI_MODEL_DEFAULT );
|
|
237
|
+
const modelRef = useRef< AiModelTypeProp >( AI_MODEL_DEFAULT );
|
|
238
|
+
const setModelAndRef = ( value: AiModelTypeProp ) => {
|
|
239
|
+
setModel( value );
|
|
240
|
+
modelRef.current = value;
|
|
241
|
+
};
|
|
224
242
|
const [ error, setError ] = useState< RequestingErrorProps >();
|
|
225
243
|
|
|
226
244
|
// Store the event source in a ref, so we can handle it if needed.
|
|
@@ -258,7 +276,7 @@ export default function useAiSuggestions( {
|
|
|
258
276
|
|
|
259
277
|
const fullSuggestion = removeLlamaArtifact( event?.detail?.message ?? event?.detail );
|
|
260
278
|
|
|
261
|
-
onDone?.( fullSuggestion, event?.detail?.source === 'chromeAI' );
|
|
279
|
+
onDone?.( fullSuggestion, event?.detail?.source === 'chromeAI', modelRef.current );
|
|
262
280
|
setRequestingState( 'done' );
|
|
263
281
|
},
|
|
264
282
|
[ onDone ]
|
|
@@ -266,7 +284,7 @@ export default function useAiSuggestions( {
|
|
|
266
284
|
|
|
267
285
|
const handleAnyError = useCallback(
|
|
268
286
|
( event: CustomEvent ) => {
|
|
269
|
-
onAllErrors?.( event?.detail );
|
|
287
|
+
onAllErrors?.( event?.detail, event?.detail?.source === 'chromeAI' );
|
|
270
288
|
},
|
|
271
289
|
[ onAllErrors ]
|
|
272
290
|
);
|
|
@@ -319,8 +337,10 @@ export default function useAiSuggestions( {
|
|
|
319
337
|
const chromeAI = await ChromeAIFactory( promptArg );
|
|
320
338
|
|
|
321
339
|
if ( chromeAI !== false ) {
|
|
340
|
+
setModelAndRef( AI_MODEL_GEMINI_NANO );
|
|
322
341
|
eventSourceRef.current = chromeAI;
|
|
323
342
|
} else {
|
|
343
|
+
setModelAndRef( AI_MODEL_DEFAULT );
|
|
324
344
|
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
325
345
|
}
|
|
326
346
|
|
|
@@ -436,6 +456,7 @@ export default function useAiSuggestions( {
|
|
|
436
456
|
return {
|
|
437
457
|
// Data
|
|
438
458
|
suggestion,
|
|
459
|
+
model,
|
|
439
460
|
error,
|
|
440
461
|
requestingState,
|
|
441
462
|
|
package/src/types.ts
CHANGED
|
@@ -93,8 +93,14 @@ export type RequestingStateProp = ( typeof REQUESTING_STATES )[ number ];
|
|
|
93
93
|
*/
|
|
94
94
|
export const AI_MODEL_GPT_3_5_Turbo_16K = 'gpt-3.5-turbo-16k' as const;
|
|
95
95
|
export const AI_MODEL_GPT_4 = 'gpt-4' as const;
|
|
96
|
+
export const AI_MODEL_DEFAULT = 'default' as const;
|
|
97
|
+
export const AI_MODEL_GEMINI_NANO = 'gemini-nano' as const;
|
|
96
98
|
|
|
97
|
-
export type AiModelTypeProp =
|
|
99
|
+
export type AiModelTypeProp =
|
|
100
|
+
| typeof AI_MODEL_GPT_3_5_Turbo_16K
|
|
101
|
+
| typeof AI_MODEL_GPT_4
|
|
102
|
+
| typeof AI_MODEL_GEMINI_NANO
|
|
103
|
+
| typeof AI_MODEL_DEFAULT;
|
|
98
104
|
|
|
99
105
|
/*
|
|
100
106
|
* Media recording types
|
|
@@ -135,43 +141,43 @@ export interface BlockEditorStore {
|
|
|
135
141
|
|
|
136
142
|
declare global {
|
|
137
143
|
interface Window {
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
LanguageDetector?: {
|
|
145
|
+
create: () => Promise< {
|
|
146
|
+
detect: ( text: string ) => Promise<
|
|
147
|
+
{
|
|
148
|
+
detectedLanguage: string;
|
|
149
|
+
confidence: number;
|
|
150
|
+
}[]
|
|
151
|
+
>;
|
|
152
|
+
ready: Promise< void >;
|
|
153
|
+
} >;
|
|
154
|
+
availability: () => Promise<
|
|
155
|
+
'unavailable' | 'available' | 'downloadable' | 'downloading' | string
|
|
156
|
+
>;
|
|
157
|
+
};
|
|
158
|
+
Translator?: {
|
|
159
|
+
create: ( options: {
|
|
140
160
|
sourceLanguage: string;
|
|
141
161
|
targetLanguage: string;
|
|
142
|
-
} ) => Promise<
|
|
143
|
-
|
|
162
|
+
} ) => Promise< { translate: ( text: string ) => Promise< string > } >;
|
|
163
|
+
availability: ( options: {
|
|
144
164
|
sourceLanguage: string;
|
|
145
165
|
targetLanguage: string;
|
|
166
|
+
} ) => Promise< 'unavailable' | 'available' | 'downloadable' | 'downloading' | string >;
|
|
167
|
+
};
|
|
168
|
+
Summarizer?: {
|
|
169
|
+
availability: () => Promise<
|
|
170
|
+
'unavailable' | 'available' | 'downloadable' | 'downloading' | string
|
|
171
|
+
>;
|
|
172
|
+
create: ( options: {
|
|
173
|
+
sharedContext?: string;
|
|
174
|
+
type?: string;
|
|
175
|
+
format?: string;
|
|
176
|
+
length?: string;
|
|
146
177
|
} ) => Promise< {
|
|
147
|
-
|
|
178
|
+
ready: Promise< void >;
|
|
179
|
+
summarize: ( text: string, summarizeOptions?: { context?: string } ) => Promise< string >;
|
|
148
180
|
} >;
|
|
149
181
|
};
|
|
150
|
-
ai?: {
|
|
151
|
-
languageDetector: {
|
|
152
|
-
create: () => Promise< {
|
|
153
|
-
detect: ( text: string ) => Promise<
|
|
154
|
-
{
|
|
155
|
-
detectedLanguage: string;
|
|
156
|
-
confidence: number;
|
|
157
|
-
}[]
|
|
158
|
-
>;
|
|
159
|
-
} >;
|
|
160
|
-
};
|
|
161
|
-
summarizer?: {
|
|
162
|
-
capabilities: () => Promise< {
|
|
163
|
-
available: 'no' | 'yes' | 'after-download';
|
|
164
|
-
} >;
|
|
165
|
-
create: ( options: {
|
|
166
|
-
sharedContext?: string;
|
|
167
|
-
type?: string;
|
|
168
|
-
format?: string;
|
|
169
|
-
length?: string;
|
|
170
|
-
} ) => Promise< {
|
|
171
|
-
ready: Promise< void >;
|
|
172
|
-
summarize: ( text: string, summarizeOptions?: { context?: string } ) => Promise< string >;
|
|
173
|
-
} >;
|
|
174
|
-
};
|
|
175
|
-
};
|
|
176
182
|
}
|
|
177
183
|
}
|