All checks were successful
Beta Release / beta (push) Successful in 1m25s
- Remove Firefox build support (CI, Makefile, wxt config) - Fix chrome.alarms undefined error (add 'alarms' permission) - Add Chat tab to side panel connected to Studio API (/api/chat) - Streaming SSE, tool calls, code blocks, thinking display - Shared chat history with desktop Studio - New lib/api.js client for extension chat endpoints 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
78 lines
2.4 KiB
JavaScript
78 lines
2.4 KiB
JavaScript
import { getServerUrl } from './config';
|
|
|
|
async function request(path, options = {}) {
|
|
const base = await getServerUrl();
|
|
const res = await fetch(`${base}/api${path}`, {
|
|
...options,
|
|
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
throw new Error(err.error || res.statusText);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function getChatHistory() {
|
|
return request('/chat/history');
|
|
}
|
|
|
|
export async function clearChat() {
|
|
return request('/chat/clear', { method: 'POST' });
|
|
}
|
|
|
|
export async function summarizeChat() {
|
|
return request('/chat/summarize', { method: 'POST' });
|
|
}
|
|
|
|
export async function sendChat(message, stream = true, onChunk, signal) {
|
|
const base = await getServerUrl();
|
|
|
|
if (!stream) {
|
|
return request('/chat', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ message, stream: false }),
|
|
});
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
fetch(`${base}/api/chat`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ message, stream: true }),
|
|
signal,
|
|
}).then(async (res) => {
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
reject(new Error(err.error || res.statusText));
|
|
return;
|
|
}
|
|
const reader = res.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let full = '';
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
const text = decoder.decode(value, { stream: true });
|
|
for (const line of text.split('\n')) {
|
|
if (!line.startsWith('data: ')) continue;
|
|
try {
|
|
const data = JSON.parse(line.slice(6));
|
|
if (data.error) { reject(new Error(data.error)); return; }
|
|
if (data.done) { resolve(full); return; }
|
|
if (data.content) {
|
|
full += data.content;
|
|
if (onChunk) onChunk(full, data);
|
|
} else if (data.thinking !== undefined || data.thinking_end) {
|
|
if (onChunk) onChunk(full, data);
|
|
} else if (data.tool_call || data.tool_result) {
|
|
if (onChunk) onChunk(full, data);
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
resolve(full);
|
|
}).catch(reject);
|
|
});
|
|
}
|