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.
174 lines
7.6 KiB
JavaScript
174 lines
7.6 KiB
JavaScript
// ── Archive Loading ──
|
|
|
|
let loadedArchiveCount = 0;
|
|
|
|
function getOrCreateArchiveContainer() {
|
|
let container = chatEl().querySelector('.archive-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.className = 'archive-container';
|
|
chatEl().prepend(container);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
function getOrCreateArchiveDivider() {
|
|
let divider = chatEl().querySelector('.archive-divider');
|
|
if (!divider) {
|
|
divider = document.createElement('div');
|
|
divider.className = 'msg system-info archive-divider';
|
|
divider.textContent = '── Aktuelle Konversation ──';
|
|
// Nach dem Container, vor den aktiven Messages
|
|
const container = getOrCreateArchiveContainer();
|
|
container.after(divider);
|
|
}
|
|
return divider;
|
|
}
|
|
|
|
async function loadLatestArchive() {
|
|
try {
|
|
const archives = await apiListArchives(getCurrentSessionId());
|
|
if (archives.length <= loadedArchiveCount) return;
|
|
|
|
const latest = archives[loadedArchiveCount];
|
|
loadedArchiveCount++;
|
|
|
|
removeArchiveLoadButton();
|
|
|
|
const data = await apiGetArchive(latest.archiveId);
|
|
const container = getOrCreateArchiveContainer();
|
|
getOrCreateArchiveDivider();
|
|
|
|
// Archiv-Block zusammenbauen
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
const sep = document.createElement('div');
|
|
sep.className = 'msg system-info';
|
|
sep.innerHTML = `<i class="fa fa-archive"></i> Archiv vom ${new Date(latest.createdAt).toLocaleString('de-AT')} (${latest.chainCount} Chains, ${latest.messageCount} Messages)`;
|
|
fragment.appendChild(sep);
|
|
|
|
const archiveImageIds = [];
|
|
for (const entry of data) {
|
|
const mergedMsgs = _mergeConsecutiveAssistantTexts(entry.messages);
|
|
for (const msg of mergedMsgs) {
|
|
const div = document.createElement('div');
|
|
switch (msg.type) {
|
|
case 'text': {
|
|
const isReviewMsg = msg.content && (msg.content.startsWith('[Review:') || msg.content.startsWith('[Review-Feedback') || msg.content.startsWith('[Finalizer:') || msg.content.startsWith('[Preparer:'));
|
|
if (msg.role === 'user' && !isReviewMsg) {
|
|
div.className = 'msg user markdown archived' + (msg.isMarked ? ' marked' : '');
|
|
if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
|
|
div.dataset.role = 'user';
|
|
div.dataset.timestamp = msg.createdAt || '';
|
|
if (msg.id) div.dataset.messageId = msg.id;
|
|
renderUserContent(div, msg.content);
|
|
_addTimestamp(div, msg.createdAt);
|
|
_addReplyButton(div);
|
|
_addDeleteButton(div, msg.id, msg.deletedOn);
|
|
_addMarkButton(div, msg.id, msg.isMarked);
|
|
_addShareButton(div, msg.id);
|
|
} else if (isReviewMsg) {
|
|
div.className = 'msg review-bubble markdown archived' + (msg.isMarked ? ' marked' : '');
|
|
if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
|
|
div.dataset.role = msg.role;
|
|
div.dataset.timestamp = msg.createdAt || '';
|
|
if (msg.id) div.dataset.messageId = msg.id;
|
|
div.innerHTML = renderMarkdown(msg.content);
|
|
parseFluentEmojis(div);
|
|
_addTimestamp(div, msg.createdAt);
|
|
_addReplyButton(div);
|
|
_addDeleteButton(div, msg.id, msg.deletedOn);
|
|
_addMarkButton(div, msg.id, msg.isMarked);
|
|
_addShareButton(div, msg.id);
|
|
} else if (msg.role === 'assistant') {
|
|
div.className = 'msg assistant markdown archived' + (msg.isMarked ? ' marked' : '');
|
|
if (msg.deletedOn) div.classList.add(isDeletedVisible() ? 'msg-deleted' : 'msg-deleted-hidden');
|
|
div.dataset.role = 'assistant';
|
|
div.dataset.timestamp = msg.createdAt || '';
|
|
if (msg.id) div.dataset.messageId = msg.id;
|
|
div.innerHTML = renderMarkdown(msg.content);
|
|
parseFluentEmojis(div);
|
|
_addTimestamp(div, msg.createdAt);
|
|
_addReplyButton(div);
|
|
_addDeleteButton(div, msg.id, msg.deletedOn);
|
|
_addMarkButton(div, msg.id, msg.isMarked);
|
|
_addShareButton(div, msg.id);
|
|
}
|
|
if (msg.role === 'user' && msg.attachmentCount > 0) {
|
|
if (/\[Bild: [^\]]+\]/.test(msg.content)) archiveImageIds.push(msg.id);
|
|
let names = [];
|
|
try { if (msg.attachmentNames) names = JSON.parse(msg.attachmentNames); } catch {}
|
|
const nameList = names.filter(n => n).join(', ');
|
|
const ind = document.createElement('div');
|
|
ind.className = 'attachment-indicator';
|
|
ind.innerHTML = `<i class="fa fa-paperclip"></i> ${msg.attachmentCount} Datei${msg.attachmentCount > 1 ? 'en' : ''}` + (nameList ? `: ${esc(nameList)}` : '');
|
|
ind.style.cursor = 'pointer';
|
|
ind.title = 'Klicken zum Anzeigen';
|
|
ind.addEventListener('click', () => loadAndShowAttachments(msg.id, ind));
|
|
div.appendChild(ind);
|
|
}
|
|
break;
|
|
}
|
|
case 'tool_use':
|
|
if (!isToolVisible(msg.toolName)) continue;
|
|
div.className = 'tool-box archived';
|
|
renderToolBoxContent(div, msg.toolName, msg.content, null, false);
|
|
break;
|
|
case 'web_search_result': {
|
|
if (!isToolVisible('web_search')) continue;
|
|
var archSrc = [];
|
|
try { archSrc = JSON.parse(msg.content || '[]'); } catch(e) {}
|
|
if (archSrc.length === 0) continue; // Leere Suchergebnisse überspringen
|
|
div.className = 'tool-box archived';
|
|
div.dataset.toolName = 'web_search';
|
|
renderToolBoxContent(div, 'web_search', null, null, false, archSrc);
|
|
break;
|
|
}
|
|
case 'tool_result': {
|
|
// An vorherigen tool-box anhängen
|
|
const prevBoxes = fragment.querySelectorAll ? fragment.querySelectorAll('.tool-box') : [];
|
|
const lastToolBox = prevBoxes[prevBoxes.length - 1];
|
|
if (lastToolBox && !lastToolBox.querySelector('.tool-result')) {
|
|
const rd = document.createElement('div');
|
|
rd.className = 'tool-result' + (msg.isError ? ' tool-error' : '');
|
|
rd.textContent = truncate(msg.content || '', 300);
|
|
lastToolBox.appendChild(rd);
|
|
}
|
|
continue; // kein separates div
|
|
}
|
|
case 'error':
|
|
div.className = 'msg system-info archived';
|
|
div.style.color = 'var(--dangerous)';
|
|
div.textContent = (msg.content || '').trim();
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
fragment.appendChild(div);
|
|
}
|
|
// Chain-Meta-Badges nach jeder Chain einfügen
|
|
if (entry.chain && isChainMetaVisible(entry.chain)) {
|
|
const badges = buildMetaBadges(entry.chain);
|
|
const metaDiv = document.createElement('div');
|
|
metaDiv.className = 'chain-meta archived';
|
|
metaDiv.innerHTML = badges.map(b => '<span class="chain-meta-badge">' + esc(b) + '</span>').join('');
|
|
fragment.appendChild(metaDiv);
|
|
}
|
|
}
|
|
|
|
// Älteres Archiv vor dem bisherigen Inhalt einfügen
|
|
container.prepend(fragment);
|
|
|
|
// Bild-Attachments automatisch laden
|
|
for (const mid of archiveImageIds) _autoLoadImageAttachments(mid);
|
|
|
|
// Button ganz oben, falls es noch ältere Archive gibt
|
|
if (archives.length > loadedArchiveCount) {
|
|
showArchiveLoadButton();
|
|
}
|
|
|
|
} catch (e) {
|
|
console.error('Archiv laden fehlgeschlagen:', e);
|
|
}
|
|
}
|