From 8fb93fa47e521643421bdf86818ee046dacac133 Mon Sep 17 00:00:00 2001 From: Augustin Date: Wed, 22 Apr 2026 19:41:42 +0200 Subject: [PATCH] feat(studio): parse AI thinking and tool launch messages in terminal panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add message type detection: thinking (Reflexion/Thought/>), tool (TOOL_CALL), and normal AI responses - Style thinking messages with italic blue, tool messages with yellow border - Add toolLaunched i18n key for both fr and en locales 💘 Generated with Crush Assisted-by: MiniMax-M2.7 via Crush --- web/src/components/Shell.jsx | 51 +++++++++++++++++++++++++++++++++++- web/src/i18n/en.js | 1 + web/src/i18n/fr.js | 1 + web/src/styles/global.css | 4 +++ 4 files changed, 56 insertions(+), 1 deletion(-) 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 (
@@ -507,6 +555,7 @@ export default function Shell({ api }) { {aiMessages.map((msg, i) => (
{msg.content} + {msg.args &&
{msg.args}
}
))} {aiLoading &&
} diff --git a/web/src/i18n/en.js b/web/src/i18n/en.js index 40a3ecd..1fa96fe 100644 --- a/web/src/i18n/en.js +++ b/web/src/i18n/en.js @@ -110,6 +110,7 @@ const en = { aiAssistant: 'AI Assistant', aiWelcome: 'Hello! I can help you with terminal commands. Ask me anything!', askAi: 'Ask AI assistant...', + toolLaunched: 'Tool launched', }, config: { diff --git a/web/src/i18n/fr.js b/web/src/i18n/fr.js index cd32c92..a2cf03b 100644 --- a/web/src/i18n/fr.js +++ b/web/src/i18n/fr.js @@ -110,6 +110,7 @@ const fr = { aiAssistant: 'Assistant IA', aiWelcome: 'Bonjour ! Je peux vous aider avec les commandes du terminal. Demandez-moi n\'importe quoi !', askAi: 'Interroger l\'assistant IA...', + toolLaunched: 'Outil lanc\u00e9', }, config: { diff --git a/web/src/styles/global.css b/web/src/styles/global.css index edc234b..c0aba4d 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -391,6 +391,10 @@ input::placeholder { color: var(--text-disabled); } .ai-message { padding: 8px 12px; border-radius: var(--radius); font-size: 13px; line-height: 1.5; word-break: break-word; } .ai-message.ai { background: var(--bg-card); border-left: 3px solid var(--accent); } .ai-message.user { background: var(--accent-bg); border-left: 3px solid var(--accent-muted); } +.ai-message.thinking { background: var(--bg-elevated); border-left: 3px solid var(--info); font-style: italic; color: var(--text-tertiary); } +.ai-message.tool { background: var(--bg-elevated); border-left: 3px solid var(--warning); } +.ai-message.tool .tool-name { font-weight: 700; color: var(--warning); } +.ai-message.tool .tool-args { font-family: var(--font-mono); font-size: 12px; color: var(--text-tertiary); margin-top: 4px; } .ai-panel-input { display: flex; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--border); } .ai-panel-input input { flex: 1; font-size: 13px; padding: 6px 10px; }