@automattic/jetpack-ai-client 0.26.1 → 0.26.3
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 +13 -0
- package/build/chrome-ai/factory.d.ts +9 -0
- package/build/chrome-ai/factory.js +99 -0
- package/build/chrome-ai/index.d.ts +2 -0
- package/build/chrome-ai/index.js +2 -0
- package/build/chrome-ai/suggestions.d.ts +35 -0
- package/build/chrome-ai/suggestions.js +87 -0
- package/build/hooks/use-ai-suggestions/index.d.ts +1 -1
- package/build/hooks/use-ai-suggestions/index.js +11 -3
- package/build/index.d.ts +4 -0
- package/build/index.js +4 -0
- package/build/types.d.ts +26 -0
- package/package.json +10 -10
- package/src/chrome-ai/factory.ts +129 -0
- package/src/chrome-ai/index.ts +2 -0
- package/src/chrome-ai/suggestions.ts +141 -0
- package/src/hooks/use-ai-suggestions/index.ts +12 -4
- package/src/index.ts +5 -0
- package/src/types.ts +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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.26.3] - 2025-02-24
|
|
9
|
+
### Changed
|
|
10
|
+
- Update package dependencies. [#41955]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Prevent Chrome AI requests from incrementing request count. [#41900]
|
|
14
|
+
|
|
15
|
+
## [0.26.2] - 2025-02-17
|
|
16
|
+
### Added
|
|
17
|
+
- Add translation support using Chrome's Gemini AI mini. [#41724]
|
|
18
|
+
|
|
8
19
|
## [0.26.1] - 2025-02-11
|
|
9
20
|
### Changed
|
|
10
21
|
- Update dependencies. [#38958]
|
|
@@ -521,6 +532,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
521
532
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
522
533
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
523
534
|
|
|
535
|
+
[0.26.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.2...v0.26.3
|
|
536
|
+
[0.26.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.1...v0.26.2
|
|
524
537
|
[0.26.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.0...v0.26.1
|
|
525
538
|
[0.26.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.7...v0.26.0
|
|
526
539
|
[0.25.7]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.6...v0.25.7
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PromptProp } from '../types.js';
|
|
2
|
+
import ChromeAISuggestionsEventSource from './suggestions.js';
|
|
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>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
|
|
2
|
+
import { PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
3
|
+
//PROMPT_TYPE_SUMMARIZE,
|
|
4
|
+
} from '../constants.js';
|
|
5
|
+
import ChromeAISuggestionsEventSource from './suggestions.js';
|
|
6
|
+
/**
|
|
7
|
+
* Check for the feature flag.
|
|
8
|
+
*
|
|
9
|
+
* @return boolean
|
|
10
|
+
*/
|
|
11
|
+
function shouldUseChromeAI() {
|
|
12
|
+
return getJetpackExtensionAvailability('ai-use-chrome-ai-sometimes').available === true;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* This will return an instance of ChromeAISuggestionsEventSource or false.
|
|
16
|
+
*
|
|
17
|
+
* @param promptArg - The messages array of the prompt.
|
|
18
|
+
* @return ChromeAISuggestionsEventSource | bool
|
|
19
|
+
*/
|
|
20
|
+
export default async function ChromeAIFactory(promptArg) {
|
|
21
|
+
if (!shouldUseChromeAI()) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const context = {
|
|
25
|
+
content: '',
|
|
26
|
+
language: '',
|
|
27
|
+
};
|
|
28
|
+
let promptType = '';
|
|
29
|
+
if (Array.isArray(promptArg)) {
|
|
30
|
+
for (let i = 0; i < promptArg.length; i++) {
|
|
31
|
+
const prompt = promptArg[i];
|
|
32
|
+
if (prompt.content) {
|
|
33
|
+
context.content = prompt.content;
|
|
34
|
+
}
|
|
35
|
+
if (!('context' in prompt)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const promptContext = prompt.context;
|
|
39
|
+
if (promptContext.type) {
|
|
40
|
+
promptType = promptContext.type;
|
|
41
|
+
}
|
|
42
|
+
if (promptContext.language) {
|
|
43
|
+
context.language = promptContext.language;
|
|
44
|
+
}
|
|
45
|
+
if (promptContext.content) {
|
|
46
|
+
context.content = promptContext.content;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (promptType.startsWith('ai-assistant-change-language')) {
|
|
51
|
+
const [language] = context.language.split(' ');
|
|
52
|
+
if (!('translation' in self) ||
|
|
53
|
+
!self.translation.createTranslator ||
|
|
54
|
+
!self.translation.canTranslate) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const languageOpts = {
|
|
58
|
+
sourceLanguage: 'en',
|
|
59
|
+
targetLanguage: language,
|
|
60
|
+
};
|
|
61
|
+
// see if we can detect the source language
|
|
62
|
+
if ('ai' in self && self.ai.languageDetector) {
|
|
63
|
+
const detector = await self.ai.languageDetector.create();
|
|
64
|
+
const confidences = await detector.detect(context.content);
|
|
65
|
+
for (const confidence of confidences) {
|
|
66
|
+
// 75% confidence is just a value that was picked. Generally
|
|
67
|
+
// 80% of higher is pretty safe, but the source language is
|
|
68
|
+
// required for the translator to work at all, which is also
|
|
69
|
+
// why en is the default language.
|
|
70
|
+
if (confidence.confidence > 0.75) {
|
|
71
|
+
languageOpts.sourceLanguage = confidence.detectedLanguage;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const canTranslate = await self.translation.canTranslate(languageOpts);
|
|
77
|
+
if (canTranslate === 'no') {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const chromeAI = new ChromeAISuggestionsEventSource({
|
|
81
|
+
content: context.content,
|
|
82
|
+
promptType: PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
83
|
+
options: languageOpts,
|
|
84
|
+
});
|
|
85
|
+
return chromeAI;
|
|
86
|
+
}
|
|
87
|
+
// TODO
|
|
88
|
+
if (promptType.startsWith('ai-assistant-summarize')) {
|
|
89
|
+
/*
|
|
90
|
+
return new ChromeAISuggestionsEventSource({
|
|
91
|
+
content: "",
|
|
92
|
+
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
93
|
+
options: {},
|
|
94
|
+
} );
|
|
95
|
+
*/
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { EventSourceMessage } from '@microsoft/fetch-event-source';
|
|
2
|
+
import { AiModelTypeProp } from '../types.js';
|
|
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
|
+
functions?: Array<object>;
|
|
12
|
+
model?: AiModelTypeProp;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
type FunctionCallProps = {
|
|
16
|
+
name?: string;
|
|
17
|
+
arguments?: string;
|
|
18
|
+
};
|
|
19
|
+
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
20
|
+
fullMessage: string;
|
|
21
|
+
fullFunctionCall: FunctionCallProps;
|
|
22
|
+
isPromptClear: boolean;
|
|
23
|
+
controller: AbortController;
|
|
24
|
+
errorUnclearPromptTriggered: boolean;
|
|
25
|
+
constructor(data: ChromeAISuggestionsEventSourceConstructorArgs);
|
|
26
|
+
initSource({ content, promptType, options, }: ChromeAISuggestionsEventSourceConstructorArgs): void;
|
|
27
|
+
initEventSource(): Promise<void>;
|
|
28
|
+
close(): void;
|
|
29
|
+
checkForUnclearPrompt(): void;
|
|
30
|
+
processEvent(e: EventSourceMessage): void;
|
|
31
|
+
processErrorEvent(e: any): void;
|
|
32
|
+
translate(text: string, target: string, source?: string): Promise<void>;
|
|
33
|
+
summarize(text: string): Promise<string>;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.js';
|
|
2
|
+
import { getErrorData } from '../hooks/use-ai-suggestions/index.js';
|
|
3
|
+
import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from '../libs/markdown/index.js';
|
|
4
|
+
import { ERROR_RESPONSE, ERROR_NETWORK } from '../types.js';
|
|
5
|
+
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
6
|
+
fullMessage;
|
|
7
|
+
fullFunctionCall;
|
|
8
|
+
isPromptClear;
|
|
9
|
+
controller;
|
|
10
|
+
errorUnclearPromptTriggered;
|
|
11
|
+
constructor(data) {
|
|
12
|
+
super();
|
|
13
|
+
this.fullMessage = '';
|
|
14
|
+
this.fullFunctionCall = {
|
|
15
|
+
name: '',
|
|
16
|
+
arguments: '',
|
|
17
|
+
};
|
|
18
|
+
this.isPromptClear = false;
|
|
19
|
+
this.controller = new AbortController();
|
|
20
|
+
this.initSource(data);
|
|
21
|
+
}
|
|
22
|
+
initSource({ content, promptType, options = {}, }) {
|
|
23
|
+
if (promptType === PROMPT_TYPE_CHANGE_LANGUAGE) {
|
|
24
|
+
this.translate(content, options.targetLanguage, options.sourceLanguage);
|
|
25
|
+
}
|
|
26
|
+
if (promptType === PROMPT_TYPE_SUMMARIZE) {
|
|
27
|
+
this.summarize(content);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async initEventSource() { }
|
|
31
|
+
close() { }
|
|
32
|
+
checkForUnclearPrompt() { }
|
|
33
|
+
processEvent(e) {
|
|
34
|
+
let data;
|
|
35
|
+
try {
|
|
36
|
+
data = JSON.parse(e.data);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
this.processErrorEvent(err);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (e.event === 'translation') {
|
|
43
|
+
this.dispatchEvent(new CustomEvent('suggestion', { detail: data.message }));
|
|
44
|
+
}
|
|
45
|
+
if (data.complete) {
|
|
46
|
+
this.dispatchEvent(new CustomEvent('done', { detail: { message: data.message, source: 'chromeAI' } }));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
processErrorEvent(e) {
|
|
50
|
+
// Dispatch a generic network error event
|
|
51
|
+
this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: e }));
|
|
52
|
+
this.dispatchEvent(new CustomEvent(ERROR_RESPONSE, {
|
|
53
|
+
detail: getErrorData(ERROR_NETWORK),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
// use the Chrome AI translator
|
|
57
|
+
async translate(text, target, source = '') {
|
|
58
|
+
if (!('translation' in self)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const translator = await self.translation.createTranslator({
|
|
62
|
+
sourceLanguage: source,
|
|
63
|
+
targetLanguage: target,
|
|
64
|
+
});
|
|
65
|
+
if (!translator) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const translation = await translator.translate(renderHTMLFromMarkdown({ content: text }));
|
|
70
|
+
this.processEvent({
|
|
71
|
+
id: '',
|
|
72
|
+
event: 'translation',
|
|
73
|
+
data: JSON.stringify({
|
|
74
|
+
message: renderMarkdownFromHTML({ content: translation }),
|
|
75
|
+
complete: true,
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
this.processErrorEvent(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// TODO
|
|
84
|
+
async summarize(text) {
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -18,7 +18,7 @@ type useAiSuggestionsOptions = {
|
|
|
18
18
|
askQuestionOptions?: AskQuestionOptionsArgProps;
|
|
19
19
|
initialRequestingState?: RequestingStateProp;
|
|
20
20
|
onSuggestion?: (suggestion: string) => void;
|
|
21
|
-
onDone?: (content: string) => void;
|
|
21
|
+
onDone?: (content: string, skipRequestCount?: boolean) => void;
|
|
22
22
|
onStop?: () => void;
|
|
23
23
|
onError?: (error: RequestingErrorProps) => void;
|
|
24
24
|
onAllErrors?: (error: RequestingErrorProps) => void;
|
|
@@ -7,6 +7,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
9
|
import askQuestion from '../../ask-question/index.js';
|
|
10
|
+
import ChromeAIFactory from '../../chrome-ai/factory.js';
|
|
10
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
12
|
/**
|
|
12
13
|
* Get the error data for a given error code.
|
|
@@ -99,8 +100,8 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
99
100
|
*/
|
|
100
101
|
const handleDone = useCallback((event) => {
|
|
101
102
|
closeEventSource();
|
|
102
|
-
const fullSuggestion = removeLlamaArtifact(event?.detail);
|
|
103
|
-
onDone?.(fullSuggestion);
|
|
103
|
+
const fullSuggestion = removeLlamaArtifact(event?.detail?.message ?? event?.detail);
|
|
104
|
+
onDone?.(fullSuggestion, event?.detail?.source === 'chromeAI');
|
|
104
105
|
setRequestingState('done');
|
|
105
106
|
}, [onDone]);
|
|
106
107
|
const handleAnyError = useCallback((event) => {
|
|
@@ -129,7 +130,14 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
|
|
|
129
130
|
setError(undefined);
|
|
130
131
|
// Set the request status.
|
|
131
132
|
setRequestingState('requesting');
|
|
132
|
-
|
|
133
|
+
// check if we can (or should) use Chrome AI
|
|
134
|
+
const chromeAI = await ChromeAIFactory(promptArg);
|
|
135
|
+
if (chromeAI !== false) {
|
|
136
|
+
eventSourceRef.current = chromeAI;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
eventSourceRef.current = await askQuestion(promptArg, options);
|
|
140
|
+
}
|
|
133
141
|
if (!eventSourceRef?.current) {
|
|
134
142
|
return;
|
|
135
143
|
}
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
package/build/types.d.ts
CHANGED
|
@@ -51,3 +51,29 @@ export interface BlockEditorStore {
|
|
|
51
51
|
[key in keyof typeof BlockEditorSelectors]: (typeof BlockEditorSelectors)[key];
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
declare global {
|
|
55
|
+
interface Window {
|
|
56
|
+
translation?: {
|
|
57
|
+
canTranslate: (options: {
|
|
58
|
+
sourceLanguage: string;
|
|
59
|
+
targetLanguage: string;
|
|
60
|
+
}) => Promise<'no' | 'yes' | string>;
|
|
61
|
+
createTranslator: (options: {
|
|
62
|
+
sourceLanguage: string;
|
|
63
|
+
targetLanguage: string;
|
|
64
|
+
}) => Promise<{
|
|
65
|
+
translate: (text: string) => Promise<string>;
|
|
66
|
+
}>;
|
|
67
|
+
};
|
|
68
|
+
ai?: {
|
|
69
|
+
languageDetector: {
|
|
70
|
+
create: () => Promise<{
|
|
71
|
+
detect: (text: string) => Promise<{
|
|
72
|
+
detectedLanguage: string;
|
|
73
|
+
confidence: number;
|
|
74
|
+
}[]>;
|
|
75
|
+
}>;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.26.
|
|
4
|
+
"version": "0.26.3",
|
|
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": {
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@storybook/addon-actions": "8.
|
|
28
|
-
"@storybook/blocks": "8.
|
|
29
|
-
"@storybook/preview-api": "8.
|
|
30
|
-
"@storybook/react": "8.
|
|
27
|
+
"@storybook/addon-actions": "8.5.8",
|
|
28
|
+
"@storybook/blocks": "8.5.8",
|
|
29
|
+
"@storybook/preview-api": "8.5.8",
|
|
30
|
+
"@storybook/react": "8.5.8",
|
|
31
31
|
"@types/markdown-it": "14.1.2",
|
|
32
32
|
"@types/turndown": "5.0.5",
|
|
33
33
|
"jest": "^29.6.2",
|
|
34
34
|
"jest-environment-jsdom": "29.7.0",
|
|
35
|
-
"storybook": "8.
|
|
35
|
+
"storybook": "8.5.8",
|
|
36
36
|
"typescript": "5.0.4"
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"main": "./build/index.js",
|
|
45
45
|
"types": "./build/index.d.ts",
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@automattic/jetpack-base-styles": "^0.6.
|
|
48
|
-
"@automattic/jetpack-components": "^0.
|
|
49
|
-
"@automattic/jetpack-connection": "^0.
|
|
50
|
-
"@automattic/jetpack-shared-extension-utils": "^0.17.
|
|
47
|
+
"@automattic/jetpack-base-styles": "^0.6.43",
|
|
48
|
+
"@automattic/jetpack-components": "^0.67.1",
|
|
49
|
+
"@automattic/jetpack-connection": "^0.37.0",
|
|
50
|
+
"@automattic/jetpack-shared-extension-utils": "^0.17.3",
|
|
51
51
|
"@microsoft/fetch-event-source": "2.0.1",
|
|
52
52
|
"@types/jest": "29.5.14",
|
|
53
53
|
"@types/react": "18.3.18",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
|
|
2
|
+
import {
|
|
3
|
+
PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
4
|
+
//PROMPT_TYPE_SUMMARIZE,
|
|
5
|
+
} from '../constants.js';
|
|
6
|
+
import { PromptProp, PromptItemProps } from '../types.js';
|
|
7
|
+
import ChromeAISuggestionsEventSource from './suggestions.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check for the feature flag.
|
|
11
|
+
*
|
|
12
|
+
* @return boolean
|
|
13
|
+
*/
|
|
14
|
+
function shouldUseChromeAI() {
|
|
15
|
+
return getJetpackExtensionAvailability( 'ai-use-chrome-ai-sometimes' ).available === true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PromptContext {
|
|
19
|
+
type?: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
language?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This will return an instance of ChromeAISuggestionsEventSource or false.
|
|
26
|
+
*
|
|
27
|
+
* @param promptArg - The messages array of the prompt.
|
|
28
|
+
* @return ChromeAISuggestionsEventSource | bool
|
|
29
|
+
*/
|
|
30
|
+
export default async function ChromeAIFactory( promptArg: PromptProp ) {
|
|
31
|
+
if ( ! shouldUseChromeAI() ) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const context = {
|
|
36
|
+
content: '',
|
|
37
|
+
language: '',
|
|
38
|
+
};
|
|
39
|
+
let promptType = '';
|
|
40
|
+
if ( Array.isArray( promptArg ) ) {
|
|
41
|
+
for ( let i = 0; i < promptArg.length; i++ ) {
|
|
42
|
+
const prompt: PromptItemProps = promptArg[ i ];
|
|
43
|
+
if ( prompt.content ) {
|
|
44
|
+
context.content = prompt.content;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ( ! ( 'context' in prompt ) ) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const promptContext: PromptContext = prompt.context;
|
|
52
|
+
|
|
53
|
+
if ( promptContext.type ) {
|
|
54
|
+
promptType = promptContext.type;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if ( promptContext.language ) {
|
|
58
|
+
context.language = promptContext.language;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ( promptContext.content ) {
|
|
62
|
+
context.content = promptContext.content;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ( promptType.startsWith( 'ai-assistant-change-language' ) ) {
|
|
68
|
+
const [ language ] = context.language.split( ' ' );
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
! ( 'translation' in self ) ||
|
|
72
|
+
! self.translation.createTranslator ||
|
|
73
|
+
! self.translation.canTranslate
|
|
74
|
+
) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const languageOpts = {
|
|
79
|
+
sourceLanguage: 'en',
|
|
80
|
+
targetLanguage: language,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// see if we can detect the source language
|
|
84
|
+
if ( 'ai' in self && self.ai.languageDetector ) {
|
|
85
|
+
const detector = await self.ai.languageDetector.create();
|
|
86
|
+
const confidences = await detector.detect( context.content );
|
|
87
|
+
|
|
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;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const canTranslate = await self.translation.canTranslate( languageOpts );
|
|
101
|
+
|
|
102
|
+
if ( canTranslate === 'no' ) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const chromeAI = new ChromeAISuggestionsEventSource( {
|
|
107
|
+
content: context.content,
|
|
108
|
+
promptType: PROMPT_TYPE_CHANGE_LANGUAGE,
|
|
109
|
+
options: languageOpts,
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
return chromeAI;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// TODO
|
|
116
|
+
if ( promptType.startsWith( 'ai-assistant-summarize' ) ) {
|
|
117
|
+
/*
|
|
118
|
+
return new ChromeAISuggestionsEventSource({
|
|
119
|
+
content: "",
|
|
120
|
+
promptType: PROMPT_TYPE_SUMMARIZE,
|
|
121
|
+
options: {},
|
|
122
|
+
} );
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { EventSourceMessage } from '@microsoft/fetch-event-source';
|
|
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 { AiModelTypeProp, ERROR_RESPONSE, ERROR_NETWORK } from '../types.js';
|
|
6
|
+
|
|
7
|
+
type ChromeAISuggestionsEventSourceConstructorArgs = {
|
|
8
|
+
content: string;
|
|
9
|
+
promptType: string;
|
|
10
|
+
options?: {
|
|
11
|
+
postId?: number | string;
|
|
12
|
+
feature?: 'ai-assistant-experimental' | string | undefined;
|
|
13
|
+
|
|
14
|
+
// translation
|
|
15
|
+
sourceLanguage?: string;
|
|
16
|
+
targetLanguage?: string;
|
|
17
|
+
|
|
18
|
+
// not sure if we need these
|
|
19
|
+
functions?: Array< object >;
|
|
20
|
+
model?: AiModelTypeProp;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ChromeAIEvent = {
|
|
25
|
+
type: string;
|
|
26
|
+
message: string;
|
|
27
|
+
complete?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type FunctionCallProps = {
|
|
31
|
+
name?: string;
|
|
32
|
+
arguments?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default class ChromeAISuggestionsEventSource extends EventTarget {
|
|
36
|
+
fullMessage: string;
|
|
37
|
+
fullFunctionCall: FunctionCallProps;
|
|
38
|
+
isPromptClear: boolean;
|
|
39
|
+
controller: AbortController;
|
|
40
|
+
|
|
41
|
+
errorUnclearPromptTriggered: boolean;
|
|
42
|
+
|
|
43
|
+
constructor( data: ChromeAISuggestionsEventSourceConstructorArgs ) {
|
|
44
|
+
super();
|
|
45
|
+
this.fullMessage = '';
|
|
46
|
+
this.fullFunctionCall = {
|
|
47
|
+
name: '',
|
|
48
|
+
arguments: '',
|
|
49
|
+
};
|
|
50
|
+
this.isPromptClear = false;
|
|
51
|
+
|
|
52
|
+
this.controller = new AbortController();
|
|
53
|
+
|
|
54
|
+
this.initSource( data );
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
initSource( {
|
|
58
|
+
content,
|
|
59
|
+
promptType,
|
|
60
|
+
options = {},
|
|
61
|
+
}: ChromeAISuggestionsEventSourceConstructorArgs ) {
|
|
62
|
+
if ( promptType === PROMPT_TYPE_CHANGE_LANGUAGE ) {
|
|
63
|
+
this.translate( content, options.targetLanguage, options.sourceLanguage );
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ( promptType === PROMPT_TYPE_SUMMARIZE ) {
|
|
67
|
+
this.summarize( content );
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async initEventSource() {}
|
|
72
|
+
|
|
73
|
+
close() {}
|
|
74
|
+
|
|
75
|
+
checkForUnclearPrompt() {}
|
|
76
|
+
|
|
77
|
+
processEvent( e: EventSourceMessage ) {
|
|
78
|
+
let data: ChromeAIEvent;
|
|
79
|
+
try {
|
|
80
|
+
data = JSON.parse( e.data );
|
|
81
|
+
} catch ( err ) {
|
|
82
|
+
this.processErrorEvent( err );
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ( e.event === 'translation' ) {
|
|
87
|
+
this.dispatchEvent( new CustomEvent( 'suggestion', { detail: data.message } ) );
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ( data.complete ) {
|
|
91
|
+
this.dispatchEvent(
|
|
92
|
+
new CustomEvent( 'done', { detail: { message: data.message, source: 'chromeAI' } } )
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
processErrorEvent( e ) {
|
|
98
|
+
// Dispatch a generic network error event
|
|
99
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
100
|
+
this.dispatchEvent(
|
|
101
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
102
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
103
|
+
} )
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// use the Chrome AI translator
|
|
108
|
+
async translate( text: string, target: string, source: string = '' ) {
|
|
109
|
+
if ( ! ( 'translation' in self ) ) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const translator = await self.translation.createTranslator( {
|
|
114
|
+
sourceLanguage: source,
|
|
115
|
+
targetLanguage: target,
|
|
116
|
+
} );
|
|
117
|
+
|
|
118
|
+
if ( ! translator ) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const translation = await translator.translate( renderHTMLFromMarkdown( { content: text } ) );
|
|
124
|
+
this.processEvent( {
|
|
125
|
+
id: '',
|
|
126
|
+
event: 'translation',
|
|
127
|
+
data: JSON.stringify( {
|
|
128
|
+
message: renderMarkdownFromHTML( { content: translation } ),
|
|
129
|
+
complete: true,
|
|
130
|
+
} ),
|
|
131
|
+
} );
|
|
132
|
+
} catch ( error ) {
|
|
133
|
+
this.processErrorEvent( error );
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// TODO
|
|
138
|
+
async summarize( text: string ) {
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -7,6 +7,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
9
|
import askQuestion from '../../ask-question/index.js';
|
|
10
|
+
import ChromeAIFactory from '../../chrome-ai/factory.js';
|
|
10
11
|
import {
|
|
11
12
|
ERROR_CONTEXT_TOO_LARGE,
|
|
12
13
|
ERROR_MODERATION,
|
|
@@ -69,7 +70,7 @@ type useAiSuggestionsOptions = {
|
|
|
69
70
|
/*
|
|
70
71
|
* onDone callback.
|
|
71
72
|
*/
|
|
72
|
-
onDone?: ( content: string ) => void;
|
|
73
|
+
onDone?: ( content: string, skipRequestCount?: boolean ) => void;
|
|
73
74
|
|
|
74
75
|
/*
|
|
75
76
|
* onStop callback.
|
|
@@ -255,9 +256,9 @@ export default function useAiSuggestions( {
|
|
|
255
256
|
( event: CustomEvent ) => {
|
|
256
257
|
closeEventSource();
|
|
257
258
|
|
|
258
|
-
const fullSuggestion = removeLlamaArtifact( event?.detail );
|
|
259
|
+
const fullSuggestion = removeLlamaArtifact( event?.detail?.message ?? event?.detail );
|
|
259
260
|
|
|
260
|
-
onDone?.( fullSuggestion );
|
|
261
|
+
onDone?.( fullSuggestion, event?.detail?.source === 'chromeAI' );
|
|
261
262
|
setRequestingState( 'done' );
|
|
262
263
|
},
|
|
263
264
|
[ onDone ]
|
|
@@ -314,7 +315,14 @@ export default function useAiSuggestions( {
|
|
|
314
315
|
// Set the request status.
|
|
315
316
|
setRequestingState( 'requesting' );
|
|
316
317
|
|
|
317
|
-
|
|
318
|
+
// check if we can (or should) use Chrome AI
|
|
319
|
+
const chromeAI = await ChromeAIFactory( promptArg );
|
|
320
|
+
|
|
321
|
+
if ( chromeAI !== false ) {
|
|
322
|
+
eventSourceRef.current = chromeAI;
|
|
323
|
+
} else {
|
|
324
|
+
eventSourceRef.current = await askQuestion( promptArg, options );
|
|
325
|
+
}
|
|
318
326
|
|
|
319
327
|
if ( ! eventSourceRef?.current ) {
|
|
320
328
|
return;
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -132,3 +132,32 @@ export interface BlockEditorStore {
|
|
|
132
132
|
[ key in keyof typeof BlockEditorSelectors ]: ( typeof BlockEditorSelectors )[ key ];
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
declare global {
|
|
137
|
+
interface Window {
|
|
138
|
+
translation?: {
|
|
139
|
+
canTranslate: ( options: {
|
|
140
|
+
sourceLanguage: string;
|
|
141
|
+
targetLanguage: string;
|
|
142
|
+
} ) => Promise< 'no' | 'yes' | string >;
|
|
143
|
+
createTranslator: ( options: {
|
|
144
|
+
sourceLanguage: string;
|
|
145
|
+
targetLanguage: string;
|
|
146
|
+
} ) => Promise< {
|
|
147
|
+
translate: ( text: string ) => Promise< string >;
|
|
148
|
+
} >;
|
|
149
|
+
};
|
|
150
|
+
ai?: {
|
|
151
|
+
languageDetector: {
|
|
152
|
+
create: () => Promise< {
|
|
153
|
+
detect: ( text: string ) => Promise<
|
|
154
|
+
{
|
|
155
|
+
detectedLanguage: string;
|
|
156
|
+
confidence: number;
|
|
157
|
+
}[]
|
|
158
|
+
>;
|
|
159
|
+
} >;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|