Frontend: TaskCreate/TaskUpdate als aggregierte Checkliste rendern
Die CLI nutzt inzwischen TaskCreate/TaskUpdate statt TodoWrite fuers Todo-Tracking. Diese fielen in den generischen Tool-Box-Pfad und wurden als rohe Tool-Calls mit JSON-Input angezeigt statt als Checkliste. Da die neuen Tools inkrementell sind (ein Call pro Anlage/Statuswechsel), werden die Einzel-Events zu EINER Checkliste aggregiert: - tool-ui.js: Task-Tracker (applyTaskEvent/ingestTaskEvent), Box-Aufbau mit wiederverwendeten todo-list-Styles + Fortschritts-Badge n/m; sealTaskBox/sealLiveTaskList zum Segmentieren - chat.js: Live-Pfad (SSE) haengt Task-Events in die aggregierte Box statt generischer Tool-Boxen; Reset beim Stream-Start - history.js: Replay-Pfad mit eigenem Tracker pro Chain; zugehoerige tool_result-Messages werden uebersprungen (kein Doppel-Render) Segmentierung: ein zusammenhaengender Task-Lauf bleibt EINE Box. Sobald dazwischen echter Inhalt gerendert wird (Fliesstext oder anderes Tool), wird die Box versiegelt und das naechste Task-Event oeffnet eine neue Box darunter - so bleibt die chronologische Reihenfolge erhalten und Narration zwischen den Tasks sichtbar. Der Task-Stand bleibt kumulativ, der Fortschritt zaehlt ueber die Segmente hinweg hoch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be44235ca9
commit
f38f3a42d1
3 files changed
+205
-1
No files matched your search
@@ -377,6 +377,163 @@ function buildTodoListFromInput(todos, isError) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// ── Task-Liste (CLI Task*-Tools) ──
|
||||
// Die CLI nutzt inzwischen TaskCreate/TaskUpdate (statt des früheren TodoWrite) für das
|
||||
// Todo-Tracking. Diese Tools sind inkrementell (ein Call pro Task-Anlage/Statuswechsel).
|
||||
// Damit die frühere Checklisten-Visualisierung erhalten bleibt, aggregieren wir sie zu
|
||||
// EINER Box, die in-place aktualisiert wird – statt viele generische Tool-Boxen zu zeigen.
|
||||
|
||||
/** True für die Task-Tracking-Tools, die zur aggregierten Checkliste gehören. */
|
||||
function isTaskListTool(rawName) {
|
||||
return rawName === 'TaskCreate' || rawName === 'TaskUpdate';
|
||||
}
|
||||
|
||||
/** Neuer, leerer Task-Tracker (pro Chain bzw. pro Live-Stream). */
|
||||
function makeTaskTracker() {
|
||||
return { tasks: [], byId: {}, box: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wendet ein TaskCreate/TaskUpdate-Event auf den Tracker an.
|
||||
* - TaskCreate: neuer Task; ID sequentiell (die CLI vergibt #1..#N in Erstell-Reihenfolge),
|
||||
* ein evtl. vorhandenes Result "Task #N ..." bestätigt/überschreibt die ID.
|
||||
* - TaskUpdate: setzt den Status des per taskId referenzierten Tasks.
|
||||
*/
|
||||
function applyTaskEvent(tracker, rawName, inputJson, resultText) {
|
||||
var input = null;
|
||||
try { input = JSON.parse(inputJson); } catch (e) {}
|
||||
if (!input || typeof input !== 'object') input = {};
|
||||
|
||||
if (rawName === 'TaskCreate') {
|
||||
var id = tracker.tasks.length + 1;
|
||||
var m = resultText && /Task #(\d+)/.exec(resultText);
|
||||
if (m) id = parseInt(m[1], 10);
|
||||
var task = {
|
||||
id: String(id),
|
||||
subject: input.subject || input.description || ('Aufgabe ' + id),
|
||||
status: 'pending'
|
||||
};
|
||||
if (!tracker.byId[task.id]) tracker.tasks.push(task);
|
||||
tracker.byId[task.id] = task;
|
||||
} else if (rawName === 'TaskUpdate') {
|
||||
var tid = String(input.taskId != null ? input.taskId : (input.id != null ? input.id : ''));
|
||||
if (!tid) return tracker;
|
||||
var existing = tracker.byId[tid];
|
||||
if (existing) {
|
||||
if (input.status) existing.status = input.status;
|
||||
} else {
|
||||
// Update ohne vorheriges Create (z.B. paginierter History-Ausschnitt) → Platzhalter
|
||||
var placeholder = { id: tid, subject: 'Aufgabe ' + tid, status: input.status || 'pending' };
|
||||
tracker.byId[tid] = placeholder;
|
||||
tracker.tasks.push(placeholder);
|
||||
}
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/** Rendert die Task-Items (Checkboxen) in den Listen-Container (in-place, leert vorher). */
|
||||
function renderTaskListItems(list, tracker) {
|
||||
list.innerHTML = '';
|
||||
var done = 0;
|
||||
for (var i = 0; i < tracker.tasks.length; i++) {
|
||||
var task = tracker.tasks[i];
|
||||
var status = task.status || 'pending';
|
||||
if (status === 'completed') done++;
|
||||
var icon = status === 'completed' ? '✅' : status === 'in_progress' ? '🔄' : '⬜';
|
||||
var statusClass = status === 'completed' ? 'todo-done' : status === 'in_progress' ? 'todo-progress' : 'todo-pending';
|
||||
|
||||
var row = document.createElement('div');
|
||||
row.className = 'todo-item ' + statusClass;
|
||||
|
||||
var iconSpan = document.createElement('span');
|
||||
iconSpan.className = 'todo-icon';
|
||||
iconSpan.textContent = icon;
|
||||
|
||||
var idSpan = document.createElement('span');
|
||||
idSpan.className = 'todo-id';
|
||||
idSpan.textContent = task.id + '.';
|
||||
|
||||
var textSpan = document.createElement('span');
|
||||
textSpan.className = 'todo-text';
|
||||
textSpan.textContent = task.subject;
|
||||
|
||||
row.appendChild(iconSpan);
|
||||
row.appendChild(idSpan);
|
||||
row.appendChild(textSpan);
|
||||
list.appendChild(row);
|
||||
}
|
||||
// Fortschritts-Badge im Header aktualisieren
|
||||
var box = list.parentElement;
|
||||
var badge = box && box.querySelector('.task-progress');
|
||||
if (badge) badge.textContent = done + '/' + tracker.tasks.length;
|
||||
parseFluentEmojis(list);
|
||||
}
|
||||
|
||||
/** Baut die komplette Task-Listen-Box (Header + Checkliste) für den aktuellen Tracker-Stand. */
|
||||
function buildTaskListBox(tracker) {
|
||||
var box = document.createElement('div');
|
||||
box.className = 'tool-box task-list-box';
|
||||
|
||||
var header = document.createElement('div');
|
||||
header.className = 'tool-box-header';
|
||||
var nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'tool-name';
|
||||
nameSpan.textContent = 'Aufgabenliste';
|
||||
header.appendChild(nameSpan);
|
||||
var progress = document.createElement('span');
|
||||
progress.className = 'tool-write-meta task-progress';
|
||||
header.appendChild(progress);
|
||||
box.appendChild(header);
|
||||
|
||||
var list = document.createElement('div');
|
||||
list.className = 'todo-list task-list';
|
||||
box.appendChild(list);
|
||||
|
||||
renderTaskListItems(list, tracker);
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verarbeitet ein Task-Event für einen Tracker und stellt die (einzige) Box sicher:
|
||||
* beim ersten Event wird sie erzeugt und via appendBox() eingehängt, danach in-place aktualisiert.
|
||||
*/
|
||||
function ingestTaskEvent(tracker, rawName, inputJson, resultText, appendBox) {
|
||||
applyTaskEvent(tracker, rawName, inputJson, resultText);
|
||||
if (!tracker.box) {
|
||||
tracker.box = buildTaskListBox(tracker);
|
||||
appendBox(tracker.box);
|
||||
} else {
|
||||
renderTaskListItems(tracker.box.querySelector('.task-list'), tracker);
|
||||
}
|
||||
return tracker.box;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Versiegelt" die aktuelle Task-Box: der Tracker behält seinen Task-Stand (kumulativ),
|
||||
* aber die nächste Task-Event erzeugt eine NEUE Box statt die alte in-place zu aktualisieren.
|
||||
* Wird aufgerufen, sobald zwischen zwei Task-Events echter Inhalt (Fließtext, anderes Tool)
|
||||
* gerendert wurde → so bleibt die chronologische Reihenfolge (Text zwischen den Tasks sichtbar).
|
||||
* Ein zusammenhängender Task-Lauf ohne Zwischentext bleibt weiterhin EINE Box.
|
||||
*/
|
||||
function sealTaskBox(tracker) {
|
||||
if (tracker) tracker.box = null;
|
||||
}
|
||||
|
||||
// Live-Stream-Tracker (ein aktiver Chain zur Zeit; Reset beim Stream-Start).
|
||||
var _liveTaskTracker = null;
|
||||
function resetLiveTaskList() { _liveTaskTracker = null; }
|
||||
/** Versiegelt die Live-Task-Box (nächstes Task-Event → neue Box). No-op wenn keine offen. */
|
||||
function sealLiveTaskList() { if (_liveTaskTracker) _liveTaskTracker.box = null; }
|
||||
|
||||
/** Live-Handler: nimmt ein TaskCreate/TaskUpdate aus dem SSE-Stream entgegen. */
|
||||
function handleLiveTaskEvent(rawName, inputJson, resultText) {
|
||||
if (!_liveTaskTracker) _liveTaskTracker = makeTaskTracker();
|
||||
ingestTaskEvent(_liveTaskTracker, rawName, inputJson, resultText, function(box) {
|
||||
chatEl().appendChild(box);
|
||||
});
|
||||
scrollDown();
|
||||
}
|
||||
|
||||
function finishToolBox(div, data) {
|
||||
if (!div) return;
|
||||
renderToolBoxContent(div, data.name, data.input, data.result, data.isError, data.sources);
|
||||
|
||||
Reference in new issue
Block a user