prompt-api-polyfill 1.0.0 → 1.1.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,383 +0,0 @@
1
- export default class MultimodalConverter {
2
- static async convert(type, value) {
3
- if (type === 'image') {
4
- return this.processImage(value);
5
- }
6
- if (type === 'audio') {
7
- return this.processAudio(value);
8
- }
9
- throw new DOMException(
10
- `Unsupported media type: ${type}`,
11
- 'NotSupportedError'
12
- );
13
- }
14
-
15
- static async processImage(source) {
16
- // Blob
17
- if (source instanceof Blob) {
18
- return this.blobToInlineData(source);
19
- }
20
-
21
- // BufferSource (ArrayBuffer/View) -> Sniff or Default
22
- if (ArrayBuffer.isView(source) || source instanceof ArrayBuffer) {
23
- const u8 =
24
- source instanceof ArrayBuffer
25
- ? new Uint8Array(source)
26
- : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
27
- const buffer = u8.buffer.slice(
28
- u8.byteOffset,
29
- u8.byteOffset + u8.byteLength
30
- );
31
- const base64 = this.arrayBufferToBase64(buffer);
32
- const mimeType = this.#sniffImageMimeType(u8);
33
- if (!mimeType) {
34
- throw new DOMException('Invalid image data', 'InvalidStateError');
35
- }
36
-
37
- return { inlineData: { data: base64, mimeType } };
38
- }
39
-
40
- // ImageBitmapSource (Canvas, Image, VideoFrame, etc.)
41
- // We draw to a canvas to standardize to PNG
42
- return this.canvasSourceToInlineData(source);
43
- }
44
-
45
- static #sniffImageMimeType(u8) {
46
- const len = u8.length;
47
- if (len < 4) {
48
- return null;
49
- }
50
-
51
- // JPEG: FF D8 FF
52
- if (u8[0] === 0xff && u8[1] === 0xd8 && u8[2] === 0xff) {
53
- return 'image/jpeg';
54
- }
55
-
56
- // PNG: 89 50 4E 47 0D 0A 1A 0A
57
- if (
58
- u8[0] === 0x89 &&
59
- u8[1] === 0x50 &&
60
- u8[2] === 0x4e &&
61
- u8[3] === 0x47 &&
62
- u8[4] === 0x0d &&
63
- u8[5] === 0x0a &&
64
- u8[6] === 0x1a &&
65
- u8[7] === 0x0a
66
- ) {
67
- return 'image/png';
68
- }
69
-
70
- // GIF: GIF87a / GIF89a
71
- if (u8[0] === 0x47 && u8[1] === 0x49 && u8[2] === 0x46 && u8[3] === 0x38) {
72
- return 'image/gif';
73
- }
74
-
75
- // WebP: RIFF (offset 0) + WEBP (offset 8)
76
- if (
77
- u8[0] === 0x52 &&
78
- u8[1] === 0x49 &&
79
- u8[2] === 0x46 &&
80
- u8[3] === 0x46 &&
81
- u8[8] === 0x57 &&
82
- u8[9] === 0x45 &&
83
- u8[10] === 0x42 &&
84
- u8[11] === 0x50
85
- ) {
86
- return 'image/webp';
87
- }
88
-
89
- // BMP: BM
90
- if (u8[0] === 0x42 && u8[1] === 0x4d) {
91
- return 'image/bmp';
92
- }
93
-
94
- // ICO: 00 00 01 00
95
- if (u8[0] === 0x00 && u8[1] === 0x00 && u8[2] === 0x01 && u8[3] === 0x00) {
96
- return 'image/x-icon';
97
- }
98
-
99
- // TIFF: II* (LE) / MM* (BE)
100
- if (
101
- (u8[0] === 0x49 && u8[1] === 0x49 && u8[2] === 0x2a) ||
102
- (u8[0] === 0x4d && u8[1] === 0x4d && u8[2] === 0x2a)
103
- ) {
104
- return 'image/tiff';
105
- }
106
-
107
- // ISOBMFF (AVIF / HEIC / HEIF)
108
- // "ftyp" at offset 4
109
- if (u8[4] === 0x66 && u8[5] === 0x74 && u8[6] === 0x79 && u8[7] === 0x70) {
110
- const type = String.fromCharCode(u8[8], u8[9], u8[10], u8[11]);
111
- if (type === 'avif' || type === 'avis') {
112
- return 'image/avif';
113
- }
114
- if (
115
- type === 'heic' ||
116
- type === 'heix' ||
117
- type === 'hevc' ||
118
- type === 'hevx'
119
- ) {
120
- return 'image/heic';
121
- }
122
- if (type === 'mif1' || type === 'msf1') {
123
- return 'image/heif';
124
- }
125
- }
126
-
127
- // JPEG XL: FF 0A or container bits
128
- if (u8[0] === 0xff && u8[1] === 0x0a) {
129
- return 'image/jxl';
130
- }
131
- // Container: 00 00 00 0c 4a 58 4c 20 0d 0a 87 0a (JXL )
132
- if (u8[0] === 0x00 && u8[4] === 0x4a && u8[5] === 0x58 && u8[6] === 0x4c) {
133
- return 'image/jxl';
134
- }
135
-
136
- // JPEG 2000
137
- if (u8[0] === 0x00 && u8[4] === 0x6a && u8[5] === 0x50 && u8[6] === 0x20) {
138
- return 'image/jp2';
139
- }
140
-
141
- // SVG: Check for <svg or <?xml (heuristics)
142
- const preview = String.fromCharCode(...u8.slice(0, 100)).toLowerCase();
143
- if (preview.includes('<svg') || preview.includes('<?xml')) {
144
- return 'image/svg+xml';
145
- }
146
-
147
- return null;
148
- }
149
-
150
- static async processAudio(source) {
151
- // Blob
152
- if (source instanceof Blob) {
153
- if (
154
- source.type &&
155
- !source.type.startsWith('audio/') &&
156
- source.type !== 'application/ogg'
157
- ) {
158
- throw new DOMException('Invalid audio mime type', 'DataError');
159
- }
160
- return this.blobToInlineData(source);
161
- }
162
-
163
- // AudioBuffer -> WAV
164
- if (source instanceof AudioBuffer) {
165
- const wavBuffer = this.audioBufferToWav(source);
166
- const base64 = this.arrayBufferToBase64(wavBuffer);
167
- return { inlineData: { data: base64, mimeType: 'audio/wav' } };
168
- }
169
-
170
- // BufferSource -> Assume it's already an audio file (mp3/wav)
171
- const isArrayBuffer =
172
- source instanceof ArrayBuffer ||
173
- (source &&
174
- source.constructor &&
175
- source.constructor.name === 'ArrayBuffer');
176
- const isView =
177
- ArrayBuffer.isView(source) ||
178
- (source &&
179
- source.buffer &&
180
- (source.buffer instanceof ArrayBuffer ||
181
- source.buffer.constructor.name === 'ArrayBuffer'));
182
-
183
- if (isArrayBuffer || isView) {
184
- const buffer = isArrayBuffer ? source : source.buffer;
185
- return {
186
- inlineData: {
187
- data: this.arrayBufferToBase64(buffer),
188
- mimeType: 'audio/wav', // Fallback assumption
189
- },
190
- };
191
- }
192
-
193
- throw new DOMException('Unsupported audio source', 'NotSupportedError');
194
- }
195
-
196
- // Low Level Converters
197
-
198
- static blobToInlineData(blob) {
199
- return new Promise((resolve, reject) => {
200
- const reader = new FileReader();
201
- reader.onloadend = () => {
202
- if (reader.error) {
203
- reject(reader.error);
204
- } else {
205
- resolve({
206
- inlineData: {
207
- data: reader.result.split(',')[1],
208
- mimeType: blob.type,
209
- },
210
- });
211
- }
212
- };
213
- reader.readAsDataURL(blob);
214
- });
215
- }
216
-
217
- static async canvasSourceToInlineData(source) {
218
- if (
219
- typeof HTMLImageElement !== 'undefined' &&
220
- source instanceof HTMLImageElement &&
221
- !source.complete
222
- ) {
223
- await source.decode().catch(() => {});
224
- }
225
-
226
- const getDimension = (name) => {
227
- const val = source[name];
228
- if (typeof val === 'object' && val !== null && 'baseVal' in val) {
229
- return val.baseVal.value;
230
- }
231
- return typeof val === 'number' ? val : 0;
232
- };
233
-
234
- let w =
235
- source.displayWidth ||
236
- source.naturalWidth ||
237
- source.videoWidth ||
238
- getDimension('width');
239
- let h =
240
- source.displayHeight ||
241
- source.naturalHeight ||
242
- source.videoHeight ||
243
- getDimension('height');
244
-
245
- // Fallback for SVG elements (like SVGImageElement in DOM)
246
- if ((!w || !h) && typeof source.getBBox === 'function') {
247
- try {
248
- const box = source.getBBox();
249
- w = w || box.width;
250
- h = h || box.height;
251
- } catch (e) {
252
- // SVG might not be in DOM or not ready
253
- }
254
- }
255
-
256
- // Last resort fallback for any element in the DOM
257
- if ((!w || !h) && typeof source.getBoundingClientRect === 'function') {
258
- try {
259
- const rect = source.getBoundingClientRect();
260
- w = w || rect.width;
261
- h = h || rect.height;
262
- } catch (e) {}
263
- }
264
-
265
- if (!w || !h) {
266
- throw new DOMException('Invalid image dimensions', 'InvalidStateError');
267
- }
268
-
269
- const canvas = document.createElement('canvas');
270
- canvas.width = w;
271
- canvas.height = h;
272
-
273
- const ctx = canvas.getContext('2d');
274
- if (typeof ImageData !== 'undefined' && source instanceof ImageData) {
275
- ctx.putImageData(source, 0, 0);
276
- } else {
277
- ctx.drawImage(source, 0, 0);
278
- }
279
-
280
- const dataUrl = canvas.toDataURL('image/png');
281
- return {
282
- inlineData: {
283
- data: dataUrl.split(',')[1],
284
- mimeType: 'image/png',
285
- },
286
- };
287
- }
288
-
289
- static arrayBufferToBase64(buffer) {
290
- let binary = '';
291
- const bytes = new Uint8Array(buffer);
292
- const len = bytes.byteLength;
293
- for (let i = 0; i < len; i++) {
294
- binary += String.fromCharCode(bytes[i]);
295
- }
296
- return window.btoa(binary);
297
- }
298
-
299
- // Simple WAV Encoder for AudioBuffer
300
- static audioBufferToWav(buffer) {
301
- const numChannels = buffer.numberOfChannels;
302
- const sampleRate = buffer.sampleRate;
303
- const format = 1; // PCM
304
- const bitDepth = 16;
305
-
306
- let result;
307
- if (numChannels === 2) {
308
- result = this.interleave(
309
- buffer.getChannelData(0),
310
- buffer.getChannelData(1)
311
- );
312
- } else {
313
- result = buffer.getChannelData(0);
314
- }
315
-
316
- return this.encodeWAV(result, format, sampleRate, numChannels, bitDepth);
317
- }
318
-
319
- static interleave(inputL, inputR) {
320
- const length = inputL.length + inputR.length;
321
- const result = new Float32Array(length);
322
- let index = 0;
323
- let inputIndex = 0;
324
- while (index < length) {
325
- result[index++] = inputL[inputIndex];
326
- result[index++] = inputR[inputIndex];
327
- inputIndex++;
328
- }
329
- return result;
330
- }
331
-
332
- static encodeWAV(samples, format, sampleRate, numChannels, bitDepth) {
333
- const bytesPerSample = bitDepth / 8;
334
- const blockAlign = numChannels * bytesPerSample;
335
-
336
- const buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
337
- const view = new DataView(buffer);
338
-
339
- /* RIFF identifier */
340
- this.writeString(view, 0, 'RIFF');
341
- /* RIFF chunk length */
342
- view.setUint32(4, 36 + samples.length * bytesPerSample, true);
343
- /* RIFF type */
344
- this.writeString(view, 8, 'WAVE');
345
- /* format chunk identifier */
346
- this.writeString(view, 12, 'fmt ');
347
- /* format chunk length */
348
- view.setUint32(16, 16, true);
349
- /* sample format (raw) */
350
- view.setUint16(20, format, true);
351
- /* channel count */
352
- view.setUint16(22, numChannels, true);
353
- /* sample rate */
354
- view.setUint32(24, sampleRate, true);
355
- /* byte rate (sample rate * block align) */
356
- view.setUint32(28, sampleRate * blockAlign, true);
357
- /* block align (channel count * bytes per sample) */
358
- view.setUint16(32, blockAlign, true);
359
- /* bits per sample */
360
- view.setUint16(34, bitDepth, true);
361
- /* data chunk identifier */
362
- this.writeString(view, 36, 'data');
363
- /* data chunk length */
364
- view.setUint32(40, samples.length * bytesPerSample, true);
365
-
366
- this.floatTo16BitPCM(view, 44, samples);
367
-
368
- return buffer;
369
- }
370
-
371
- static floatTo16BitPCM(output, offset, input) {
372
- for (let i = 0; i < input.length; i++, offset += 2) {
373
- const s = Math.max(-1, Math.min(1, input[i]));
374
- output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
375
- }
376
- }
377
-
378
- static writeString(view, offset, string) {
379
- for (let i = 0; i < string.length; i++) {
380
- view.setUint8(offset + i, string.charCodeAt(i));
381
- }
382
- }
383
- }