Refactor ZeroGPU handling and update documentation. Reduced default BORDERLESS_GPU_DURATION to 60 seconds in .env.example and README.md. Enhanced error messaging in app.js and gradio_api.js for better user feedback on quota issues. Updated GPU_DURATION logic in config.py to enforce minimum and maximum limits.
fbe6753 | function unwrapStreamData(data) { | |
| if (Array.isArray(data)) { | |
| return data.length === 1 ? data[0] : data; | |
| } | |
| return data; | |
| } | |
| export async function gradioStream(apiName, payload = {}, onChunk) { | |
| const route = apiName.replace(/^\//, ""); | |
| const start = await fetch(`/gradio_api/call/v2/${route}`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| credentials: "include", | |
| }); | |
| if (!start.ok) { | |
| throw new Error(`Gradio API failed to start (${start.status})`); | |
| } | |
| const { event_id: eventId } = await start.json(); | |
| if (!eventId) { | |
| throw new Error("Gradio API did not return an event id"); | |
| } | |
| const stream = await fetch(`/gradio_api/call/${route}/${eventId}`, { | |
| credentials: "include", | |
| }); | |
| if (!stream.ok) { | |
| throw new Error(`Gradio API stream failed (${stream.status})`); | |
| } | |
| const reader = stream.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ""; | |
| let lastData = null; | |
| let errorMessage = null; | |
| let currentEvent = null; | |
| const processLine = (line) => { | |
| if (line.startsWith("event: ")) { | |
| currentEvent = line.slice(7).trim(); | |
| if (currentEvent === "error") { | |
| errorMessage = "Gradio API returned an error"; | |
| } | |
| return; | |
| } | |
| if (!line.startsWith("data: ")) { | |
| return; | |
| } | |
| try { | |
| const parsed = JSON.parse(line.slice(6)); | |
| lastData = parsed; | |
| if (currentEvent === "error") { | |
| errorMessage = | |
| typeof parsed === "string" | |
| ? parsed | |
| : parsed?.error || JSON.stringify(parsed); | |
| return; | |
| } | |
| const chunk = unwrapStreamData(parsed); | |
| if (chunk !== null && chunk !== undefined) { | |
| onChunk(chunk); | |
| } | |
| } catch { | |
| // Ignore malformed SSE chunks and keep the last valid payload. | |
| } | |
| }; | |
| 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() || ""; | |
| for (const line of lines) { | |
| if (line.trim() === "") { | |
| currentEvent = null; | |
| continue; | |
| } | |
| processLine(line); | |
| } | |
| } | |
| if (buffer.trim()) { | |
| for (const line of buffer.split("\n")) { | |
| if (line.trim() === "") { | |
| currentEvent = null; | |
| continue; | |
| } | |
| processLine(line); | |
| } | |
| } | |
| if (errorMessage) { | |
| throw new Error(errorMessage); | |
| } | |
| if (lastData === null) { | |
| throw new Error("Gradio API returned no data"); | |
| } | |
| return { data: lastData }; | |
| } | |
| export async function gradioPredict(apiName, payload = {}) { | |
| let resultData = null; | |
| await gradioStream(apiName, payload, (chunk) => { | |
| resultData = chunk; | |
| }); | |
| return { data: [resultData] }; | |
| } | |