Files
gott/wwwroot/js/chat.js
T
Meleki 449c872296 Agents: CRUD, DB table, session binding, replace model picker
- agents table with name, avatar, description, model, persona, instructions, soft-delete
- AgentEndpoint (GET/POST/PUT/DELETE /api/agents)
- sessions + chains now reference agent_id
- ChatEndpoint resolves agent from session (no agent = no chat)
- system prompt built from agent persona + instructions
- frontend: agent editor with avatar upload, agent cards on dashboard
- session: agent dropdown replaces model picker, choice persisted
2026-02-26 20:59:19 +01:00

118 lines
3.1 KiB
JavaScript

// ── Chat State ──
const messages = [];
let isStreaming = false;
async function clearChat() {
const archive = await apiArchive(getCurrentSessionId());
if (!archive) return;
messages.length = 0;
chatEl().innerHTML = '';
lastKnownMessageId = 0;
loadedArchiveCount = 0;
setMeta(`Archiviert: ${archive.chainCount} Chains, ${archive.messageCount} Messages`);
showArchiveLoadButton();
}
// ── Send Message ──
async function sendMessage() {
const input = $('#input');
const btn = $('#send');
const systemPrompt = $('#systemPrompt').value.trim();
const text = input.value.trim();
if (!text) return;
messages.push({ role: 'user', content: text });
addUserBubble(text, true);
input.value = '';
input.style.height = 'auto';
btn.disabled = true;
isStreaming = true;
setMetaHtml('<span style="color:#7c83ff">⏳ Streaming...</span>');
let fullText = '';
let currentToolBox = null;
try {
const resp = await apiChat(messages, systemPrompt, getCurrentSessionId());
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let eventType = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('event: ')) {
eventType = line.substring(7).trim();
} else if (line.startsWith('data: ') && eventType) {
let data;
try { data = JSON.parse(line.substring(6)); } catch { continue; }
switch (eventType) {
case 'text_start':
endStreamBubble();
startStreamBubble();
break;
case 'text_delta':
appendToStream(data.text);
fullText += data.text;
break;
case 'text_end':
endStreamBubble();
break;
case 'tool_start':
endStreamBubble();
currentToolBox = addToolBoxLoading(data.name);
break;
case 'tool_end':
finishToolBox(currentToolBox, data);
currentToolBox = null;
break;
case 'error':
endStreamBubble();
addErrorBubble(data.error);
break;
case 'done':
endStreamBubble();
let meta = `Model: ${data.model || '?'} | In: ${data.inputTokens || 0} | Out: ${data.outputTokens || 0}`;
if (data.iterations > 1) meta += ` | Iterations: ${data.iterations}`;
if (data.durationMs) meta += ` | ${(data.durationMs / 1000).toFixed(1)}s`;
setMeta(meta);
break;
}
eventType = null;
}
}
}
} catch (e) {
endStreamBubble();
addErrorBubble('Netzwerk-Fehler: ' + e.message);
} finally {
btn.disabled = false;
isStreaming = false;
endStreamBubble();
// Erst neue aus DB laden, dann gestreamte Elemente ersetzen
await pollHistory();
removeStreamedElements();
}
}