From 9c507e471648750a2054b6e7f9ee2090b1a8b832 Mon Sep 17 00:00:00 2001 From: Meleki Date: Fri, 20 Mar 2026 07:07:12 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20TodoWrite-Tool=20=C3=BCber=20CLI=20korre?= =?UTF-8?q?kt=20als=20Todo-Liste=20rendern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TodoWrite → todo_write Mapping in Backend und Frontend ergänzt. Todo-Liste wird jetzt aus dem Input-Array gebaut statt aus dem Result, da die CLI nur einen generischen Erfolgstext zurückgibt. Co-Authored-By: Claude Opus 4.6 --- Services/AnthropicService.Cli.cs | 1 + wwwroot/js/tool-ui.js | 69 ++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Services/AnthropicService.Cli.cs b/Services/AnthropicService.Cli.cs index f685e6a..b8dd81c 100644 --- a/Services/AnthropicService.Cli.cs +++ b/Services/AnthropicService.Cli.cs @@ -340,6 +340,7 @@ public partial class AnthropicService "WebSearch" => "web_search", "WebFetch" => "web_fetch", "NotebookEdit" => "notebook_edit", + "TodoWrite" => "todo_write", _ => cliName?.StartsWith("mcp__") == true ? cliName.Split("__").LastOrDefault() ?? cliName : cliName }; } diff --git a/wwwroot/js/tool-ui.js b/wwwroot/js/tool-ui.js index 214364f..6067c6c 100644 --- a/wwwroot/js/tool-ui.js +++ b/wwwroot/js/tool-ui.js @@ -10,6 +10,7 @@ const CLI_TOOL_NAME_MAP = { 'WebSearch': 'web_search', 'WebFetch': 'web_fetch', 'NotebookEdit': 'notebook_edit', + 'TodoWrite': 'todo_write', 'ToolSearch': 'tool_search', 'mcp__gott-remote__remote_execute': 'remote_execute' }; @@ -195,8 +196,13 @@ function renderToolBoxContent(div, rawName, inputJson, result, isError, sources) foldTarget = preview; } else if (name === 'todo_write') { - // Todo-Liste: kein tool-input, standardmäßig sichtbar, custom parser - if (result) { + // Todo-Liste: aus Input bauen (CLI hat nur generisches Result, API formatiert Result) + // Priorität: parsedInput.todos (immer vorhanden) > result (nur bei API-Pfad formatiert) + if (parsedInput && parsedInput.todos && Array.isArray(parsedInput.todos)) { + const todoList = buildTodoListFromInput(parsedInput.todos, isError); + div.appendChild(todoList); + foldTarget = todoList; + } else if (result) { const todoList = buildTodoList(result, isError); div.appendChild(todoList); foldTarget = todoList; @@ -325,6 +331,52 @@ function buildTodoList(result, isError) { return container; } +/** Baut Todo-Liste direkt aus dem Input-Array (für CLI-Pfad, wo Result nur generischer Text ist). */ +function buildTodoListFromInput(todos, isError) { + const container = document.createElement('div'); + container.className = 'todo-list' + (isError ? ' tool-error' : ''); + + for (var i = 0; i < todos.length; i++) { + const todo = todos[i]; + const row = document.createElement('div'); + row.className = 'todo-item'; + + const status = todo.status || 'pending'; + const icon = status === 'completed' ? '✅' : status === 'in_progress' ? '🔄' : '⬜'; + const statusClass = status === 'completed' ? 'todo-done' : status === 'in_progress' ? 'todo-progress' : 'todo-pending'; + row.classList.add(statusClass); + + const iconSpan = document.createElement('span'); + iconSpan.className = 'todo-icon'; + iconSpan.textContent = icon; + + const idSpan = document.createElement('span'); + idSpan.className = 'todo-id'; + idSpan.textContent = (todo.id || (i + 1)) + '.'; + + const textSpan = document.createElement('span'); + textSpan.className = 'todo-text'; + // CLI hat "activeForm" statt nur "content" – zeige content (Imperativ-Form) + textSpan.textContent = todo.content || todo.activeForm || ''; + + row.appendChild(iconSpan); + row.appendChild(idSpan); + row.appendChild(textSpan); + + if (todo.priority) { + const prioSpan = document.createElement('span'); + prioSpan.className = 'todo-prio todo-prio-' + todo.priority.toLowerCase(); + prioSpan.textContent = todo.priority; + row.appendChild(prioSpan); + } + + container.appendChild(row); + } + + parseFluentEmojis(container); + return container; +} + function finishToolBox(div, data) { if (!div) return; renderToolBoxContent(div, data.name, data.input, data.result, data.isError, data.sources); @@ -334,10 +386,21 @@ function finishToolBox(div, data) { /** Fügt ein Tool-Result nachträglich an eine bestehende Tool-Box an (CLI tool_result Events). */ function appendToolResult(div, result, isError) { if (!div || !result) return; + var toolName = div.dataset.toolName; + + // todo_write: Liste wurde bereits aus Input gebaut (in renderToolBoxContent) + if (toolName === 'todo_write') { + if (div.querySelector('.todo-list')) return; // Bereits vorhanden + // Fallback: versuche aus Result zu parsen (API-Pfad) + var todoList = buildTodoList(result, isError); + div.appendChild(todoList); + scrollDown(); + return; + } + // Nur anfügen wenn noch kein Result vorhanden if (div.querySelector('.tool-result')) return; // Bei edit_file/write_file: Diff reicht, kein extra Result nötig - var toolName = div.dataset.toolName; if (toolName === 'edit_file' || toolName === 'write_file') return; var rd = document.createElement('div');