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.
111 lines
3.4 KiB
JavaScript
111 lines
3.4 KiB
JavaScript
// ── File Handling ──
|
|
|
|
let pendingFiles = []; // [{ name, type, base64 }]
|
|
let pendingRefs = []; // [{ path, from, to }] - Workspace-Dateireferenzen
|
|
|
|
function handleFileSelect(file) {
|
|
if (!file) return;
|
|
|
|
const maxSize = 20 * 1024 * 1024; // 20MB
|
|
if (file.size > maxSize) {
|
|
alert('Datei zu groß (max. 20MB)');
|
|
return;
|
|
}
|
|
|
|
const allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'];
|
|
if (!allowed.includes(file.type)) {
|
|
alert('Nicht unterstützter Dateityp. Erlaubt: JPEG, PNG, GIF, WebP, PDF');
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
const base64 = reader.result.split(',')[1];
|
|
pendingFiles.push({ name: file.name, type: file.type, base64 });
|
|
renderFilePreview();
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
function handleMultipleFiles(files) {
|
|
for (const file of files) handleFileSelect(file);
|
|
}
|
|
|
|
function renderFilePreview() {
|
|
const preview = $('#filePreview');
|
|
if (!preview || pendingFiles.length === 0) {
|
|
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
|
|
return;
|
|
}
|
|
|
|
preview.innerHTML = pendingFiles.map((f, i) => {
|
|
const isImage = f.type.startsWith('image/');
|
|
return `<div class="file-preview-inner">
|
|
${isImage ? '<img src="data:' + f.type + ';base64,' + f.base64 + '" class="file-thumb">' : '<span class="file-icon"><i class="fa fa-file-text-o"></i></span>'}
|
|
<span class="file-name">${esc(f.name)}</span>
|
|
<button class="file-remove" onclick="removePendingFile(${i})"><i class="fa fa-times"></i></button>
|
|
</div>`;
|
|
}).join('');
|
|
preview.hidden = false;
|
|
}
|
|
|
|
function removePendingFile(index) {
|
|
pendingFiles.splice(index, 1);
|
|
renderFilePreview();
|
|
}
|
|
|
|
function clearPendingFiles() {
|
|
pendingFiles.length = 0;
|
|
const preview = $('#filePreview');
|
|
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
|
|
const fileInput = $('#fileInput');
|
|
if (fileInput) fileInput.value = '';
|
|
}
|
|
|
|
// ── Workspace Refs ──
|
|
|
|
function addPendingRef(ref) {
|
|
// ref = "path", "path:line" oder "path:from-to"
|
|
const rangeMatch = ref.match(/^(.+?):(\d+)-(\d+)$/);
|
|
const lineMatch = !rangeMatch && ref.match(/^(.+?):(\d+)$/);
|
|
if (rangeMatch) {
|
|
pendingRefs.push({ path: rangeMatch[1], from: parseInt(rangeMatch[2]), to: parseInt(rangeMatch[3]) });
|
|
} else if (lineMatch) {
|
|
pendingRefs.push({ path: lineMatch[1], from: parseInt(lineMatch[2]), to: parseInt(lineMatch[2]) });
|
|
} else {
|
|
pendingRefs.push({ path: ref, from: null, to: null });
|
|
}
|
|
renderRefPreview();
|
|
}
|
|
|
|
function removePendingRef(index) {
|
|
pendingRefs.splice(index, 1);
|
|
renderRefPreview();
|
|
}
|
|
|
|
function clearPendingRefs() {
|
|
pendingRefs.length = 0;
|
|
renderRefPreview();
|
|
}
|
|
|
|
function renderRefPreview() {
|
|
const container = $('#refPreview');
|
|
if (!container) return;
|
|
|
|
if (pendingRefs.length === 0) {
|
|
container.innerHTML = '';
|
|
container.hidden = true;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = pendingRefs.map((r, i) => {
|
|
const label = r.from && r.to && r.from !== r.to ? `${r.path}:${r.from}-${r.to}` : r.from ? `${r.path}:${r.from}` : r.path;
|
|
return `<div class="ref-preview-item">
|
|
<span class="ref-icon"><i class="fa fa-folder-open"></i></span>
|
|
<span class="ref-label">${esc(label)}</span>
|
|
<button class="file-remove" onclick="removePendingRef(${i})"><i class="fa fa-times"></i></button>
|
|
</div>`;
|
|
}).join('');
|
|
container.hidden = false;
|
|
}
|