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.
This commit is contained in:
1 parent
a82d8cfb05
commit
0dcc05ebf9
15 files changed
+1885
-1873
No files matched your search
@@ -175,9 +175,6 @@ function togglePlanMode() {
|
||||
// ── Chat State ──
|
||||
|
||||
let isStreaming = false;
|
||||
let pendingFiles = []; // [{ name, type, base64 }]
|
||||
let pendingRefs = []; // [{ path, from, to }] - Workspace-Dateireferenzen
|
||||
let pendingReply = null; // { index, role, text } - Zitierte Nachricht
|
||||
let _lastDoneChainData = null; // Chain-Daten vom letzten done-Event
|
||||
let _currentToolBox = null; // Aktuell laufende Tool-Box (für Stream-Events)
|
||||
let _chatAbandoned = false; // true wenn User weg-navigiert während Stream läuft
|
||||
@@ -449,164 +446,3 @@ async function handleChoiceClick(toolCallId, choice, bubbleDiv) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── File Handling ──
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ── 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;
|
||||
}
|
||||
|
||||
function clearPendingFiles() {
|
||||
pendingFiles.length = 0;
|
||||
const preview = $('#filePreview');
|
||||
if (preview) { preview.innerHTML = ''; preview.hidden = true; }
|
||||
const fileInput = $('#fileInput');
|
||||
if (fileInput) fileInput.value = '';
|
||||
}
|
||||
|
||||
// ── Reply / Quote ──
|
||||
|
||||
function _formatReplyTimestamp(ts) {
|
||||
if (!ts) return 'Jetzt';
|
||||
try {
|
||||
const d = new Date(ts);
|
||||
const now = new Date();
|
||||
const hh = d.getHours().toString().padStart(2, '0');
|
||||
const mm = d.getMinutes().toString().padStart(2, '0');
|
||||
// Gleiches Datum → nur Uhrzeit, sonst Datum + Uhrzeit
|
||||
if (d.toDateString() === now.toDateString()) {
|
||||
return hh + ':' + mm;
|
||||
}
|
||||
const dd = d.getDate().toString().padStart(2, '0');
|
||||
const mo = (d.getMonth() + 1).toString().padStart(2, '0');
|
||||
return dd + '.' + mo + '. ' + hh + ':' + mm;
|
||||
} catch { return 'Jetzt'; }
|
||||
}
|
||||
|
||||
function setReply(reply) {
|
||||
pendingReply = reply;
|
||||
renderReplyPreview();
|
||||
document.getElementById('input')?.focus();
|
||||
}
|
||||
|
||||
function clearReply() {
|
||||
pendingReply = null;
|
||||
renderReplyPreview();
|
||||
}
|
||||
|
||||
function renderReplyPreview() {
|
||||
const container = $('#replyPreview');
|
||||
if (!container) return;
|
||||
|
||||
if (!pendingReply) {
|
||||
container.innerHTML = '';
|
||||
container.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const roleLabel = pendingReply.role === 'user' ? 'User' : 'Assistant';
|
||||
const timeLabel = _formatReplyTimestamp(pendingReply.timestamp);
|
||||
const preview = truncate(pendingReply.text, 150);
|
||||
|
||||
container.innerHTML = `<div class="reply-preview-item">
|
||||
<span class="reply-icon"><i class="fa fa-reply"></i></span>
|
||||
<span class="reply-meta">${esc(timeLabel)}, ${esc(roleLabel)}</span>
|
||||
<span class="reply-text">${esc(preview)}</span>
|
||||
<button class="file-remove" onclick="clearReply()"><i class="fa fa-times"></i></button>
|
||||
</div>`;
|
||||
container.hidden = false;
|
||||
}
|
||||
Reference in new issue
Block a user