172 lines
7.3 KiB
JavaScript
172 lines
7.3 KiB
JavaScript
// Per-agent display metadata (icons + role blurb). Built-ins + known custom agents.
|
|
const ROUTER_AGENT_META = {
|
|
opencode: { icon: '🔧', blurb: 'Code & DevOps' },
|
|
hermes: { icon: '⚡', blurb: 'Memory & Scheduling' },
|
|
gemini: { icon: '🧠', blurb: 'Research & Analysis' },
|
|
jarvis: { icon: '🤖', blurb: 'Local-first AI / Deep Research' },
|
|
kilocode: { icon: '💻', blurb: 'AI Coding Assistant' },
|
|
codex: { icon: '🧩', blurb: 'Code Generation' },
|
|
};
|
|
function routerAgentMeta(name) {
|
|
return ROUTER_AGENT_META[name] || { icon: '🤖', blurb: 'Custom agent' };
|
|
}
|
|
|
|
async function renderSmartRouter() {
|
|
const content = document.getElementById('pageContent');
|
|
content.innerHTML = `
|
|
<div class="page-header">
|
|
<div class="page-header-left">
|
|
<div class="page-title">Smart Router</div>
|
|
<div class="page-subtitle">Intelligent task routing — auto-suggest or manually pick an agent</div>
|
|
</div>
|
|
</div>
|
|
<div class="card" style="margin-bottom:20px">
|
|
<div class="card-header">
|
|
<div class="card-title">Route a Task</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Describe your task</label>
|
|
<textarea class="form-textarea" id="routerTaskInput" placeholder="e.g., Deploy the CloudMart infrastructure to GCP..." rows="3"></textarea>
|
|
</div>
|
|
<div class="flex gap-3" style="align-items:flex-end">
|
|
<div class="form-group" style="flex:1">
|
|
<label class="form-label">Route to Agent</label>
|
|
<select class="form-select" id="routerAgentSelect">
|
|
<option value="auto">🤖 Auto (AI suggests)</option>
|
|
<!-- agent options injected by loadRouterAgents() -->
|
|
</select>
|
|
</div>
|
|
<button class="btn btn-primary" onclick="suggestRouter()" style="margin-bottom:16px">🤖 Suggest Agent</button>
|
|
<button class="btn btn-gradient" onclick="routeTask()" style="margin-bottom:16px">🚀 Route Task</button>
|
|
</div>
|
|
</div>
|
|
<div id="routerResult"></div>
|
|
<div class="section-title" style="margin-top:20px">Routing Rules</div>
|
|
<div class="card">
|
|
<table id="routerRulesTable">
|
|
<tr><th>Agent</th><th>Best For</th><th>Keywords</th></tr>
|
|
<!-- rows injected by loadRouterAgents() -->
|
|
</table>
|
|
</div>
|
|
`;
|
|
await loadRouterAgents();
|
|
}
|
|
|
|
// Pull the live agent list from the server so the dropdown + rules table
|
|
// always reflect every registered agent (built-in AND custom).
|
|
async function loadRouterAgents() {
|
|
let agents = [];
|
|
try {
|
|
const data = await api.getAgents();
|
|
agents = (data.agents || []).map(a => a.name);
|
|
} catch (e) {
|
|
// Fallback to known built-ins if the API is unreachable.
|
|
agents = ['opencode', 'hermes', 'gemini'];
|
|
}
|
|
|
|
const select = document.getElementById('routerAgentSelect');
|
|
const table = document.getElementById('routerRulesTable');
|
|
if (!select || !table) return;
|
|
|
|
// Populate dropdown (preserve the Auto option already in the DOM).
|
|
agents.forEach(name => {
|
|
const meta = routerAgentMeta(name);
|
|
const opt = document.createElement('option');
|
|
opt.value = name;
|
|
opt.textContent = `${meta.icon} ${name} (${meta.blurb})`;
|
|
select.appendChild(opt);
|
|
});
|
|
|
|
// Populate routing-rules table.
|
|
const rows = agents.map(name => {
|
|
const meta = routerAgentMeta(name);
|
|
const kw = (window._routerKeywords && window._routerKeywords[name]) || [];
|
|
return `<tr>
|
|
<td><strong>${meta.icon} ${name}</strong></td>
|
|
<td>${meta.blurb}</td>
|
|
<td class="text-muted text-sm">${kw.join(', ') || '—'}</td>
|
|
</tr>`;
|
|
}).join('');
|
|
table.innerHTML = `<tr><th>Agent</th><th>Best For</th><th>Keywords</th></tr>${rows}`;
|
|
}
|
|
|
|
async function suggestRouter() {
|
|
const task = document.getElementById('routerTaskInput').value.trim();
|
|
if (!task) { showToast('Describe your task first', 'warning'); return; }
|
|
const btn = document.querySelector('button[onclick="suggestRouter()"]');
|
|
if (btn) { btn.disabled = true; btn.textContent = '⏳ Thinking...'; }
|
|
try {
|
|
const data = await api.suggestRouter(task);
|
|
// Cache keyword map from scores for the rules table refresh.
|
|
window._routerKeywords = data.scores ? Object.keys(data.scores).reduce((m, a) => m, {}) : {};
|
|
const result = document.getElementById('routerResult');
|
|
const agentIcons = {
|
|
opencode: '🔧', hermes: '⚡', gemini: '🧠',
|
|
jarvis: '🤖', kilocode: '💻', codex: '🧩'
|
|
};
|
|
const confidenceColors = { high: 'var(--green)', medium: 'var(--yellow)', low: 'var(--text-muted)' };
|
|
result.innerHTML = `
|
|
<div class="card" style="border-color:${confidenceColors[data.confidence] || 'var(--border)'};margin-bottom:12px">
|
|
<div class="router-suggestion" style="border:none;padding:0;background:none">
|
|
<div>
|
|
<div class="router-suggestion-agent" style="font-size:18px">
|
|
${agentIcons[data.suggested_agent] || '🤖'} ${data.suggested_agent}
|
|
</div>
|
|
<div style="font-size:12px;color:var(--text-secondary);margin-top:4px">
|
|
Confidence: <span style="color:${confidenceColors[data.confidence] || 'var(--text-muted)'}">${data.confidence}</span>
|
|
${data.confidence === 'high' ? '✅' : data.confidence === 'medium' ? '⚠️' : '❓'}
|
|
</div>
|
|
</div>
|
|
<div style="flex:1;text-align:right">
|
|
<span class="badge badge-accent">Best Match</span>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;padding-top:8px;border-top:1px solid var(--border)">
|
|
${Object.entries(data.scores || {}).map(([agent, score]) => `
|
|
<span class="badge ${score > 0 ? 'badge-success' : 'badge-info'}">
|
|
${agentIcons[agent] || '🤖'} ${agent}: ${score}
|
|
</span>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.getElementById('routerAgentSelect').value = data.suggested_agent || 'auto';
|
|
} catch (err) {
|
|
showToast('Suggestion failed: ' + err.message, 'error');
|
|
} finally {
|
|
if (btn) { btn.disabled = false; btn.textContent = '🤖 Suggest Agent'; }
|
|
}
|
|
}
|
|
|
|
async function routeTask() {
|
|
const task = document.getElementById('routerTaskInput').value.trim();
|
|
if (!task) { showToast('Describe your task first', 'warning'); return; }
|
|
let agent = document.getElementById('routerAgentSelect').value;
|
|
if (agent === 'auto') {
|
|
showToast('Click "Suggest Agent" first or pick an agent manually', 'warning');
|
|
return;
|
|
}
|
|
const btn = document.querySelector('button[onclick="routeTask()"]');
|
|
if (btn) { btn.disabled = true; btn.textContent = '⏳ Routing...'; }
|
|
try {
|
|
const data = await api.routeTask(task, agent);
|
|
showToast(`✅ Task routed to ${agent}`, 'success');
|
|
const result = document.getElementById('routerResult');
|
|
result.innerHTML += `
|
|
<div class="card" style="margin-top:8px;border-color:var(--green)">
|
|
<div style="display:flex;align-items:center;gap:12px">
|
|
<span style="font-size:24px">✅</span>
|
|
<div>
|
|
<div style="font-weight:600">Task Routed</div>
|
|
<div class="text-muted text-sm">${data.message}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
} catch (err) {
|
|
showToast('Routing failed: ' + err.message, 'error');
|
|
} finally {
|
|
if (btn) { btn.disabled = false; btn.textContent = '🚀 Route Task'; }
|
|
}
|
|
}
|