import { For, Show } from "solid-js" import type { ToolState } from "@opencode-ai/sdk/v2" import type { ToolRenderer } from "../types" import { readToolStatePayload } from "../utils" import { useI18n, tGlobal } from "../../../lib/i18n" export type TodoViewStatus = "pending" | "in_progress" | "completed" | "cancelled" export interface TodoViewItem { id: string content: string status: TodoViewStatus } function normalizeTodoStatus(rawStatus: unknown): TodoViewStatus { if (rawStatus === "completed" || rawStatus === "in_progress" || rawStatus === "cancelled") return rawStatus return "pending" } function extractTodosFromState(state?: ToolState): TodoViewItem[] { if (!state) return [] const { metadata } = readToolStatePayload(state) const todos = Array.isArray((metadata as any).todos) ? (metadata as any).todos : [] const items: TodoViewItem[] = [] for (let index = 0; index < todos.length; index++) { const todo = todos[index] const content = typeof todo?.content === "string" ? todo.content.trim() : "" if (!content) continue const status = normalizeTodoStatus((todo as any).status) const id = typeof todo?.id === "string" && todo.id.length > 0 ? todo.id : `${index}-${content}` items.push({ id, content, status }) } return items } function summarizeTodos(todos: TodoViewItem[]) { return todos.reduce( (acc, todo) => { acc.total += 1 acc[todo.status] = (acc[todo.status] || 0) + 1 return acc }, { total: 0, pending: 0, in_progress: 0, completed: 0, cancelled: 0 } as Record, ) } function getTodoStatusLabel(t: (key: string) => string, status: TodoViewStatus): string { switch (status) { case "completed": return t("toolCall.renderer.todo.status.completed") case "in_progress": return t("toolCall.renderer.todo.status.inProgress") case "cancelled": return t("toolCall.renderer.todo.status.cancelled") default: return t("toolCall.renderer.todo.status.pending") } } interface TodoListViewProps { state?: ToolState emptyLabel?: string showStatusLabel?: boolean } export function TodoListView(props: TodoListViewProps) { const { t } = useI18n() const todos = extractTodosFromState(props.state) const counts = summarizeTodos(todos) if (counts.total === 0) { return
{props.emptyLabel ?? t("toolCall.renderer.todo.empty")}
} return (
{(todo) => { const label = getTodoStatusLabel(t, todo.status) return (
{todo.content} {label}
) }}
) } export function getTodoTitle(state?: ToolState): string { if (!state) return tGlobal("toolCall.renderer.todo.title.plan") const todos = extractTodosFromState(state) if (state.status !== "completed" || todos.length === 0) return tGlobal("toolCall.renderer.todo.title.plan") const counts = summarizeTodos(todos) if (counts.pending === counts.total) return tGlobal("toolCall.renderer.todo.title.creating") if (counts.completed === counts.total) return tGlobal("toolCall.renderer.todo.title.completing") return tGlobal("toolCall.renderer.todo.title.updating") } export const todoRenderer: ToolRenderer = { tools: ["todowrite", "todoread"], getAction: () => tGlobal("toolCall.renderer.action.planning"), getTitle({ toolState }) { return getTodoTitle(toolState()) }, renderBody({ toolState }) { const state = toolState() if (!state) return null return }, }