scrollDown() wartet jetzt auf document.fonts.ready und scrollt erneut. chat-page.js wartet nach loadHistory() zusätzlich per requestAnimationFrame auf den Repaint nach Font-Swap.
201 lines
6.4 KiB
JavaScript
201 lines
6.4 KiB
JavaScript
// ── 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);
|
|
}
|
|
|
|
scrollDown();
|
|
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.textContent = `📦 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.textContent = `📎 ${msg.attachmentCount} Datei${msg.attachmentCount > 1 ? 'en' : ''}` + (nameList ? `: ${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);
|
|
}
|
|
}
|
|
|
|
// Ä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);
|
|
}
|
|
}
|