// ── History Loading ── let lastKnownMessageId = 0; let historyPollInterval = null; let loadedArchiveCount = 0; async function loadHistory() { try { const sid = getCurrentSessionId(); const archives = await apiListArchives(sid); if (archives.length > 0) { showArchiveLoadButton(); } const data = await apiHistory(sid); data.reverse(); for (const entry of data) { renderMessages(entry.messages, true); if (entry.chain) addChainMetaBubble(entry.chain, false); } scrollDown(); setMeta('Bereit'); startHistoryPolling(); } catch (e) { console.error('History laden fehlgeschlagen:', e); } } function renderMessages(msgs, addToConversation) { for (const msg of msgs) { if (msg.id > lastKnownMessageId) lastKnownMessageId = msg.id; switch (msg.type) { case 'text': if (msg.role === 'user') { addUserBubble(msg.content); if (msg.attachmentCount > 0) addAttachmentIndicator(msg.id, msg.attachmentCount, msg.attachmentNames); if (addToConversation) messages.push({ role: 'user', content: msg.content }); } else if (msg.role === 'assistant') { addAssistantBubble(msg.content); if (addToConversation) messages.push({ role: 'assistant', content: msg.content }); } break; case 'tool_use': addToolBoxStatic(msg.toolName, msg.content, null, false); break; case 'tool_result': // tool_result wird dem vorherigen tool_use-Box angehängt wenn möglich appendToolResult(msg.content, msg.isError); break; case 'error': addErrorBubble(msg.content); break; } } } // ── Archive Loading ── 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 = ` Archiv vom ${new Date(latest.createdAt).toLocaleString('de-AT')} (${latest.chainCount} Chains, ${latest.messageCount} Messages)`; fragment.appendChild(sep); for (const entry of data) { for (const msg of entry.messages) { const div = document.createElement('div'); switch (msg.type) { case 'text': if (msg.role === 'user') { div.className = 'msg user archived'; renderUserContent(div, msg.content); } else { div.className = 'msg assistant markdown archived'; div.innerHTML = renderMarkdown(msg.content); } if (msg.role === 'user' && msg.attachmentCount > 0) { 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 = ` ${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': div.className = 'tool-box archived'; renderToolBoxContent(div, msg.toolName, msg.content, null, false); 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) { const badges = buildMetaBadges(entry.chain); const metaDiv = document.createElement('div'); metaDiv.className = 'chain-meta archived'; metaDiv.innerHTML = badges.map(b => '' + esc(b) + '').join(''); fragment.appendChild(metaDiv); } } // Älteres Archiv vor dem bisherigen Inhalt einfügen container.prepend(fragment); // Button ganz oben, falls es noch ältere Archive gibt if (archives.length > loadedArchiveCount) { showArchiveLoadButton(); } } catch (e) { console.error('Archiv laden fehlgeschlagen:', e); } } // ── Polling ── function startHistoryPolling(intervalMs = 3000) { if (historyPollInterval) return; historyPollInterval = setInterval(pollHistory, intervalMs); } function stopHistoryPolling() { if (historyPollInterval) { clearInterval(historyPollInterval); historyPollInterval = null; } } async function pollHistory(force = false) { if (isStreaming && !force) return; try { const newMessages = await apiHistoryPoll(getCurrentSessionId(), lastKnownMessageId); if (newMessages.length > 0) { renderMessages(newMessages, true); scrollDown(); } } catch (e) { console.error('History poll fehlgeschlagen:', e); } }