import { useEffect, useState, useMemo } from "react"; // ── Types ───────────────────────────────────────────────────────────────── type Template = "gpt-oss" | "qwen"; interface Message { role: "user" | "assistant"; content: string; } interface Row { query_id: string; excerpt: string; messages_gpt: Message[] | null; messages_qwen: Message[] | null; } // ── Color palette ───────────────────────────────────────────────────────── const COLORS = { user: { bg: "bg-amber-950/40", border: "border-amber-600", label: "text-amber-400", tag: "USER MESSAGE" }, reasoning: { bg: "bg-purple-950/40", border: "border-purple-600", label: "text-purple-400", tag: "REASONING" }, tool_call: { bg: "bg-blue-950/40", border: "border-blue-600", label: "text-blue-400", tag: "TOOL CALL" }, tool_resp: { bg: "bg-gray-800/60", border: "border-gray-600", label: "text-gray-400", tag: "TOOL RESPONSE" }, }; // ── Block component ─────────────────────────────────────────────────────── function Block({ kind, label, children }: { kind: keyof typeof COLORS; label?: string; children: React.ReactNode; }) { const c = COLORS[kind]; return (
{label ?? c.tag}
{children}
); } // ── Left panel: parse excerpt (newline-separated JSON objects) ──────────── interface ExcerptItem { type: "reasoning" | "function_call" | "function_call_output" | string; [key: string]: unknown; } function parseExcerptItems(excerpt: string): ExcerptItem[] { return excerpt .split(/\n\n/) .map(s => s.trim()) .filter(Boolean) .flatMap(chunk => { try { const obj = JSON.parse(chunk); return typeof obj === "object" && obj !== null && "type" in obj ? [obj as ExcerptItem] : []; } catch { return []; } }); } function ExcerptPanel({ excerpt, userContent }: { excerpt: string; userContent: string }) { const items = useMemo(() => parseExcerptItems(excerpt), [excerpt]); return (
          {userContent}
        
{items.map((item, i) => { const raw = JSON.stringify(item, null, 2); if (item.type === "reasoning") { return (
{raw}
); } if (item.type === "function_call") { return (
{raw}
); } if (item.type === "function_call_output") { return (
{raw}
); } return (
{raw}
); })}
); } // ── Right panel: split assistant content into typed segments ────────────── type SegKind = "reasoning" | "tool_call"; /** * For gpt-oss: split at .... * Segments outside tags are reasoning; tags themselves are tool_call. */ function splitGptContent(content: string): { kind: SegKind; text: string }[] { const segs: { kind: SegKind; text: string }[] = []; const re = /([\s\S]*?<\/tool_call>)/g; let last = 0; let m: RegExpExecArray | null; while ((m = re.exec(content)) !== null) { const pre = content.slice(last, m.index).trim(); if (pre) segs.push({ kind: "reasoning", text: pre }); segs.push({ kind: "tool_call", text: m[1] }); last = m.index + m[1].length; } const tail = content.slice(last).trim(); if (tail) segs.push({ kind: "reasoning", text: tail }); return segs; } /** * For qwen: reasoning is inside ..., tool calls in .... * Both may appear in one assistant message; extract all in order. */ function splitQwenContent(content: string): { kind: SegKind; text: string }[] { const segs: { kind: SegKind; text: string }[] = []; const re = /([\s\S]*?<\/think>|[\s\S]*?<\/tool_call>)/g; let last = 0; let m: RegExpExecArray | null; while ((m = re.exec(content)) !== null) { const gap = content.slice(last, m.index).trim(); if (gap) segs.push({ kind: "reasoning", text: gap }); if (m[1].startsWith("")) { segs.push({ kind: "reasoning", text: m[1] }); } else { segs.push({ kind: "tool_call", text: m[1] }); } last = m.index + m[1].length; } const tail = content.slice(last).trim(); if (tail) segs.push({ kind: "reasoning", text: tail }); return segs; } function AssistantBlock({ content, template }: { content: string; template: Template }) { const segs = template === "qwen" ? splitQwenContent(content) : splitGptContent(content); const hasToolCall = segs.some(s => s.kind === "tool_call"); const outerColor = COLORS.reasoning; return (
{hasToolCall ? "REASONING + TOOL CALL" : "REASONING"}
{segs.map((seg, j) => { if (seg.kind === "tool_call") { const tc = COLORS.tool_call; return (
TOOL CALL
{seg.text}
); } return (
            {seg.text}
          
); })}
); } function MessagesPanel({ messages, template }: { messages: Message[]; template: Template }) { return (
{messages.map((msg, i) => { if (msg.role === "user" && i === 0) { return (
{msg.content}
); } if (msg.role === "user") { return (
{msg.content}
); } return ; })}
); } // ── Legend ──────────────────────────────────────────────────────────────── function Legend() { return (
Legend {(Object.entries(COLORS) as [keyof typeof COLORS, typeof COLORS[keyof typeof COLORS]][]).map(([k, c]) => ( {c.tag} ))}
); } // ── Template dropdown ───────────────────────────────────────────────────── function TemplateDropdown({ value, onChange }: { value: Template; onChange: (t: Template) => void }) { return ( ); } // ── Main component ──────────────────────────────────────────────────────── export default function SftDiffApp() { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedIdx, setSelectedIdx] = useState(0); const [search, setSearch] = useState(""); const [template, setTemplate] = useState