Files
gott/wwwroot/js/pages/agent-page.js
T
Meleki 057d37d76f feat: Avatar Scale (0.5×–3×) + Krea AI Bildgenerierung für Avatare
Backend:
- SessionRecord + AgentRecord: neues Feld AvatarScale (double, Default 1.0)
- DatabaseService: avatar_scale Migration, in Create/Update/GetWithStats
- SessionEndpoint + AgentEndpoint: avatarScale in Create/Update
- api.js: apiKreaStatus(), apiKreaGenerate()

Frontend:
- Session + Agent Templates: Range-Slider (0.5×–3×) in Avatar-Section
- Krea AI Generate: Prompt-Input + Magic-Button (nur wenn KreaToken konfiguriert)
- Live-Preview: transform: scale() auf Avatar-Vorschau
- Dashboard: scale() auf Session- und Agent-Card-Images
- form.css: avatar-scale-row + avatar-ai-section Styling
2026-03-06 10:59:09 +01:00

273 lines
10 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 _agentAvatarData = null;
let _agentPasteHandler = 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 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 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');
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');
const scaleRow = document.getElementById('agentScaleRow');
const scaleInput = document.getElementById('agentAvatarScale');
const scaleValue = document.getElementById('agentScaleValue');
_agentAvatarData = null;
function updatePreviewScale() {
var img = avatarPreview.querySelector('img');
if (img) img.style.scale = scaleInput.value;
scaleValue.textContent = scaleInput.value + '×';
}
scaleInput.addEventListener('input', updatePreviewScale);
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';
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 || '';
_agentAvatarData = agent.avatar || null;
deleteBtn.hidden = false;
if (agent.avatarScale && agent.avatarScale !== 1) {
scaleInput.value = agent.avatarScale;
scaleValue.textContent = agent.avatarScale + '×';
}
if (agent.avatar) {
avatarPreview.innerHTML = `<img src="${agent.avatar}" alt="Avatar" style="scale: ${agent.avatarScale || 1}">`;
scaleRow.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));
// AI Bild-Generierung (nur wenn Krea konfiguriert)
const aiSection = document.getElementById('agentAiSection');
const aiPrompt = document.getElementById('agentAiPrompt');
const aiGenerateBtn = document.getElementById('agentAiGenerateBtn');
apiKreaStatus().then(status => {
if (status.configured) aiSection.hidden = false;
});
aiGenerateBtn.addEventListener('click', async () => {
const prompt = aiPrompt.value.trim();
if (!prompt) return;
aiGenerateBtn.disabled = true;
aiGenerateBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
try {
const result = await apiKreaGenerate(prompt);
if (result.files && result.files.length > 0) {
const imgResp = await fetch('/api/images/file/' + result.files[0]);
const blob = await imgResp.blob();
const reader = new FileReader();
reader.onload = () => {
_agentAvatarData = reader.result;
avatarPreview.innerHTML = `<img src="${reader.result}" alt="Avatar">`;
scaleRow.hidden = false;
updatePreviewScale();
};
reader.readAsDataURL(blob);
}
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
} finally {
aiGenerateBtn.disabled = false;
aiGenerateBtn.innerHTML = '<i class="fa fa-magic"></i>';
}
});
aiPrompt.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); aiGenerateBtn.click(); }
});
// 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">`;
scaleRow.hidden = false;
updatePreviewScale();
};
reader.readAsDataURL(file);
});
avatarClearBtn.addEventListener('click', () => {
_agentAvatarData = null;
avatarPreview.innerHTML = '?';
avatarInput.value = '';
scaleRow.hidden = true;
scaleInput.value = 1;
scaleValue.textContent = '1×';
});
// Avatar Paste (Ctrl+V)
document.addEventListener('paste', _agentPasteHandler = (e) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
_agentAvatarData = reader.result;
avatarPreview.innerHTML = `<img src="${reader.result}" alt="Avatar">`;
scaleRow.hidden = false;
updatePreviewScale();
};
reader.readAsDataURL(file);
return;
}
}
});
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',
autoCommit: autoCommitEl.value,
useSoul: useSoulEl.checked,
useIdentity: useIdentityEl.checked,
useUserInfo: useUserInfoEl.checked,
useMemory: useMemoryEl.checked,
webSearchVersion: webSearchVersionEl.value,
avatar: _agentAvatarData,
avatarScale: parseFloat(scaleInput.value) || 1,
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 (_agentPasteHandler) {
document.removeEventListener('paste', _agentPasteHandler);
_agentPasteHandler = null;
}
_agentAvatarData = 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);
}