// Use empty string for same-origin requests in production (HF Spaces) // Only fallback to localhost:8000 if VITE_API_BASE_URL is truly undefined const baseURL = import.meta.env.VITE_API_BASE_URL !== undefined ? import.meta.env.VITE_API_BASE_URL : "http://localhost:8000"; export interface ResearchRequest { topic: string; search_api?: string; } export interface ResearchStreamEvent { type: string; [key: string]: unknown; } export interface StreamOptions { signal?: AbortSignal; } export async function runResearchStream( payload: ResearchRequest, onEvent: (event: ResearchStreamEvent) => void, options: StreamOptions = {} ): Promise { const response = await fetch(`${baseURL}/research/stream`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/event-stream" }, body: JSON.stringify(payload), signal: options.signal }); if (!response.ok) { const errorText = await response.text().catch(() => ""); throw new Error( errorText || `Research request failed, status code: ${response.status}` ); } const body = response.body; if (!body) { throw new Error("Browser does not support streaming responses, unable to get research progress"); } const reader = body.getReader(); const decoder = new TextDecoder("utf-8"); let buffer = ""; while (true) { const { value, done } = await reader.read(); buffer += decoder.decode(value || new Uint8Array(), { stream: !done }); let boundary = buffer.indexOf("\n\n"); while (boundary !== -1) { const rawEvent = buffer.slice(0, boundary).trim(); buffer = buffer.slice(boundary + 2); if (rawEvent.startsWith("data:")) { const dataPayload = rawEvent.slice(5).trim(); if (dataPayload) { try { const event = JSON.parse(dataPayload) as ResearchStreamEvent; onEvent(event); if (event.type === "error" || event.type === "done") { return; } } catch (error) { console.error("Failed to parse streaming event:", error, dataPayload); } } } boundary = buffer.indexOf("\n\n"); } if (done) { // Handle possible trailing events if (buffer.trim()) { const rawEvent = buffer.trim(); if (rawEvent.startsWith("data:")) { const dataPayload = rawEvent.slice(5).trim(); if (dataPayload) { try { const event = JSON.parse(dataPayload) as ResearchStreamEvent; onEvent(event); } catch (error) { console.error("Failed to parse streaming event:", error, dataPayload); } } } } break; } } }