@automattic/jetpack-ai-client 0.27.10 → 0.28.0

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