Spaces:
Running
Running
import { Hono } from 'hono'; | |
import { streamSSE } from 'hono/streaming'; | |
import { config } from '../config'; | |
const chatApp = new Hono(); | |
// Visual RAG Chat SSE endpoint - matches Next.js /api/visual-rag-chat | |
chatApp.get('/', async (c) => { | |
const queryId = c.req.query('queryId'); | |
const query = c.req.query('query'); | |
const docIds = c.req.query('docIds'); | |
if (!queryId || !query || !docIds) { | |
return c.json({ error: 'Missing required parameters: queryId, query, docIds' }, 400); | |
} | |
return streamSSE(c, async (stream) => { | |
try { | |
// Create abort controller for cleanup | |
const abortController = new AbortController(); | |
// Forward request to backend /get-message endpoint | |
const chatUrl = `${config.backendUrl}/get-message?query_id=${encodeURIComponent(queryId)}&query=${encodeURIComponent(query)}&doc_ids=${encodeURIComponent(docIds)}`; | |
const response = await fetch(chatUrl, { | |
headers: { | |
'Accept': 'text/event-stream', | |
}, | |
signal: abortController.signal, | |
}); | |
if (!response.ok) { | |
await stream.writeSSE({ | |
event: 'error', | |
data: JSON.stringify({ error: `Backend returned ${response.status}` }), | |
}); | |
return; | |
} | |
if (!response.body) { | |
await stream.writeSSE({ | |
event: 'error', | |
data: JSON.stringify({ error: 'No response body' }), | |
}); | |
return; | |
} | |
// Stream the response | |
const reader = response.body.getReader(); | |
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'); | |
// Keep the last incomplete line in the buffer | |
buffer = lines.pop() || ''; | |
for (const line of lines) { | |
if (line.trim() === '') continue; | |
if (line.startsWith('data: ')) { | |
const data = line.slice(6); | |
await stream.writeSSE({ data }); | |
} else if (line.startsWith('event: ')) { | |
// Handle event lines if backend sends them | |
const event = line.slice(7).trim(); | |
// Look for the next data line | |
const nextLineIndex = lines.indexOf(line) + 1; | |
if (nextLineIndex < lines.length) { | |
const nextLine = lines[nextLineIndex]; | |
if (nextLine.startsWith('data: ')) { | |
const data = nextLine.slice(6); | |
await stream.writeSSE({ event, data }); | |
lines.splice(nextLineIndex, 1); // Remove processed line | |
} | |
} | |
} | |
} | |
} | |
// Handle any remaining data in buffer | |
if (buffer.trim()) { | |
if (buffer.startsWith('data: ')) { | |
await stream.writeSSE({ data: buffer.slice(6) }); | |
} | |
} | |
// Cleanup | |
abortController.abort(); | |
} catch (error) { | |
console.error('Chat streaming error:', error); | |
await stream.writeSSE({ | |
event: 'error', | |
data: JSON.stringify({ | |
error: 'Streaming failed', | |
message: error instanceof Error ? error.message : 'Unknown error' | |
}), | |
}); | |
} | |
}); | |
}); | |
export { chatApp }; |