| |
|
|
| export const LS = { |
| get: (k: string, d: string | null = null) => localStorage.getItem("celestial_" + k) ?? d, |
| set: (k: string, v: string) => localStorage.setItem("celestial_" + k, v), |
| del: (k: string) => localStorage.removeItem("celestial_" + k), |
| } |
|
|
| |
| function deviceId(): string { |
| let d = LS.get("device") |
| if (!d) { |
| d = crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}` |
| LS.set("device", d) |
| } |
| return d |
| } |
| export const DEVICE = deviceId() |
|
|
| export async function api<T = unknown>(name: string, args: unknown[] = []): Promise<T> { |
| const post = await fetch(`/gradio_api/call/${name}`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ data: args }), |
| }) |
| if (!post.ok) throw new Error(`后端未应答 (${post.status})`) |
| const { event_id } = await post.json() |
| const res = await fetch(`/gradio_api/call/${name}/${event_id}`) |
| const text = await res.text() |
| let event = "" |
| let data: unknown[] | null = null |
| for (const raw of text.split("\n")) { |
| if (raw.startsWith("event:")) event = raw.slice(6).trim() |
| else if (raw.startsWith("data:")) { |
| const payload = raw.slice(5).trim() |
| if (event === "complete" && payload) data = JSON.parse(payload) |
| if (event === "error") throw new Error(payload && payload !== "null" ? payload : "天问服务出错") |
| } |
| } |
| if (data === null) throw new Error("天问服务未应答,请确认 app.py 已运行") |
| return data[0] as T |
| } |
|
|
| |
| export const fileUrl = (fd: { url?: string }) => String(fd.url || "").replace(/\\/g, "/") |
|
|
| |
| |
| export async function apiStream(path: string, body: object, onText: (text: string) => void): Promise<void> { |
| |
| const ctrl = new AbortController() |
| const timer = setTimeout(() => ctrl.abort(), 90000) |
| try { |
| const res = await fetch(path, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| signal: ctrl.signal, |
| }) |
| if (!res.ok || !res.body) throw new Error(`流式服务未应答 (${res.status})`) |
| const reader = res.body.getReader() |
| const decoder = new TextDecoder() |
| let buf = "" |
| for (;;) { |
| const { done, value } = await reader.read() |
| if (done) break |
| buf += decoder.decode(value, { stream: true }) |
| const events = buf.split("\n\n") |
| buf = events.pop() ?? "" |
| for (const evt of events) { |
| const dataLine = evt.split("\n").find((l) => l.startsWith("data:")) |
| if (!dataLine) continue |
| try { |
| const o = JSON.parse(dataLine.slice(5).trim()) |
| if (typeof o.text === "string") onText(o.text) |
| } catch { } |
| } |
| } |
| } finally { |
| clearTimeout(timer) |
| } |
| } |
|
|