refactor: Agent-Auswahl in Session-Form als Card + Picker-Modal

- buildAgentCard() Helper in ui.js erstellt (wiederverwendbar für Dashboard + Picker)
- Session-Form: select-Dropdown durch klickbare Agent-Card ersetzt
- Agent-Picker-Modal zeigt alle Agents mit identischen Cards wie am Dashboard
- Dashboard refactored: nutzt buildAgentCard() statt dupliziertem Inline-HTML
- Kein duplizierter Code mehr für Agent-Card-Rendering (Badges, Skills, Avatar)
This commit is contained in:
Meleki committed 2026-03-06 14:01:27 +01:00
1 parent a82540e7db
commit 92c20402eb
5 files changed
+252 -42

No files matched your search

+98
View File
@@ -297,6 +297,104 @@ label.skill-item {
margin-left: auto;
}
/* ── Agent Select Card (in Session-Formular) ── */
.agent-select-card {
display: flex;
align-items: center;
gap: var(--box-spacing);
padding: var(--box-padding);
background: var(--f3-bg);
border: var(--border-width) solid var(--f3-border);
border-radius: var(--border-radius);
cursor: pointer;
transition: border-color var(--transition-duration);
}
.agent-select-card:hover {
border-color: var(--primary);
}
.agent-select-card .card-image {
flex: 0 0 4em;
height: 4em;
}
.agent-select-empty {
opacity: var(--semi-transparent);
display: flex;
align-items: center;
gap: var(--badge-spacing);
}
.agent-select-change {
margin-left: auto;
opacity: var(--semi-transparent);
flex-shrink: 0;
transition: opacity var(--transition-duration);
}
.agent-select-card:hover .agent-select-change {
opacity: 1;
color: var(--primary);
}
/* ── Agent Picker Modal ── */
.agent-picker-overlay {
position: fixed;
inset: 0;
background: rgb(from var(--f1-bg) r g b / var(--semi-transparent));
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.agent-picker-modal {
background: var(--f2-bg);
border: var(--border-width) solid var(--f2-border);
border-radius: var(--border-radius);
width: 90vw;
max-width: var(--modal-width);
max-height: 80vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.agent-picker-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--box-padding);
border-bottom: var(--border-width) solid var(--f1-border);
background: var(--f1-bg);
font-weight: bold;
}
.agent-picker-body {
padding: var(--badge-spacing);
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--badge-spacing);
}
.agent-picker-item {
cursor: pointer;
transition: border-color var(--transition-duration), filter var(--transition-duration);
}
.agent-picker-item:hover {
border-color: var(--primary);
filter: var(--hover-filter);
}
.agent-picker-item.selected {
border-color: var(--primary);
}
/* ── Profile ── */
.profile-message {
+4 -1
View File
@@ -104,7 +104,10 @@
<label>Name</label>
<input type="text" id="sessionName" placeholder="Name der Session">
<label>Agent</label>
<select id="agentSelect"></select>
<div id="agentSelectCard" class="agent-select-card">
<div class="agent-select-empty"><i class="fa fa-user-circle-o"></i> Kein Agent ausgewählt</div>
</div>
<input type="hidden" id="agentSelect" value="">
<div class="more">
<a>Weitere Einstellungen</a>
<div hidden>
+6 -33
View File
@@ -176,46 +176,19 @@ registerPage('dashboard', {
}));
for (const a of agents) {
const card = document.createElement('div');
card.className = 'card';
const badges = [];
if (a.toolChoice === 'any') badges.push('<span class="badge badge-forced-feature" title="Tool-Nutzung erzwingen"><i class="fa fa-wrench"></i> Erzwungen</span>');
if (a.autoCommit === 'mention') badges.push('<span class="badge badge-feature" title="Git-Commits nur auf Anweisung"><i class="fa fa-code-fork"></i> Anmerken</span>');
if (a.autoCommit === 'enforce') badges.push('<span class="badge badge-forced-feature" title="Git-Commits nach jeder Änderung erzwungen"><i class="fa fa-code-fork"></i> Erzwungen</span>');
if (a.autoCommit === 'forbid') badges.push('<span class="badge badge-forced-feature" title="Git-Commits strikt verboten"><i class="fa fa-code-fork"></i> Verboten</span>');
if (a.useSoul) badges.push('<span class="badge badge-feature" title="Hat eine Seele (SOUL.md)"><i class="fa fa-heart"></i> Seele</span>');
if (a.useIdentity) badges.push('<span class="badge badge-feature" title="Hat eine Identität (IDENTITY.md)"><i class="fa fa-id-badge"></i> Ich</span>');
if (a.useUserInfo) badges.push('<span class="badge badge-feature" title="Kennt sein Gegenüber (Benutzer-Persona)"><i class="fa fa-user"></i> Du</span>');
if (a.useMemory) badges.push('<span class="badge badge-feature" title="Zugriff auf Erinnerungen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
if (a.webSearchVersion) {
const wsYear = a.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026';
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
}
const skills = agentSkillsMap[a.agentId] || [];
for (const skill of skills) {
badges.push(`<span class="badge badge-skill" title="${esc(skill.description || skill.slug)}"><i class="fa fa-puzzle-piece"></i> ${esc(skill.name)}</span>`);
}
const card = buildAgentCard(a, skills);
const agentScaleStyle = a.avatarScale && a.avatarScale !== 1 ? ` style="transform: scale(${a.avatarScale})"` : '';
card.innerHTML = `
<div class="card-image">${a.avatar ? `<img src="${a.avatar}" alt=""${agentScaleStyle}>` : ''}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(a.name)}</span>
<span class="badge">${prettyModelName(a.model)}</span>
<div class="card-menu">
// Menü zum title-row hinzufügen
const titleRow = card.querySelector('.card-title-row');
const menuHtml = `<div class="card-menu">
<button class="card-menu-trigger icon-btn"><i class="fa fa-ellipsis-v"></i></button>
<div class="card-menu-dropdown" hidden>
<button class="card-menu-item" data-action="edit"><i class="fa fa-pencil"></i> Bearbeiten</button>
<button class="card-menu-item" data-action="files"><i class="fa fa-folder"></i> Dateien verwalten</button>
</div>
</div>
</div>
<div class="card-badges">${badges.join('')}</div>
</div>
`;
</div>`;
titleRow.insertAdjacentHTML('beforeend', menuHtml);
const menuTrigger = card.querySelector('.card-menu-trigger');
const menuDropdown = card.querySelector('.card-menu-dropdown');
menuTrigger.addEventListener('click', (e) => {
+97 -8
View File
@@ -20,6 +20,7 @@ registerPage('session', {
const contextEl = document.getElementById('sessionContext');
const workspaceEl = document.getElementById('sessionWorkspace');
const agentSelect = document.getElementById('agentSelect');
const agentSelectCard = document.getElementById('agentSelectCard');
const imagePreview = document.getElementById('sessionImagePreview');
const saveBtn = document.getElementById('sessionSaveBtn');
const cancelBtn = document.getElementById('sessionCancelBtn');
@@ -72,16 +73,45 @@ registerPage('session', {
initSessionTelegram(sessionId);
}
// Agents laden
// Agents laden + Skills parallel
const agents = await apiGetAgents();
agentSelect.innerHTML = '<option value="">-- Kein Agent --</option>';
for (const a of agents) {
const opt = document.createElement('option');
opt.value = a.agentId;
opt.textContent = `${a.name} (${a.model})`;
agentSelect.appendChild(opt);
const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a;
const agentSkillsMap = {};
await Promise.all(agents.map(async a => {
agentSkillsMap[a.agentId] = await apiGetAgentSkills(a.agentId);
}));
function renderAgentSelectCard(agent) {
agentSelectCard.innerHTML = '';
if (!agent) {
agentSelectCard.innerHTML = '<div class="agent-select-empty"><i class="fa fa-user-circle-o"></i> Kein Agent ausgewählt</div>';
return;
}
const skills = agentSkillsMap[agent.agentId] || [];
const inner = buildAgentCard(agent, skills);
// Die inneren Elemente (card-image + card-body) in die Select-Card übernehmen
while (inner.firstChild) agentSelectCard.appendChild(inner.firstChild);
// Pencil-Icon hinzufügen
agentSelectCard.insertAdjacentHTML('beforeend', '<span class="agent-select-change"><i class="fa fa-pencil"></i></span>');
}
if (session && session.agentId) agentSelect.value = session.agentId;
// Initiale Anzeige
if (session && session.agentId) {
agentSelect.value = session.agentId;
renderAgentSelectCard(agentMap[session.agentId] || null);
} else {
renderAgentSelectCard(null);
}
// Klick auf Card → Modal öffnen
agentSelectCard.addEventListener('click', () => {
openAgentPickerModal(agents, agentSkillsMap, agentSelect.value, (selectedId) => {
agentSelect.value = selectedId || '';
renderAgentSelectCard(selectedId ? agentMap[selectedId] : null);
});
});
more.addEventListener('click', () => {
const content = more.querySelector('[hidden]');
@@ -177,6 +207,65 @@ registerPage('session', {
}
});
function openAgentPickerModal(agents, agentSkillsMap, currentId, onSelect) {
// Overlay
const overlay = document.createElement('div');
overlay.className = 'agent-picker-overlay';
const modal = document.createElement('div');
modal.className = 'agent-picker-modal';
// Header
const header = document.createElement('div');
header.className = 'agent-picker-header';
header.innerHTML = '<span>Agent auswählen</span><button class="icon-btn agent-picker-close"><i class="fa fa-times"></i></button>';
modal.appendChild(header);
// Body
const body = document.createElement('div');
body.className = 'agent-picker-body';
// "Kein Agent" Option
const noneCard = document.createElement('div');
noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : '');
noneCard.innerHTML = `
<div class="card-image" style="display:flex;align-items:center;justify-content:center"><i class="fa fa-ban" style="font-size:2em;opacity:var(--semi-transparent)"></i></div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">Kein Agent</span>
</div>
</div>
`;
noneCard.addEventListener('click', () => {
onSelect(null);
overlay.remove();
});
body.appendChild(noneCard);
// Agent Cards via Helper
for (const a of agents) {
const skills = agentSkillsMap[a.agentId] || [];
const item = buildAgentCard(a, skills);
item.classList.add('agent-picker-item');
if (a.agentId === currentId) item.classList.add('selected');
item.addEventListener('click', () => {
onSelect(a.agentId);
overlay.remove();
});
body.appendChild(item);
}
modal.appendChild(body);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Schließen
header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove());
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove();
});
}
function initSessionTelegram(sessionId) {
var tokenInput = document.getElementById('sessionTelegramToken');
var secretInput = document.getElementById('sessionTelegramSecret');
+47
View File
@@ -1,3 +1,50 @@
// ── Agent Card Helper ──
// Erzeugt ein .card-Element für einen Agent (wiederverwendbar für Dashboard + Picker).
// agent: Agent-Objekt, skills: Array von Skills (optional)
// Gibt ein DOM-Element zurück (.card mit .card-image + .card-body inkl. Badges).
function buildAgentCard(agent, skills) {
const card = document.createElement('div');
card.className = 'card';
const scaleStyle = agent.avatarScale && agent.avatarScale !== 1 ? ` style="transform: scale(${agent.avatarScale})"` : '';
const imageHtml = agent.avatar ? `<img src="${agent.avatar}" alt=""${scaleStyle}>` : '';
// Badges
const badges = [];
if (agent.toolChoice === 'any') badges.push('<span class="badge badge-forced-feature" title="Tool-Nutzung erzwingen"><i class="fa fa-wrench"></i> Erzwungen</span>');
if (agent.autoCommit === 'mention') badges.push('<span class="badge badge-feature" title="Git-Commits nur auf Anweisung"><i class="fa fa-code-fork"></i> Anmerken</span>');
if (agent.autoCommit === 'enforce') badges.push('<span class="badge badge-forced-feature" title="Git-Commits nach jeder Änderung erzwungen"><i class="fa fa-code-fork"></i> Erzwungen</span>');
if (agent.autoCommit === 'forbid') badges.push('<span class="badge badge-forced-feature" title="Git-Commits strikt verboten"><i class="fa fa-code-fork"></i> Verboten</span>');
if (agent.useSoul) badges.push('<span class="badge badge-feature" title="Hat eine Seele (SOUL.md)"><i class="fa fa-heart"></i> Seele</span>');
if (agent.useIdentity) badges.push('<span class="badge badge-feature" title="Hat eine Identität (IDENTITY.md)"><i class="fa fa-id-badge"></i> Ich</span>');
if (agent.useUserInfo) badges.push('<span class="badge badge-feature" title="Kennt sein Gegenüber (Benutzer-Persona)"><i class="fa fa-user"></i> Du</span>');
if (agent.useMemory) badges.push('<span class="badge badge-feature" title="Zugriff auf Erinnerungen (Memory-Tools)"><i class="fa fa-database"></i> Erinnerungen</span>');
if (agent.webSearchVersion) {
const wsYear = agent.webSearchVersion.includes('2025') ? 'Version 2025' : 'v2026';
badges.push(`<span class="badge badge-feature" title="Web-Suche - ${wsYear}"><i class="fa fa-search"></i> Web-Suche</span>`);
}
if (skills) {
for (const skill of skills) {
badges.push(`<span class="badge badge-skill" title="${esc(skill.description || skill.slug)}"><i class="fa fa-puzzle-piece"></i> ${esc(skill.name)}</span>`);
}
}
card.innerHTML = `
<div class="card-image">${imageHtml}</div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(agent.name)}</span>
<span class="badge">${prettyModelName(agent.model)}</span>
</div>
${badges.length ? `<div class="card-badges">${badges.join('')}</div>` : ''}
</div>
`;
return card;
}
// ── Toast Notifications ──
function showToast(message, type) {