| | import { useRef, useEffect } from "react"; |
| | import type { DatasetInfo, InstanceDetail, TrajectoryMode } from "../types"; |
| | import { RawBubble, AtifBubble } from "./ChatBubble"; |
| | import { StepDetail } from "./StepDetail"; |
| |
|
| | interface Props { |
| | dataset: DatasetInfo; |
| | detail: InstanceDetail; |
| | mode: TrajectoryMode; |
| | isSingle: boolean; |
| | } |
| |
|
| | export function TrajectoryView({ dataset, detail, mode, isSingle }: Props) { |
| | const scrollRef = useRef<HTMLDivElement>(null); |
| |
|
| | useEffect(() => { |
| | if (scrollRef.current) { |
| | scrollRef.current.scrollTop = 0; |
| | } |
| | }, [detail.instance_id, mode]); |
| |
|
| | return ( |
| | <div |
| | className={`flex flex-col overflow-hidden ${ |
| | isSingle ? "flex-1" : "flex-1 border-r border-gray-800 last:border-r-0" |
| | }`} |
| | > |
| | {/* Header */} |
| | <div className="px-4 py-2 bg-gray-900/30 border-b border-gray-800 flex items-center justify-between"> |
| | <div className="flex items-center gap-2"> |
| | <span |
| | className={`w-2 h-2 rounded-full ${ |
| | detail.resolved ? "bg-emerald-400" : "bg-red-400" |
| | }`} |
| | /> |
| | <span className="text-xs text-gray-300 truncate" title={dataset.repo}> |
| | {dataset.name} |
| | </span> |
| | </div> |
| | <div className="flex items-center gap-2"> |
| | <span className="text-xs text-gray-500"> |
| | {detail.agent || detail.model} |
| | </span> |
| | {detail.duration_seconds > 0 && ( |
| | <span className="text-xs text-gray-600"> |
| | {Math.round(detail.duration_seconds)}s |
| | </span> |
| | )} |
| | </div> |
| | </div> |
| | |
| | {/* Chat stream */} |
| | <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 py-4 space-y-1"> |
| | {mode === "raw" && ( |
| | <> |
| | {detail.raw_steps.map((step) => ( |
| | <RawBubble key={step.index} step={step} /> |
| | ))} |
| | {detail.raw_steps.length === 0 && ( |
| | <div className="text-center text-gray-500 text-sm mt-8"> |
| | No raw trajectory data available. |
| | {detail.n_atif_steps > 0 && " Try ATIF Steps mode."} |
| | </div> |
| | )} |
| | </> |
| | )} |
| | |
| | {mode === "atif" && ( |
| | <> |
| | {detail.atif.steps.map((step) => ( |
| | <AtifBubble key={step.index} step={step} /> |
| | ))} |
| | {detail.atif.steps.length === 0 && ( |
| | <div className="text-center text-gray-500 text-sm mt-8"> |
| | No ATIF trajectory data available. |
| | {detail.n_raw_steps > 0 && " Try Raw Messages mode."} |
| | </div> |
| | )} |
| | </> |
| | )} |
| |
|
| | {} |
| | <div className="flex justify-center pt-4"> |
| | <span |
| | className={`px-4 py-2 rounded-lg text-sm font-medium ${ |
| | detail.resolved |
| | ? "bg-emerald-900/50 text-emerald-300 border border-emerald-700" |
| | : "bg-red-900/50 text-red-300 border border-red-700" |
| | }`} |
| | > |
| | {detail.resolved ? "RESOLVED" : "FAILED"} |
| | {detail.error && ( |
| | <span className="ml-2 text-xs opacity-70">({detail.error})</span> |
| | )} |
| | </span> |
| | </div> |
| | </div> |
| |
|
| | {} |
| | <StepDetail dsId={dataset.id} instanceId={detail.instance_id} /> |
| | </div> |
| | ); |
| | } |
| |
|