forked from felixarntz/ai-services
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.js
277 lines (248 loc) · 7.47 KB
/
helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
* Internal dependencies
*/
import CandidatesStreamProcessor from './classes/candidates-stream-processor';
import HistoryPersistence from './classes/history-persistence';
import { ContentRole } from './enums';
/**
* Converts a text string to a Content object.
*
* @since 0.2.0
*
* @param {string} text The text.
* @param {string} role Optional. The role to use for the content. Default 'user'.
* @return {Object} The Content object.
*/
export function textToContent( text, role = ContentRole.USER ) {
return {
role,
parts: [ { text } ],
};
}
/**
* Converts a text string and attachment to a multimodal Content instance.
*
* The text will be included as a prompt as the first part of the content, and the attachment (e.g. an image or
* audio file) will be included as the second part.
*
* @since 0.5.0
*
* @param {string} text The text.
* @param {Object} attachment The attachment object.
* @param {string} role Optional. The role to use for the content. Default 'user'.
* @return {Object} The Content object.
*/
export async function textAndAttachmentToContent(
text,
attachment,
role = ContentRole.USER
) {
const mimeType = attachment.mime;
const data = await fileToBase64DataUrl(
attachment.sizes?.large?.url || attachment.url
);
return {
role,
parts: [ { text }, { inlineData: { mimeType, data } } ],
};
}
/**
* Converts a Content object to a text string.
*
* This function will return the combined text from all consecutive text parts in the content.
* Realistically, this should almost always return the text from just one part, as API responses typically do not
* contain multiple text parts in a row - but it might be possible.
*
* @since 0.2.0
*
* @param {Object} content The Content object.
* @return {string} The text, or an empty string if there are no text parts.
*/
export function contentToText( content ) {
const textParts = [];
for ( const part of content.parts ) {
/*
* If there is any non-text part present, we want to ensure that no interrupted text content is returned.
* Therefore, we break the loop as soon as we encounter a non-text part, unless no text parts have been
* found yet, in which case the text may only start with a later part.
*/
if ( part.text === undefined ) {
if ( textParts.length > 0 ) {
break;
}
continue;
}
textParts.push( part.text );
}
if ( textParts.length === 0 ) {
return '';
}
return textParts.join( '\n\n' );
}
/**
* Gets the text from the first Content object in the given list which contains text.
*
* @since 0.2.0
*
* @param {Object[]} contents The list of Content objects.
* @return {string} The text, or an empty string if no Content object has text parts.
*/
export function getTextFromContents( contents ) {
for ( const content of contents ) {
const text = contentToText( content );
if ( text ) {
return text;
}
}
return '';
}
/**
* Gets the first Content object in the given list which contains text.
*
* @since 0.5.0
*
* @param {Object[]} contents The list of Content objects.
* @return {?Object} The Content object, or null if no Content object has text parts.
*/
export function getTextContentFromContents( contents ) {
for ( const content of contents ) {
const text = contentToText( content );
if ( text ) {
return content;
}
}
return null;
}
/**
* Gets the Content objects for each candidate in the given list.
*
* @since 0.2.0
*
* @param {Object[]} candidates The list of candidates.
* @return {Object[]} The list of Content objects.
*/
export function getCandidateContents( candidates ) {
const contents = [];
for ( const candidate of candidates ) {
if ( candidate.content ) {
contents.push( candidate.content );
}
}
return contents;
}
/**
* Processes a stream of candidates, aggregating the candidates chunks into a single candidates instance.
*
* This method returns a stream processor instance that can be used to read all chunks from the given candidates
* generator and process them with a callback. Alternatively, you can read from the generator yourself and provide
* all chunks to the processor manually.
*
* @since 0.3.0
*
* @param {Object} generator The generator that yields the chunks of response candidates.
* @return {CandidatesStreamProcessor} The stream processor instance.
*/
export function processCandidatesStream( generator ) {
return new CandidatesStreamProcessor( generator );
}
let historyPersistenceInstance;
/**
* Gets the history persistence instance, to load, save, and clear histories.
*
* @since 0.5.0
*
* @return {HistoryPersistence} The history persistence instance.
*/
export function historyPersistence() {
if ( ! historyPersistenceInstance ) {
historyPersistenceInstance = new HistoryPersistence();
}
return historyPersistenceInstance;
}
/**
* Returns the base64-encoded data URL representation of the given file URL.
*
* @since 0.5.0
*
* @param {string} file The file URL.
* @param {string} mimeType Optional. The MIME type of the file. If provided, the base64-encoded data URL will
* be prefixed with `data:{mime_type};base64,`. Default empty string.
* @return {string} The base64-encoded file data URL, or empty string on failure.
*/
export async function fileToBase64DataUrl( file, mimeType = '' ) {
const blob = await fileToBlob( file, mimeType );
if ( ! blob ) {
return '';
}
return blobToBase64DataUrl( blob );
}
/**
* Returns the binary data blob representation of the given file URL.
*
* @since 0.5.0
*
* @param {string} file The file URL.
* @param {string} mimeType Optional. The MIME type of the file. If provided, the automatically detected MIME type will
* be overwritten. Default empty string.
* @return {Blob?} The binary data blob, or null on failure.
*/
export async function fileToBlob( file, mimeType = '' ) {
const data = await fetch( file );
const blob = await data.blob();
if ( ! blob ) {
return null;
}
if ( mimeType && mimeType !== blob.type ) {
return new Blob( [ blob ], { type: mimeType } );
}
return blob;
}
/**
* Returns the base64-encoded data URL representation of the given binary data blob.
*
* @since 0.5.0
*
* @param {Blob} blob The binary data blob.
* @return {string} The base64-encoded data URL, or empty string on failure.
*/
export async function blobToBase64DataUrl( blob ) {
const base64DataUrl = await new Promise( ( resolve ) => {
const reader = new window.FileReader();
reader.readAsDataURL( blob );
reader.onloadend = () => {
const base64data = reader.result;
resolve( base64data );
};
} );
return base64DataUrl;
}
/**
* Returns the binary data blob representation of the given base64-encoded data URL.
*
* @since 0.5.0
*
* @param {string} base64DataUrl The base64-encoded data URL.
* @return {Blob?} The binary data blob, or null on failure.
*/
export async function base64DataUrlToBlob( base64DataUrl ) {
const prefixMatch = base64DataUrl.match(
/^data:([a-z0-9-]+\/[a-z0-9-]+);base64,/
);
if ( ! prefixMatch ) {
return null;
}
const base64Data = base64DataUrl.substring( prefixMatch[ 0 ].length );
const binaryData = atob( base64Data );
const byteArrays = [];
for ( let offset = 0; offset < binaryData.length; offset += 512 ) {
const slice = binaryData.slice( offset, offset + 512 );
const byteNumbers = new Array( slice.length );
for ( let i = 0; i < slice.length; i++ ) {
byteNumbers[ i ] = slice.charCodeAt( i );
}
byteArrays.push( new Uint8Array( byteNumbers ) );
}
return new Blob( byteArrays, {
type: prefixMatch[ 1 ],
} );
}