diff --git a/server.js b/server.js index b4cd8ab..c7b66fa 100644 --- a/server.js +++ b/server.js @@ -37,9 +37,13 @@ app.post('/api/chat', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); // establish SSE connection immediately const env = { ...process.env, CLAUDE_CONFIG_DIR: configDir }; + // heartbeat to keep connection alive during long generation + const heartbeat = setInterval(() => res.write(': ping\n\n'), 15000); + const proc = spawn(CLAUDE_BIN, ['-p', text, '--model', modelId, '--output-format', 'stream-json', '--verbose'], { env, stdio: ['ignore', 'pipe', 'pipe'], @@ -49,24 +53,17 @@ app.post('/api/chat', (req, res) => { proc.stdout.on('data', (chunk) => { buf += chunk.toString(); const lines = buf.split('\n'); - buf = lines.pop(); // keep incomplete line + buf = lines.pop(); for (const line of lines) { if (!line.trim()) continue; try { const obj = JSON.parse(line); - // streaming delta - if (obj.type === 'content_block_delta' && obj.delta?.type === 'text_delta') { - res.write(`data: ${JSON.stringify({ text: obj.delta.text })}\n\n`); - } - // fallback: full assistant message - else if (obj.type === 'assistant' && obj.message?.content) { + if (obj.type === 'assistant' && obj.message?.content) { for (const block of obj.message.content) { if (block.type === 'text') res.write(`data: ${JSON.stringify({ text: block.text })}\n\n`); } - } - // error in result - else if (obj.type === 'result' && obj.is_error) { - res.write(`data: ${JSON.stringify({ error: obj.result || 'unknown error' })}\n\n`); + } else if (obj.type === 'result' && obj.is_error) { + res.write(`data: ${JSON.stringify({ error: obj.result || 'error' })}\n\n`); } } catch (e) {} } @@ -85,6 +82,7 @@ app.post('/api/chat', (req, res) => { }); proc.on('close', (code, signal) => { + clearInterval(heartbeat); console.log(`[claude close] code=${code} signal=${signal} stderr=${stderrBuf.substring(0,300)}`); if (code !== 0) { const msg = stderrBuf.trim() || `code=${code} signal=${signal}`; @@ -96,7 +94,7 @@ app.post('/api/chat', (req, res) => { let finished = false; proc.on('close', () => { finished = true; }); - res.on('close', () => { if (!finished) try { proc.kill(); } catch (e) {} }); + res.on('close', () => { clearInterval(heartbeat); if (!finished) try { proc.kill(); } catch (e) {} }); }); app.listen(8108, () => console.log('sechenov :8108'));