@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 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,2 @@
1
+ export { default as ChromeAIFactory } from './factory.js';
2
+ export { default as ChromeAISuggestionsEventSource } from './suggestions.js';
@@ -0,0 +1,2 @@
1
+ export { default as ChromeAIFactory } from './factory.js';
2
+ export { default as ChromeAISuggestionsEventSource } from './suggestions.js';
@@ -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
- eventSourceRef.current = await askQuestion(promptArg, options);
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
@@ -20,3 +20,7 @@ export * from './types.js';
20
20
  export * from './libs/index.js';
21
21
  export * from './constants.js';
22
22
  export * from './logo-generator/index.js';
23
+ /**
24
+ * Chrome AI
25
+ */
26
+ export * from './chrome-ai/index.js';
package/build/index.js CHANGED
@@ -47,3 +47,7 @@ export * from './constants.js';
47
47
  * Logo Generator
48
48
  */
49
49
  export * from './logo-generator/index.js';
50
+ /**
51
+ * Chrome AI
52
+ */
53
+ export * from './chrome-ai/index.js';
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.1",
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.4.7",
28
- "@storybook/blocks": "8.4.7",
29
- "@storybook/preview-api": "8.4.7",
30
- "@storybook/react": "8.4.7",
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.4.7",
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.42",
48
- "@automattic/jetpack-components": "^0.66.1",
49
- "@automattic/jetpack-connection": "^0.36.6",
50
- "@automattic/jetpack-shared-extension-utils": "^0.17.1",
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,2 @@
1
+ export { default as ChromeAIFactory } from './factory.js';
2
+ export { default as ChromeAISuggestionsEventSource } from './suggestions.js';
@@ -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
- eventSourceRef.current = await askQuestion( promptArg, options );
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
@@ -55,3 +55,8 @@ export * from './constants.js';
55
55
  * Logo Generator
56
56
  */
57
57
  export * from './logo-generator/index.js';
58
+
59
+ /**
60
+ * Chrome AI
61
+ */
62
+ export * from './chrome-ai/index.js';
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
+ }