Spaces:
Sleeping
Sleeping
File size: 6,515 Bytes
ce51e88 c09a7e7 ce51e88 375924d ce51e88 375924d df78c68 c09a7e7 ed5dd6f 375924d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | import type {
ChatRequest,
ChatResponse,
EvalScores,
Persona,
TurnaroundRequest,
} from "../types";
const API_BASE = "";
export async function fetchUsers(): Promise<Persona[]> {
const res = await fetch(`${API_BASE}/users`);
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
return data.users;
}
export async function sendChat(req: ChatRequest): Promise<ChatResponse> {
const res = await fetch(`${API_BASE}/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
export async function sendTurnaround(
req: TurnaroundRequest
): Promise<ChatResponse> {
const res = await fetch(`${API_BASE}/chat/turnaround`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
export async function resetSession(userId: string): Promise<void> {
const res = await fetch(
`${API_BASE}/session/reset?user_id=${encodeURIComponent(userId)}`,
{ method: "POST" }
);
if (!res.ok) throw new Error(`API error: ${res.status}`);
}
export type StreamEvent =
| { type: "candidate_start"; idx: number; strategy: string; grounded_buckets: string[] }
| { type: "token"; idx: number; delta: string }
| { type: "candidate_done"; idx: number; text: string }
| { type: "candidate_error"; idx: number; error: string }
| { type: "complete"; response: ChatResponse }
| { type: "error"; message: string };
async function readSSE(
res: Response,
onEvent: (evt: StreamEvent) => void,
): Promise<void> {
if (!res.body) throw new Error("no response body");
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
const emitFrame = (frame: string) => {
const line = frame.split("\n").find((l) => l.startsWith("data:"));
if (!line) return;
const json = line.slice(5).trim();
if (!json) return;
try {
onEvent(JSON.parse(json) as StreamEvent);
} catch (e) {
console.warn("SSE parse failed", e, json.slice(0, 200));
}
};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE frames are separated by blank lines.
const parts = buffer.split("\n\n");
buffer = parts.pop() ?? "";
for (const part of parts) emitFrame(part);
}
// Server closed cleanly but the final frame didn't end with \n\n —
// emit whatever remains so the terminal event isn't dropped.
if (buffer.trim()) emitFrame(buffer);
}
export async function streamChat(
req: ChatRequest,
onEvent: (evt: StreamEvent) => void,
): Promise<void> {
const res = await fetch(`${API_BASE}/chat/stream`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
await readSSE(res, onEvent);
}
export async function streamRegenerate(
args: { user_id: string; turn_id: number; rejected_texts: string[] },
onEvent: (evt: StreamEvent) => void,
): Promise<void> {
const res = await fetch(`${API_BASE}/chat/regenerate/stream`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
await readSSE(res, onEvent);
}
export async function sendRegenerate(args: {
user_id: string;
turn_id: number;
rejected_texts: string[];
}): Promise<ChatResponse> {
const res = await fetch(`${API_BASE}/chat/regenerate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
export async function sendPick(args: {
run_id: string;
user_id: string;
picked_idx: number;
}): Promise<void> {
const res = await fetch(`${API_BASE}/chat/pick`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
}
export type EvalsStatus = "pending" | "ready" | "failed" | "unknown";
export interface EvalsFetchResult {
status: EvalsStatus;
run_id: string;
eval_scores: EvalScores | null;
}
export async function fetchEvals(runId: string): Promise<EvalsFetchResult> {
const res = await fetch(`${API_BASE}/evals/${encodeURIComponent(runId)}`);
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
export async function pollEvals(
runId: string,
opts: {
initialDelayMs?: number;
maxDelayMs?: number;
timeoutMs?: number;
signal?: AbortSignal;
} = {}
): Promise<EvalScores | null> {
const maxDelayMs = opts.maxDelayMs ?? 2000;
const timeoutMs = opts.timeoutMs ?? 20000;
let delay = opts.initialDelayMs ?? 300;
const start = performance.now();
// Track consecutive "unknown" responses so transient race conditions (poll
// racing the server picking up the new run_id) don't immediately give up.
let unknownStreak = 0;
while (performance.now() - start < timeoutMs) {
if (opts.signal?.aborted) return null;
try {
const r = await fetchEvals(runId);
if (r.status === "ready") return r.eval_scores;
if (r.status === "failed") return null;
if (r.status === "unknown") {
unknownStreak += 1;
if (unknownStreak >= 3) return null;
} else {
unknownStreak = 0;
}
} catch (e) {
console.warn("pollEvals: transient error", e);
}
await new Promise((res) => setTimeout(res, delay));
delay = Math.min(delay * 2, maxDelayMs);
}
return null;
}
export async function submitRating(args: {
run_id: string;
user_id: string;
authenticity: number;
rater_id?: string;
notes?: string;
}): Promise<void> {
const res = await fetch(`${API_BASE}/feedback/rating`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
}
export async function checkHealth(): Promise<boolean> {
try {
const res = await fetch(`${API_BASE}/health`);
if (!res.ok) return false;
const data = await res.json();
return data.models_ready === true;
} catch {
return false;
}
}
|