Files
gott/wwwroot/js/tool-ui.js
T
Meleki 0dcc05ebf9 refactor: ui.js, chat.js, history.js in 13 modulare Dateien aufteilen
Die monolithische ui.js (1534 Zeilen) zusammen mit chat.js (612) und
history.js (626) in fokussierte Module aufgeteilt:

Neue Dateien aus ui.js:
- ui-helpers.js (126) - DOM-Utilities, $, esc, truncate, scrollDown, Toast
- markdown.js (143) - renderMarkdown, wrapKreaGalleries, LaTeX/Code-Schutz
- cards.js (90) - buildSessionCard, buildAgentCard
- msg-actions.js (208) - Delete, Mark, Share, Reply-Button, Timestamp
- tool-ui.js (332) - Tool-Boxes, Diffs, Todo-Liste, Visibility
- modals.js (190) - System-Prompt, Command-Output, Krea-Image Modals
- stream-ui.js (124) - Stream-Bubble, Review-Streaming
- bubbles.js (312) - User/Assistant/Error/Warning/Choice/Review Bubbles

Neue Dateien aus chat.js:
- reply.js (54) - Reply/Quote-Handling mit pendingReply State
- files.js (110) - File-Upload, Workspace-Refs mit pendingFiles/Refs State

Neue Datei aus history.js:
- archive.js (173) - Archiv-Loading mit loadedArchiveCount State

Verbleibend:
- chat.js (448) - Stream-Events, sendMessage, handleChoiceClick, Plan-Mode
- history.js (464) - History laden, Polling, Message-Rendering

Kein Code verändert, nur verschoben. Alle 94 Funktionen verifiziert.
Globale Variablen in die Module verschoben, wo sie primär genutzt werden.
2026-03-07 19:32:33 +01:00

