thebird 1.2.15 → 1.2.17
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 +7 -0
- package/deno.json +6 -0
- package/docs/app.js +14 -23
- package/package.json +1 -1
- package/wasi/cli.ts +47 -0
package/CHANGELOG.md
ADDED
package/deno.json
ADDED
package/docs/app.js
CHANGED
|
@@ -14,7 +14,7 @@ async function fetchModels(apiKey) {
|
|
|
14
14
|
.map(m => ({ id: m.name.replace('models/', ''), label: m.displayName || m.name }));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
async function
|
|
17
|
+
async function streamGenerate(apiKey, model, contents, onChunk) {
|
|
18
18
|
const res = await fetch(`${BASE}/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`, {
|
|
19
19
|
method: 'POST',
|
|
20
20
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -38,7 +38,7 @@ async function* streamGenerate(apiKey, model, contents) {
|
|
|
38
38
|
const chunk = JSON.parse(json);
|
|
39
39
|
for (const c of (chunk.candidates || []))
|
|
40
40
|
for (const p of (c.content?.parts || []))
|
|
41
|
-
if (p.text && !p.thought)
|
|
41
|
+
if (p.text && !p.thought) onChunk(p.text);
|
|
42
42
|
} catch {}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -84,14 +84,7 @@ class BirdChat extends HTMLElement {
|
|
|
84
84
|
if (this.state.apiKey) this.loadModels(this.state.apiKey);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
setState(patch) {
|
|
88
|
-
Object.assign(this.state, patch);
|
|
89
|
-
this.render();
|
|
90
|
-
if (this.streamWrap && !this.querySelector('#msg-list')?.contains(this.streamWrap)) {
|
|
91
|
-
const list = this.querySelector('#msg-list');
|
|
92
|
-
if (list) list.appendChild(this.streamWrap);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
87
|
+
setState(patch) { Object.assign(this.state, patch); this.render(); }
|
|
95
88
|
|
|
96
89
|
async loadModels(apiKey) {
|
|
97
90
|
this.setState({ modelsLoading: true, status: '' });
|
|
@@ -163,26 +156,24 @@ class BirdChat extends HTMLElement {
|
|
|
163
156
|
this.setState({ messages, streaming: true, status: '', streamingText: '' });
|
|
164
157
|
try {
|
|
165
158
|
let full = '';
|
|
166
|
-
|
|
167
|
-
|
|
159
|
+
const streamEl = document.createElement('div');
|
|
160
|
+
streamEl.className = 'msg-bubble card bg-base-200 text-base-content px-4 py-3 text-sm leading-relaxed';
|
|
168
161
|
const cursor = document.createElement('span');
|
|
169
162
|
cursor.className = 'animate-pulse ml-1';
|
|
170
163
|
cursor.textContent = '▋';
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
164
|
+
const wrap = document.createElement('div');
|
|
165
|
+
wrap.className = 'flex justify-start';
|
|
166
|
+
wrap.appendChild(streamEl);
|
|
167
|
+
wrap.appendChild(cursor);
|
|
175
168
|
const list = this.querySelector('#msg-list');
|
|
176
|
-
if (list) list.appendChild(
|
|
177
|
-
|
|
169
|
+
if (list) list.appendChild(wrap);
|
|
170
|
+
await streamGenerate(apiKey, model, convertMessages(messages), chunk => {
|
|
178
171
|
full += chunk;
|
|
179
|
-
|
|
172
|
+
streamEl.textContent = full;
|
|
180
173
|
const l = this.querySelector('#msg-list');
|
|
181
174
|
if (l) l.scrollTop = l.scrollHeight;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.streamEl = null;
|
|
185
|
-
this.streamWrap = null;
|
|
175
|
+
});
|
|
176
|
+
wrap.remove();
|
|
186
177
|
this.setState({ messages: [...messages, { role: 'assistant', content: full || '(empty)' }], streaming: false, streamingText: '' });
|
|
187
178
|
const l2 = this.querySelector('#msg-list');
|
|
188
179
|
if (l2) l2.scrollTop = l2.scrollHeight;
|
package/package.json
CHANGED
package/wasi/cli.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const BASE = 'https://generativelanguage.googleapis.com/v1beta';
|
|
2
|
+
|
|
3
|
+
const args = Deno.args.slice();
|
|
4
|
+
const modelIdx = args.indexOf('--model');
|
|
5
|
+
const model = modelIdx >= 0 ? args.splice(modelIdx, 2)[1] : 'gemini-2.5-flash';
|
|
6
|
+
const sysIdx = args.indexOf('--system');
|
|
7
|
+
const system = sysIdx >= 0 ? args.splice(sysIdx, 2)[1] : undefined;
|
|
8
|
+
const prompt = args.join(' ').trim();
|
|
9
|
+
|
|
10
|
+
const apiKey = Deno.env.get('GEMINI_API_KEY');
|
|
11
|
+
if (!apiKey) throw new Error('GEMINI_API_KEY env var required');
|
|
12
|
+
if (!prompt) throw new Error('Usage: deno run --allow-net --allow-env wasi/cli.ts [--model MODEL] [--system SYSTEM] <prompt>');
|
|
13
|
+
|
|
14
|
+
const contents = [{ role: 'user', parts: [{ text: prompt }] }];
|
|
15
|
+
const body = { contents, generationConfig: { maxOutputTokens: 8192, temperature: 0.7 } };
|
|
16
|
+
if (system) body.systemInstruction = { parts: [{ text: system }] };
|
|
17
|
+
|
|
18
|
+
const res = await fetch(BASE + '/models/' + model + ':streamGenerateContent?alt=sse&key=' + apiKey, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify(body),
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) throw new Error('Gemini API ' + res.status + ': ' + await res.text());
|
|
24
|
+
|
|
25
|
+
const reader = res.body.getReader();
|
|
26
|
+
const dec = new TextDecoder();
|
|
27
|
+
const enc = new TextEncoder();
|
|
28
|
+
let buf = '';
|
|
29
|
+
while (true) {
|
|
30
|
+
const { done, value } = await reader.read();
|
|
31
|
+
if (done) break;
|
|
32
|
+
buf += dec.decode(value, { stream: true });
|
|
33
|
+
const lines = buf.split('\n');
|
|
34
|
+
buf = lines.pop();
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
if (!line.startsWith('data: ')) continue;
|
|
37
|
+
const json = line.slice(6).trim();
|
|
38
|
+
if (!json || json === '[DONE]') continue;
|
|
39
|
+
try {
|
|
40
|
+
const chunk = JSON.parse(json);
|
|
41
|
+
for (const c of (chunk.candidates || []))
|
|
42
|
+
for (const p of (c.content?.parts || []))
|
|
43
|
+
if (p.text && !p.thought) await Deno.stdout.write(enc.encode(p.text));
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
await Deno.stdout.write(enc.encode('\n'));
|