- OpenAI als vollwertiger Chat-Provider (Modell-Auswahl, Key-Routing, StreamChat) - Hardcoded OpenAI-Modelle im Cache (gpt-5.x, gpt-4.1, o3, o4-mini etc.) - /api/models/openai Endpoint, OpenAI-Optgroup im Agent-Dropdown - Key-Routing: OpenAI-Key aus api_keys-Tabelle, Fallback via GetFirstActiveKeyByProvider - OpenAI-Checker: 2-Schritt-Check (Models + chat/completions mit max_tokens=1) - Google-Checker: 2-Schritt-Check (Models + generateContent mit maxOutputTokens=1) - 429 insufficient_quota und billing_not_active korrekt als quota_exceeded erkannt - Provider-Erkennung beim Adopt via KeyPatterns.DetectType() statt hardcoded anthropic - HandleCheck/HandleCheckAll unterstützt jetzt Anthropic, OpenAI und Google - Neue scanner_logs-Tabelle mit details-Spalte (JSON) für strukturierte Auswertung - GitHubEventScannerService: AddLog persistiert in DB, detaillierte Key-Fund-Logs - KeyScannerService: AddLog-Methode mit DB-Persistierung, detaillierte Logs - Frontend: Ungeprüfte-prüfen-Button (sequenziell, stoppt bei valid) - Frontend: Provider-Indicator im Key-Switch-Modal (Anthropic/OpenAI/Google) - Frontend: Check-Button für alle Provider (nicht nur Anthropic)
225 lines
9.0 KiB
JavaScript
225 lines
9.0 KiB
JavaScript
// ── 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) {
|
||
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 nvidiaOptgroup = document.getElementById('nvidiaOptgroup');
|
||
const pollinationsOptgroup = document.getElementById('pollinationsOptgroup');
|
||
const geminiOptgroup = document.getElementById('geminiOptgroup');
|
||
const openaiOptgroup = document.getElementById('openaiOptgroup');
|
||
|
||
// 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();
|
||
}
|
||
};
|
||
|
||
await Promise.all([
|
||
loadProviderModels('/api/models/openai', 'openai', openaiOptgroup),
|
||
loadProviderModels('/api/models/nvidia', 'nvidia', nvidiaOptgroup),
|
||
loadProviderModels('/api/models/pollinations', 'pollinations', pollinationsOptgroup),
|
||
loadProviderModels('/api/models/gemini', 'gemini', geminiOptgroup)
|
||
]);
|
||
const allowUserChoiceEl = document.getElementById('agentAllowUserChoice');
|
||
const autoCommitEl = document.getElementById('agentAutoCommit');
|
||
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');
|
||
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 || '';
|
||
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,
|
||
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);
|
||
}
|
||
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() {
|
||
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);
|
||
}
|