diff --git a/src/components/chat/ChatFlowList.jsx b/src/components/chat/ChatFlowList.jsx index 11571be..bf1a2a6 100644 --- a/src/components/chat/ChatFlowList.jsx +++ b/src/components/chat/ChatFlowList.jsx @@ -8,11 +8,10 @@ import PsychologyRoundedIcon from '@mui/icons-material/PsychologyRounded'; import BuildRoundedIcon from '@mui/icons-material/BuildRounded'; import HealingMarkdown from './HealingMarkdown'; import { + CHROMA, SHADOW_BUBBLE_ASSISTANT, SHADOW_BUBBLE_USER, SURFACE_RAISED, - SURFACE_SUBTLE_PAIR, - byMode, } from '../../colors'; function formatMessageTime(isoString) { @@ -47,44 +46,42 @@ function formatUsageText(usage) { } function formatToolTooltip(content, result, parameters) { - let text = ''; + const parts = []; + if (parameters !== undefined && parameters !== null && parameters !== '') { try { const parsed = typeof parameters === 'string' ? JSON.parse(parameters) : parameters; - text = 'Aufruf:\n' + (typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : String(parameters)); + parts.push(typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : String(parameters)); } catch { - text = 'Aufruf:\n' + String(parameters); + parts.push(String(parameters)); } } - let contentText = ''; if (content !== undefined && content !== null && content !== '') { try { const parsed = typeof content === 'string' ? JSON.parse(content) : content; - contentText = typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : String(content); + const str = typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : String(content); + if (!parts.includes(str)) { + parts.push(str); + } } catch { - contentText = String(content); - } - } - - if (contentText) { - if (text) { - text += '\n\nErgebnis:\n' + contentText; - } else { - text = contentText; + const str = String(content); + if (!parts.includes(str)) { + parts.push(str); + } } } if (result !== undefined && result !== null) { try { const resStr = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result); - if (!text.includes(resStr)) { - text = text ? text + '\n\n' + resStr : resStr; + if (!parts.some((p) => p.includes(resStr))) { + parts.push(resStr); } } catch {} } - return text || 'Werkzeug wird ausgeführt…'; + return parts.join('\n\n') || ''; } class ClickTooltip extends PureComponent { @@ -110,6 +107,10 @@ class ClickTooltip extends PureComponent { disableFocusListener disableHoverListener disableTouchListener + enterDelay={0} + leaveDelay={0} + TransitionProps={{ timeout: 0 }} + slotProps={{ transition: { timeout: 0 } }} title={title} arrow placement="top" @@ -138,23 +139,19 @@ function HandshakeIconView({ state }) { return ( ({ + sx={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, color: isGreen - ? theme.palette.mode === 'dark' - ? '#66bb6a' - : '#2e7d32' + ? CHROMA.GREEN.dark : isBlack - ? theme.palette.mode === 'dark' - ? '#f0f0f0' - : '#111111' + ? '#000000' : '#9e9e9e', transition: 'color 180ms ease-in-out', - })} + }} > @@ -163,7 +160,7 @@ function HandshakeIconView({ state }) { function BrainIconView({ reasoning }) { const tooltipContent = reasoning?.text?.trim() || ''; - if (!tooltipContent) return null; + if (!tooltipContent && reasoning?.sealed) return null; return ( - {tooltipContent} + {tooltipContent || 'Wird überlegt…'} } > ({ + sx={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, - color: theme.palette.mode === 'dark' ? '#b388ff' : '#651fff', + color: CHROMA.PURPLE.dark, borderRadius: 1, '&:hover': { bgcolor: 'action.hover', }, - })} + }} > @@ -227,24 +224,18 @@ function WorkIconView({ tool }) { } > ({ + sx={{ display: 'inline-flex', alignItems: 'center', height: 24, gap: 0.5, - color: isGreen - ? theme.palette.mode === 'dark' - ? '#66bb6a' - : '#2e7d32' - : theme.palette.mode === 'dark' - ? '#f0f0f0' - : '#111111', + color: isGreen ? CHROMA.GREEN.dark : '#000000', px: 0.25, borderRadius: 1, '&:hover': { bgcolor: 'action.hover', }, - })} + }} > {tool?.name ? ( @@ -294,7 +285,14 @@ function UsageChipView({ usage }) { export function TurnRow({ turn, isLive = false }) { if (!turn) return null; + const hasReasonings = Boolean( + turn.reasonings && + turn.reasonings.some((r) => !r.sealed || Boolean(r.text && r.text.trim())) + ); + const hasTools = Boolean(turn.tools && turn.tools.length > 0); const hasContent = Boolean(turn.content); + const showHandshake = !hasReasonings && !hasTools && !hasContent; + const showStatusLine = showHandshake || hasReasonings || hasTools; return ( {/* Turn status line: left of the chat */} - - + {showStatusLine ? ( + + {showHandshake ? : null} - {turn.reasonings?.map((r, i) => ( - - ))} + {turn.reasonings?.map((r, i) => ( + + ))} - {turn.tools?.map((t, i) => ( - - ))} - + {turn.tools?.map((t, i) => ( + + ))} + + ) : null} {/* Assistant message content for this turn (if any) */} {hasContent ? ( @@ -543,15 +543,8 @@ function MessageRow({ msg, showTime = true }) { const payload = parseMessagePayload(msg.message); const textContent = payload.content !== undefined ? payload.content : msg.message; - const toolCalls = Array.isArray(payload.tool_calls) && payload.tool_calls.length > 0 ? payload.tool_calls : null; - const isUser = msg.role === 'user'; - const isSystem = msg.role === 'system'; - const isTool = msg.role === 'tool'; - const isReasoning = msg.role === 'reasoning' || Boolean(payload.reasoning_content && !toolCalls && payload.content === payload.reasoning_content); - const timeStr = showTime ? formatMessageTime(msg.created_at) : ''; - - if (isSystem) { + if (msg.role === 'system') { return ( - - - ); - } - - if (toolCalls) { - const formattedCalls = toolCalls - .map((tc) => { - let args = {}; - try { - if (typeof tc.function?.arguments === 'string') { - args = JSON.parse(tc.function.arguments); - } else if (tc.function?.arguments) { - args = tc.function.arguments; - } - } catch {} - - if (tc.function?.name === 'ask_user') { - const q = args.question || ''; - return `ask_user: ${q}`; - } - - const method = args.method || (tc.function?.name ? tc.function.name.toUpperCase() : 'CALL'); - const path = args.path || args.url || ''; - if (path) { - return `${method} ${path}`; - } - return `${tc.function?.name || 'tool'} ${JSON.stringify(args)}`; - }) - .join('\n'); - - return ( - - ({ - bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR), - color: 'text.secondary', - px: 1.25, - py: 0.35, - borderRadius: 2, - fontSize: '0.72rem', - maxWidth: '90%', - fontFamily: 'monospace', - border: `1px solid ${theme.palette.divider}`, - display: 'inline-flex', - alignItems: 'center', - gap: 1, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - })} - > - - Werkzeug - - - {formattedCalls} - - - - ); - } - - if (isTool) { - let statusText = ''; - let bodyLength = null; - - let resObj = null; - if (typeof textContent === 'object' && textContent !== null) { - resObj = textContent; - } else if (typeof textContent === 'string') { - try { - const parsed = JSON.parse(textContent); - if (parsed && typeof parsed === 'object') { - resObj = parsed; - } - } catch {} - } - - if (resObj && ('status' in resObj || 'statusCode' in resObj || 'body' in resObj)) { - const status = resObj.status ?? resObj.statusCode ?? 200; - statusText = `Status ${status}`; - if (typeof resObj.body === 'string') { - bodyLength = resObj.body.length; - } else if (resObj.body != null) { - bodyLength = JSON.stringify(resObj.body).length; - } else if (resObj.data != null) { - bodyLength = JSON.stringify(resObj.data).length; - } - } else if (typeof textContent === 'string') { - bodyLength = textContent.length; - } - - const isToolAnswer = - Boolean(resObj) && - ('answer' in resObj || 'content' in resObj) && - 'tool_call_id' in resObj && - !('status' in resObj); - - const displayText = isToolAnswer - ? resObj.answer || resObj.content || '' - : statusText - ? `${statusText}${bodyLength != null ? ` · ${bodyLength} byte` : ''}` - : bodyLength != null - ? `${bodyLength} byte` - : String(textContent); - - return ( - - ({ - bgcolor: byMode(theme, SURFACE_SUBTLE_PAIR), - color: 'text.secondary', - px: 1.25, - py: 0.35, - borderRadius: 2, - fontSize: '0.72rem', - maxWidth: '90%', - fontFamily: 'monospace', - border: `1px solid ${theme.palette.divider}`, - display: 'inline-flex', - alignItems: 'center', - gap: 1, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - })} - > - - Ergebnis - - - {displayText} - - - - ); - } + const isUser = msg.role === 'user'; + const timeStr = showTime ? formatMessageTime(msg.created_at) : ''; return (