Spaces:
Runtime error
Runtime error
File size: 5,175 Bytes
28cf767 60ffeeb 28cf767 60ffeeb 28cf767 c293cc8 28cf767 4573dd4 28cf767 c293cc8 28cf767 4573dd4 28cf767 bbab1dd 28cf767 4573dd4 28cf767 bbab1dd 28cf767 bbab1dd 28cf767 c293cc8 28cf767 c293cc8 314e0fe 28cf767 314e0fe 28cf767 4573dd4 28cf767 | 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 | "use client";
import { useCallback, useRef, useState } from "react";
import type { NodeState, StageEvent } from "@/types/pipeline";
const API_URL =
process.env.NEXT_PUBLIC_API_URL ??
(typeof window !== "undefined" && window.location.origin !== "http://localhost:3000"
? window.location.origin
: "http://localhost:8001");
// Default idle nodes shown before any query is run
const DEFAULT_NODES: NodeState[] = [
{ id: "intent", label: "Intent Router", status: "idle", detail: "" },
{ id: "rewrite", label: "Query Rewrite", status: "idle", detail: "" },
{ id: "cache", label: "Cache Check", status: "idle", detail: "" },
{ id: "rag_init", label: "Loading RAG Store", status: "idle", detail: "" },
{ id: "retrieval_1", label: "Retrieval", status: "idle", detail: "" },
{ id: "retrieval_2", label: "Retrieval (retry)", status: "idle", detail: "" },
{ id: "retrieval_3", label: "Retrieval (retry 2)", status: "idle", detail: "" },
{ id: "citations", label: "Citation Enforcement", status: "idle", detail: "" },
{ id: "verification", label: "Claim Verification", status: "idle", detail: "" },
{ id: "answer", label: "Answer", status: "idle", detail: "" },
];
export function useQueryStream() {
const [nodes, setNodes] = useState<NodeState[]>(DEFAULT_NODES);
const [answer, setAnswer] = useState<string>("");
const [streamingAnswer, setStreamingAnswer] = useState<string>("");
const [streaming, setStreaming] = useState(false);
const [error, setError] = useState<string>("");
const [followUps, setFollowUps] = useState<string[]>([]);
const [lastQuestion, setLastQuestion] = useState<string>("");
const abortRef = useRef<AbortController | null>(null);
const reset = useCallback(() => {
setNodes(DEFAULT_NODES.map((n) => ({ ...n, status: "idle", detail: "" })));
setAnswer("");
setStreamingAnswer("");
setError("");
setFollowUps([]);
}, []);
const updateNode = useCallback((event: StageEvent) => {
setNodes((prev) =>
prev.map((n) =>
n.id === event.id
? { ...n, label: event.label, status: event.status, detail: event.detail ?? "" }
: n
)
);
}, []);
const submit = useCallback(
async (question: string, mode?: string) => {
// Cancel any in-flight request
abortRef.current?.abort();
abortRef.current = new AbortController();
setLastQuestion(question);
reset();
setStreaming(true);
try {
const payload: Record<string, unknown> = { question };
if (mode) {
payload.mode = mode;
}
const response = await fetch(`${API_URL}/query`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: abortRef.current.signal,
});
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
const reader = response.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
let currentEvent = "";
for (const line of lines) {
if (line.startsWith("event: ")) {
currentEvent = line.slice(7).trim();
} else if (line.startsWith("data: ")) {
const raw = line.slice(6).trim();
try {
const parsed = JSON.parse(raw);
if (currentEvent === "stage") {
updateNode(parsed as StageEvent);
} else if (currentEvent === "token") {
setStreamingAnswer((prev) => prev + (parsed.token ?? ""));
} else if (currentEvent === "done") {
setAnswer(parsed.answer ?? "");
setStreamingAnswer("");
const f = parsed.follow_up_questions;
if (Array.isArray(f)) {
setFollowUps(
f
.map((q: unknown) => (typeof q === "string" ? q.trim() : ""))
.filter((q: string) => q.length > 0)
.slice(0, 3)
);
} else {
setFollowUps([]);
}
} else if (currentEvent === "error") {
setError(parsed.message ?? "Unknown error");
setFollowUps([]);
}
} catch {
// malformed JSON — skip
}
currentEvent = "";
}
}
}
} catch (err: unknown) {
if (err instanceof Error && err.name !== "AbortError") {
setError(err.message);
}
} finally {
setStreaming(false);
}
},
[reset, updateNode]
);
return { nodes, answer, streamingAnswer, streaming, error, submit, reset, followUps, lastQuestion };
}
|