Files
gott/wwwroot/js/pages/agent-page.js
T
MelekiandClaude Opus 5 7305a5a50f OpenAI: Server-Key-Setting, Filter-Fix fuer o-Reihe, ehrlicher Modell-Refresh
Der Modell-Refresh war fuer OpenAI faktisch ein No-Op: der Key wurde nur aus
der api_keys-Tabelle geholt, und ohne Key blieb die alte Liste im Speicher
stehen und wurde direkt wieder in models_cache.json geschrieben - waehrend der
Endpoint pauschal Erfolg meldete.

- ServerSettingsService: neues Setting OpenAiApiKey (OpenAI war der einzige
  Provider ohne Server-Key, analog Gemini/MiniMax/z.ai)
- OpenAiService: ResolveOpenAiKey() (Setting -> api_keys), HasServerOpenAiKey(),
  ResolveProviderConfig zieht den Setting-Key auch im Chat
- OpenAiService: IsOpenAiChatModel() erfasst die o-Reihe inkl. bare IDs
  (o1, o3, kuenftig o5) - StartsWith("o3-") hat die verschluckt
- RefreshModelsFromApis() liefert ProviderRefreshResult[] pro Provider;
  ModelsEndpoint gibt providers[] zurueck, agent-page.js meldet uebersprungene
  Provider als Fehler-Toast statt pauschalem Erfolg
- ChatEndpoint/TelegramService/CronjobService: OpenAI ohne Agent-Key faellt auf
  den Server-Key zurueck statt hart abzubrechen
- ARCHITECTURE.md nachgezogen

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 11:55:22 +02:00

272 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── 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 = '<i class="fa fa-spinner fa-spin"></i>';
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 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 || '';
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,
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 = '<div class="skill-empty">Keine Skills verfügbar. Lege Skills in <code>data/skills/</code> ab.</div>';
return;
}
for (const skill of allSkills) {
const checked = assignedSlugs.includes(skill.slug) ? 'checked' : '';
const disabled = !skill.hasPromptMd ? 'disabled' : '';
const version = skill.version ? ` <span class="skill-version">v${skill.version}</span>` : '';
const desc = skill.description ? `<span class="skill-desc">${skill.description}</span>` : '';
const warning = !skill.hasPromptMd ? '<span class="skill-warning">⚠ PROMPT.md fehlt – Skill nicht verwendbar</span>' : '';
const row = document.createElement('label');
row.className = 'skill-item' + (!skill.hasPromptMd ? ' skill-unavailable' : '');
row.innerHTML = `
<input type="checkbox" value="${skill.slug}" ${checked} ${disabled}>
<span class="skill-name">${skill.name}${version}</span>
${desc}
${warning}
`;
container.appendChild(row);
}
}
function getSelectedSkills(container) {
return Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
.map(cb => cb.value);
}