Spaces:
Sleeping
Sleeping
| import { useState, useCallback } from 'react'; | |
| const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || (import.meta.env.DEV ? 'http://localhost:8000' : ''); | |
| export default function useOperatorSSE() { | |
| const [toolCalls, setToolCalls] = useState([]); | |
| const [projectStatuses, setProjectStatuses] = useState({}); | |
| const [briefText, setBriefText] = useState(null); | |
| const [isScanning, setIsScanning] = useState(false); | |
| const [isLive, setIsLive] = useState(false); | |
| const startScan = useCallback(() => { | |
| setToolCalls([]); | |
| setProjectStatuses({}); | |
| setBriefText(null); | |
| setIsScanning(true); | |
| setIsLive(false); | |
| const source = new EventSource(`${BACKEND_URL}/api/run`); | |
| source.addEventListener('mode', (e) => { | |
| const data = JSON.parse(e.data); | |
| setIsLive(data.live); | |
| }); | |
| source.addEventListener('tool_call', (e) => { | |
| const data = JSON.parse(e.data); | |
| setToolCalls(prev => [...prev, { tool: data.tool, project: data.project, status: 'running' }]); | |
| }); | |
| source.addEventListener('tool_result', (e) => { | |
| const data = JSON.parse(e.data); | |
| setToolCalls(prev => { | |
| const updated = [...prev]; | |
| const idx = updated.findLastIndex(t => t.tool === data.tool && t.project === data.project); | |
| if (idx >= 0) updated[idx] = { ...updated[idx], status: 'done' }; | |
| return updated; | |
| }); | |
| }); | |
| source.addEventListener('initiative', (e) => { | |
| const data = JSON.parse(e.data); | |
| setProjectStatuses(prev => ({ ...prev, [data.project]: data })); | |
| }); | |
| source.addEventListener('brief', (e) => { | |
| const data = JSON.parse(e.data); | |
| setBriefText(data.text); | |
| setIsScanning(false); | |
| source.close(); | |
| }); | |
| source.onerror = () => { | |
| setIsScanning(false); | |
| source.close(); | |
| }; | |
| }, []); | |
| const sendChat = useCallback(async (message) => { | |
| const res = await fetch(`${BACKEND_URL}/api/chat`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ message }), | |
| }); | |
| const reader = res.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let replyText = ''; | |
| 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() || ''; | |
| for (const line of lines) { | |
| if (line.startsWith('data:') || line.startsWith('data: ')) { | |
| try { | |
| const data = JSON.parse(line.replace(/^data:\s*/, '')); | |
| if (data.type === 'chat_reply') { | |
| replyText = data.text; | |
| } | |
| } catch { /* skip */ } | |
| } | |
| } | |
| } | |
| return replyText; | |
| }, []); | |
| return { toolCalls, projectStatuses, briefText, isScanning, isLive, startScan, sendChat }; | |
| } | |