import { memo, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import type { WebSnapshot } from "../../../../protocol/types.ts"; import { buildTrajectory } from "./trajectory.ts"; const EMPTY: NonNullable["entries"] = []; export const Trajectory = memo(function Trajectory({ snapshot, running, }: { snapshot: WebSnapshot; running: boolean; }) { const { t } = useTranslation(); const entries = snapshot.selectedSession?.entries ?? EMPTY; const nodes = useMemo(() => buildTrajectory(entries), [entries]); const [selectedKey, select] = useState(null); const [limit, setLimit] = useState(50); const selectedIndex = nodes.findIndex((node) => node.key === selectedKey); const start = Math.min( Math.max(0, nodes.length - limit), selectedIndex < 0 ? nodes.length : selectedIndex, ); const visible = nodes.slice(start); const selected = visible.find((node) => node.key === selectedKey) ?? visible.at(-1); const truncation = snapshot.selectedSession?.truncation; const output = selected?.result; const message = selected?.message; return (

{t("trajectory")}

{t("trajectoryScope")}

{running &&

{t("trajectoryRunning")}

} {truncation?.truncated && (

{t("trajectoryTruncated", { entries: truncation.entriesOmitted, parts: truncation.messagePartsOmitted, messages: truncation.messagesTruncated, })}

)}
{!nodes.length ? (

{t("trajectoryEmpty")}

) : ( <>
{start > 0 && ( )}
    {visible.map((node) => (
  1. ))}
{selected && (

{selected.title}

{selected.timestamp && (

{t("trajectoryRecordedAt")}:{" "}

)} {selected.callId && (

Tool call ID: {selected.callId}

)} {(message?.truncation || output?.truncation) && (

{t("trajectoryEvidenceTruncated")}

)} {selected.input !== undefined && ( <>

{t("trajectoryArguments")}

{selected.input}
)} {message && selected.kind !== "call" && ( <>

{t("trajectoryRecordedContent")}

{message.content || t("noOutput")}
{message.parts?.some( (part) => part.type === "thinking", ) && (
{t("trajectoryThinking")}
                          {message.parts
                            .filter((part) => part.type === "thinking")
                            .map((part) => part.text)
                            .join("\n\n")}
                        
)} )} {(selected.kind === "call" || selected.kind === "result") && ( <>

{t("trajectoryOutput")}

{t(`trajectory_${selected.outcome ?? "unknown"}`)}

{output ? (
{output.content || t("noOutput")}
) : (

{t("trajectoryMissingResult")}

)} {output?.details !== undefined && (
{t("trajectoryStructured")}
{JSON.stringify(output.details, null, 2)}
)} )} {selected.kind === "event" && !message && (

{t("trajectoryEventOnly")}

)}
)}
)}
); });