diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx index 964dcab..8f3a16b 100644 --- a/web/src/components/Shell.jsx +++ b/web/src/components/Shell.jsx @@ -380,13 +380,61 @@ export default function Shell({ api }) { setAiLoading(true) try { const res = await api.runCommand(`echo "AI: ${text}"`, '') - setAiMessages(prev => [...prev, { role: 'ai', content: res.output || t('shell.noResponse') }]) + const output = res.output || t('shell.noResponse') + parseAndAddAiMessages(output) } catch (err) { setAiMessages(prev => [...prev, { role: 'ai', content: `${t('shell.error')}: ${err.message}` }]) } setAiLoading(false) } + const parseAndAddAiMessages = (text) => { + const lines = text.split('\n') + let buffer = '' + let inBlock = false + + const flushBuffer = () => { + if (buffer.trim()) { + setAiMessages(prev => [...prev, { role: 'ai', content: buffer.trim() }]) + } + buffer = '' + } + + for (const line of lines) { + const toolMatch = line.match(/^\[TOOL_CALL:\{.*\}\]$/) + if (toolMatch) { + flushBuffer() + try { + const toolData = JSON.parse(toolMatch[0].slice(10, -1)) + setAiMessages(prev => [...prev, { + role: 'tool', + content: `${t('shell.toolLaunched')}: ${toolData.tool || 'tool'}`, + args: toolData.task || toolData.args || '', + }]) + } catch { + setAiMessages(prev => [...prev, { role: 'tool', content: line, args: '' }]) + } + } else if (line.match(/^(Reflexion|Thought|thinking):/i) || line.startsWith('>')) { + if (buffer.trim() && !inBlock) { + flushBuffer() + } + inBlock = true + const cleaned = line.replace(/^(Reflexion|Thought|thinking):\s*/i, '').replace(/^>\s*/, '') + if (buffer) buffer += ' ' + buffer += cleaned + } else { + if (inBlock && buffer.trim()) { + setAiMessages(prev => [...prev, { role: 'thinking', content: buffer.trim() }]) + buffer = '' + } + inBlock = false + if (buffer) buffer += '\n' + buffer += line + } + } + flushBuffer() + } + return (