245 lines
7.6 KiB
JavaScript
245 lines
7.6 KiB
JavaScript
// ── Chat Page ──
|
|
|
|
let _streamingPollInterval = null;
|
|
let _recoveryChainId = null;
|
|
|
|
function startStreamingPoll(sessionId, sendBtn, chainId) {
|
|
_recoveryChainId = chainId;
|
|
if (_streamingPollInterval) clearInterval(_streamingPollInterval);
|
|
_streamingPollInterval = setInterval(async () => {
|
|
try {
|
|
// Neue Nachrichten laden (inkl. der noch streamenden)
|
|
await pollHistory(true);
|
|
|
|
const status = await apiIsStreaming(sessionId);
|
|
if (!status.streaming) {
|
|
clearInterval(_streamingPollInterval);
|
|
_streamingPollInterval = null;
|
|
isStreaming = false;
|
|
if (sendBtn) sendBtn.disabled = false;
|
|
|
|
// Finale Nachrichten nochmal laden
|
|
await pollHistory(true);
|
|
|
|
// Chain-Daten laden für Meta-Info
|
|
const cid = _recoveryChainId || status.chainId;
|
|
if (cid) {
|
|
const chain = await apiGetChain(cid);
|
|
if (chain) {
|
|
let meta = `Model: ${chain.model || '?'} | In: ${chain.inputTokens || 0} | Out: ${chain.outputTokens || 0}`;
|
|
if (chain.iterations > 1) meta += ` | Iterations: ${chain.iterations}`;
|
|
if (chain.durationMs) meta += ` | ${(chain.durationMs / 1000).toFixed(1)}s`;
|
|
if (chain.stopReason && chain.stopReason !== 'end_turn' && chain.stopReason !== 'tool_use') meta += ` | Stop: ${chain.stopReason}`;
|
|
setMeta(meta);
|
|
} else {
|
|
setMeta('Bereit');
|
|
}
|
|
} else {
|
|
setMeta('Bereit');
|
|
}
|
|
_recoveryChainId = null;
|
|
}
|
|
} catch (e) {
|
|
console.error('Streaming-Poll fehlgeschlagen:', e);
|
|
}
|
|
}, 1500);
|
|
}
|
|
|
|
registerPage('chat', {
|
|
async init(params) {
|
|
const sessionId = params.id;
|
|
if (!sessionId) return navigateTo('dashboard');
|
|
|
|
currentSessionId = sessionId;
|
|
|
|
const session = await apiGetSession(sessionId);
|
|
if (!session) return navigateTo('dashboard');
|
|
|
|
// Kein Agent = kein Chat
|
|
if (!session.agentId) {
|
|
navigateTo('session', { id: sessionId });
|
|
return;
|
|
}
|
|
|
|
const titleEl = document.getElementById('sessionTitle');
|
|
if (titleEl) {
|
|
titleEl.textContent = session.name || 'Unbenannt';
|
|
}
|
|
|
|
// Custom Command Buttons
|
|
const cmdBar = document.getElementById('customCmdBar');
|
|
if (cmdBar) {
|
|
cmdBar.innerHTML = '';
|
|
let cmds = [];
|
|
try { cmds = JSON.parse(session.customCommands || '[]') || []; } catch(e) {}
|
|
cmds.forEach(cmd => {
|
|
if (!cmd.name || !cmd.command) return;
|
|
const btn = document.createElement('button');
|
|
btn.className = 'custom-cmd-btn';
|
|
btn.textContent = cmd.name;
|
|
btn.title = cmd.command;
|
|
btn.addEventListener('click', async () => {
|
|
btn.disabled = true;
|
|
btn.classList.add('running');
|
|
try {
|
|
await apiExecCommand(sessionId, cmd.command);
|
|
} catch(e) {
|
|
console.error('Exec failed:', e);
|
|
}
|
|
setTimeout(() => {
|
|
btn.disabled = false;
|
|
btn.classList.remove('running');
|
|
}, 1000);
|
|
});
|
|
cmdBar.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
// Zurück zur Session-Edit-Seite
|
|
const backBtn = document.getElementById('chatBackBtn');
|
|
if (backBtn) {
|
|
backBtn.addEventListener('click', () => navigateTo('session', { id: sessionId }));
|
|
}
|
|
|
|
// Send
|
|
const sendBtn = document.getElementById('send');
|
|
if (sendBtn) sendBtn.addEventListener('click', sendMessage);
|
|
|
|
const inputEl = document.getElementById('input');
|
|
if (inputEl) {
|
|
inputEl.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
|
e.preventDefault();
|
|
sendMessage();
|
|
}
|
|
});
|
|
inputEl.addEventListener('input', function () {
|
|
this.style.height = 'auto';
|
|
this.style.height = Math.min(this.scrollHeight, 150) + 'px';
|
|
});
|
|
inputEl.dispatchEvent(new Event('input'))
|
|
}
|
|
|
|
// Workspace Explorer
|
|
const explorerBtn = document.getElementById('explorerBtn');
|
|
if (explorerBtn) {
|
|
explorerBtn.addEventListener('click', () => {
|
|
openFileExplorer({
|
|
sessionId,
|
|
onSelect(ref) {
|
|
addPendingRef(ref);
|
|
document.getElementById('input')?.focus();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// File Upload
|
|
const fileBtn = document.getElementById('fileBtn');
|
|
const fileInput = document.getElementById('fileInput');
|
|
if (fileBtn && fileInput) {
|
|
fileBtn.addEventListener('click', () => fileInput.click());
|
|
fileInput.addEventListener('change', (e) => {
|
|
if (e.target.files.length > 0) handleMultipleFiles(e.target.files);
|
|
});
|
|
}
|
|
|
|
// Drag & Drop
|
|
const chatContainer = document.querySelector('.chat-page');
|
|
if (chatContainer) {
|
|
chatContainer.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); });
|
|
chatContainer.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (e.dataTransfer.files.length > 0) handleMultipleFiles(e.dataTransfer.files);
|
|
});
|
|
}
|
|
|
|
// Paste
|
|
if (inputEl) {
|
|
inputEl.addEventListener('paste', (e) => {
|
|
const items = e.clipboardData?.items;
|
|
if (!items) return;
|
|
for (const item of items) {
|
|
if (item.kind === 'file') {
|
|
e.preventDefault();
|
|
handleFileSelect(item.getAsFile());
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Header-Menü (3-Dot)
|
|
const chatMenuBtn = document.getElementById('chatMenuBtn');
|
|
const chatMenuDropdown = document.getElementById('chatMenuDropdown');
|
|
if (chatMenuBtn && chatMenuDropdown) {
|
|
chatMenuBtn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
chatMenuDropdown.hidden = !chatMenuDropdown.hidden;
|
|
});
|
|
document.addEventListener('click', this._closeMenu = () => {
|
|
chatMenuDropdown.hidden = true;
|
|
});
|
|
}
|
|
|
|
// System-Prompt anzeigen
|
|
const systemPromptBtn = document.getElementById('systemPromptBtn');
|
|
if (systemPromptBtn) systemPromptBtn.addEventListener('click', () => {
|
|
if (chatMenuDropdown) chatMenuDropdown.hidden = true;
|
|
showSystemPromptModal(sessionId);
|
|
});
|
|
|
|
// Archivieren
|
|
const archiveBtn = document.getElementById('archiveBtn');
|
|
if (archiveBtn) archiveBtn.addEventListener('click', () => {
|
|
if (chatMenuDropdown) chatMenuDropdown.hidden = true;
|
|
clearChat();
|
|
});
|
|
|
|
// State zurücksetzen und History laden
|
|
messages.length = 0;
|
|
lastKnownMessageId = 0;
|
|
loadedArchiveCount = 0;
|
|
isStreaming = false;
|
|
|
|
await loadHistory();
|
|
|
|
// Nach Font-Loading nochmal scrollen (Schriftart kann Höhe verändern)
|
|
document.fonts.ready.then(() => {
|
|
requestAnimationFrame(() => scrollDown());
|
|
});
|
|
|
|
// Prüfen ob Backend gerade noch streamt (z.B. nach Page-Refresh)
|
|
try {
|
|
const status = await apiIsStreaming(sessionId);
|
|
if (status.streaming) {
|
|
isStreaming = true;
|
|
if (sendBtn) sendBtn.disabled = true;
|
|
setMetaHtml('<span>⏳ Streaming...</span>');
|
|
startStreamingPoll(sessionId, sendBtn, status.chainId);
|
|
}
|
|
} catch (e) {
|
|
console.error('Streaming-Status prüfen fehlgeschlagen:', e);
|
|
}
|
|
},
|
|
|
|
destroy() {
|
|
if (this._closeMenu) {
|
|
document.removeEventListener('click', this._closeMenu);
|
|
this._closeMenu = null;
|
|
}
|
|
if (_streamingPollInterval) {
|
|
clearInterval(_streamingPollInterval);
|
|
_streamingPollInterval = null;
|
|
}
|
|
stopHistoryPolling();
|
|
messages.length = 0;
|
|
lastKnownMessageId = 0;
|
|
loadedArchiveCount = 0;
|
|
isStreaming = false;
|
|
clearPendingFiles();
|
|
clearPendingRefs();
|
|
}
|
|
});
|