import { memo, useState, useCallback } from "react"; import { marked } from "marked"; import type { ChatTurn as ChatTurnType, ChatTurnBlockTool } from "../lib/api"; import ToolCallDisplay from "./ToolCallDisplay"; interface ChatTurnProps { turn: ChatTurnType; } // Configure marked with breaks enabled marked.use({ breaks: true, }); const ChatTurn = memo(function ChatTurn({ turn }: ChatTurnProps) { const formatDuration = (ms: number): string => { if (ms < 1000) return `${ms}ms`; const seconds = (ms / 1000).toFixed(1); return `${seconds}s`; }; const formatTokenCount = (count: number): string => { if (count >= 1000) { return `${(count / 1000).toFixed(1)}k`; } return count.toString(); }; const modelId = turn.llm || "Unknown"; const modeLabel = turn.mode?.charAt(0).toUpperCase() + turn.mode?.slice(1) || "Unknown"; const statusLabel = turn.status?.charAt(0).toUpperCase() + turn.status?.slice(1) || "Unknown"; const startedAt = new Date(turn.createdAt); return (
{/* Turn Header */}
{startedAt.toLocaleTimeString()}
{modelId}
{modeLabel}
{statusLabel}
{turn.stats && ( <>
{formatDuration(turn.stats.durationMs)}
{turn.stats.inputTokens ? formatTokenCount(turn.stats.inputTokens) : "0"}{" "} in /{" "} {turn.stats.responseTokens ? formatTokenCount(turn.stats.responseTokens) : "0"}{" "} out
)}
{/* Error Alert */} {turn.errorMessage && turn.status === "error" && (
⚠️ Error
{turn.errorMessage}
)} {/* Aborted notice */} {turn.status === "aborted" && (
The turn was aborted by you.
)} {/* User Prompt */}
You
{turn.prompts?.user || ""}
{/* Agent Response */} {turn.blocks && turn.blocks.length > 0 && (
Gadget
{/* Render blocks in order */} {turn.blocks.map((block, idx) => { if (block.mode === "thinking") { return (
Thinking
); } else if (block.mode === "responding") { return (
); } else if (block.mode === "tool") { const toolCall = block.content; const subagent = toolCall.subagent; return (
{subagent && }
); } return null; })}
{startedAt.toLocaleTimeString()}
)}
); }); function SubagentDisplay({ subagent, }: { subagent: NonNullable; }) { const [expanded, setExpanded] = useState(true); const toggle = useCallback(() => setExpanded((p) => !p), []); const formatDuration = (ms: number): string => { if (ms < 1000) return `${ms}ms`; const seconds = (ms / 1000).toFixed(1); return `${seconds}s`; }; const formatTokenCount = (count: number): string => { if (count >= 1000) return `${(count / 1000).toFixed(1)}k`; return count.toString(); }; return (
{expanded && (
{/* Thinking */} {subagent.thinking && (
Thinking
)} {/* Subagent Tool Calls */} {subagent.toolCalls.length > 0 && (
Tool Calls ({subagent.toolCalls.length})
{subagent.toolCalls.map((tc, i) => ( ))}
)} {/* Response */} {subagent.response && (
Response
)} {/* Stats */} {subagent.stats && (
{formatDuration(subagent.stats.durationMs)} {formatTokenCount(subagent.stats.inputTokens)} in {formatTokenCount(subagent.stats.responseTokens)} out {subagent.stats.thinkingTokenCount > 0 && ( {formatTokenCount(subagent.stats.thinkingTokenCount)} thinking )}
)}
)}
); } export default ChatTurn;