@automattic/jetpack-ai-client 0.33.31 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,235 +0,0 @@
1
- import { EventSourceMessage } from '@microsoft/fetch-event-source';
2
- import debugFactory from 'debug';
3
- import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.ts';
4
- import { getErrorData } from '../hooks/use-ai-suggestions/index.ts';
5
- import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from '../libs/markdown/index.ts';
6
- import { AiModelTypeProp, ERROR_RESPONSE, ERROR_NETWORK } from '../types.ts';
7
-
8
- type ChromeAISuggestionsEventSourceConstructorArgs = {
9
- content: string;
10
- promptType: string;
11
- options?: {
12
- postId?: number | string;
13
- feature?: 'ai-assistant-experimental' | string | undefined;
14
-
15
- // translation
16
- sourceLanguage?: string;
17
- targetLanguage?: string;
18
-
19
- // summarization
20
- tone?: string;
21
- wordCount?: number;
22
-
23
- // not sure if we need these
24
- functions?: Array< object >;
25
- model?: AiModelTypeProp;
26
- };
27
- };
28
-
29
- type ChromeAIEvent = {
30
- type: string;
31
- message: string;
32
- complete?: boolean;
33
- };
34
-
35
- type FunctionCallProps = {
36
- name?: string;
37
- arguments?: string;
38
- };
39
-
40
- const debug = debugFactory( 'ai-client:chrome-ai-suggestions' );
41
-
42
- export default class ChromeAISuggestionsEventSource extends EventTarget {
43
- fullMessage: string;
44
- fullFunctionCall: FunctionCallProps;
45
- isPromptClear: boolean;
46
- controller: AbortController;
47
-
48
- errorUnclearPromptTriggered: boolean;
49
-
50
- constructor( data: ChromeAISuggestionsEventSourceConstructorArgs ) {
51
- super();
52
- this.fullMessage = '';
53
- this.fullFunctionCall = {
54
- name: '',
55
- arguments: '',
56
- };
57
- this.isPromptClear = false;
58
-
59
- this.controller = new AbortController();
60
-
61
- this.initSource( data );
62
- }
63
-
64
- initSource( {
65
- content,
66
- promptType,
67
- options = {},
68
- }: ChromeAISuggestionsEventSourceConstructorArgs ) {
69
- debug( 'initSource', content, promptType, options );
70
- if ( promptType === PROMPT_TYPE_CHANGE_LANGUAGE ) {
71
- this.translate( content, options.targetLanguage, options.sourceLanguage );
72
- }
73
-
74
- if ( promptType === PROMPT_TYPE_SUMMARIZE ) {
75
- this.summarize( content, options.tone, options.wordCount );
76
- }
77
- }
78
-
79
- async initEventSource() {}
80
-
81
- close() {}
82
-
83
- checkForUnclearPrompt() {}
84
-
85
- processEvent( e: EventSourceMessage ) {
86
- let data: ChromeAIEvent;
87
- debug( 'processEvent', e );
88
- try {
89
- data = JSON.parse( e.data );
90
- } catch ( err ) {
91
- this.processErrorEvent( err );
92
- return;
93
- }
94
-
95
- if ( e.event === 'translation' || e.event === 'summary' ) {
96
- this.dispatchEvent( new CustomEvent( 'suggestion', { detail: data.message } ) );
97
- }
98
-
99
- if ( data.complete ) {
100
- this.dispatchEvent(
101
- new CustomEvent( 'done', { detail: { message: data.message, source: 'chromeAI' } } )
102
- );
103
- }
104
- }
105
-
106
- processErrorEvent( e ) {
107
- debug( 'processErrorEvent', e );
108
- // Dispatch a generic network error event
109
- this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
110
- this.dispatchEvent(
111
- new CustomEvent( ERROR_RESPONSE, {
112
- detail: getErrorData( ERROR_NETWORK ),
113
- } )
114
- );
115
- }
116
-
117
- // use the Chrome AI translator
118
- async translate( text: string, target: string, source: string = '' ) {
119
- if ( ! ( 'Translator' in self ) ) {
120
- return;
121
- }
122
-
123
- const translatorAvailability = await self.Translator.availability( {
124
- sourceLanguage: source,
125
- targetLanguage: target,
126
- } );
127
-
128
- if ( translatorAvailability === 'unavailable' ) {
129
- debug( 'awaiting translator ready' );
130
- this.processErrorEvent( {
131
- message: 'Translator is unavailable',
132
- } );
133
- return;
134
- }
135
-
136
- const translator = await self.Translator.create( {
137
- sourceLanguage: source,
138
- targetLanguage: target,
139
- } );
140
-
141
- if ( ! translator ) {
142
- this.processErrorEvent( {
143
- message: 'Translator failed to initialize',
144
- } );
145
- return;
146
- }
147
-
148
- try {
149
- const translation = await translator.translate( renderHTMLFromMarkdown( { content: text } ) );
150
-
151
- this.processEvent( {
152
- id: '',
153
- event: 'translation',
154
- data: JSON.stringify( {
155
- message: renderMarkdownFromHTML( { content: translation } ),
156
- complete: true,
157
- } ),
158
- } );
159
- } catch ( error ) {
160
- this.processErrorEvent( error );
161
- }
162
- }
163
-
164
- // Helper function to format summarizer options
165
- private getSummarizerOptions( tone?: string, wordCount?: number ) {
166
- let sharedContext = `The summary you write should contain strictly less than ${
167
- wordCount ?? 50
168
- } words. Strive for precision in word count without compromising clarity and significance`;
169
-
170
- if ( tone ) {
171
- sharedContext += `\n - Write with a ${ tone } tone.\n`;
172
- }
173
-
174
- const options = {
175
- sharedContext: sharedContext,
176
- type: 'teaser',
177
- format: 'plain-text',
178
- length: 'medium',
179
- };
180
-
181
- return options;
182
- }
183
-
184
- // use the Chrome AI summarizer
185
- async summarize( text: string, tone?: string, wordCount?: number ) {
186
- debug( 'summarize', text, tone, wordCount );
187
- if ( ! ( 'Summarizer' in self ) ) {
188
- return;
189
- }
190
-
191
- const availability = await self.Summarizer.availability();
192
-
193
- if ( availability === 'unavailable' ) {
194
- this.processErrorEvent( {
195
- data: { message: 'Summarizer is unavailable' },
196
- } );
197
- return;
198
- }
199
-
200
- const summarizerOptions = this.getSummarizerOptions( tone, wordCount );
201
-
202
- const summarizer = await self.Summarizer.create( summarizerOptions );
203
-
204
- if ( availability !== 'available' ) {
205
- debug( 'awaiting summarizer ready' );
206
- await summarizer.ready;
207
- }
208
-
209
- try {
210
- const context = `Write with a ${ tone } tone.`;
211
- debug( 'context', context );
212
- let summary = await summarizer.summarize( text, { context: context } );
213
- debug( 'summary', summary );
214
- wordCount = wordCount ?? 50;
215
-
216
- // gemini-nano has a tendency to exceed the word count, so we need to check and summarize again if necessary
217
- if ( summary.split( ' ' ).length > wordCount ) {
218
- debug( 'summary exceeds word count' );
219
- summary = await summarizer.summarize( summary, { context: context } );
220
- }
221
-
222
- this.processEvent( {
223
- id: '',
224
- event: 'summary',
225
- data: JSON.stringify( {
226
- message: summary,
227
- complete: true,
228
- } ),
229
- } );
230
- } catch ( error ) {
231
- debug( 'error', error );
232
- this.processErrorEvent( error );
233
- }
234
- }
235
- }