| | import { useState } from "react"; |
| | import type { RlmIterDetail } from "../types"; |
| |
|
| | interface IterationDetailProps { |
| | data: RlmIterDetail; |
| | } |
| |
|
| | function parsePromptMessages(promptStr: string): { role: string; content: string }[] { |
| | try { |
| | const parsed = JSON.parse(promptStr); |
| | if (Array.isArray(parsed)) return parsed; |
| | } catch { } |
| | return [{ role: "raw", content: promptStr }]; |
| | } |
| |
|
| | const roleColors: Record<string, string> = { |
| | system: "border-violet-500 bg-violet-950", |
| | user: "border-emerald-500 bg-emerald-950", |
| | assistant: "border-sky-500 bg-sky-950", |
| | raw: "border-gray-500 bg-gray-900", |
| | }; |
| |
|
| | export default function IterationDetail({ data }: IterationDetailProps) { |
| | const [promptExpanded, setPromptExpanded] = useState(false); |
| | const messages = parsePromptMessages(data.prompt); |
| |
|
| | return ( |
| | <div className="space-y-4 border border-gray-700 rounded-lg p-4 bg-gray-900"> |
| | {/* Stats */} |
| | <div className="flex gap-4 text-xs text-gray-400"> |
| | <span>Model: <span className="text-gray-200">{data.model}</span></span> |
| | <span>In: <span className="text-emerald-300">{(data.input_tokens / 1000).toFixed(1)}k</span></span> |
| | <span>Out: <span className="text-emerald-300">{(data.output_tokens / 1000).toFixed(1)}k</span></span> |
| | <span>Time: <span className="text-gray-200">{data.execution_time.toFixed(1)}s</span></span> |
| | </div> |
| | |
| | {/* Prompt section (collapsible) */} |
| | <div> |
| | <button |
| | className="flex items-center gap-2 text-sm font-semibold text-gray-300 hover:text-gray-100 mb-2" |
| | onClick={() => setPromptExpanded(!promptExpanded)} |
| | > |
| | <span className={`transform transition-transform ${promptExpanded ? "rotate-90" : ""}`}> |
| | ▶ |
| | </span> |
| | Prompt ({messages.length} messages) |
| | </button> |
| | {promptExpanded && ( |
| | <div className="space-y-2 ml-4"> |
| | {messages.map((msg, i) => ( |
| | <div |
| | key={i} |
| | className={`border-l-2 rounded-r-lg px-3 py-2 ${roleColors[msg.role] || roleColors.raw}`} |
| | > |
| | <div className="text-xs font-semibold text-gray-400 mb-1 uppercase">{msg.role}</div> |
| | <div className="text-sm text-gray-200 whitespace-pre-wrap max-h-96 overflow-y-auto"> |
| | {msg.content.length > 8000 ? msg.content.slice(0, 8000) + "\n...(truncated)" : msg.content} |
| | </div> |
| | </div> |
| | ))} |
| | </div> |
| | )} |
| | </div> |
| | |
| | {/* Response */} |
| | <div> |
| | <div className="text-sm font-semibold text-gray-300 mb-2">Response</div> |
| | <div className="bg-gray-800 border border-gray-700 rounded-lg p-3"> |
| | <div className="text-sm text-gray-200 whitespace-pre-wrap max-h-96 overflow-y-auto font-mono"> |
| | {data.response} |
| | </div> |
| | </div> |
| | </div> |
| | |
| | {/* Code Blocks */} |
| | {data.code_blocks.length > 0 && ( |
| | <div> |
| | <div className="text-sm font-semibold text-gray-300 mb-2"> |
| | Code Blocks ({data.code_blocks.length}) |
| | </div> |
| | <div className="space-y-3"> |
| | {data.code_blocks.map((cb, i) => ( |
| | <div key={i} className="border border-gray-700 rounded-lg overflow-hidden"> |
| | <div className="bg-gray-800 px-3 py-1.5 text-xs text-gray-400 border-b border-gray-700 flex items-center gap-2"> |
| | <span className="text-emerald-400 font-mono">python</span> |
| | <span>Block {i + 1}</span> |
| | </div> |
| | <pre className="bg-gray-900 p-3 text-sm text-gray-200 overflow-x-auto font-mono leading-relaxed"> |
| | {cb.code} |
| | </pre> |
| | {cb.stdout && ( |
| | <div className="border-t border-gray-700"> |
| | <div className="bg-gray-800 px-3 py-1 text-xs text-gray-400">stdout</div> |
| | <pre className="bg-emerald-950 p-3 text-sm text-emerald-200 overflow-x-auto font-mono"> |
| | {cb.stdout} |
| | </pre> |
| | </div> |
| | )} |
| | </div> |
| | ))} |
| | </div> |
| | </div> |
| | )} |
| | |
| | {/* Final Answer */} |
| | {data.final_answer && ( |
| | <div className="bg-emerald-950 border border-emerald-700 rounded-lg p-4"> |
| | <div className="text-xs font-semibold text-emerald-400 mb-2">Final Answer</div> |
| | <div className="text-sm text-gray-200 whitespace-pre-wrap max-h-96 overflow-y-auto"> |
| | {data.final_answer} |
| | </div> |
| | </div> |
| | )} |
| | </div> |
| | ); |
| | } |
| |
|