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.
333 lines
10 KiB
JavaScript
333 lines
10 KiB
JavaScript
// ── 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);
|
||
}
|
||
}
|