@automattic/jetpack-ai-client 0.26.0 → 0.26.2
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 +10 -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.js +9 -1
- package/build/index.d.ts +4 -0
- package/build/index.js +4 -0
- package/build/types.d.ts +26 -0
- package/package.json +4 -4
- package/src/chrome-ai/factory.ts +129 -0
- package/src/chrome-ai/index.ts +2 -0
- package/src/chrome-ai/suggestions.ts +139 -0
- package/src/hooks/use-ai-suggestions/index.ts +9 -1
- package/src/index.ts +5 -0
- package/src/types.ts +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.2] - 2025-02-17
|
|
9
|
+
### Added
|
|
10
|
+
- Jetpack AI: Adding translation support using Chrome's Gemini AI mini. [#41724]
|
|
11
|
+
|
|
12
|
+
## [0.26.1] - 2025-02-11
|
|
13
|
+
### Changed
|
|
14
|
+
- Update dependencies. [#38958]
|
|
15
|
+
|
|
8
16
|
## [0.26.0] - 2025-02-10
|
|
9
17
|
### Added
|
|
10
18
|
- Add shared components from ai-assistant-plugin [#41078]
|
|
@@ -517,6 +525,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
517
525
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
518
526
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
519
527
|
|
|
528
|
+
[0.26.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.1...v0.26.2
|
|
529
|
+
[0.26.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.26.0...v0.26.1
|
|
520
530
|
[0.26.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.7...v0.26.0
|
|
521
531
|
[0.25.7]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.6...v0.25.7
|
|
522
532
|
[0.25.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.5...v0.25.6
|
|
@@ -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: data.message }));
|
|
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
|
+
}
|
|
@@ -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.
|
|
@@ -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.2",
|
|
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,9 +45,9 @@
|
|
|
45
45
|
"types": "./build/index.d.ts",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@automattic/jetpack-base-styles": "^0.6.42",
|
|
48
|
-
"@automattic/jetpack-components": "^0.
|
|
49
|
-
"@automattic/jetpack-connection": "^0.36.
|
|
50
|
-
"@automattic/jetpack-shared-extension-utils": "^0.17.
|
|
48
|
+
"@automattic/jetpack-components": "^0.67.0",
|
|
49
|
+
"@automattic/jetpack-connection": "^0.36.7",
|
|
50
|
+
"@automattic/jetpack-shared-extension-utils": "^0.17.2",
|
|
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,139 @@
|
|
|
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( new CustomEvent( 'done', { detail: data.message } ) );
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
processErrorEvent( e ) {
|
|
96
|
+
// Dispatch a generic network error event
|
|
97
|
+
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
|
|
98
|
+
this.dispatchEvent(
|
|
99
|
+
new CustomEvent( ERROR_RESPONSE, {
|
|
100
|
+
detail: getErrorData( ERROR_NETWORK ),
|
|
101
|
+
} )
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// use the Chrome AI translator
|
|
106
|
+
async translate( text: string, target: string, source: string = '' ) {
|
|
107
|
+
if ( ! ( 'translation' in self ) ) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const translator = await self.translation.createTranslator( {
|
|
112
|
+
sourceLanguage: source,
|
|
113
|
+
targetLanguage: target,
|
|
114
|
+
} );
|
|
115
|
+
|
|
116
|
+
if ( ! translator ) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const translation = await translator.translate( renderHTMLFromMarkdown( { content: text } ) );
|
|
122
|
+
this.processEvent( {
|
|
123
|
+
id: '',
|
|
124
|
+
event: 'translation',
|
|
125
|
+
data: JSON.stringify( {
|
|
126
|
+
message: renderMarkdownFromHTML( { content: translation } ),
|
|
127
|
+
complete: true,
|
|
128
|
+
} ),
|
|
129
|
+
} );
|
|
130
|
+
} catch ( error ) {
|
|
131
|
+
this.processErrorEvent( error );
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// TODO
|
|
136
|
+
async summarize( text: string ) {
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -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,
|
|
@@ -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
|
+
}
|