333 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── Tool Call Visibility ──
const MODIFICATION_TOOLS = ['write_file', 'edit_file', 'delete_file', 'memory_write'];
const TOOL_DISPLAY_NAMES = {
write_file: 'Datei schreiben',
edit_file: 'Datei bearbeiten',
delete_file: 'Datei löschen',
memory_write: 'Erinnerungen festhalten',
web_search: 'Web-Suche'
};
function isToolVisible(toolName) {
const mode = loadSettings().toolCallDisplay;
if (mode === 'all') return true;
if (mode === 'hidden') return false;
// 'modifications' – nur modifizierende Tools + web_search anzeigen
if (toolName === 'web_search') return true;
return MODIFICATION_TOOLS.includes(toolName);
}
// ── Chain Meta Visibility ──
function isChainMetaVisible(chainData) {
const mode = loadSettings().chainMetaDisplay;
if (mode === 'always') return true;
if (mode === 'never') return false;
// 'sometimes' – nur bei auffälligen Chains anzeigen
if (!chainData) return false;
const inOut = (chainData.inputTokens || 0) + (chainData.outputTokens || 0);
if (inOut >= 10000) return true;
if ((chainData.iterations || 0) >= 25) return true;
if ((chainData.durationMs || 0) >= 90000) return true;
return false;
}
// ── Tool Boxes ──
function addToolBoxLoading(name) {
const div = document.createElement('div');
div.className = 'tool-box streamed';
div.dataset.toolName = name;
const displayName = TOOL_DISPLAY_NAMES[name] || name;
div.innerHTML = '<span class="tool-name">' + esc(displayName) + '</span> <span class="tool-spinner"><i class="fa fa-hourglass-half"></i></span>';
chatEl().appendChild(div);
scrollDown();
return div;
}
function renderToolBoxContent(div, name, inputJson, result, isError, sources) {
let parsedInput = null;
try { parsedInput = JSON.parse(inputJson); } catch {}
const header = document.createElement('div');
header.className = 'tool-box-header';
const nameSpan = document.createElement('span');
nameSpan.className = 'tool-name';
nameSpan.textContent = TOOL_DISPLAY_NAMES[name] || name;
header.appendChild(nameSpan);
if (isError) {
const errSpan = document.createElement('span');
errSpan.className = 'tool-error-badge';
errSpan.textContent = 'error';
header.appendChild(errSpan);
}
div.innerHTML = '';
div.appendChild(header);
let foldTarget = null;
// Web-Search Sources – immer sichtbar, kein Toggle
if (name === 'web_search' && sources && sources.length > 0) {
const sourcesList = document.createElement('div');
sourcesList.className = 'web-search-sources';
sources.forEach(function(s) {
const link = document.createElement('a');
link.href = s.url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.className = 'web-search-source';
var domain = '';
try { domain = new URL(s.url).hostname.replace('www.', ''); } catch(e) { domain = s.url; }
link.innerHTML = '<i class="fa fa-external-link"></i> <span class="source-domain">' + esc(domain) + '</span>' +
(s.title ? '<span class="source-title">' + esc(s.title) + '</span>' : '');
sourcesList.appendChild(link);
});
div.appendChild(sourcesList);
}
if (name === 'edit_file' && parsedInput && parsedInput.path) {
const pathSpan = document.createElement('span');
pathSpan.className = 'tool-input';
pathSpan.textContent = parsedInput.path;
header.appendChild(pathSpan);
const diff = buildEditDiff(parsedInput.old_text || '', parsedInput.new_text || '');
diff.hidden = true;
div.appendChild(diff);
foldTarget = diff;
} else if (name === 'write_file' && parsedInput && parsedInput.path) {
const pathSpan = document.createElement('span');
pathSpan.className = 'tool-input';
pathSpan.textContent = parsedInput.path;
header.appendChild(pathSpan);
const content = parsedInput.content || '';
const lines = content.split('\n');
const lineCount = lines.length;
const charCount = content.length;
const meta = document.createElement('span');
meta.className = 'tool-write-meta';
meta.textContent = lineCount + ' Zeilen, ' + charCount + ' Zeichen';
header.appendChild(meta);
const preview = buildWritePreview(content);
preview.hidden = true;
div.appendChild(preview);
foldTarget = preview;
} else if (name === 'todo_write') {
// Todo-Liste: kein tool-input, standardmäßig sichtbar, custom parser
if (result) {
const todoList = buildTodoList(result, isError);
div.appendChild(todoList);
foldTarget = todoList;
}
} else {
// Standard: path/command als input anzeigen
let inputLabel = '';
if (parsedInput) {
inputLabel = parsedInput.path || parsedInput.command || JSON.stringify(parsedInput).substring(0, 120);
} else {
inputLabel = truncate(inputJson, 120);
}
if (inputLabel) {
const inputSpan = document.createElement('span');
inputSpan.className = 'tool-input';
inputSpan.textContent = inputLabel;
header.appendChild(inputSpan);
}
const resultDiv = document.createElement('div');
resultDiv.className = 'tool-result' + (isError ? ' tool-error' : '');
resultDiv.textContent = result || '';
resultDiv.hidden = true;
div.appendChild(resultDiv);
foldTarget = resultDiv;
}
if (foldTarget && name !== 'todo_write') {
header.style.cursor = 'pointer';
header.addEventListener('click', () => {
foldTarget.hidden = !foldTarget.hidden;
});
}
}
function buildEditDiff(oldText, newText) {
const oldLines = oldText.split('\n');
const newLines = newText.split('\n');
const container = document.createElement('div');
container.className = 'tool-diff';
// Entfernte Zeilen (-)
for (const line of oldLines) {
const row = document.createElement('div');
row.className = 'diff-line diff-removed';
const marker = document.createElement('span');
marker.className = 'diff-marker';
marker.textContent = '-';
const content = document.createElement('span');
content.className = 'diff-content';
content.textContent = line;
row.appendChild(marker);
row.appendChild(content);
container.appendChild(row);
}
// Hinzugefügte Zeilen (+)
for (const line of newLines) {
const row = document.createElement('div');
row.className = 'diff-line diff-added';
const marker = document.createElement('span');
marker.className = 'diff-marker';
marker.textContent = '+';
const content = document.createElement('span');
content.className = 'diff-content';
content.textContent = line;
row.appendChild(marker);
row.appendChild(content);
container.appendChild(row);
}
return container;
}
function buildWritePreview(content) {
const lines = content.split('\n');
const container = document.createElement('div');
container.className = 'tool-diff';
for (const line of lines) {
const row = document.createElement('div');
row.className = 'diff-line diff-added';
const marker = document.createElement('span');
marker.className = 'diff-marker';
marker.textContent = '+';
const contentSpan = document.createElement('span');
contentSpan.className = 'diff-content';
contentSpan.textContent = line;
row.appendChild(marker);
row.appendChild(contentSpan);
container.appendChild(row);
}
return container;
}
function buildTodoList(result, isError) {
const container = document.createElement('div');
container.className = 'todo-list' + (isError ? ' tool-error' : '');
const lines = result.split('\n').filter(l => l.trim());
for (const line of lines) {
// Format: ICON [id] [priority] content
const m = line.match(/^(\S+)\s+\[([^\]]+)\]\s+(?:\[([^\]]+)\]\s+)?(.+)$/);
const row = document.createElement('div');
row.className = 'todo-item';
if (m) {
const icon = m[1]; // ✅ / 🔄 / ⬜
const id = m[2];
const prio = m[3] || null;
const text = m[4];
const statusClass =
icon === '✅' ? 'todo-done' :
icon === '🔄' ? '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 = id + '.';
const textSpan = document.createElement('span');
textSpan.className = 'todo-text';
textSpan.textContent = text;
row.appendChild(iconSpan);
row.appendChild(idSpan);
row.appendChild(textSpan);
if (prio) {
const prioSpan = document.createElement('span');
prioSpan.className = 'todo-prio todo-prio-' + prio.toLowerCase();
prioSpan.textContent = prio;
row.appendChild(prioSpan);
}
} else {
row.textContent = line;
}
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);
scrollDown();
}
function addToolBoxStatic(name, input, result, isError, sources) {
const div = document.createElement('div');
div.className = 'tool-box';
div.dataset.toolName = name;
renderToolBoxContent(div, name, input, result, isError, sources);
chatEl().appendChild(div);
scrollDown();
}
function appendToolResult(result, isError) {
// Letzten tool-box im Chat finden und Result in die bereits vorhandene .tool-result-Div schreiben
const boxes = chatEl().querySelectorAll('.tool-box');
const lastBox = boxes[boxes.length - 1];
if (!lastBox) return;
// Wenn Diff vorhanden (edit_file/write_file) → kein Result anhängen
if (lastBox.querySelector('.tool-diff')) return;
const toolName = lastBox.dataset.toolName || '';
const isTodoWrite = toolName === 'todo_write';
if (isTodoWrite) {
// Vorhandene todo-list ersetzen oder neu erstellen
const existing = lastBox.querySelector('.todo-list');
if (existing) existing.remove();
if (result && !isError) {
const todoList = buildTodoList(result, isError);
lastBox.appendChild(todoList);
}
return;
}
const existing = lastBox.querySelector('.tool-result');
if (existing) {
existing.textContent = result || '';
if (isError) existing.classList.add('tool-error');
} else {
const resultDiv = document.createElement('div');
resultDiv.className = 'tool-result' + (isError ? ' tool-error' : '');
resultDiv.textContent = result || '';
resultDiv.hidden = true;
lastBox.appendChild(resultDiv);
}
}