// ── 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 = '' + esc(displayName) + ' ';
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 = ' ' + esc(domain) + '' +
(s.title ? '' + esc(s.title) + '' : '');
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 _buildDiffLines(container, lines, type) {
const cssClass = type === 'added' ? 'diff-added' : 'diff-removed';
const markerChar = type === 'added' ? '+' : '-';
for (const line of lines) {
const row = document.createElement('div');
row.className = 'diff-line ' + cssClass;
const marker = document.createElement('span');
marker.className = 'diff-marker';
marker.textContent = markerChar;
const content = document.createElement('span');
content.className = 'diff-content';
content.textContent = line;
row.appendChild(marker);
row.appendChild(content);
container.appendChild(row);
}
}
function buildEditDiff(oldText, newText) {
const container = document.createElement('div');
container.className = 'tool-diff';
_buildDiffLines(container, oldText.split('\n'), 'removed');
_buildDiffLines(container, newText.split('\n'), 'added');
return container;
}
function buildWritePreview(content) {
const container = document.createElement('div');
container.className = 'tool-diff';
_buildDiffLines(container, content.split('\n'), 'added');
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);
}
}