agent's progress on ChatTurn component
This commit is contained in:
parent
ead431eade
commit
3a8f2e4f44
183
gadget-code/frontend/src/components/ChatTurn.tsx
Normal file
183
gadget-code/frontend/src/components/ChatTurn.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import { useState, memo, useCallback } from "react";
|
||||||
|
import type { ChatTurn as ChatTurnType } from "../lib/api";
|
||||||
|
|
||||||
|
interface ChatTurnProps {
|
||||||
|
turn: ChatTurnType;
|
||||||
|
onUpdate: (turnId: string, updates: Partial<ChatTurnType>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatTurn = memo(function ChatTurn({ turn, onUpdate }: ChatTurnProps) {
|
||||||
|
const [thinkingExpanded, setThinkingExpanded] = useState(false);
|
||||||
|
const [toolCallsExpanded, setToolCallsExpanded] = useState(true);
|
||||||
|
|
||||||
|
const handleThinkingToggle = useCallback(() => {
|
||||||
|
setThinkingExpanded((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleToolCallsToggle = useCallback(() => {
|
||||||
|
setToolCallsExpanded((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="border-b border-border-subtle pb-4 last:border-b-0">
|
||||||
|
{/* Turn Header */}
|
||||||
|
<div className="flex items-center gap-4 text-xs text-text-muted mb-3 px-4">
|
||||||
|
<div>{startedAt.toLocaleTimeString()}</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-mono">{modelId}</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-px h-3 bg-border-subtle" />
|
||||||
|
<div>{modeLabel}</div>
|
||||||
|
<div className="w-px h-3 bg-border-subtle" />
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
turn.status === "finished"
|
||||||
|
? "text-green-500"
|
||||||
|
: turn.status === "error"
|
||||||
|
? "text-red-500"
|
||||||
|
: "text-yellow-500"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{statusLabel}
|
||||||
|
</div>
|
||||||
|
<div className="w-px h-3 bg-border-subtle" />
|
||||||
|
{turn.stats && (
|
||||||
|
<>
|
||||||
|
<div className="w-px h-3 bg-border-subtle" />
|
||||||
|
<div>{formatDuration(turn.stats.durationMs)}</div>
|
||||||
|
<div className="w-px h-3 bg-border-subtle" />
|
||||||
|
<div>
|
||||||
|
{turn.stats.inputTokens
|
||||||
|
? formatTokenCount(turn.stats.inputTokens)
|
||||||
|
: "0"}{" "}
|
||||||
|
in /{" "}
|
||||||
|
{turn.stats.responseTokens
|
||||||
|
? formatTokenCount(turn.stats.responseTokens)
|
||||||
|
: "0"}{" "}
|
||||||
|
out
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Prompt */}
|
||||||
|
<div className="max-w-[80%] ml-0 mb-4">
|
||||||
|
<div className="bg-brand text-white rounded p-4">
|
||||||
|
<div className="text-sm font-semibold mb-2">You</div>
|
||||||
|
<div className="whitespace-pre-wrap">{turn.prompts?.user || ""}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Agent Response */}
|
||||||
|
{(turn.thinking || turn.response || (turn.toolCalls && turn.toolCalls.length > 0)) && (
|
||||||
|
<div className="max-w-[80%] ml-auto mb-4">
|
||||||
|
<div className="bg-bg-tertiary border border-border-default rounded p-4">
|
||||||
|
<div className="text-sm font-semibold mb-3 text-text-secondary">
|
||||||
|
Gadget
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thinking Section */}
|
||||||
|
{turn.thinking && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<button
|
||||||
|
onClick={handleThinkingToggle}
|
||||||
|
className="text-xs text-text-muted hover:text-text-secondary flex items-center gap-1 transition-colors"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`transform transition-transform ${thinkingExpanded ? "rotate-90" : ""}`}
|
||||||
|
>
|
||||||
|
▶
|
||||||
|
</span>
|
||||||
|
Thinking ({formatTokenCount(turn.thinking.length)} chars)
|
||||||
|
</button>
|
||||||
|
{thinkingExpanded && (
|
||||||
|
<div className="mt-2 p-3 bg-bg-secondary rounded text-sm text-text-secondary whitespace-pre-wrap font-mono text-xs">
|
||||||
|
{turn.thinking}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Response Section */}
|
||||||
|
{turn.response && (
|
||||||
|
<div className="mb-3 whitespace-pre-wrap text-text-primary">
|
||||||
|
{turn.response}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tool Calls Section */}
|
||||||
|
{turn.toolCalls && turn.toolCalls.length > 0 && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<button
|
||||||
|
onClick={handleToolCallsToggle}
|
||||||
|
className="text-xs text-text-muted hover:text-text-secondary flex items-center gap-1 transition-colors"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`transform transition-transform ${toolCallsExpanded ? "rotate-90" : ""}`}
|
||||||
|
>
|
||||||
|
▶
|
||||||
|
</span>
|
||||||
|
Tool Calls ({turn.toolCalls.length})
|
||||||
|
</button>
|
||||||
|
{toolCallsExpanded && (
|
||||||
|
<div className="mt-2 space-y-2">
|
||||||
|
{turn.toolCalls.map((toolCall, idx) => (
|
||||||
|
<div
|
||||||
|
key={toolCall.callId || idx}
|
||||||
|
className="p-2 bg-bg-secondary rounded border border-border-default"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-xs font-mono text-text-secondary">
|
||||||
|
<span className="text-brand">⚡</span>
|
||||||
|
<span>{toolCall.name}</span>
|
||||||
|
{toolCall.response && (
|
||||||
|
<span className="text-green-500">✓</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{toolCall.parameters && (
|
||||||
|
<div className="mt-1 text-xs font-mono text-text-muted whitespace-pre-wrap break-all">
|
||||||
|
{toolCall.parameters}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{toolCall.response && (
|
||||||
|
<div className="mt-1 text-xs font-mono text-green-500 whitespace-pre-wrap break-all">
|
||||||
|
{toolCall.response}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-2 text-xs text-text-muted">
|
||||||
|
{startedAt.toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ChatTurn;
|
||||||
Loading…
Reference in New Issue
Block a user