Agent Builder UI: #builder -tabi, lomake, CRUD-integraatio
- Uusi välilehti: Agent Builder (navigointi #builder) - Agenttilista: ladataan /api/v1/agents, näytetään kortteina - Lomake: avatar-valitsin, rooli-template, malli, väri, docs, prompt, parametrit - Tallenna → POST /api/v1/agents, Poista → DELETE /api/v1/agents/:id - Avatar-grid: 8 valmista hahmoa valittavissa Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -724,6 +724,7 @@
|
|||||||
<div class="main-tab" onclick="switchMainTab('network')" data-i18n="tab_network">Laskentaverkko</div>
|
<div class="main-tab" onclick="switchMainTab('network')" data-i18n="tab_network">Laskentaverkko</div>
|
||||||
<div class="main-tab" onclick="switchMainTab('codelab')" data-i18n="tab_codelab">Koodilaboratorio</div>
|
<div class="main-tab" onclick="switchMainTab('codelab')" data-i18n="tab_codelab">Koodilaboratorio</div>
|
||||||
<div class="main-tab active" onclick="switchMainTab('agents')" data-i18n="tab_agents">Kipinä Agentic Playground</div>
|
<div class="main-tab active" onclick="switchMainTab('agents')" data-i18n="tab_agents">Kipinä Agentic Playground</div>
|
||||||
|
<div class="main-tab" onclick="switchMainTab('builder')" data-i18n="tab_builder">Agent Builder</div>
|
||||||
<div class="main-tab" onclick="switchMainTab('guide')" data-i18n="tab_guide">Opas</div>
|
<div class="main-tab" onclick="switchMainTab('guide')" data-i18n="tab_guide">Opas</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1131,6 +1132,70 @@
|
|||||||
</div><!-- /panel-agents -->
|
</div><!-- /panel-agents -->
|
||||||
|
|
||||||
<!-- PANEELI 4: Opas -->
|
<!-- PANEELI 4: Opas -->
|
||||||
|
<div id="panel-builder" class="main-panel">
|
||||||
|
<div style="max-width:800px;margin:0 auto;padding:20px">
|
||||||
|
<h2 style="color:#f0f6fc;margin-bottom:16px">Agent Builder</h2>
|
||||||
|
<p style="color:#8b949e;margin-bottom:20px">Luo ja muokkaa agentteja. Agentit tallentuvat palvelimelle ja ovat kaikkien käytettävissä.</p>
|
||||||
|
|
||||||
|
<!-- Agenttilista -->
|
||||||
|
<div id="builder-agent-list" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-bottom:24px"></div>
|
||||||
|
|
||||||
|
<!-- Lomake -->
|
||||||
|
<div id="builder-form" style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:20px;display:none">
|
||||||
|
<div style="display:grid;grid-template-columns:auto 1fr;gap:16px;align-items:start">
|
||||||
|
<!-- Avatar-valitsin -->
|
||||||
|
<div>
|
||||||
|
<img id="builder-avatar-preview" src="/avatars/kipina_notext.png" style="width:80px;height:80px;border-radius:16px;border:2px solid #30363d;cursor:pointer;object-fit:cover" onclick="document.getElementById('builder-avatar-select').style.display=document.getElementById('builder-avatar-select').style.display==='none'?'flex':'none'">
|
||||||
|
<div id="builder-avatar-select" style="display:none;flex-wrap:wrap;gap:6px;margin-top:8px;max-width:200px"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Kentät -->
|
||||||
|
<div style="display:flex;flex-direction:column;gap:10px">
|
||||||
|
<div style="display:flex;gap:10px">
|
||||||
|
<input id="builder-id" placeholder="tunniste (esim. tofuist)" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:6px 10px;color:#c9d1d9;font-size:13px">
|
||||||
|
<input id="builder-name" placeholder="Näyttönimi" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:6px 10px;color:#c9d1d9;font-size:13px">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px">
|
||||||
|
<select id="builder-role" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:6px 10px;color:#c9d1d9;font-size:13px">
|
||||||
|
<option value="coder">Koodari</option>
|
||||||
|
<option value="qa">QA / Testaus</option>
|
||||||
|
<option value="devops">DevOps</option>
|
||||||
|
<option value="devsecops">DevSecOps</option>
|
||||||
|
<option value="architect">Arkkitehti</option>
|
||||||
|
<option value="iac">IaC / Infra</option>
|
||||||
|
<option value="data">Data</option>
|
||||||
|
<option value="manager">Manageri</option>
|
||||||
|
<option value="writer">Kirjoittaja</option>
|
||||||
|
<option value="custom">Vapaa</option>
|
||||||
|
</select>
|
||||||
|
<input id="builder-model" placeholder="Malli (esim. qwen2.5-coder:7b)" value="qwen2.5-coder:7b" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:6px 10px;color:#c9d1d9;font-size:13px">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px">
|
||||||
|
<input id="builder-color" type="color" value="#3fb950" style="width:40px;height:32px;border:1px solid #30363d;border-radius:4px;background:#0d1117;cursor:pointer">
|
||||||
|
<input id="builder-docs" placeholder="Docs URL (valinnainen, esim. /docs/tofu-cheatsheet.md)" style="flex:1;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:6px 10px;color:#c9d1d9;font-size:13px">
|
||||||
|
</div>
|
||||||
|
<textarea id="builder-prompt" rows="4" placeholder="System prompt..." style="background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:8px 10px;color:#c9d1d9;font-size:13px;resize:vertical;font-family:inherit"></textarea>
|
||||||
|
<div style="display:flex;gap:10px;align-items:center">
|
||||||
|
<label style="color:#8b949e;font-size:12px">Temp:</label>
|
||||||
|
<input id="builder-temp" type="number" value="0.7" min="0" max="2" step="0.1" style="width:60px;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:4px 8px;color:#c9d1d9;font-size:12px">
|
||||||
|
<label style="color:#8b949e;font-size:12px">Top-k:</label>
|
||||||
|
<input id="builder-topk" type="number" value="40" min="1" max="200" style="width:60px;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:4px 8px;color:#c9d1d9;font-size:12px">
|
||||||
|
<label style="color:#8b949e;font-size:12px">Max tok:</label>
|
||||||
|
<input id="builder-maxtokens" type="number" value="512" min="32" max="4096" step="32" style="width:70px;background:#0d1117;border:1px solid #30363d;border-radius:4px;padding:4px 8px;color:#c9d1d9;font-size:12px">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;justify-content:flex-end">
|
||||||
|
<button onclick="builderSave()" style="background:#238636;border:1px solid #2ea043;color:#fff;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px">Tallenna</button>
|
||||||
|
<button onclick="builderDelete()" id="builder-delete-btn" style="background:#21262d;border:1px solid #f85149;color:#f85149;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px;display:none">Poista</button>
|
||||||
|
<button onclick="builderCancel()" style="background:#21262d;border:1px solid #30363d;color:#c9d1d9;padding:6px 16px;border-radius:4px;cursor:pointer;font-size:13px">Peruuta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Uusi agentti -nappi -->
|
||||||
|
<button onclick="builderNew()" id="builder-new-btn" style="margin-top:12px;background:#238636;border:1px solid #2ea043;color:#fff;padding:8px 20px;border-radius:6px;cursor:pointer;font-size:14px">+ Uusi agentti</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="panel-guide" class="main-panel">
|
<div id="panel-guide" class="main-panel">
|
||||||
<div id="guide-content" style="max-width:800px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--text-color);line-height:1.7;font-size:15px">
|
<div id="guide-content" style="max-width:800px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--text-color);line-height:1.7;font-size:15px">
|
||||||
<p style="color:#8b949e">Ladataan opasta...</p>
|
<p style="color:#8b949e">Ladataan opasta...</p>
|
||||||
@@ -1723,7 +1788,7 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
|||||||
|
|
||||||
// URL-hash navigointi
|
// URL-hash navigointi
|
||||||
const initHash = window.location.hash.replace('#', '');
|
const initHash = window.location.hash.replace('#', '');
|
||||||
const hashMap = { 'laskentaverkko': 'network', 'network': 'network', 'codelab': 'codelab', 'agents': 'agents', 'guide': 'guide' };
|
const hashMap = { 'laskentaverkko': 'network', 'network': 'network', 'codelab': 'codelab', 'agents': 'agents', 'builder': 'builder', 'guide': 'guide' };
|
||||||
if (hashMap[initHash]) {
|
if (hashMap[initHash]) {
|
||||||
switchMainTab(hashMap[initHash]);
|
switchMainTab(hashMap[initHash]);
|
||||||
}
|
}
|
||||||
@@ -4521,6 +4586,142 @@ ${filesHtml}
|
|||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Agent Builder ──
|
||||||
|
|
||||||
|
const BUILDER_AVATARS = [
|
||||||
|
'/avatars/kipina_notext.png', '/avatars/karhunpentu.png', '/avatars/kettu_notext.png',
|
||||||
|
'/avatars/pesukarhu_notext.png', '/avatars/susi_notext.png', '/avatars/laiskiainen_notext.png',
|
||||||
|
'/avatars/aikuinen_susi.png', '/avatars/gecko_notext.png'
|
||||||
|
];
|
||||||
|
|
||||||
|
let builderAgents = [];
|
||||||
|
let builderEditing = null; // null = uusi, string = id
|
||||||
|
|
||||||
|
async function builderLoad() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v1/agents');
|
||||||
|
if (res.ok) builderAgents = await res.json();
|
||||||
|
} catch(e) {}
|
||||||
|
builderRenderList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function builderRenderList() {
|
||||||
|
const list = document.getElementById('builder-agent-list');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = builderAgents.map(a => `
|
||||||
|
<div onclick="builderEdit('${a.id}')" style="background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:12px;cursor:pointer;transition:border-color 0.2s;display:flex;align-items:center;gap:10px" onmouseover="this.style.borderColor='${a.color}'" onmouseout="this.style.borderColor='#30363d'">
|
||||||
|
<img src="${a.avatar}" style="width:40px;height:40px;border-radius:10px;border:2px solid ${a.color};object-fit:cover">
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:600;color:${a.color};font-size:14px">${esc(a.name)}</div>
|
||||||
|
<div style="color:#8b949e;font-size:11px">${esc(a.model)} · ${a.role}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function builderNew() {
|
||||||
|
builderEditing = null;
|
||||||
|
document.getElementById('builder-form').style.display = 'block';
|
||||||
|
document.getElementById('builder-new-btn').style.display = 'none';
|
||||||
|
document.getElementById('builder-delete-btn').style.display = 'none';
|
||||||
|
document.getElementById('builder-id').value = '';
|
||||||
|
document.getElementById('builder-id').disabled = false;
|
||||||
|
document.getElementById('builder-name').value = '';
|
||||||
|
document.getElementById('builder-role').value = 'coder';
|
||||||
|
document.getElementById('builder-model').value = 'qwen2.5-coder:7b';
|
||||||
|
document.getElementById('builder-color').value = '#3fb950';
|
||||||
|
document.getElementById('builder-docs').value = '';
|
||||||
|
document.getElementById('builder-prompt').value = '';
|
||||||
|
document.getElementById('builder-temp').value = '0.7';
|
||||||
|
document.getElementById('builder-topk').value = '40';
|
||||||
|
document.getElementById('builder-maxtokens').value = '512';
|
||||||
|
document.getElementById('builder-avatar-preview').src = '/avatars/kipina_notext.png';
|
||||||
|
builderRenderAvatarSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function builderEdit(id) {
|
||||||
|
const a = builderAgents.find(x => x.id === id);
|
||||||
|
if (!a) return;
|
||||||
|
builderEditing = id;
|
||||||
|
document.getElementById('builder-form').style.display = 'block';
|
||||||
|
document.getElementById('builder-new-btn').style.display = 'none';
|
||||||
|
document.getElementById('builder-delete-btn').style.display = a.is_default ? 'none' : 'inline-block';
|
||||||
|
document.getElementById('builder-id').value = a.id;
|
||||||
|
document.getElementById('builder-id').disabled = true;
|
||||||
|
document.getElementById('builder-name').value = a.name;
|
||||||
|
document.getElementById('builder-role').value = a.role;
|
||||||
|
document.getElementById('builder-model').value = a.model;
|
||||||
|
document.getElementById('builder-color').value = a.color;
|
||||||
|
document.getElementById('builder-docs').value = a.docs || '';
|
||||||
|
document.getElementById('builder-prompt').value = a.prompt;
|
||||||
|
document.getElementById('builder-temp').value = a.temperature;
|
||||||
|
document.getElementById('builder-topk').value = a.top_k;
|
||||||
|
document.getElementById('builder-maxtokens').value = a.max_tokens;
|
||||||
|
document.getElementById('builder-avatar-preview').src = a.avatar;
|
||||||
|
builderRenderAvatarSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function builderRenderAvatarSelect() {
|
||||||
|
const container = document.getElementById('builder-avatar-select');
|
||||||
|
container.innerHTML = BUILDER_AVATARS.map(src =>
|
||||||
|
`<img src="${src}" style="width:36px;height:36px;border-radius:8px;border:2px solid #30363d;cursor:pointer;object-fit:cover" onclick="document.getElementById('builder-avatar-preview').src='${src}';document.getElementById('builder-avatar-select').style.display='none'">`
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function builderSave() {
|
||||||
|
const payload = {
|
||||||
|
id: document.getElementById('builder-id').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, ''),
|
||||||
|
name: document.getElementById('builder-name').value.trim(),
|
||||||
|
avatar: document.getElementById('builder-avatar-preview').src.replace(location.origin, ''),
|
||||||
|
role: document.getElementById('builder-role').value,
|
||||||
|
model: document.getElementById('builder-model').value.trim(),
|
||||||
|
color: document.getElementById('builder-color').value,
|
||||||
|
docs: document.getElementById('builder-docs').value.trim() || null,
|
||||||
|
prompt: document.getElementById('builder-prompt').value,
|
||||||
|
temperature: parseFloat(document.getElementById('builder-temp').value) || 0.7,
|
||||||
|
top_k: parseInt(document.getElementById('builder-topk').value) || 40,
|
||||||
|
max_tokens: parseInt(document.getElementById('builder-maxtokens').value) || 512,
|
||||||
|
repetition_penalty: 1.15,
|
||||||
|
};
|
||||||
|
if (!payload.id || !payload.name) { alert('Tunniste ja nimi vaaditaan'); return; }
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v1/agents', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
builderCancel();
|
||||||
|
await builderLoad();
|
||||||
|
} else {
|
||||||
|
alert('Virhe: ' + await res.text());
|
||||||
|
}
|
||||||
|
} catch(e) { alert('Virhe: ' + e.message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function builderDelete() {
|
||||||
|
if (!builderEditing) return;
|
||||||
|
if (!confirm('Poistetaanko agentti "' + builderEditing + '"?')) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v1/agents/' + builderEditing, { method: 'DELETE' });
|
||||||
|
if (res.ok) {
|
||||||
|
builderCancel();
|
||||||
|
await builderLoad();
|
||||||
|
} else {
|
||||||
|
alert('Virhe: ' + await res.text());
|
||||||
|
}
|
||||||
|
} catch(e) { alert('Virhe: ' + e.message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function builderCancel() {
|
||||||
|
document.getElementById('builder-form').style.display = 'none';
|
||||||
|
document.getElementById('builder-new-btn').style.display = 'inline-block';
|
||||||
|
builderEditing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ladataan agentit kun builder-tabi avataan
|
||||||
|
builderLoad();
|
||||||
|
|
||||||
// GUIDE.md:n lataus ja renderöinti
|
// GUIDE.md:n lataus ja renderöinti
|
||||||
(async function loadGuide() {
|
(async function loadGuide() {
|
||||||
const container = document.getElementById('guide-content');
|
const container = document.getElementById('guide-content');
|
||||||
|
|||||||
Reference in New Issue
Block a user