import { formatCost, formatLatency, formatTokens, } from "@/app/workspace/dashboard/utils/chartUtils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"; import { getProviderLabel, ProviderName, RequestTypeColors, RequestTypeLabels, Status, StatusBarColors, } from "@/lib/constants/logs"; import { ChatMessageContent, LogEntry, ResponsesMessageContentBlock, } from "@/lib/types/logs"; import { cn } from "@/lib/utils"; import { ColumnDef } from "@tanstack/react-table"; import { format, formatDistanceToNow } from "date-fns"; import { ArrowUpDown, Trash2 } from "lucide-react"; function getAssistantToolCallSummary(log?: LogEntry): string { const toolCalls = log?.output_message?.tool_calls || []; return toolCalls .map((toolCall) => { const name = toolCall?.function?.name; if (!name) { return ""; } const argumentsText = toolCall?.function?.arguments?.trim(); return argumentsText ? `${name}(${argumentsText})` : name; }) .filter(Boolean) .join("\n"); } function getMessageFromContent(content?: ChatMessageContent): string { if (content == undefined) { return ""; } if (typeof content === "string") { return content; } let lastTextContentBlock = ""; for (const block of content) { if ( (block.type === "text" || block.type === "input_text" || block.type === "output_text") && block.text ) { lastTextContentBlock = block.text; } } return lastTextContentBlock; } export function getRealtimeTurnMessages(log?: LogEntry): { tool?: string; user?: string; assistant?: string; assistantToolCall?: string; } { const toolMessages = log?.input_history?.filter((message) => message.role === "tool") || []; const userMessages = log?.input_history?.filter((message) => message.role === "user") || []; return { tool: toolMessages .map((m) => getMessageFromContent(m.content)) .filter(Boolean) .join("\n") || "", user: userMessages .map((m) => getMessageFromContent(m.content)) .filter(Boolean) .join("\n") || "", assistant: log?.output_message ? getMessageFromContent(log.output_message.content) : "", assistantToolCall: getAssistantToolCallSummary(log), }; } export function getMessage(log?: LogEntry) { if (log?.object === "list_models") { return "N/A"; } if (log?.object === "realtime.turn") { const messages = getRealtimeTurnMessages(log); const parts = [ messages.tool ? `Tool Result: ${messages.tool}` : "", messages.user ? `User: ${messages.user}` : "", messages.assistantToolCall ? `Assistant Tool Call: ${messages.assistantToolCall}` : "", messages.assistant ? `Assistant: ${messages.assistant}` : "", ].filter(Boolean); if (parts.length > 0) { return parts.join("\n"); } return ""; } if (log?.input_history && log.input_history.length > 0) { return getMessageFromContent( log.input_history[log.input_history.length - 1].content, ); } else if ( log?.responses_input_history && log.responses_input_history.length > 0 ) { let lastMessage = log.responses_input_history[log.responses_input_history.length - 1]; let lastMessageContent = lastMessage.content; if (typeof lastMessageContent === "string") { return lastMessageContent; } let lastTextContentBlock = ""; for (const block of (lastMessageContent ?? []) as ResponsesMessageContentBlock[]) { if (block.text && block.text !== "") { lastTextContentBlock = block.text; } } // If no content found in content field, check output field for Responses API if (!lastTextContentBlock && lastMessage.output) { // Handle output field - it could be a string, an array of content blocks, or a computer tool call output data if (typeof lastMessage.output === "string") { return lastMessage.output; } else if (Array.isArray(lastMessage.output)) { return lastMessage.output.map((block) => block.text).join("\n"); } else if ( lastMessage.output.type && lastMessage.output.type === "computer_screenshot" ) { return lastMessage.output.image_url; } } return lastTextContentBlock ?? ""; } else if (log?.output_message) { return getMessageFromContent(log.output_message.content); } else if (log?.speech_input) { return log.speech_input.input; } else if (log?.transcription_input) { return "Audio file"; } else if (log?.image_generation_input?.prompt) { return log.image_generation_input.prompt; } const obj = log?.object as string | undefined; if ( obj === "image_edit" || obj === "image_edit_stream" || obj === "image_variation" ) { return "Image file"; } if (log?.content_summary) { return log.content_summary; } return ""; } export function LogMessageCell({ log, maxWidth = "max-w-[400px]", }: { log: LogEntry; maxWidth?: string; }) { const input = getMessage(log); const isLargePayload = log.is_large_payload_request || log.is_large_payload_response; const realtimeMessages = log.object === "realtime.turn" ? getRealtimeTurnMessages(log) : null; return (
{isLargePayload && ( LP )} {realtimeMessages && (realtimeMessages.tool || realtimeMessages.user || realtimeMessages.assistantToolCall || realtimeMessages.assistant) ? (
{realtimeMessages.tool ? (
Tool Result: {realtimeMessages.tool}
) : null} {realtimeMessages.user ? (
User: {realtimeMessages.user}
) : null} {realtimeMessages.assistantToolCall ? (
Assistant Tool Call: {realtimeMessages.assistantToolCall}
) : null} {realtimeMessages.assistant ? (
Assistant: {realtimeMessages.assistant}
) : null}
) : (
{input || (isLargePayload ? `Large payload ${log.is_large_payload_request && log.is_large_payload_response ? "request & response" : log.is_large_payload_request ? "request" : "response"}` : "-")}
)}
); } export const createColumns = ( onDelete: (log: LogEntry) => void, hasDeleteAccess = true, metadataKeys: string[] = [], ): ColumnDef[] => { const baseColumns: ColumnDef[] = [ { accessorKey: "status", header: "", size: 8, maxSize: 8, cell: ({ row }) => { const status = row.original.status as Status; return (
); }, }, { accessorKey: "timestamp", header: ({ column }) => ( ), size: 130, cell: ({ row }) => { const timestamp = row.original.timestamp; const date = timestamp ? new Date(timestamp) : null; const isValid = date && date.toString() !== "Invalid Date"; if (!isValid) { return
N/A
; } return (
{format(date, "MMM dd HH:mm:ss")} {formatDistanceToNow(date, { addSuffix: true })}
); }, }, { id: "request_type", header: "Type", size: 150, cell: ({ row }) => { return ( { RequestTypeLabels[ row.original.object as keyof typeof RequestTypeLabels ] } ); }, }, { accessorKey: "input", header: "Message", size: 350, cell: ({ row }) => , }, { accessorKey: "model", header: "Model", size: 190, cell: ({ row }) => { const provider = row.original.provider as ProviderName | undefined; const model = row.original.model; return (
{provider ? ( ) : null}
{model || "N/A"} {provider ? getProviderLabel(provider) : "N/A"}
); }, }, { accessorKey: "latency", header: ({ column }) => ( ), size: 170, cell: ({ row }) => { const latency = row.original.latency; if (latency === undefined || latency === null) { return
N/A
; } const tone = latency >= 5000 ? "bg-red-500" : latency >= 2000 ? "bg-amber-500" : "bg-emerald-500"; const pct = Math.min(100, (latency / 5000) * 100); return (
{formatLatency(latency)}
); }, }, { accessorKey: "tokens", header: ({ column }) => ( ), size: 190, cell: ({ row }) => { const tokenUsage = row.original.token_usage; if (!tokenUsage) { return
N/A
; } const prompt = tokenUsage.prompt_tokens ?? 0; const completion = tokenUsage.completion_tokens ?? 0; const total = tokenUsage.total_tokens ?? 0; const hasSplit = tokenUsage.completion_tokens != null && tokenUsage.prompt_tokens != null; const splitBase = prompt + completion || 1; const inPct = (prompt / splitBase) * 100; return (
{formatTokens(total)} {hasSplit && (
)}
{hasSplit && (
{formatTokens(prompt)} / {formatTokens(completion)}
)}
); }, }, { accessorKey: "cost", header: ({ column }) => ( ), size: 120, cell: ({ row }) => { if (row.original.cost == null) { return
N/A
; } return (
{formatCost(row.original.cost)}
); }, }, ]; const metadataColumns: ColumnDef[] = metadataKeys.map((key) => ({ id: `metadata_${key}`, header: key.charAt(0).toUpperCase() + key.slice(1), size: 126, cell: ({ row }) => { const value = row.original.metadata?.[key]; return (
{value ?? "-"}
); }, })); const actionsColumn: ColumnDef = { id: "actions", size: 72, cell: ({ row }) => { const log = row.original; return ( ); }, }; return [...baseColumns, ...metadataColumns, actionsColumn]; };