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
This commit is contained in:
Meleki committed 2026-02-26 20:59:19 +01:00
1 parent 0cd2d51d32
commit 449c872296
16 files changed
+552 -46

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
// ── Agent Page ──
let _agentAvatarData = null;
registerPage('agent', {
async init(params) {
const agentId = params.id;
const isNew = !agentId;
let agent = null;
if (!isNew) {
agent = await apiGetAgent(agentId);
if (!agent) return navigateTo('dashboard');
}
const title = document.getElementById('agentPageTitle');
const nameEl = document.getElementById('agentName');
const descEl = document.getElementById('agentDescription');
const modelEl = document.getElementById('agentModel');
const personaEl = document.getElementById('agentPersona');
const instructionsEl = document.getElementById('agentInstructions');
const avatarPreview = document.getElementById('avatarPreview');
const avatarInput = document.getElementById('avatarInput');
const avatarBtn = document.getElementById('avatarBtn');
const avatarClearBtn = document.getElementById('avatarClearBtn');
const saveBtn = document.getElementById('agentSaveBtn');
const cancelBtn = document.getElementById('agentCancelBtn');
const deleteBtn = document.getElementById('agentDeleteBtn');
_agentAvatarData = null;
if (agent) {
title.textContent = agent.name;
nameEl.value = agent.name || '';
descEl.value = agent.description || '';
modelEl.value = agent.model || 'sonnet';
personaEl.value = agent.persona || '';
instructionsEl.value = agent.instructions || '';
_agentAvatarData = agent.avatar || null;
deleteBtn.hidden = false;
if (agent.avatar) {
avatarPreview.innerHTML = `<img src="${agent.avatar}" alt="Avatar">`;
}
}
// Avatar Upload
avatarBtn.addEventListener('click', () => avatarInput.click());
avatarInput.addEventListener('change', () => {
const file = avatarInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
_agentAvatarData = reader.result;
avatarPreview.innerHTML = `<img src="${reader.result}" alt="Avatar">`;
};
reader.readAsDataURL(file);
});
avatarClearBtn.addEventListener('click', () => {
_agentAvatarData = null;
avatarPreview.innerHTML = '?';
avatarInput.value = '';
});
// Speichern
saveBtn.addEventListener('click', async () => {
const data = {
name: nameEl.value.trim() || 'Unbenannt',
description: descEl.value.trim(),
model: modelEl.value,
persona: personaEl.value,
instructions: instructionsEl.value,
avatar: _agentAvatarData
};
if (isNew) {
await apiCreateAgent(data);
} else {
await apiUpdateAgent(agentId, data);
}
navigateTo('dashboard');
});
// Abbrechen
cancelBtn.addEventListener('click', () => navigateTo('dashboard'));
// Löschen
deleteBtn.addEventListener('click', async () => {
if (!confirm('Agent wirklich löschen?')) return;
await apiDeleteAgent(agentId);
navigateTo('dashboard');
});
},
destroy() {
_agentAvatarData = null;
}
});
+33 -4
View File
@@ -2,6 +2,7 @@
registerPage('dashboard', {
async init() {
// Sessions laden
const sessions = await apiGetSessions();
const grid = document.getElementById('sessionCards');
grid.innerHTML = '';
@@ -19,14 +20,42 @@ registerPage('dashboard', {
grid.appendChild(card);
}
// Neue Session Button
const btn = document.getElementById('newSessionBtn');
if (btn) {
btn.addEventListener('click', async () => {
const newSessionBtn = document.getElementById('newSessionBtn');
if (newSessionBtn) {
newSessionBtn.addEventListener('click', async () => {
const session = await apiCreateSession();
navigateTo('session', { id: session.sessionId });
});
}
// Agents laden
const agents = await apiGetAgents();
const agentGrid = document.getElementById('agentCards');
agentGrid.innerHTML = '';
for (const a of agents) {
const card = document.createElement('div');
card.className = 'card clickable';
card.innerHTML = `
<div class="agent-card-avatar">${a.avatar ? `<img src="${a.avatar}" alt="">` : '?'}</div>
<div class="card-title">${escHtml(a.name)}</div>
<div class="card-sub">${escHtml(a.model || 'sonnet')}</div>
`;
card.addEventListener('click', () => {
navigateTo('agent', { id: a.agentId });
});
agentGrid.appendChild(card);
}
// Neuer Agent Placeholder
const placeholder = document.createElement('div');
placeholder.className = 'card placeholder clickable';
placeholder.innerHTML = `
<div class="card-icon">+</div>
<div class="card-title">Neuer Agent</div>
`;
placeholder.addEventListener('click', () => navigateTo('agent'));
agentGrid.appendChild(placeholder);
},
destroy() {}
});
+39 -10
View File
@@ -1,7 +1,5 @@
// ── Session Page ──
let _sessionPageCleanup = [];
registerPage('session', {
async init(params) {
const sessionId = params.id;
@@ -9,7 +7,7 @@ registerPage('session', {
currentSessionId = sessionId;
// Session-Name laden
// Session laden
const session = await apiGetSession(sessionId);
const titleEl = document.getElementById('sessionTitle');
if (session && titleEl) {
@@ -17,6 +15,42 @@ registerPage('session', {
titleEl.addEventListener('dblclick', () => editTitle(sessionId, titleEl));
}
// Agent-Auswahl laden
const agentSelect = document.getElementById('agentSelect');
const sendBtn = document.getElementById('send');
const inputEl = document.getElementById('input');
if (agentSelect) {
const agents = await apiGetAgents();
agentSelect.innerHTML = '<option value="">-- Agent wählen --</option>';
for (const a of agents) {
const opt = document.createElement('option');
opt.value = a.agentId;
opt.textContent = `${a.name} (${a.model})`;
agentSelect.appendChild(opt);
}
// Gespeicherten Agent setzen
if (session && session.agentId) {
agentSelect.value = session.agentId;
}
// Agent-Wechsel speichern
agentSelect.addEventListener('change', async () => {
const val = agentSelect.value || null;
await apiUpdateSession(sessionId, { agentId: val });
updateSendState();
});
}
// Send-Button State
function updateSendState() {
const hasAgent = agentSelect && agentSelect.value;
if (sendBtn) sendBtn.disabled = !hasAgent;
if (inputEl) inputEl.disabled = !hasAgent;
}
updateSendState();
// System Prompt Toggle
const showSystem = document.getElementById('showSystem');
const systemPrompt = document.getElementById('systemPrompt');
@@ -27,18 +61,13 @@ registerPage('session', {
}
// Send
const sendBtn = document.getElementById('send');
if (sendBtn) sendBtn.addEventListener('click', sendMessage);
// Ctrl+Enter
const inputEl = document.getElementById('input');
if (inputEl) {
const keyHandler = (e) => {
inputEl.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') sendMessage();
};
inputEl.addEventListener('keydown', keyHandler);
// Auto-Resize
});
inputEl.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 150) + 'px';