mirror of
https://github.com/multipleof4/devsune.git
synced 2026-01-14 16:37:54 +00:00
95 lines
3.0 KiB
JavaScript
95 lines
3.0 KiB
JavaScript
const CH = "chat-stream-v1";
|
|
let bc, streams = /* @__PURE__ */ new Map();
|
|
self.addEventListener("install", (e) => self.skipWaiting());
|
|
self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim()));
|
|
function send(m) {
|
|
try {
|
|
bc || (bc = new BroadcastChannel(CH));
|
|
bc.postMessage(m);
|
|
} catch {
|
|
}
|
|
}
|
|
function hint(x) {
|
|
x = String(x || "");
|
|
if (/401|unauthorized/i.test(x)) return "Unauthorized (check API key).";
|
|
if (/429|rate/i.test(x)) return "Rate limited (slow down or upgrade).";
|
|
if (/403|forbidden|access/i.test(x)) return "Forbidden (model or key scope).";
|
|
return "Request failed.";
|
|
}
|
|
async function start(id, p) {
|
|
bc || (bc = new BroadcastChannel(CH));
|
|
const body = typeof p.body === "string" ? p.body : JSON.stringify(p.body || {}), ctrl = new AbortController();
|
|
let done = false, buf = "";
|
|
streams.set(id, { ctrl, buf: () => buf, done: () => done });
|
|
try {
|
|
const r = await fetch(p.url, { method: "POST", headers: p.headers || {}, body, signal: ctrl.signal });
|
|
if (!r.ok) throw new Error(await r.text().catch(() => "") || "HTTP " + r.status);
|
|
const rd = r.body.getReader(), dc = new TextDecoder("utf-8");
|
|
let acc = "";
|
|
const doneOnce = () => {
|
|
if (done) return;
|
|
done = true;
|
|
send({ t: "done", id });
|
|
};
|
|
for (; ; ) {
|
|
const { value, done: d } = await rd.read();
|
|
if (d) break;
|
|
acc += dc.decode(value, { stream: true });
|
|
let i;
|
|
while ((i = acc.indexOf("\n\n")) !== -1) {
|
|
const raw = acc.slice(0, i).trim();
|
|
acc = acc.slice(i + 2);
|
|
if (!raw || !raw.startsWith("data:")) continue;
|
|
const data = raw.slice(5).trim();
|
|
if (data === "[DONE]") {
|
|
doneOnce();
|
|
continue;
|
|
}
|
|
try {
|
|
const j = JSON.parse(data);
|
|
const delta = j.choices?.[0]?.delta?.content ?? "";
|
|
if (delta) {
|
|
buf += delta;
|
|
send({ t: "delta", id, data: delta });
|
|
}
|
|
const fin = j.choices?.[0]?.finish_reason;
|
|
if (fin) doneOnce();
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
doneOnce();
|
|
} catch (e) {
|
|
const aborted = e?.name === "AbortError" || /abort/i.test(String(e?.message || ""));
|
|
if (aborted) send({ t: "done", id, aborted: true });
|
|
else send({ t: "error", id, hint: hint(e?.message) });
|
|
} finally {
|
|
streams.delete(id);
|
|
}
|
|
}
|
|
function stop(id) {
|
|
const s = streams.get(id);
|
|
if (!s) return;
|
|
try {
|
|
s.ctrl.abort();
|
|
} catch {
|
|
} finally {
|
|
streams.delete(id);
|
|
}
|
|
}
|
|
function replay(id, offset = 0) {
|
|
const s = streams.get(id);
|
|
if (!s) return;
|
|
const cur = s.buf(), remain = cur.slice(Math.max(0, offset));
|
|
if (remain) send({ t: "delta", id, data: remain });
|
|
if (s.done()) send({ t: "done", id });
|
|
}
|
|
bc = new BroadcastChannel(CH);
|
|
bc.onmessage = (e) => {
|
|
const m = e.data || {};
|
|
if (m.t === "hello") send({ t: "hello-ack" });
|
|
else if (m.t === "start" && m.id && m.payload) start(m.id, m.payload);
|
|
else if (m.t === "stop" && m.id) stop(m.id);
|
|
else if (m.t === "replay" && m.id) replay(m.id, m.offset | 0);
|
|
};
|