Add debug transcript components

This commit is contained in:
Xin Wang
2026-03-13 07:11:48 +08:00
parent def6a11338
commit d87d3616e9
7 changed files with 1096 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
import React, { useMemo } from 'react';
import { MessageSquare } from 'lucide-react';
import { cn } from '@/lib/utils';
import TranscriptAssistantBlock from './TranscriptAssistantBlock';
import TranscriptItem from './TranscriptItem';
import type { DebugTranscriptRow, DebugTranscriptTextRow, DebugTranscriptToolRow } from './types';
type AssistantRenderBlock = {
kind: 'assistant-block';
id: string;
message?: DebugTranscriptTextRow;
tools: DebugTranscriptToolRow[];
};
type TranscriptRenderItem =
| { kind: 'row'; id: string; row: DebugTranscriptRow }
| AssistantRenderBlock;
const getCorrelationKey = (row: Pick<DebugTranscriptRow, 'turnId' | 'utteranceId' | 'responseId'>) => {
if (row.responseId) return `response:${row.responseId}`;
if (row.turnId && row.utteranceId) return `turn:${row.turnId}:utterance:${row.utteranceId}`;
if (row.turnId) return `turn:${row.turnId}`;
if (row.utteranceId) return `utterance:${row.utteranceId}`;
return '';
};
const buildRenderItems = (messages: DebugTranscriptRow[]): TranscriptRenderItem[] => {
const items: TranscriptRenderItem[] = [];
const assistantBlocks = new Map<string, AssistantRenderBlock>();
messages.forEach((row) => {
if (row.kind === 'text' && row.role === 'assistant') {
const correlationKey = getCorrelationKey(row);
if (!correlationKey) {
items.push({ kind: 'row', id: row.id, row });
return;
}
const existingBlock = assistantBlocks.get(correlationKey);
if (existingBlock) {
existingBlock.message = row;
return;
}
const block: AssistantRenderBlock = {
kind: 'assistant-block',
id: `assistant-block:${correlationKey}`,
message: row,
tools: [],
};
assistantBlocks.set(correlationKey, block);
items.push(block);
return;
}
if (row.kind === 'tool') {
const correlationKey = getCorrelationKey(row);
if (!correlationKey) {
items.push({ kind: 'row', id: row.id, row });
return;
}
const existingBlock = assistantBlocks.get(correlationKey);
if (existingBlock) {
existingBlock.tools.push(row);
return;
}
const block: AssistantRenderBlock = {
kind: 'assistant-block',
id: `assistant-block:${correlationKey}`,
tools: [row],
};
assistantBlocks.set(correlationKey, block);
items.push(block);
return;
}
items.push({ kind: 'row', id: row.id, row });
});
return items;
};
const TranscriptList: React.FC<{
scrollRef: React.RefObject<HTMLDivElement | null>;
messages: DebugTranscriptRow[];
isLoading: boolean;
className?: string;
}> = ({ scrollRef, messages, isLoading, className = '' }) => {
const renderItems = useMemo(() => buildRenderItems(messages), [messages]);
return (
<div
ref={scrollRef}
className={cn(
'flex-1 overflow-y-auto overflow-x-hidden rounded-md border border-white/5 bg-black/20 p-2 min-h-0 custom-scrollbar',
className
)}
>
{messages.length === 0 && !isLoading ? (
<div className="flex h-full flex-col items-center justify-center space-y-3 text-muted-foreground/60">
<MessageSquare className="h-8 w-8 opacity-20" />
<p className="text-xs"></p>
</div>
) : (
<div className="space-y-4 pb-4">
{renderItems.map((item) =>
item.kind === 'assistant-block' ? (
<TranscriptAssistantBlock key={item.id} message={item.message} tools={item.tools} />
) : (
<TranscriptItem key={item.id} row={item.row} />
)
)}
</div>
)}
</div>
);
};
export default React.memo(TranscriptList);