@automattic/jetpack-ai-client 0.33.32 → 0.34.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 +11 -0
- package/build/hooks/use-ai-feature/index.d.ts +0 -1
- package/build/hooks/use-ai-suggestions/index.d.ts +2 -2
- package/build/hooks/use-ai-suggestions/index.js +5 -15
- package/build/index.d.ts +0 -4
- package/build/index.js +0 -4
- package/build/types.d.ts +1 -42
- package/build/types.js +0 -1
- package/declarations.d.ts +5 -0
- package/package.json +7 -7
- package/src/hooks/use-ai-suggestions/index.ts +6 -17
- package/src/index.ts +0 -5
- package/src/types.ts +0 -46
- package/build/chrome-ai/factory.d.ts +0 -9
- package/build/chrome-ai/factory.js +0 -150
- package/build/chrome-ai/get-availability.d.ts +0 -7
- package/build/chrome-ai/get-availability.js +0 -67
- package/build/chrome-ai/index.d.ts +0 -2
- package/build/chrome-ai/index.js +0 -2
- package/build/chrome-ai/suggestions.d.ts +0 -38
- package/build/chrome-ai/suggestions.js +0 -160
- package/src/chrome-ai/factory.ts +0 -199
- package/src/chrome-ai/get-availability.ts +0 -105
- package/src/chrome-ai/index.ts +0 -2
- package/src/chrome-ai/suggestions.ts +0 -235
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.34.0] - 2026-02-10
|
|
9
|
+
### Added
|
|
10
|
+
- AI Client: Add explicit window interface with missing types. [#46938]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Update package dependencies. [#46905]
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
- Removed Chrome AI built-in API integration code. [#46896]
|
|
17
|
+
|
|
8
18
|
## [0.33.32] - 2026-02-02
|
|
9
19
|
### Changed
|
|
10
20
|
- Update package dependencies. [#46854]
|
|
@@ -790,6 +800,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
790
800
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
791
801
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
792
802
|
|
|
803
|
+
[0.34.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.33.32...v0.34.0
|
|
793
804
|
[0.33.32]: https://github.com/Automattic/jetpack-ai-client/compare/v0.33.31...v0.33.32
|
|
794
805
|
[0.33.31]: https://github.com/Automattic/jetpack-ai-client/compare/v0.33.30...v0.33.31
|
|
795
806
|
[0.33.30]: https://github.com/Automattic/jetpack-ai-client/compare/v0.33.29...v0.33.30
|
|
@@ -30,5 +30,4 @@ export default function useAiFeature(): {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
featuresControl?: import("@automattic/jetpack-shared-extension-utils/store/wordpress-com/types").FeaturesControl;
|
|
33
|
-
chromeAiTokens?: import("@automattic/jetpack-shared-extension-utils/store/wordpress-com/types").ChromeAiTokens;
|
|
34
33
|
};
|
|
@@ -18,10 +18,10 @@ type useAiSuggestionsOptions = {
|
|
|
18
18
|
askQuestionOptions?: AskQuestionOptionsArgProps;
|
|
19
19
|
initialRequestingState?: RequestingStateProp;
|
|
20
20
|
onSuggestion?: (suggestion: string) => void;
|
|
21
|
-
onDone?: (content: string,
|
|
21
|
+
onDone?: (content: string, modelUsed?: AiModelTypeProp) => void;
|
|
22
22
|
onStop?: () => void;
|
|
23
23
|
onError?: (error: RequestingErrorProps) => void;
|
|
24
|
-
onAllErrors?: (error: RequestingErrorProps
|
|
24
|
+
onAllErrors?: (error: RequestingErrorProps) => void;
|
|
25
25
|
};
|
|
26
26
|
type useAiSuggestionsProps = {
|
|
27
27
|
suggestion: string;
|
|
@@ -8,8 +8,7 @@ import debugFactory from 'debug';
|
|
|
8
8
|
* Internal dependencies
|
|
9
9
|
*/
|
|
10
10
|
import askQuestion from "../../ask-question/index.js";
|
|
11
|
-
import
|
|
12
|
-
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";
|
|
11
|
+
import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, AI_MODEL_DEFAULT, } from "../../types.js";
|
|
13
12
|
const debug = debugFactory('ai-client:use-ai-suggestions');
|
|
14
13
|
/**
|
|
15
14
|
* Get the error data for a given error code.
|
|
@@ -111,11 +110,11 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
111
110
|
const handleDone = useCallback((event) => {
|
|
112
111
|
closeEventSource();
|
|
113
112
|
const fullSuggestion = removeLlamaArtifact(event?.detail?.message ?? event?.detail);
|
|
114
|
-
onDone?.(fullSuggestion,
|
|
113
|
+
onDone?.(fullSuggestion, modelRef.current);
|
|
115
114
|
setRequestingState('done');
|
|
116
115
|
}, [onDone]);
|
|
117
116
|
const handleAnyError = useCallback((event) => {
|
|
118
|
-
onAllErrors?.(event?.detail
|
|
117
|
+
onAllErrors?.(event?.detail);
|
|
119
118
|
}, [onAllErrors]);
|
|
120
119
|
const handleError = useCallback((errorCode) => {
|
|
121
120
|
eventSourceRef?.current?.close();
|
|
@@ -140,17 +139,8 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
140
139
|
setError(undefined);
|
|
141
140
|
// Set the request status.
|
|
142
141
|
setRequestingState('requesting');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
debug('chromeAI', chromeAI !== false);
|
|
146
|
-
if (chromeAI !== false) {
|
|
147
|
-
setModelAndRef(AI_MODEL_GEMINI_NANO);
|
|
148
|
-
eventSourceRef.current = chromeAI;
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
setModelAndRef(AI_MODEL_DEFAULT);
|
|
152
|
-
eventSourceRef.current = await askQuestion(promptArg, options);
|
|
153
|
-
}
|
|
142
|
+
setModelAndRef(AI_MODEL_DEFAULT);
|
|
143
|
+
eventSourceRef.current = await askQuestion(promptArg, options);
|
|
154
144
|
if (!eventSourceRef?.current) {
|
|
155
145
|
debug('no event source');
|
|
156
146
|
return;
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
package/build/types.d.ts
CHANGED
|
@@ -30,8 +30,7 @@ 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
32
|
export declare const AI_MODEL_DEFAULT: "default";
|
|
33
|
-
export
|
|
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
|
+
export type AiModelTypeProp = typeof AI_MODEL_GPT_3_5_Turbo_16K | typeof AI_MODEL_GPT_4 | typeof AI_MODEL_DEFAULT;
|
|
35
34
|
export type { RecordingState } from './hooks/use-media-recording/index.ts';
|
|
36
35
|
export type CancelablePromise<T = void> = Promise<T> & {
|
|
37
36
|
canceled?: boolean;
|
|
@@ -53,43 +52,3 @@ export interface BlockEditorStore {
|
|
|
53
52
|
[key in keyof typeof BlockEditorSelectors]: (typeof BlockEditorSelectors)[key];
|
|
54
53
|
};
|
|
55
54
|
}
|
|
56
|
-
declare global {
|
|
57
|
-
interface Window {
|
|
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: {
|
|
70
|
-
sourceLanguage: string;
|
|
71
|
-
targetLanguage: string;
|
|
72
|
-
}) => Promise<{
|
|
73
|
-
translate: (text: string) => Promise<string>;
|
|
74
|
-
}>;
|
|
75
|
-
availability: (options: {
|
|
76
|
-
sourceLanguage: string;
|
|
77
|
-
targetLanguage: string;
|
|
78
|
-
}) => Promise<'unavailable' | 'available' | 'downloadable' | 'downloading' | string>;
|
|
79
|
-
};
|
|
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
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
package/build/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-ai-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"private": false,
|
|
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",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@automattic/jetpack-base-styles": "^1.0.15",
|
|
37
|
-
"@automattic/jetpack-components": "^1.4.
|
|
38
|
-
"@automattic/jetpack-connection": "^1.4.
|
|
37
|
+
"@automattic/jetpack-components": "^1.4.12",
|
|
38
|
+
"@automattic/jetpack-connection": "^1.4.33",
|
|
39
39
|
"@automattic/jetpack-explat": "workspace:*",
|
|
40
40
|
"@automattic/jetpack-script-data": "^0.5.4",
|
|
41
|
-
"@automattic/jetpack-shared-extension-utils": "^1.4.
|
|
41
|
+
"@automattic/jetpack-shared-extension-utils": "^1.4.11",
|
|
42
42
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
43
43
|
"@types/jest": "30.0.0",
|
|
44
44
|
"@types/react": "18.3.26",
|
|
@@ -65,13 +65,13 @@
|
|
|
65
65
|
"turndown": "7.1.2"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@storybook/addon-docs": "10.
|
|
69
|
-
"@storybook/react": "10.
|
|
68
|
+
"@storybook/addon-docs": "10.2.3",
|
|
69
|
+
"@storybook/react": "10.2.3",
|
|
70
70
|
"@testing-library/dom": "10.4.1",
|
|
71
71
|
"@types/markdown-it": "14.1.2",
|
|
72
72
|
"@types/turndown": "5.0.6",
|
|
73
73
|
"jest": "30.2.0",
|
|
74
|
-
"storybook": "10.
|
|
74
|
+
"storybook": "10.2.3",
|
|
75
75
|
"typescript": "5.9.3"
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -8,7 +8,6 @@ import debugFactory from 'debug';
|
|
|
8
8
|
* Internal dependencies
|
|
9
9
|
*/
|
|
10
10
|
import askQuestion from '../../ask-question/index.ts';
|
|
11
|
-
import ChromeAIFactory from '../../chrome-ai/factory.ts';
|
|
12
11
|
import {
|
|
13
12
|
ERROR_CONTEXT_TOO_LARGE,
|
|
14
13
|
ERROR_MODERATION,
|
|
@@ -18,7 +17,6 @@ import {
|
|
|
18
17
|
ERROR_UNCLEAR_PROMPT,
|
|
19
18
|
ERROR_RESPONSE,
|
|
20
19
|
AI_MODEL_DEFAULT,
|
|
21
|
-
AI_MODEL_GEMINI_NANO,
|
|
22
20
|
} from '../../types.ts';
|
|
23
21
|
/**
|
|
24
22
|
* Types & constants
|
|
@@ -80,7 +78,7 @@ type useAiSuggestionsOptions = {
|
|
|
80
78
|
/*
|
|
81
79
|
* onDone callback.
|
|
82
80
|
*/
|
|
83
|
-
onDone?: ( content: string,
|
|
81
|
+
onDone?: ( content: string, modelUsed?: AiModelTypeProp ) => void;
|
|
84
82
|
|
|
85
83
|
/*
|
|
86
84
|
* onStop callback.
|
|
@@ -95,7 +93,7 @@ type useAiSuggestionsOptions = {
|
|
|
95
93
|
/*
|
|
96
94
|
* Error callback common for all errors.
|
|
97
95
|
*/
|
|
98
|
-
onAllErrors?: ( error: RequestingErrorProps
|
|
96
|
+
onAllErrors?: ( error: RequestingErrorProps ) => void;
|
|
99
97
|
};
|
|
100
98
|
|
|
101
99
|
type useAiSuggestionsProps = {
|
|
@@ -281,7 +279,7 @@ export default function useAiSuggestions( {
|
|
|
281
279
|
|
|
282
280
|
const fullSuggestion = removeLlamaArtifact( event?.detail?.message ?? event?.detail );
|
|
283
281
|
|
|
284
|
-
onDone?.( fullSuggestion,
|
|
282
|
+
onDone?.( fullSuggestion, modelRef.current );
|
|
285
283
|
setRequestingState( 'done' );
|
|
286
284
|
},
|
|
287
285
|
[ onDone ]
|
|
@@ -289,7 +287,7 @@ export default function useAiSuggestions( {
|
|
|
289
287
|
|
|
290
288
|
const handleAnyError = useCallback(
|
|
291
289
|
( event: CustomEvent ) => {
|
|
292
|
-
onAllErrors?.( event?.detail
|
|
290
|
+
onAllErrors?.( event?.detail );
|
|
293
291
|
},
|
|
294
292
|
[ onAllErrors ]
|
|
295
293
|
);
|
|
@@ -338,17 +336,8 @@ export default function useAiSuggestions( {
|
|
|
338
336
|
// Set the request status.
|
|
339
337
|
setRequestingState( 'requesting' );
|
|
340
338
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
debug( 'chromeAI', chromeAI !== false );
|
|
344
|
-
|
|
345
|
-
if ( chromeAI !== false ) {
|
|
346
|
-
setModelAndRef( AI_MODEL_GEMINI_NANO );
|
|
347
|
-
eventSourceRef.current = chromeAI;
|
|
348
|
-
} else {
|
|
349
|
-
setModelAndRef( AI_MODEL_DEFAULT );
|
|
350
|
-
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
351
|
-
}
|
|
339
|
+
setModelAndRef( AI_MODEL_DEFAULT );
|
|
340
|
+
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
352
341
|
|
|
353
342
|
if ( ! eventSourceRef?.current ) {
|
|
354
343
|
debug( 'no event source' );
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -94,12 +94,9 @@ export type RequestingStateProp = ( typeof REQUESTING_STATES )[ number ];
|
|
|
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
96
|
export const AI_MODEL_DEFAULT = 'default' as const;
|
|
97
|
-
export const AI_MODEL_GEMINI_NANO = 'gemini-nano' as const;
|
|
98
|
-
|
|
99
97
|
export type AiModelTypeProp =
|
|
100
98
|
| typeof AI_MODEL_GPT_3_5_Turbo_16K
|
|
101
99
|
| typeof AI_MODEL_GPT_4
|
|
102
|
-
| typeof AI_MODEL_GEMINI_NANO
|
|
103
100
|
| typeof AI_MODEL_DEFAULT;
|
|
104
101
|
|
|
105
102
|
/*
|
|
@@ -138,46 +135,3 @@ export interface BlockEditorStore {
|
|
|
138
135
|
[ key in keyof typeof BlockEditorSelectors ]: ( typeof BlockEditorSelectors )[ key ];
|
|
139
136
|
};
|
|
140
137
|
}
|
|
141
|
-
|
|
142
|
-
declare global {
|
|
143
|
-
interface Window {
|
|
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: {
|
|
160
|
-
sourceLanguage: string;
|
|
161
|
-
targetLanguage: string;
|
|
162
|
-
} ) => Promise< { translate: ( text: string ) => Promise< string > } >;
|
|
163
|
-
availability: ( options: {
|
|
164
|
-
sourceLanguage: string;
|
|
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;
|
|
177
|
-
} ) => Promise< {
|
|
178
|
-
ready: Promise< void >;
|
|
179
|
-
summarize: ( text: string, summarizeOptions?: { context?: string } ) => Promise< string >;
|
|
180
|
-
} >;
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { PromptProp } from '../types.ts';
|
|
2
|
-
import ChromeAISuggestionsEventSource from './suggestions.ts';
|
|
3
|
-
/**
|
|
4
|
-
* This will return an instance of ChromeAISuggestionsEventSource or false.
|
|
5
|
-
*
|
|
6
|
-
* @param promptArg - The messages array of the prompt.
|
|
7
|
-
* @return ChromeAISuggestionsEventSource | bool
|
|
8
|
-
*/
|
|
9
|
-
export default function ChromeAIFactory(promptArg: PromptProp): Promise<false | ChromeAISuggestionsEventSource>;
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import debugFactory from 'debug';
|
|
2
|
-
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from "../constants.js";
|
|
3
|
-
import { isChromeAIAvailable } from "./get-availability.js";
|
|
4
|
-
import ChromeAISuggestionsEventSource from "./suggestions.js";
|
|
5
|
-
const debug = debugFactory('ai-client:chrome-ai-factory');
|
|
6
|
-
/**
|
|
7
|
-
* This will return an instance of ChromeAISuggestionsEventSource or false.
|
|
8
|
-
*
|
|
9
|
-
* @param promptArg - The messages array of the prompt.
|
|
10
|
-
* @return ChromeAISuggestionsEventSource | bool
|
|
11
|
-
*/
|
|
12
|
-
export default async function ChromeAIFactory(promptArg) {
|
|
13
|
-
if (!(await isChromeAIAvailable())) {
|
|
14
|
-
debug('Chrome AI is not available');
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
const context = {
|
|
18
|
-
content: '',
|
|
19
|
-
language: '',
|
|
20
|
-
};
|
|
21
|
-
let promptType = '';
|
|
22
|
-
let tone = null;
|
|
23
|
-
let wordCount = null;
|
|
24
|
-
debug('promptArg', promptArg);
|
|
25
|
-
if (Array.isArray(promptArg)) {
|
|
26
|
-
for (let i = 0; i < promptArg.length; i++) {
|
|
27
|
-
const prompt = promptArg[i];
|
|
28
|
-
if (prompt.content) {
|
|
29
|
-
context.content = prompt.content;
|
|
30
|
-
}
|
|
31
|
-
if (!('context' in prompt)) {
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const promptContext = prompt.context;
|
|
35
|
-
if (promptContext.type) {
|
|
36
|
-
promptType = promptContext.type;
|
|
37
|
-
}
|
|
38
|
-
if (promptContext.language) {
|
|
39
|
-
context.language = promptContext.language;
|
|
40
|
-
}
|
|
41
|
-
if (promptContext.content) {
|
|
42
|
-
context.content = promptContext.content;
|
|
43
|
-
}
|
|
44
|
-
if (promptContext.tone) {
|
|
45
|
-
tone = promptContext.tone;
|
|
46
|
-
}
|
|
47
|
-
if (promptContext.words) {
|
|
48
|
-
wordCount = promptContext.words;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
debug('promptType', promptType);
|
|
53
|
-
// Early return if the prompt type is not supported.
|
|
54
|
-
if (!promptType.startsWith('ai-assistant-change-language') &&
|
|
55
|
-
!promptType.startsWith('ai-content-lens')) {
|
|
56
|
-
debug('promptType is not supported');
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
// If the languageDetector is not available, we can't use the translation or summary features—it's safer to fall back
|
|
60
|
-
// to the default AI model than to risk an unexpected error.
|
|
61
|
-
if (!('LanguageDetector' in self) ||
|
|
62
|
-
!self.LanguageDetector.create ||
|
|
63
|
-
!self.LanguageDetector.availability) {
|
|
64
|
-
debug('LanguageDetector is not available');
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
const languageDetectorAvailability = await self.LanguageDetector.availability();
|
|
68
|
-
if (languageDetectorAvailability === 'unavailable') {
|
|
69
|
-
debug('LanguageDetector is unavailable');
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const detector = await self.LanguageDetector.create();
|
|
73
|
-
if (languageDetectorAvailability !== 'available') {
|
|
74
|
-
debug('awaiting detector ready');
|
|
75
|
-
await detector.ready;
|
|
76
|
-
}
|
|
77
|
-
if (promptType.startsWith('ai-assistant-change-language')) {
|
|
78
|
-
const [language] = context.language.split(' ');
|
|
79
|
-
if (!('Translator' in self) ||
|
|
80
|
-
!self.Translator.create ||
|
|
81
|
-
!self.Translator.availability) {
|
|
82
|
-
debug('Translator is not available');
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
const languageOpts = {
|
|
86
|
-
sourceLanguage: 'en',
|
|
87
|
-
targetLanguage: language,
|
|
88
|
-
};
|
|
89
|
-
const confidences = await detector.detect(context.content);
|
|
90
|
-
for (const confidence of confidences) {
|
|
91
|
-
// 75% confidence is just a value that was picked. Generally
|
|
92
|
-
// 80% of higher is pretty safe, but the source language is
|
|
93
|
-
// required for the translator to work at all, which is also
|
|
94
|
-
// why en is the default language.
|
|
95
|
-
if (confidence.confidence > 0.75) {
|
|
96
|
-
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
debug('languageOpts', languageOpts);
|
|
101
|
-
const translationAvailability = await self.Translator.availability(languageOpts);
|
|
102
|
-
debug('translationAvailability', translationAvailability);
|
|
103
|
-
if (translationAvailability === 'unavailable') {
|
|
104
|
-
debug('Translator is unavailable');
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
const chromeAI = new ChromeAISuggestionsEventSource({
|
|
108
|
-
content: context.content,
|
|
109
|
-
promptType: PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
110
|
-
options: languageOpts,
|
|
111
|
-
});
|
|
112
|
-
return chromeAI;
|
|
113
|
-
}
|
|
114
|
-
if (promptType.startsWith('ai-content-lens')) {
|
|
115
|
-
if (!('Summarizer' in self)) {
|
|
116
|
-
debug('Summarizer is not available');
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if (context.language && context.language !== 'en (English)') {
|
|
120
|
-
debug('Summary is not English');
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
debug('awaiting detector detect');
|
|
124
|
-
const confidences = await detector.detect(context.content);
|
|
125
|
-
// if it doesn't look like the content is in English, we can't use the summary feature
|
|
126
|
-
for (const confidence of confidences) {
|
|
127
|
-
// 75% confidence is just a value that was picked. Generally
|
|
128
|
-
// 80% of higher is pretty safe, but the source language is
|
|
129
|
-
// required for the translator to work at all, which is also
|
|
130
|
-
// why en is the default language.
|
|
131
|
-
if (confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en') {
|
|
132
|
-
debug('Confidence for non-English content');
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const summaryOpts = {
|
|
137
|
-
tone: tone,
|
|
138
|
-
wordCount: wordCount,
|
|
139
|
-
};
|
|
140
|
-
debug('summaryOpts', summaryOpts);
|
|
141
|
-
const chromeAiEventSourceOpts = {
|
|
142
|
-
content: context.content,
|
|
143
|
-
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
144
|
-
options: summaryOpts,
|
|
145
|
-
};
|
|
146
|
-
debug('chromeAiEventSourceOpts', chromeAiEventSourceOpts);
|
|
147
|
-
return new ChromeAISuggestionsEventSource(chromeAiEventSourceOpts);
|
|
148
|
-
}
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { initializeExPlat, createExPlatClient } from '@automattic/jetpack-explat';
|
|
5
|
-
import { select } from '@wordpress/data';
|
|
6
|
-
import { addQueryArgs } from '@wordpress/url';
|
|
7
|
-
import debugFactory from 'debug';
|
|
8
|
-
/**
|
|
9
|
-
* Internal dependencies
|
|
10
|
-
*/
|
|
11
|
-
import apiFetch from "../api-fetch/index.js";
|
|
12
|
-
const debug = debugFactory('ai-client:chrome-ai-availability');
|
|
13
|
-
/**
|
|
14
|
-
* Get the AI Assistant feature.
|
|
15
|
-
*
|
|
16
|
-
* @return {object} The AI Assistant feature.
|
|
17
|
-
*/
|
|
18
|
-
function getAiAssistantFeature() {
|
|
19
|
-
const { getAiAssistantFeature: getFeature } = select('wordpress-com/plans');
|
|
20
|
-
return getFeature();
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Fetch an experiment assignment.
|
|
24
|
-
*
|
|
25
|
-
* @param {boolean} asConnectedUser - Whether the user is connected.
|
|
26
|
-
* @return {Function} A function that fetches an experiment assignment.
|
|
27
|
-
*/
|
|
28
|
-
const fetchExperimentAssignmentWithConnectedUser = async ({ experimentName, }) => {
|
|
29
|
-
const params = {
|
|
30
|
-
experiment_name: experimentName,
|
|
31
|
-
anon_id: undefined,
|
|
32
|
-
as_connected_user: true,
|
|
33
|
-
};
|
|
34
|
-
debug('params', params);
|
|
35
|
-
const assignmentsRequestUrl = addQueryArgs('https://public-api.wordpress.com/wpcom/v2/experiments/0.1.0/assignments/jetpack', params);
|
|
36
|
-
debug('assignmentsRequestUrl', assignmentsRequestUrl);
|
|
37
|
-
return apiFetch({
|
|
38
|
-
url: assignmentsRequestUrl,
|
|
39
|
-
credentials: 'include',
|
|
40
|
-
mode: 'cors',
|
|
41
|
-
global: true,
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* Check if Chrome AI can be enabled.
|
|
46
|
-
*
|
|
47
|
-
* @return {boolean} Whether Chrome AI can be enabled.
|
|
48
|
-
*/
|
|
49
|
-
export async function isChromeAIAvailable() {
|
|
50
|
-
const { featuresControl } = getAiAssistantFeature();
|
|
51
|
-
// Extra check if we want to control this via the feature flag for now
|
|
52
|
-
if (featuresControl?.['chrome-ai']?.enabled !== true) {
|
|
53
|
-
debug('feature is disabled for this site/user');
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
initializeExPlat();
|
|
57
|
-
const { loadExperimentAssignment: loadExperimentAssignmentWithAuth } = createExPlatClient({
|
|
58
|
-
fetchExperimentAssignment: fetchExperimentAssignmentWithConnectedUser,
|
|
59
|
-
getAnonId: async () => null,
|
|
60
|
-
logError: debug,
|
|
61
|
-
isDevelopmentMode: false,
|
|
62
|
-
});
|
|
63
|
-
const { variationName } = await loadExperimentAssignmentWithAuth('calypso_jetpack_ai_gemini_api_202503_v2');
|
|
64
|
-
debug('variationName', variationName);
|
|
65
|
-
return variationName === 'treatment';
|
|
66
|
-
}
|
|
67
|
-
export default isChromeAIAvailable;
|
package/build/chrome-ai/index.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { EventSourceMessage } from '@microsoft/fetch-event-source';
|
|
2
|
-
import { AiModelTypeProp } from '../types.ts';
|
|
3
|
-
type ChromeAISuggestionsEventSourceConstructorArgs = {
|
|
4
|
-
content: string;
|
|
5
|
-
promptType: string;
|
|
6
|
-
options?: {
|
|
7
|
-
postId?: number | string;
|
|
8
|
-
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
9
|
-
sourceLanguage?: string;
|
|
10
|
-
targetLanguage?: string;
|
|
11
|
-
tone?: string;
|
|
12
|
-
wordCount?: number;
|
|
13
|
-
functions?: Array<object>;
|
|
14
|
-
model?: AiModelTypeProp;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
type FunctionCallProps = {
|
|
18
|
-
name?: string;
|
|
19
|
-
arguments?: string;
|
|
20
|
-
};
|
|
21
|
-
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
22
|
-
fullMessage: string;
|
|
23
|
-
fullFunctionCall: FunctionCallProps;
|
|
24
|
-
isPromptClear: boolean;
|
|
25
|
-
controller: AbortController;
|
|
26
|
-
errorUnclearPromptTriggered: boolean;
|
|
27
|
-
constructor(data: ChromeAISuggestionsEventSourceConstructorArgs);
|
|
28
|
-
initSource({ content, promptType, options, }: ChromeAISuggestionsEventSourceConstructorArgs): void;
|
|
29
|
-
initEventSource(): Promise<void>;
|
|
30
|
-
close(): void;
|
|
31
|
-
checkForUnclearPrompt(): void;
|
|
32
|
-
processEvent(e: EventSourceMessage): void;
|
|
33
|
-
processErrorEvent(e: any): void;
|
|
34
|
-
translate(text: string, target: string, source?: string): Promise<void>;
|
|
35
|
-
private getSummarizerOptions;
|
|
36
|
-
summarize(text: string, tone?: string, wordCount?: number): Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
export {};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import debugFactory from 'debug';
|
|
2
|
-
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from "../constants.js";
|
|
3
|
-
import { getErrorData } from "../hooks/use-ai-suggestions/index.js";
|
|
4
|
-
import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from "../libs/markdown/index.js";
|
|
5
|
-
import { ERROR_RESPONSE, ERROR_NETWORK } from "../types.js";
|
|
6
|
-
const debug = debugFactory('ai-client:chrome-ai-suggestions');
|
|
7
|
-
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
8
|
-
fullMessage;
|
|
9
|
-
fullFunctionCall;
|
|
10
|
-
isPromptClear;
|
|
11
|
-
controller;
|
|
12
|
-
errorUnclearPromptTriggered;
|
|
13
|
-
constructor(data) {
|
|
14
|
-
super();
|
|
15
|
-
this.fullMessage = '';
|
|
16
|
-
this.fullFunctionCall = {
|
|
17
|
-
name: '',
|
|
18
|
-
arguments: '',
|
|
19
|
-
};
|
|
20
|
-
this.isPromptClear = false;
|
|
21
|
-
this.controller = new AbortController();
|
|
22
|
-
this.initSource(data);
|
|
23
|
-
}
|
|
24
|
-
initSource({ content, promptType, options = {}, }) {
|
|
25
|
-
debug('initSource', content, promptType, options);
|
|
26
|
-
if (promptType === PROMPT_TYPE_CHANGE_LANGUAGE) {
|
|
27
|
-
this.translate(content, options.targetLanguage, options.sourceLanguage);
|
|
28
|
-
}
|
|
29
|
-
if (promptType === PROMPT_TYPE_SUMMARIZE) {
|
|
30
|
-
this.summarize(content, options.tone, options.wordCount);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async initEventSource() { }
|
|
34
|
-
close() { }
|
|
35
|
-
checkForUnclearPrompt() { }
|
|
36
|
-
processEvent(e) {
|
|
37
|
-
let data;
|
|
38
|
-
debug('processEvent', e);
|
|
39
|
-
try {
|
|
40
|
-
data = JSON.parse(e.data);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
this.processErrorEvent(err);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (e.event === 'translation' || e.event === 'summary') {
|
|
47
|
-
this.dispatchEvent(new CustomEvent('suggestion', { detail: data.message }));
|
|
48
|
-
}
|
|
49
|
-
if (data.complete) {
|
|
50
|
-
this.dispatchEvent(new CustomEvent('done', { detail: { message: data.message, source: 'chromeAI' } }));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
processErrorEvent(e) {
|
|
54
|
-
debug('processErrorEvent', e);
|
|
55
|
-
// Dispatch a generic network error event
|
|
56
|
-
this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: e }));
|
|
57
|
-
this.dispatchEvent(new CustomEvent(ERROR_RESPONSE, {
|
|
58
|
-
detail: getErrorData(ERROR_NETWORK),
|
|
59
|
-
}));
|
|
60
|
-
}
|
|
61
|
-
// use the Chrome AI translator
|
|
62
|
-
async translate(text, target, source = '') {
|
|
63
|
-
if (!('Translator' in self)) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const translatorAvailability = await self.Translator.availability({
|
|
67
|
-
sourceLanguage: source,
|
|
68
|
-
targetLanguage: target,
|
|
69
|
-
});
|
|
70
|
-
if (translatorAvailability === 'unavailable') {
|
|
71
|
-
debug('awaiting translator ready');
|
|
72
|
-
this.processErrorEvent({
|
|
73
|
-
message: 'Translator is unavailable',
|
|
74
|
-
});
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const translator = await self.Translator.create({
|
|
78
|
-
sourceLanguage: source,
|
|
79
|
-
targetLanguage: target,
|
|
80
|
-
});
|
|
81
|
-
if (!translator) {
|
|
82
|
-
this.processErrorEvent({
|
|
83
|
-
message: 'Translator failed to initialize',
|
|
84
|
-
});
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const translation = await translator.translate(renderHTMLFromMarkdown({ content: text }));
|
|
89
|
-
this.processEvent({
|
|
90
|
-
id: '',
|
|
91
|
-
event: 'translation',
|
|
92
|
-
data: JSON.stringify({
|
|
93
|
-
message: renderMarkdownFromHTML({ content: translation }),
|
|
94
|
-
complete: true,
|
|
95
|
-
}),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
this.processErrorEvent(error);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Helper function to format summarizer options
|
|
103
|
-
getSummarizerOptions(tone, wordCount) {
|
|
104
|
-
let sharedContext = `The summary you write should contain strictly less than ${wordCount ?? 50} words. Strive for precision in word count without compromising clarity and significance`;
|
|
105
|
-
if (tone) {
|
|
106
|
-
sharedContext += `\n - Write with a ${tone} tone.\n`;
|
|
107
|
-
}
|
|
108
|
-
const options = {
|
|
109
|
-
sharedContext: sharedContext,
|
|
110
|
-
type: 'teaser',
|
|
111
|
-
format: 'plain-text',
|
|
112
|
-
length: 'medium',
|
|
113
|
-
};
|
|
114
|
-
return options;
|
|
115
|
-
}
|
|
116
|
-
// use the Chrome AI summarizer
|
|
117
|
-
async summarize(text, tone, wordCount) {
|
|
118
|
-
debug('summarize', text, tone, wordCount);
|
|
119
|
-
if (!('Summarizer' in self)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const availability = await self.Summarizer.availability();
|
|
123
|
-
if (availability === 'unavailable') {
|
|
124
|
-
this.processErrorEvent({
|
|
125
|
-
data: { message: 'Summarizer is unavailable' },
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const summarizerOptions = this.getSummarizerOptions(tone, wordCount);
|
|
130
|
-
const summarizer = await self.Summarizer.create(summarizerOptions);
|
|
131
|
-
if (availability !== 'available') {
|
|
132
|
-
debug('awaiting summarizer ready');
|
|
133
|
-
await summarizer.ready;
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
const context = `Write with a ${tone} tone.`;
|
|
137
|
-
debug('context', context);
|
|
138
|
-
let summary = await summarizer.summarize(text, { context: context });
|
|
139
|
-
debug('summary', summary);
|
|
140
|
-
wordCount = wordCount ?? 50;
|
|
141
|
-
// gemini-nano has a tendency to exceed the word count, so we need to check and summarize again if necessary
|
|
142
|
-
if (summary.split(' ').length > wordCount) {
|
|
143
|
-
debug('summary exceeds word count');
|
|
144
|
-
summary = await summarizer.summarize(summary, { context: context });
|
|
145
|
-
}
|
|
146
|
-
this.processEvent({
|
|
147
|
-
id: '',
|
|
148
|
-
event: 'summary',
|
|
149
|
-
data: JSON.stringify({
|
|
150
|
-
message: summary,
|
|
151
|
-
complete: true,
|
|
152
|
-
}),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
debug('error', error);
|
|
157
|
-
this.processErrorEvent(error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
package/src/chrome-ai/factory.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import debugFactory from 'debug';
|
|
2
|
-
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.ts';
|
|
3
|
-
import { PromptProp, PromptItemProps } from '../types.ts';
|
|
4
|
-
import { isChromeAIAvailable } from './get-availability.ts';
|
|
5
|
-
import ChromeAISuggestionsEventSource from './suggestions.ts';
|
|
6
|
-
|
|
7
|
-
const debug = debugFactory( 'ai-client:chrome-ai-factory' );
|
|
8
|
-
|
|
9
|
-
interface PromptContext {
|
|
10
|
-
type?: string;
|
|
11
|
-
content?: string;
|
|
12
|
-
language?: string;
|
|
13
|
-
tone?: string;
|
|
14
|
-
words?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* This will return an instance of ChromeAISuggestionsEventSource or false.
|
|
19
|
-
*
|
|
20
|
-
* @param promptArg - The messages array of the prompt.
|
|
21
|
-
* @return ChromeAISuggestionsEventSource | bool
|
|
22
|
-
*/
|
|
23
|
-
export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
24
|
-
if ( ! ( await isChromeAIAvailable() ) ) {
|
|
25
|
-
debug( 'Chrome AI is not available' );
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const context = {
|
|
30
|
-
content: '',
|
|
31
|
-
language: '',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
let promptType = '';
|
|
35
|
-
let tone = null;
|
|
36
|
-
let wordCount = null;
|
|
37
|
-
|
|
38
|
-
debug( 'promptArg', promptArg );
|
|
39
|
-
if ( Array.isArray( promptArg ) ) {
|
|
40
|
-
for ( let i = 0; i < promptArg.length; i++ ) {
|
|
41
|
-
const prompt: PromptItemProps = promptArg[ i ];
|
|
42
|
-
if ( prompt.content ) {
|
|
43
|
-
context.content = prompt.content;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if ( ! ( 'context' in prompt ) ) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const promptContext: PromptContext = prompt.context;
|
|
51
|
-
|
|
52
|
-
if ( promptContext.type ) {
|
|
53
|
-
promptType = promptContext.type;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if ( promptContext.language ) {
|
|
57
|
-
context.language = promptContext.language;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if ( promptContext.content ) {
|
|
61
|
-
context.content = promptContext.content;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if ( promptContext.tone ) {
|
|
65
|
-
tone = promptContext.tone;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if ( promptContext.words ) {
|
|
69
|
-
wordCount = promptContext.words;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
debug( 'promptType', promptType );
|
|
75
|
-
// Early return if the prompt type is not supported.
|
|
76
|
-
if (
|
|
77
|
-
! promptType.startsWith( 'ai-assistant-change-language' ) &&
|
|
78
|
-
! promptType.startsWith( 'ai-content-lens' )
|
|
79
|
-
) {
|
|
80
|
-
debug( 'promptType is not supported' );
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// If the languageDetector is not available, we can't use the translation or summary features—it's safer to fall back
|
|
85
|
-
// to the default AI model than to risk an unexpected error.
|
|
86
|
-
if (
|
|
87
|
-
! ( 'LanguageDetector' in self ) ||
|
|
88
|
-
! self.LanguageDetector.create ||
|
|
89
|
-
! self.LanguageDetector.availability
|
|
90
|
-
) {
|
|
91
|
-
debug( 'LanguageDetector is not available' );
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const languageDetectorAvailability = await self.LanguageDetector.availability();
|
|
96
|
-
if ( languageDetectorAvailability === 'unavailable' ) {
|
|
97
|
-
debug( 'LanguageDetector is unavailable' );
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const detector = await self.LanguageDetector.create();
|
|
102
|
-
if ( languageDetectorAvailability !== 'available' ) {
|
|
103
|
-
debug( 'awaiting detector ready' );
|
|
104
|
-
await detector.ready;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if ( promptType.startsWith( 'ai-assistant-change-language' ) ) {
|
|
108
|
-
const [ language ] = context.language.split( ' ' );
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
! ( 'Translator' in self ) ||
|
|
112
|
-
! self.Translator.create ||
|
|
113
|
-
! self.Translator.availability
|
|
114
|
-
) {
|
|
115
|
-
debug( 'Translator is not available' );
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const languageOpts = {
|
|
120
|
-
sourceLanguage: 'en',
|
|
121
|
-
targetLanguage: language,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const confidences = await detector.detect( context.content );
|
|
125
|
-
|
|
126
|
-
for ( const confidence of confidences ) {
|
|
127
|
-
// 75% confidence is just a value that was picked. Generally
|
|
128
|
-
// 80% of higher is pretty safe, but the source language is
|
|
129
|
-
// required for the translator to work at all, which is also
|
|
130
|
-
// why en is the default language.
|
|
131
|
-
if ( confidence.confidence > 0.75 ) {
|
|
132
|
-
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
debug( 'languageOpts', languageOpts );
|
|
138
|
-
const translationAvailability = await self.Translator.availability( languageOpts );
|
|
139
|
-
|
|
140
|
-
debug( 'translationAvailability', translationAvailability );
|
|
141
|
-
if ( translationAvailability === 'unavailable' ) {
|
|
142
|
-
debug( 'Translator is unavailable' );
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const chromeAI = new ChromeAISuggestionsEventSource( {
|
|
147
|
-
content: context.content,
|
|
148
|
-
promptType: PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
149
|
-
options: languageOpts,
|
|
150
|
-
} );
|
|
151
|
-
|
|
152
|
-
return chromeAI;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if ( promptType.startsWith( 'ai-content-lens' ) ) {
|
|
156
|
-
if ( ! ( 'Summarizer' in self ) ) {
|
|
157
|
-
debug( 'Summarizer is not available' );
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if ( context.language && context.language !== 'en (English)' ) {
|
|
162
|
-
debug( 'Summary is not English' );
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
debug( 'awaiting detector detect' );
|
|
167
|
-
const confidences = await detector.detect( context.content );
|
|
168
|
-
|
|
169
|
-
// if it doesn't look like the content is in English, we can't use the summary feature
|
|
170
|
-
for ( const confidence of confidences ) {
|
|
171
|
-
// 75% confidence is just a value that was picked. Generally
|
|
172
|
-
// 80% of higher is pretty safe, but the source language is
|
|
173
|
-
// required for the translator to work at all, which is also
|
|
174
|
-
// why en is the default language.
|
|
175
|
-
if ( confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en' ) {
|
|
176
|
-
debug( 'Confidence for non-English content' );
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const summaryOpts = {
|
|
182
|
-
tone: tone,
|
|
183
|
-
wordCount: wordCount,
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
debug( 'summaryOpts', summaryOpts );
|
|
187
|
-
|
|
188
|
-
const chromeAiEventSourceOpts = {
|
|
189
|
-
content: context.content,
|
|
190
|
-
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
191
|
-
options: summaryOpts,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
debug( 'chromeAiEventSourceOpts', chromeAiEventSourceOpts );
|
|
195
|
-
return new ChromeAISuggestionsEventSource( chromeAiEventSourceOpts );
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { initializeExPlat, createExPlatClient } from '@automattic/jetpack-explat';
|
|
5
|
-
import { select } from '@wordpress/data';
|
|
6
|
-
import { addQueryArgs } from '@wordpress/url';
|
|
7
|
-
import debugFactory from 'debug';
|
|
8
|
-
/**
|
|
9
|
-
* Internal dependencies
|
|
10
|
-
*/
|
|
11
|
-
import apiFetch from '../api-fetch/index.ts';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Types
|
|
15
|
-
*/
|
|
16
|
-
type FeatureControl = {
|
|
17
|
-
enabled: boolean;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type PlansSelect = {
|
|
21
|
-
getAiAssistantFeature: () => {
|
|
22
|
-
currentTier?: { value: number };
|
|
23
|
-
featuresControl?: Record< string, FeatureControl >;
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const debug = debugFactory( 'ai-client:chrome-ai-availability' );
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get the AI Assistant feature.
|
|
31
|
-
*
|
|
32
|
-
* @return {object} The AI Assistant feature.
|
|
33
|
-
*/
|
|
34
|
-
function getAiAssistantFeature() {
|
|
35
|
-
const { getAiAssistantFeature: getFeature } = select( 'wordpress-com/plans' ) as PlansSelect;
|
|
36
|
-
return getFeature();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Fetch an experiment assignment.
|
|
41
|
-
*
|
|
42
|
-
* @param {boolean} asConnectedUser - Whether the user is connected.
|
|
43
|
-
* @return {Function} A function that fetches an experiment assignment.
|
|
44
|
-
*/
|
|
45
|
-
const fetchExperimentAssignmentWithConnectedUser = async ( {
|
|
46
|
-
experimentName,
|
|
47
|
-
}: {
|
|
48
|
-
experimentName: string;
|
|
49
|
-
} ): Promise< unknown > => {
|
|
50
|
-
const params = {
|
|
51
|
-
experiment_name: experimentName,
|
|
52
|
-
anon_id: undefined,
|
|
53
|
-
as_connected_user: true,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
debug( 'params', params );
|
|
57
|
-
|
|
58
|
-
const assignmentsRequestUrl = addQueryArgs(
|
|
59
|
-
'https://public-api.wordpress.com/wpcom/v2/experiments/0.1.0/assignments/jetpack',
|
|
60
|
-
params
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
debug( 'assignmentsRequestUrl', assignmentsRequestUrl );
|
|
64
|
-
|
|
65
|
-
return apiFetch( {
|
|
66
|
-
url: assignmentsRequestUrl,
|
|
67
|
-
credentials: 'include',
|
|
68
|
-
mode: 'cors',
|
|
69
|
-
global: true,
|
|
70
|
-
} );
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Check if Chrome AI can be enabled.
|
|
75
|
-
*
|
|
76
|
-
* @return {boolean} Whether Chrome AI can be enabled.
|
|
77
|
-
*/
|
|
78
|
-
export async function isChromeAIAvailable() {
|
|
79
|
-
const { featuresControl } = getAiAssistantFeature();
|
|
80
|
-
|
|
81
|
-
// Extra check if we want to control this via the feature flag for now
|
|
82
|
-
if ( featuresControl?.[ 'chrome-ai' ]?.enabled !== true ) {
|
|
83
|
-
debug( 'feature is disabled for this site/user' );
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
initializeExPlat();
|
|
88
|
-
|
|
89
|
-
const { loadExperimentAssignment: loadExperimentAssignmentWithAuth } = createExPlatClient( {
|
|
90
|
-
fetchExperimentAssignment: fetchExperimentAssignmentWithConnectedUser,
|
|
91
|
-
getAnonId: async () => null,
|
|
92
|
-
logError: debug,
|
|
93
|
-
isDevelopmentMode: false,
|
|
94
|
-
} );
|
|
95
|
-
|
|
96
|
-
const { variationName } = await loadExperimentAssignmentWithAuth(
|
|
97
|
-
'calypso_jetpack_ai_gemini_api_202503_v2'
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
debug( 'variationName', variationName );
|
|
101
|
-
|
|
102
|
-
return variationName === 'treatment';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export default isChromeAIAvailable;
|
package/src/chrome-ai/index.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { EventSourceMessage } from '@microsoft/fetch-event-source';
|
|
2
|
-
import debugFactory from 'debug';
|
|
3
|
-
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.ts';
|
|
4
|
-
import { getErrorData } from '../hooks/use-ai-suggestions/index.ts';
|
|
5
|
-
import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from '../libs/markdown/index.ts';
|
|
6
|
-
import { AiModelTypeProp, ERROR_RESPONSE, ERROR_NETWORK } from '../types.ts';
|
|
7
|
-
|
|
8
|
-
type ChromeAISuggestionsEventSourceConstructorArgs = {
|
|
9
|
-
content: string;
|
|
10
|
-
promptType: string;
|
|
11
|
-
options?: {
|
|
12
|
-
postId?: number | string;
|
|
13
|
-
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
14
|
-
|
|
15
|
-
// translation
|
|
16
|
-
sourceLanguage?: string;
|
|
17
|
-
targetLanguage?: string;
|
|
18
|
-
|
|
19
|
-
// summarization
|
|
20
|
-
tone?: string;
|
|
21
|
-
wordCount?: number;
|
|
22
|
-
|
|
23
|
-
// not sure if we need these
|
|
24
|
-
functions?: Array< object >;
|
|
25
|
-
model?: AiModelTypeProp;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type ChromeAIEvent = {
|
|
30
|
-
type: string;
|
|
31
|
-
message: string;
|
|
32
|
-
complete?: boolean;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type FunctionCallProps = {
|
|
36
|
-
name?: string;
|
|
37
|
-
arguments?: string;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const debug = debugFactory( 'ai-client:chrome-ai-suggestions' );
|
|
41
|
-
|
|
42
|
-
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
43
|
-
fullMessage: string;
|
|
44
|
-
fullFunctionCall: FunctionCallProps;
|
|
45
|
-
isPromptClear: boolean;
|
|
46
|
-
controller: AbortController;
|
|
47
|
-
|
|
48
|
-
errorUnclearPromptTriggered: boolean;
|
|
49
|
-
|
|
50
|
-
constructor( data: ChromeAISuggestionsEventSourceConstructorArgs ) {
|
|
51
|
-
super();
|
|
52
|
-
this.fullMessage = '';
|
|
53
|
-
this.fullFunctionCall = {
|
|
54
|
-
name: '',
|
|
55
|
-
arguments: '',
|
|
56
|
-
};
|
|
57
|
-
this.isPromptClear = false;
|
|
58
|
-
|
|
59
|
-
this.controller = new AbortController();
|
|
60
|
-
|
|
61
|
-
this.initSource( data );
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
initSource( {
|
|
65
|
-
content,
|
|
66
|
-
promptType,
|
|
67
|
-
options = {},
|
|
68
|
-
}: ChromeAISuggestionsEventSourceConstructorArgs ) {
|
|
69
|
-
debug( 'initSource', content, promptType, options );
|
|
70
|
-
if ( promptType === PROMPT_TYPE_CHANGE_LANGUAGE ) {
|
|
71
|
-
this.translate( content, options.targetLanguage, options.sourceLanguage );
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if ( promptType === PROMPT_TYPE_SUMMARIZE ) {
|
|
75
|
-
this.summarize( content, options.tone, options.wordCount );
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async initEventSource() {}
|
|
80
|
-
|
|
81
|
-
close() {}
|
|
82
|
-
|
|
83
|
-
checkForUnclearPrompt() {}
|
|
84
|
-
|
|
85
|
-
processEvent( e: EventSourceMessage ) {
|
|
86
|
-
let data: ChromeAIEvent;
|
|
87
|
-
debug( 'processEvent', e );
|
|
88
|
-
try {
|
|
89
|
-
data = JSON.parse( e.data );
|
|
90
|
-
} catch ( err ) {
|
|
91
|
-
this.processErrorEvent( err );
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if ( e.event === 'translation' || e.event === 'summary' ) {
|
|
96
|
-
this.dispatchEvent( new CustomEvent( 'suggestion', { detail: data.message } ) );
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if ( data.complete ) {
|
|
100
|
-
this.dispatchEvent(
|
|
101
|
-
new CustomEvent( 'done', { detail: { message: data.message, source: 'chromeAI' } } )
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
processErrorEvent( e ) {
|
|
107
|
-
debug( 'processErrorEvent', e );
|
|
108
|
-
// Dispatch a generic network error event
|
|
109
|
-
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
110
|
-
this.dispatchEvent(
|
|
111
|
-
new CustomEvent( ERROR_RESPONSE, {
|
|
112
|
-
detail: getErrorData( ERROR_NETWORK ),
|
|
113
|
-
} )
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// use the Chrome AI translator
|
|
118
|
-
async translate( text: string, target: string, source: string = '' ) {
|
|
119
|
-
if ( ! ( 'Translator' in self ) ) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const translatorAvailability = await self.Translator.availability( {
|
|
124
|
-
sourceLanguage: source,
|
|
125
|
-
targetLanguage: target,
|
|
126
|
-
} );
|
|
127
|
-
|
|
128
|
-
if ( translatorAvailability === 'unavailable' ) {
|
|
129
|
-
debug( 'awaiting translator ready' );
|
|
130
|
-
this.processErrorEvent( {
|
|
131
|
-
message: 'Translator is unavailable',
|
|
132
|
-
} );
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const translator = await self.Translator.create( {
|
|
137
|
-
sourceLanguage: source,
|
|
138
|
-
targetLanguage: target,
|
|
139
|
-
} );
|
|
140
|
-
|
|
141
|
-
if ( ! translator ) {
|
|
142
|
-
this.processErrorEvent( {
|
|
143
|
-
message: 'Translator failed to initialize',
|
|
144
|
-
} );
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
const translation = await translator.translate( renderHTMLFromMarkdown( { content: text } ) );
|
|
150
|
-
|
|
151
|
-
this.processEvent( {
|
|
152
|
-
id: '',
|
|
153
|
-
event: 'translation',
|
|
154
|
-
data: JSON.stringify( {
|
|
155
|
-
message: renderMarkdownFromHTML( { content: translation } ),
|
|
156
|
-
complete: true,
|
|
157
|
-
} ),
|
|
158
|
-
} );
|
|
159
|
-
} catch ( error ) {
|
|
160
|
-
this.processErrorEvent( error );
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Helper function to format summarizer options
|
|
165
|
-
private getSummarizerOptions( tone?: string, wordCount?: number ) {
|
|
166
|
-
let sharedContext = `The summary you write should contain strictly less than ${
|
|
167
|
-
wordCount ?? 50
|
|
168
|
-
} words. Strive for precision in word count without compromising clarity and significance`;
|
|
169
|
-
|
|
170
|
-
if ( tone ) {
|
|
171
|
-
sharedContext += `\n - Write with a ${ tone } tone.\n`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const options = {
|
|
175
|
-
sharedContext: sharedContext,
|
|
176
|
-
type: 'teaser',
|
|
177
|
-
format: 'plain-text',
|
|
178
|
-
length: 'medium',
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
return options;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// use the Chrome AI summarizer
|
|
185
|
-
async summarize( text: string, tone?: string, wordCount?: number ) {
|
|
186
|
-
debug( 'summarize', text, tone, wordCount );
|
|
187
|
-
if ( ! ( 'Summarizer' in self ) ) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const availability = await self.Summarizer.availability();
|
|
192
|
-
|
|
193
|
-
if ( availability === 'unavailable' ) {
|
|
194
|
-
this.processErrorEvent( {
|
|
195
|
-
data: { message: 'Summarizer is unavailable' },
|
|
196
|
-
} );
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const summarizerOptions = this.getSummarizerOptions( tone, wordCount );
|
|
201
|
-
|
|
202
|
-
const summarizer = await self.Summarizer.create( summarizerOptions );
|
|
203
|
-
|
|
204
|
-
if ( availability !== 'available' ) {
|
|
205
|
-
debug( 'awaiting summarizer ready' );
|
|
206
|
-
await summarizer.ready;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const context = `Write with a ${ tone } tone.`;
|
|
211
|
-
debug( 'context', context );
|
|
212
|
-
let summary = await summarizer.summarize( text, { context: context } );
|
|
213
|
-
debug( 'summary', summary );
|
|
214
|
-
wordCount = wordCount ?? 50;
|
|
215
|
-
|
|
216
|
-
// gemini-nano has a tendency to exceed the word count, so we need to check and summarize again if necessary
|
|
217
|
-
if ( summary.split( ' ' ).length > wordCount ) {
|
|
218
|
-
debug( 'summary exceeds word count' );
|
|
219
|
-
summary = await summarizer.summarize( summary, { context: context } );
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
this.processEvent( {
|
|
223
|
-
id: '',
|
|
224
|
-
event: 'summary',
|
|
225
|
-
data: JSON.stringify( {
|
|
226
|
-
message: summary,
|
|
227
|
-
complete: true,
|
|
228
|
-
} ),
|
|
229
|
-
} );
|
|
230
|
-
} catch ( error ) {
|
|
231
|
-
debug( 'error', error );
|
|
232
|
-
this.processErrorEvent( error );
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|