import { IconCheck as Check, IconListCheck as ListTodo } from '@tabler/icons-react' import { toPublicErrorPayload } from '@/lib/errors/public-error' import { Part, TodoItem } from '@/lib/types/ai' import { cn } from '@/lib/utils' import { useArtifact } from './artifact/artifact-context' import { CollapsibleMessage } from './collapsible-message' import ProcessHeader from './process-header' import TodoListContent from './todo-list-content' interface ToolTodoDisplayProps { tool: 'todoWrite' state: | 'input-streaming' | 'input-available' | 'output-available' | 'output-error' input?: { todos?: TodoItem[] } output?: { todos?: TodoItem[] message?: string completedCount?: number totalCount?: number } errorText?: string toolCallId: string isOpen?: boolean onOpenChange?: (open: boolean) => void borderless?: boolean isFirst?: boolean isLast?: boolean } export function ToolTodoDisplay({ tool, state, output, errorText, toolCallId, isOpen = false, onOpenChange, borderless = false, isFirst = false, isLast = false }: ToolTodoDisplayProps) { const { open: openArtifact } = useArtifact() // Calculate counts for display const completedCount = output?.completedCount ?? (output?.todos ? output.todos.filter(t => t.status === 'completed').length : 0) const totalCount = output?.totalCount ?? (output?.todos ? output.todos.length : 0) const errorMessage = state === 'output-error' ? toPublicErrorPayload(errorText ?? output?.message, { fallbackMessage: 'Todo tool failed' }).error : undefined const isLoading = state === 'input-streaming' || state === 'input-available' const openInspector = () => { if (!(state === 'output-available' && output)) return const part: Part = { type: 'tool-todoWrite', toolCallId, state, input: { todos: output.todos || [] }, output } openArtifact(part) } const header = ( {state === 'output-available' && output ? output.message || 'Updated tasks' : 'Updating tasks...'} } meta={ state === 'output-available' && totalCount > 0 ? ( {completedCount === totalCount ? ( ) : null} ({completedCount}/{totalCount}) ) : undefined } ariaExpanded={isOpen} /> ) return (
{/* Rails for header - show based on position */} {borderless && ( <> {!isFirst && (
)} {!isLast && (
)} )}
{/* Rail space - always reserved when grouped */} {borderless && ( <>
)}
{state === 'output-available' ? ( ) : state === 'output-error' ? (
{errorMessage}
) : null}
) }