- Neues Feld AllowUserChoice (bool, default true) im AgentRecord - DB-Migration: allow_user_choice Spalte mit DEFAULT 1 - Agent-Form: Checkbox 'Rückfragen erlauben' nach 'Tool-Nutzung erzwingen' - ToolService.GetToolDefinitions: neuer Parameter includeUserChoice - ask_user_choice Tool wird nur eingebunden wenn Agent es erlaubt - Review-Agents und Non-Streaming (Cronjobs) erhalten nie ask_user_choice - ChatEndpoint reicht Agent.AllowUserChoice an StreamChat durch
175 lines
6.7 KiB
JavaScript
175 lines
6.7 KiB
JavaScript
// ── Agent Page ──
|
||
|
||
let _agentAvatarEditor = 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 toolChoiceEl = document.getElementById('agentToolChoice');
|
||
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 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 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
|
||
});
|
||
|
||
if (agent) {
|
||
title.textContent = agent.name;
|
||
nameEl.value = agent.name || '';
|
||
descEl.value = agent.description || '';
|
||
modelEl.value = agent.model || 'sonnet';
|
||
toolChoiceEl.checked = (agent.toolChoice || 'any') === 'any';
|
||
allowUserChoiceEl.checked = agent.allowUserChoice !== false;
|
||
autoCommitEl.value = agent.autoCommit || '';
|
||
useSoulEl.checked = agent.useSoul !== false;
|
||
useIdentityEl.checked = agent.useIdentity !== false;
|
||
useUserInfoEl.checked = agent.useUserInfo !== false;
|
||
useMemoryEl.checked = agent.useMemory !== false;
|
||
webSearchVersionEl.value = agent.webSearchVersion || '';
|
||
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));
|
||
|
||
more.addEventListener('click', () => {
|
||
const content = more.querySelector('[hidden]');
|
||
while (content.firstChild)
|
||
more.parentNode.insertBefore(content.firstChild, more);
|
||
more.remove();
|
||
});
|
||
|
||
// Speichern
|
||
saveBtn.addEventListener('click', async () => {
|
||
const data = {
|
||
name: nameEl.value.trim() || 'Unbenannt',
|
||
description: descEl.value.trim(),
|
||
model: modelEl.value,
|
||
toolChoice: toolChoiceEl.checked ? 'any' : 'auto',
|
||
allowUserChoice: allowUserChoiceEl.checked,
|
||
autoCommit: autoCommitEl.value,
|
||
useSoul: useSoulEl.checked,
|
||
useIdentity: useIdentityEl.checked,
|
||
useUserInfo: useUserInfoEl.checked,
|
||
useMemory: useMemoryEl.checked,
|
||
webSearchVersion: webSearchVersionEl.value,
|
||
avatar: _agentAvatarEditor.getImage(),
|
||
avatarScale: _agentAvatarEditor.getScale(),
|
||
avatarOffsetX: _agentAvatarEditor.getOffsetX(),
|
||
avatarOffsetY: _agentAvatarEditor.getOffsetY(),
|
||
customCommands: getCommandsFromUI(commandsList)
|
||
};
|
||
|
||
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 (_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);
|
||
}
|