prompt-api-polyfill 0.1.0 → 0.2.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/README.md +143 -66
- package/json-schema-converter.js +3 -1
- package/multimodal-converter.js +138 -12
- package/package.json +19 -4
- package/prompt-api-polyfill.js +478 -444
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
# Prompt API Polyfill
|
|
1
|
+
# Prompt API Polyfill
|
|
2
2
|
|
|
3
3
|
This package provides a browser polyfill for the
|
|
4
|
-
[Prompt API `LanguageModel`](https://github.com/webmachinelearning/prompt-api)
|
|
5
|
-
|
|
4
|
+
[Prompt API `LanguageModel`](https://github.com/webmachinelearning/prompt-api),
|
|
5
|
+
supporting dynamic backends:
|
|
6
|
+
|
|
7
|
+
- **Firebase AI Logic**
|
|
8
|
+
- **Google Gemini API**
|
|
9
|
+
- **OpenAI API**
|
|
6
10
|
|
|
7
11
|
When loaded in the browser, it defines a global:
|
|
8
12
|
|
|
@@ -13,8 +17,28 @@ window.LanguageModel;
|
|
|
13
17
|
so you can use the Prompt API shape even in environments where it is not yet
|
|
14
18
|
natively available.
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
## Supported Backends
|
|
21
|
+
|
|
22
|
+
### Firebase AI Logic
|
|
23
|
+
|
|
24
|
+
- **Uses**: `firebase/ai` SDK.
|
|
25
|
+
- **Select by setting**: `window.FIREBASE_CONFIG`.
|
|
26
|
+
- **Model**: Uses default if not specified (see
|
|
27
|
+
[`backends/defaults.js`](backends/defaults.js)).
|
|
28
|
+
|
|
29
|
+
### Google Gemini API
|
|
30
|
+
|
|
31
|
+
- **Uses**: `@google/generative-ai` SDK.
|
|
32
|
+
- **Select by setting**: `window.GEMINI_CONFIG`.
|
|
33
|
+
- **Model**: Uses default if not specified (see
|
|
34
|
+
[`backends/defaults.js`](backends/defaults.js)).
|
|
35
|
+
|
|
36
|
+
### OpenAI API
|
|
37
|
+
|
|
38
|
+
- **Uses**: `openai` SDK.
|
|
39
|
+
- **Select by setting**: `window.OPENAI_CONFIG`.
|
|
40
|
+
- **Model**: Uses default if not specified (see
|
|
41
|
+
[`backends/defaults.js`](backends/defaults.js)).
|
|
18
42
|
|
|
19
43
|
---
|
|
20
44
|
|
|
@@ -28,37 +52,78 @@ npm install prompt-api-polyfill
|
|
|
28
52
|
|
|
29
53
|
## Quick start
|
|
30
54
|
|
|
31
|
-
|
|
32
|
-
below).
|
|
33
|
-
2. **Provide your Firebase config** on `window.FIREBASE_CONFIG`.
|
|
34
|
-
3. **Import the polyfill** so it can attach `window.LanguageModel`.
|
|
55
|
+
### Backed by Firebase
|
|
35
56
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[Configuring `dot_env.json` / `.env.json`](#configuring-dot_envjson--envjson))
|
|
40
|
-
and then use it from a browser entry point:
|
|
57
|
+
1. **Create a Firebase project with Generative AI enabled**.
|
|
58
|
+
2. **Provide your Firebase config** on `window.FIREBASE_CONFIG`.
|
|
59
|
+
3. **Import the polyfill**.
|
|
41
60
|
|
|
42
61
|
```html
|
|
43
62
|
<script type="module">
|
|
44
63
|
import firebaseConfig from './.env.json' with { type: 'json' };
|
|
45
64
|
|
|
46
|
-
//
|
|
65
|
+
// Set FIREBASE_CONFIG to select the Firebase backend
|
|
47
66
|
window.FIREBASE_CONFIG = firebaseConfig;
|
|
48
67
|
|
|
49
|
-
// Only load the polyfill if LanguageModel is not available natively
|
|
50
68
|
if (!('LanguageModel' in window)) {
|
|
51
69
|
await import('prompt-api-polyfill');
|
|
52
70
|
}
|
|
53
71
|
|
|
54
72
|
const session = await LanguageModel.create();
|
|
55
|
-
const text = await session.prompt('Say hello from the polyfill!');
|
|
56
|
-
console.log(text);
|
|
57
73
|
</script>
|
|
58
74
|
```
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
### Backed by Gemini API
|
|
77
|
+
|
|
78
|
+
1. **Get a Gemini API Key** from
|
|
79
|
+
[Google AI Studio](https://aistudio.google.com/).
|
|
80
|
+
2. **Provide your API Key** on `window.GEMINI_CONFIG`.
|
|
81
|
+
3. **Import the polyfill**.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<script type="module">
|
|
85
|
+
// NOTE: Do not expose real keys in production source code!
|
|
86
|
+
// Set GEMINI_CONFIG to select the Gemini backend
|
|
87
|
+
window.GEMINI_CONFIG = { apiKey: 'YOUR_GEMINI_API_KEY' };
|
|
88
|
+
|
|
89
|
+
if (!('LanguageModel' in window)) {
|
|
90
|
+
await import('prompt-api-polyfill');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const session = await LanguageModel.create();
|
|
94
|
+
</script>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Backed by OpenAI API
|
|
98
|
+
|
|
99
|
+
1. **Get an OpenAI API Key** from the
|
|
100
|
+
[OpenAI Platform](https://platform.openai.com/).
|
|
101
|
+
2. **Provide your API Key** on `window.OPENAI_CONFIG`.
|
|
102
|
+
3. **Import the polyfill**.
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<script type="module">
|
|
106
|
+
// NOTE: Do not expose real keys in production source code!
|
|
107
|
+
// Set OPENAI_CONFIG to select the OpenAI backend
|
|
108
|
+
window.OPENAI_CONFIG = { apiKey: 'YOUR_OPENAI_API_KEY' };
|
|
109
|
+
|
|
110
|
+
if (!('LanguageModel' in window)) {
|
|
111
|
+
await import('prompt-api-polyfill');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const session = await LanguageModel.create();
|
|
115
|
+
</script>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
### Example (using a JSON config file)
|
|
123
|
+
|
|
124
|
+
Create a `.env.json` file (see
|
|
125
|
+
[Configuring `dot_env.json` / `.env.json`](#configuring-dot_envjson--envjson))
|
|
126
|
+
and then use it from a browser entry point.
|
|
62
127
|
|
|
63
128
|
### Example based on `index.html` in this repo
|
|
64
129
|
|
|
@@ -76,8 +141,8 @@ A simplified version of how it is wired up:
|
|
|
76
141
|
|
|
77
142
|
```html
|
|
78
143
|
<script type="module">
|
|
79
|
-
|
|
80
|
-
window.
|
|
144
|
+
// Set GEMINI_CONFIG to select the Gemini backend
|
|
145
|
+
window.GEMINI_CONFIG = { apiKey: 'YOUR_GEMINI_API_KEY' };
|
|
81
146
|
|
|
82
147
|
// Load the polyfill only when necessary
|
|
83
148
|
if (!('LanguageModel' in window)) {
|
|
@@ -110,17 +175,20 @@ This repo ships with a template file:
|
|
|
110
175
|
```jsonc
|
|
111
176
|
// dot_env.json
|
|
112
177
|
{
|
|
113
|
-
|
|
178
|
+
// For Firebase:
|
|
114
179
|
"projectId": "",
|
|
115
180
|
"appId": "",
|
|
116
181
|
"modelName": "",
|
|
182
|
+
|
|
183
|
+
// For Firebase OR Gemini OR OpenAI:
|
|
184
|
+
"apiKey": "",
|
|
117
185
|
}
|
|
118
186
|
```
|
|
119
187
|
|
|
120
188
|
You should treat `dot_env.json` as a **template** and create a real `.env.json`
|
|
121
189
|
that is **not committed** with your secrets.
|
|
122
190
|
|
|
123
|
-
###
|
|
191
|
+
### Create `.env.json`
|
|
124
192
|
|
|
125
193
|
Copy the template:
|
|
126
194
|
|
|
@@ -128,63 +196,56 @@ Copy the template:
|
|
|
128
196
|
cp dot_env.json .env.json
|
|
129
197
|
```
|
|
130
198
|
|
|
131
|
-
Then open `.env.json` and fill in the values
|
|
199
|
+
Then open `.env.json` and fill in the values.
|
|
200
|
+
|
|
201
|
+
**For Firebase:**
|
|
132
202
|
|
|
133
203
|
```json
|
|
134
204
|
{
|
|
135
205
|
"apiKey": "YOUR_FIREBASE_WEB_API_KEY",
|
|
136
206
|
"projectId": "your-gcp-project-id",
|
|
137
207
|
"appId": "YOUR_FIREBASE_APP_ID",
|
|
138
|
-
"modelName": "
|
|
208
|
+
"modelName": "choose-model-for-firebase"
|
|
139
209
|
}
|
|
140
210
|
```
|
|
141
211
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
- `apiKey` Your **Firebase Web API key**. You can find this in the Firebase
|
|
145
|
-
Console under: _Project settings → General → Your apps → Web app_.
|
|
212
|
+
**For Gemini:**
|
|
146
213
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"apiKey": "YOUR_GEMINI_CONFIG",
|
|
217
|
+
"modelName": "choose-model-for-gemini"
|
|
218
|
+
}
|
|
219
|
+
```
|
|
153
220
|
|
|
154
|
-
|
|
155
|
-
"modelName": "gemini-2.5-flash-lite"
|
|
156
|
-
```
|
|
221
|
+
**For OpenAI:**
|
|
157
222
|
|
|
158
|
-
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"apiKey": "YOUR_OPENAI_API_KEY",
|
|
226
|
+
"modelName": "choose-model-for-openai"
|
|
227
|
+
}
|
|
228
|
+
```
|
|
159
229
|
|
|
160
|
-
|
|
230
|
+
### Field-by-field explanation
|
|
161
231
|
|
|
162
|
-
- `
|
|
163
|
-
-
|
|
232
|
+
- `apiKey`:
|
|
233
|
+
- **Firebase**: Your Firebase Web API key.
|
|
234
|
+
- **Gemini**: Your Gemini API Key.
|
|
235
|
+
- **OpenAI**: Your OpenAI API Key.
|
|
236
|
+
- `projectId` / `appId`: **Firebase only**.
|
|
164
237
|
|
|
165
|
-
|
|
238
|
+
- `modelName` (optional): The model ID to use. If not provided, the polyfill
|
|
239
|
+
uses the defaults defined in [`backends/defaults.js`](backends/defaults.js).
|
|
166
240
|
|
|
167
241
|
> **Important:** Do **not** commit a real `.env.json` with production
|
|
168
242
|
> credentials to source control. Use `dot_env.json` as the committed template
|
|
169
243
|
> and keep `.env.json` local.
|
|
170
244
|
|
|
171
|
-
###
|
|
245
|
+
### Wiring the config into the polyfill
|
|
172
246
|
|
|
173
|
-
Once `.env.json` is filled out, you can import it and expose it to the polyfill
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
import firebaseConfig from './.env.json' with { type: 'json' };
|
|
178
|
-
|
|
179
|
-
window.FIREBASE_CONFIG = firebaseConfig;
|
|
180
|
-
|
|
181
|
-
if (!('LanguageModel' in window)) {
|
|
182
|
-
await import('prompt-api-polyfill');
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
From this point on, `LanguageModel.create()` will use your Firebase
|
|
187
|
-
configuration.
|
|
247
|
+
Once `.env.json` is filled out, you can import it and expose it to the polyfill.
|
|
248
|
+
See the [Quick start](#quick-start) examples above.
|
|
188
249
|
|
|
189
250
|
---
|
|
190
251
|
|
|
@@ -200,8 +261,7 @@ For a complete, end-to-end example, see the `index.html` file in this directory.
|
|
|
200
261
|
|
|
201
262
|
## Running the demo locally
|
|
202
263
|
|
|
203
|
-
1. Install dependencies
|
|
204
|
-
another project):
|
|
264
|
+
1. Install dependencies:
|
|
205
265
|
|
|
206
266
|
```bash
|
|
207
267
|
npm install
|
|
@@ -211,17 +271,34 @@ For a complete, end-to-end example, see the `index.html` file in this directory.
|
|
|
211
271
|
|
|
212
272
|
```bash
|
|
213
273
|
cp dot_env.json .env.json
|
|
214
|
-
# then edit .env.json with your Firebase and model settings
|
|
215
274
|
```
|
|
216
275
|
|
|
217
276
|
3. Serve `index.html`:
|
|
218
|
-
|
|
219
277
|
```bash
|
|
220
278
|
npm start
|
|
221
279
|
```
|
|
222
280
|
|
|
223
|
-
You should see network requests to the
|
|
224
|
-
|
|
281
|
+
You should see network requests to the backends logs.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Testing
|
|
286
|
+
|
|
287
|
+
The project includes a comprehensive test suite that runs in a headless browser.
|
|
288
|
+
|
|
289
|
+
### Running Browser Tests
|
|
290
|
+
|
|
291
|
+
Uses `playwright` to run tests in a real Chromium instance. This is the
|
|
292
|
+
recommended way to verify environmental fidelity and multimodal support.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npm run test:browser
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
To see the browser and DevTools while testing, you can modify
|
|
299
|
+
`vitest.browser.config.js` to set `headless: false`.
|
|
300
|
+
|
|
301
|
+
---
|
|
225
302
|
|
|
226
303
|
## License
|
|
227
304
|
|
package/json-schema-converter.js
CHANGED
|
@@ -6,7 +6,9 @@ import { Schema } from 'https://esm.run/firebase/ai';
|
|
|
6
6
|
* @returns {Schema} - The Firebase Vertex AI Schema instance.
|
|
7
7
|
*/
|
|
8
8
|
export function convertJsonSchemaToVertexSchema(jsonSchema) {
|
|
9
|
-
if (!jsonSchema)
|
|
9
|
+
if (!jsonSchema) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
10
12
|
|
|
11
13
|
// Extract common base parameters supported by all Schema types
|
|
12
14
|
const baseParams = {
|
package/multimodal-converter.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export default class MultimodalConverter {
|
|
2
2
|
static async convert(type, value) {
|
|
3
|
-
if (type === 'image')
|
|
4
|
-
|
|
3
|
+
if (type === 'image') {
|
|
4
|
+
return this.processImage(value);
|
|
5
|
+
}
|
|
6
|
+
if (type === 'audio') {
|
|
7
|
+
return this.processAudio(value);
|
|
8
|
+
}
|
|
5
9
|
throw new DOMException(
|
|
6
10
|
`Unsupported media type: ${type}`,
|
|
7
11
|
'NotSupportedError'
|
|
@@ -16,13 +20,16 @@ export default class MultimodalConverter {
|
|
|
16
20
|
|
|
17
21
|
// BufferSource (ArrayBuffer/View) -> Sniff or Default
|
|
18
22
|
if (ArrayBuffer.isView(source) || source instanceof ArrayBuffer) {
|
|
19
|
-
const
|
|
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
|
+
);
|
|
20
31
|
const base64 = this.arrayBufferToBase64(buffer);
|
|
21
|
-
|
|
22
|
-
const u8 = new Uint8Array(buffer);
|
|
23
|
-
let mimeType = 'image/png'; // Default
|
|
24
|
-
if (u8[0] === 0xff && u8[1] === 0xd8) mimeType = 'image/jpeg';
|
|
25
|
-
else if (u8[0] === 0x89 && u8[1] === 0x50) mimeType = 'image/png';
|
|
32
|
+
const mimeType = this.#sniffImageMimeType(u8) || 'image/png';
|
|
26
33
|
|
|
27
34
|
return { inlineData: { data: base64, mimeType } };
|
|
28
35
|
}
|
|
@@ -32,6 +39,111 @@ export default class MultimodalConverter {
|
|
|
32
39
|
return this.canvasSourceToInlineData(source);
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
static #sniffImageMimeType(u8) {
|
|
43
|
+
const len = u8.length;
|
|
44
|
+
if (len < 4) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// JPEG: FF D8 FF
|
|
49
|
+
if (u8[0] === 0xff && u8[1] === 0xd8 && u8[2] === 0xff) {
|
|
50
|
+
return 'image/jpeg';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
54
|
+
if (
|
|
55
|
+
u8[0] === 0x89 &&
|
|
56
|
+
u8[1] === 0x50 &&
|
|
57
|
+
u8[2] === 0x4e &&
|
|
58
|
+
u8[3] === 0x47 &&
|
|
59
|
+
u8[4] === 0x0d &&
|
|
60
|
+
u8[5] === 0x0a &&
|
|
61
|
+
u8[6] === 0x1a &&
|
|
62
|
+
u8[7] === 0x0a
|
|
63
|
+
) {
|
|
64
|
+
return 'image/png';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// GIF: GIF87a / GIF89a
|
|
68
|
+
if (u8[0] === 0x47 && u8[1] === 0x49 && u8[2] === 0x46 && u8[3] === 0x38) {
|
|
69
|
+
return 'image/gif';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// WebP: RIFF (offset 0) + WEBP (offset 8)
|
|
73
|
+
if (
|
|
74
|
+
u8[0] === 0x52 &&
|
|
75
|
+
u8[1] === 0x49 &&
|
|
76
|
+
u8[2] === 0x46 &&
|
|
77
|
+
u8[3] === 0x46 &&
|
|
78
|
+
u8[8] === 0x57 &&
|
|
79
|
+
u8[9] === 0x45 &&
|
|
80
|
+
u8[10] === 0x42 &&
|
|
81
|
+
u8[11] === 0x50
|
|
82
|
+
) {
|
|
83
|
+
return 'image/webp';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// BMP: BM
|
|
87
|
+
if (u8[0] === 0x42 && u8[1] === 0x4d) {
|
|
88
|
+
return 'image/bmp';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ICO: 00 00 01 00
|
|
92
|
+
if (u8[0] === 0x00 && u8[1] === 0x00 && u8[2] === 0x01 && u8[3] === 0x00) {
|
|
93
|
+
return 'image/x-icon';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// TIFF: II* (LE) / MM* (BE)
|
|
97
|
+
if (
|
|
98
|
+
(u8[0] === 0x49 && u8[1] === 0x49 && u8[2] === 0x2a) ||
|
|
99
|
+
(u8[0] === 0x4d && u8[1] === 0x4d && u8[2] === 0x2a)
|
|
100
|
+
) {
|
|
101
|
+
return 'image/tiff';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ISOBMFF (AVIF / HEIC / HEIF)
|
|
105
|
+
// "ftyp" at offset 4
|
|
106
|
+
if (u8[4] === 0x66 && u8[5] === 0x74 && u8[6] === 0x79 && u8[7] === 0x70) {
|
|
107
|
+
const type = String.fromCharCode(u8[8], u8[9], u8[10], u8[11]);
|
|
108
|
+
if (type === 'avif' || type === 'avis') {
|
|
109
|
+
return 'image/avif';
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
type === 'heic' ||
|
|
113
|
+
type === 'heix' ||
|
|
114
|
+
type === 'hevc' ||
|
|
115
|
+
type === 'hevx'
|
|
116
|
+
) {
|
|
117
|
+
return 'image/heic';
|
|
118
|
+
}
|
|
119
|
+
if (type === 'mif1' || type === 'msf1') {
|
|
120
|
+
return 'image/heif';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// JPEG XL: FF 0A or container bits
|
|
125
|
+
if (u8[0] === 0xff && u8[1] === 0x0a) {
|
|
126
|
+
return 'image/jxl';
|
|
127
|
+
}
|
|
128
|
+
// Container: 00 00 00 0c 4a 58 4c 20 0d 0a 87 0a (JXL )
|
|
129
|
+
if (u8[0] === 0x00 && u8[4] === 0x4a && u8[5] === 0x58 && u8[6] === 0x4c) {
|
|
130
|
+
return 'image/jxl';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// JPEG 2000
|
|
134
|
+
if (u8[0] === 0x00 && u8[4] === 0x6a && u8[5] === 0x50 && u8[6] === 0x20) {
|
|
135
|
+
return 'image/jp2';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// SVG: Check for <svg or <?xml (heuristics)
|
|
139
|
+
const preview = String.fromCharCode(...u8.slice(0, 100)).toLowerCase();
|
|
140
|
+
if (preview.includes('<svg') || preview.includes('<?xml')) {
|
|
141
|
+
return 'image/svg+xml';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
35
147
|
static async processAudio(source) {
|
|
36
148
|
// Blob
|
|
37
149
|
if (source instanceof Blob) {
|
|
@@ -46,8 +158,20 @@ export default class MultimodalConverter {
|
|
|
46
158
|
}
|
|
47
159
|
|
|
48
160
|
// BufferSource -> Assume it's already an audio file (mp3/wav)
|
|
49
|
-
|
|
50
|
-
|
|
161
|
+
const isArrayBuffer =
|
|
162
|
+
source instanceof ArrayBuffer ||
|
|
163
|
+
(source &&
|
|
164
|
+
source.constructor &&
|
|
165
|
+
source.constructor.name === 'ArrayBuffer');
|
|
166
|
+
const isView =
|
|
167
|
+
ArrayBuffer.isView(source) ||
|
|
168
|
+
(source &&
|
|
169
|
+
source.buffer &&
|
|
170
|
+
(source.buffer instanceof ArrayBuffer ||
|
|
171
|
+
source.buffer.constructor.name === 'ArrayBuffer'));
|
|
172
|
+
|
|
173
|
+
if (isArrayBuffer || isView) {
|
|
174
|
+
const buffer = isArrayBuffer ? source : source.buffer;
|
|
51
175
|
return {
|
|
52
176
|
inlineData: {
|
|
53
177
|
data: this.arrayBufferToBase64(buffer),
|
|
@@ -65,14 +189,16 @@ export default class MultimodalConverter {
|
|
|
65
189
|
return new Promise((resolve, reject) => {
|
|
66
190
|
const reader = new FileReader();
|
|
67
191
|
reader.onloadend = () => {
|
|
68
|
-
if (reader.error)
|
|
69
|
-
|
|
192
|
+
if (reader.error) {
|
|
193
|
+
reject(reader.error);
|
|
194
|
+
} else {
|
|
70
195
|
resolve({
|
|
71
196
|
inlineData: {
|
|
72
197
|
data: reader.result.split(',')[1],
|
|
73
198
|
mimeType: blob.type,
|
|
74
199
|
},
|
|
75
200
|
});
|
|
201
|
+
}
|
|
76
202
|
};
|
|
77
203
|
reader.readAsDataURL(blob);
|
|
78
204
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-api-polyfill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Polyfill for the Prompt API (`LanguageModel`) backed by Firebase AI Logic.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Polyfill for the Prompt API (`LanguageModel`) backed by Firebase AI Logic, Gemini API, or OpenAI API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./prompt-api-polyfill.js",
|
|
7
7
|
"module": "./prompt-api-polyfill.js",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"language-model",
|
|
23
23
|
"polyfill",
|
|
24
24
|
"firebase",
|
|
25
|
+
"gemini",
|
|
26
|
+
"openai",
|
|
25
27
|
"web-ai"
|
|
26
28
|
],
|
|
27
29
|
"repository": {
|
|
@@ -35,9 +37,22 @@
|
|
|
35
37
|
"homepage": "https://github.com/GoogleChromeLabs/web-ai-demos/tree/main/prompt-api-polyfill/README.md",
|
|
36
38
|
"license": "Apache-2.0",
|
|
37
39
|
"scripts": {
|
|
38
|
-
"start": "npx http-server"
|
|
40
|
+
"start": "npx http-server",
|
|
41
|
+
"test:browser": "node scripts/list-backends.js && vitest run -c vitest.browser.config.js .browser.test.js",
|
|
42
|
+
"fix": "npx prettier --write ."
|
|
39
43
|
},
|
|
40
44
|
"devDependencies": {
|
|
41
|
-
"
|
|
45
|
+
"@firebase/ai": "^2.6.1",
|
|
46
|
+
"@google/generative-ai": "^0.24.1",
|
|
47
|
+
"@vitest/browser": "^4.0.17",
|
|
48
|
+
"@vitest/browser-playwright": "^4.0.17",
|
|
49
|
+
"ajv": "^8.17.1",
|
|
50
|
+
"firebase": "^12.7.0",
|
|
51
|
+
"http-server": "^14.1.1",
|
|
52
|
+
"jsdom": "^27.4.0",
|
|
53
|
+
"openai": "^6.16.0",
|
|
54
|
+
"playwright": "^1.57.0",
|
|
55
|
+
"prettier-plugin-curly": "^0.4.1",
|
|
56
|
+
"vitest": "^4.0.17"
|
|
42
57
|
}
|
|
43
58
|
}
|