// ── Agent Page ── let _agentAvatarEditor = null; let _agentTaskMde = null; registerPage('agent', { async init(params) { const agentId = params.id; const isNew = !agentId; let agent = null; if (isNew && currentUser && currentUser.canCreateAgents === false && !currentUser.isAdmin) { showToast('Keine Berechtigung zum Erstellen von Agents', 'error'); return navigateTo('dashboard'); } 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'); // Modelle dynamisch laden const loadProviderModels = async (url, prefix, optgroup) => { try { const resp = await fetch(url); const data = await resp.json(); if (optgroup && data.models && data.models.length > 0) { optgroup.innerHTML = ''; for (const m of data.models) { const opt = document.createElement('option'); opt.value = prefix + ':' + m.id; opt.textContent = m.displayName; optgroup.appendChild(opt); } } else if (optgroup) { optgroup.remove(); } } catch (e) { if (optgroup) optgroup.remove(); } }; // Optgroups bei jedem Aufruf frisch abfragen (nach dem ersten Laden können leere entfernt worden sein) const loadAllModels = () => Promise.all([ loadProviderModels('/api/models/openai', 'openai', document.getElementById('openaiOptgroup')), loadProviderModels('/api/models/nvidia', 'nvidia', document.getElementById('nvidiaOptgroup')), loadProviderModels('/api/models/pollinations', 'pollinations', document.getElementById('pollinationsOptgroup')), loadProviderModels('/api/models/gemini', 'gemini', document.getElementById('geminiOptgroup')), loadProviderModels('/api/models/minimax', 'minimax', document.getElementById('minimaxOptgroup')), loadProviderModels('/api/models/glm', 'glm', document.getElementById('glmOptgroup')) ]); await loadAllModels(); // Refresh-Button: Modelle live von den Providern neu laden (Backend erneuert seinen Cache) const refreshModelsBtn = document.getElementById('refreshModelsBtn'); if (refreshModelsBtn) { refreshModelsBtn.addEventListener('click', async () => { const origHtml = refreshModelsBtn.innerHTML; refreshModelsBtn.innerHTML = ''; refreshModelsBtn.disabled = true; const prevValue = modelEl.value; try { const res = await apiRefreshModels(); await loadAllModels(); modelEl.value = prevValue; // vorherige Auswahl wiederherstellen (falls noch vorhanden) // Ehrlich melden, was tatsächlich geladen wurde – ein übersprungener Provider // (z.B. OpenAI ohne Key) darf nicht als Erfolg durchgehen. const skipped = (res && res.providers || []).filter(p => !p.loaded); if (skipped.length) { showToast('Neu geladen, übersprungen: ' + skipped.map(p => p.provider + ' – ' + p.note).join('; '), 'error'); } else { showToast('Modell-Listen neu geladen'); } } catch (err) { showToast(err.message || 'Fehler beim Neu-Laden der Modelle', 'error'); } finally { refreshModelsBtn.innerHTML = origHtml; refreshModelsBtn.disabled = false; } }); } const allowUserChoiceEl = document.getElementById('agentAllowUserChoice'); const autoCommitEl = document.getElementById('agentAutoCommit'); const effortEl = document.getElementById('agentEffort'); const maxOutputTokensEl = document.getElementById('agentMaxOutputTokens'); const disableThinkingEl = document.getElementById('agentDisableThinking'); const useSoulEl = document.getElementById('agentUseSoul'); const useIdentityEl = document.getElementById('agentUseIdentity'); const useUserInfoEl = document.getElementById('agentUseUserInfo'); const useMemoryEl = document.getElementById('agentUseMemory'); const useAutoContextEl = document.getElementById('agentUseAutoContext'); const useGitHubSearchEl = document.getElementById('agentUseGitHubSearch'); const useSerperSearchEl = document.getElementById('agentUseSerperSearch'); const webSearchVersionEl = document.getElementById('agentWebSearchVersion'); const avatarPreview = document.getElementById('avatarPreview'); const saveBtn = document.getElementById('agentSaveBtn'); const cancelBtn = document.getElementById('agentCancelBtn'); const deleteBtn = document.getElementById('agentDeleteBtn'); // Rücksprung nach Speichern/Abbrechen: zurück zum Chat, wenn von dort geöffnet – sonst Dashboard const goBack = () => { if (params.returnTo === 'chat' && params.returnId) { navigateTo('chat', { id: params.returnId }); } else { navigateTo('dashboard'); } }; const commandsList = document.getElementById('agentCommandsList'); const addCommandBtn = document.getElementById('agentAddCommandBtn'); const skillsSection = document.getElementById('agentSkillsSection'); const skillList = document.getElementById('agentSkillList'); const isPublicEl = document.getElementById('agentIsPublic'); const more = document.querySelector('.more'); // Avatar Editor initialisieren _agentAvatarEditor = AvatarEditor.init({ previewEl: avatarPreview, initialImage: (agent && agent.avatar) || null, initialScale: (agent && agent.avatarScale) || 1, initialOffsetX: (agent && agent.avatarOffsetX) || 0, initialOffsetY: (agent && agent.avatarOffsetY) || 0 }); // EasyMDE für Task-Instruction const taskEl = document.getElementById('agentTaskInstruction'); _agentTaskMde = createMDE(taskEl, { toolbar: ['bold', 'italic', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', '|', 'link', 'code', '|', 'preview', 'guide'], placeholder: 'Spezifische Anweisungen für diesen Agent (Markdown, optional)' }); if (agent) { title.textContent = agent.name; nameEl.value = agent.name || ''; descEl.value = agent.description || ''; modelEl.value = agent.model || 'sonnet'; allowUserChoiceEl.checked = agent.allowUserChoice !== false; _agentTaskMde.value(agent.taskInstruction || ''); autoCommitEl.value = agent.autoCommit || ''; effortEl.value = agent.effort || ''; maxOutputTokensEl.value = agent.maxOutputTokens > 0 ? agent.maxOutputTokens : ''; disableThinkingEl.checked = agent.disableThinking || false; useSoulEl.checked = agent.useSoul !== false; useIdentityEl.checked = agent.useIdentity !== false; useUserInfoEl.checked = agent.useUserInfo !== false; useMemoryEl.checked = agent.useMemory !== false; useAutoContextEl.checked = agent.useAutoContext !== false; useGitHubSearchEl.checked = agent.useGitHubSearch || false; useSerperSearchEl.checked = agent.useSerperSearch || false; webSearchVersionEl.value = agent.webSearchVersion || ''; isPublicEl.checked = agent.isPublic || false; deleteBtn.hidden = false; // Custom Commands laden loadCommandsIntoUI(commandsList, agent.customCommands); // Skills laden const [allSkills, agentSkills] = await Promise.all([ apiGetSkills(), apiGetAgentSkills(agentId) ]); const assignedSlugs = agentSkills.map(s => s.slug); skillsSection.hidden = false; renderSkillCheckboxes(skillList, allSkills, assignedSlugs); } else { // Neue Agents: Skills-Liste auch anzeigen const allSkills = await apiGetSkills(); if (allSkills.length > 0) { skillsSection.hidden = false; renderSkillCheckboxes(skillList, allSkills, []); } } // Befehl hinzufügen addCommandBtn.addEventListener('click', () => createCommandRow(commandsList)); initMoreButton(more); // Speichern saveBtn.addEventListener('click', async () => { const data = { name: nameEl.value.trim() || 'Unbenannt', description: descEl.value.trim(), model: modelEl.value, allowUserChoice: allowUserChoiceEl.checked, autoCommit: autoCommitEl.value, effort: effortEl.value, // Leer / 0 / ungültig → 0 = aus (Modell-Default) maxOutputTokens: Math.max(0, parseInt(maxOutputTokensEl.value, 10) || 0), disableThinking: disableThinkingEl.checked, useSoul: useSoulEl.checked, useIdentity: useIdentityEl.checked, useUserInfo: useUserInfoEl.checked, useMemory: useMemoryEl.checked, useAutoContext: useAutoContextEl.checked, useGitHubSearch: useGitHubSearchEl.checked, useSerperSearch: useSerperSearchEl.checked, webSearchVersion: webSearchVersionEl.value, avatar: _agentAvatarEditor.getImage(), avatarScale: _agentAvatarEditor.getScale(), avatarOffsetX: _agentAvatarEditor.getOffsetX(), avatarOffsetY: _agentAvatarEditor.getOffsetY(), customCommands: getCommandsFromUI(commandsList), taskInstruction: _agentTaskMde ? _agentTaskMde.value().trim() || null : null, isPublic: isPublicEl.checked }; if (isNew) { const created = await apiCreateAgent(data); // Skills zuweisen falls welche ausgewählt const selectedSlugs = getSelectedSkills(skillList); if (selectedSlugs.length > 0) { await apiSetAgentSkills(created.agentId, selectedSlugs); } } else { await apiUpdateAgent(agentId, data); // Skills aktualisieren const selectedSlugs = getSelectedSkills(skillList); await apiSetAgentSkills(agentId, selectedSlugs); } goBack(); }); // Abbrechen cancelBtn.addEventListener('click', () => goBack()); // Löschen deleteBtn.addEventListener('click', async () => { if (!confirm('Agent wirklich löschen?')) return; await apiDeleteAgent(agentId); navigateTo('dashboard'); }); }, destroy() { if (_agentTaskMde) { try { _agentTaskMde.toTextArea(); } catch (_) {} _agentTaskMde = null; } if (_agentAvatarEditor) { _agentAvatarEditor.destroy(); _agentAvatarEditor = null; } } }); function renderSkillCheckboxes(container, allSkills, assignedSlugs) { container.innerHTML = ''; if (allSkills.length === 0) { container.innerHTML = '
data/skills/ ab.