Intelligent Agent Router: LLM-powered natural language routing with suggestion UI

This commit is contained in:
2026-04-14 14:41:44 +00:00
parent f39bd13fc6
commit f01553c511
4 changed files with 413 additions and 1 deletions
+91
View File
@@ -95,6 +95,20 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
.bridge-bar .bridge-dot.offline{background:var(--red)}
.bridge-bar .bridge-actions{display:flex;gap:.5rem}
/* Router */
.router-bar{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:1rem 1.25rem;margin-bottom:1.5rem;display:flex;gap:.75rem;align-items:center}
.router-bar input{flex:1;padding:.6rem .85rem;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;outline:none}
.router-bar input:focus{border-color:var(--accent)}
.router-bar input::placeholder{color:var(--text-dim)}
.router-bar button{padding:.6rem 1.25rem;background:var(--accent);color:#fff;border:none;border-radius:6px;font-size:.9rem;cursor:pointer;white-space:nowrap}
.router-bar button:hover{background:var(--accent-hover)}
.router-bar button:disabled{opacity:.5;cursor:not-allowed}
.router-result{background:var(--surface);border:1px solid var(--accent);border-radius:10px;padding:1.25rem;margin-bottom:1.5rem;display:none}
.router-result .rr-action{font-size:.75rem;text-transform:uppercase;color:var(--accent);font-weight:600;letter-spacing:.04em;margin-bottom:.4rem}
.router-result .rr-agent{font-size:1.05rem;font-weight:600;margin-bottom:.5rem}
.router-result .rr-reasoning{color:var(--text-dim);font-size:.85rem;margin-bottom:1rem;line-height:1.5}
.router-result .rr-actions{display:flex;gap:.5rem}
.empty-state{text-align:center;padding:3rem;color:var(--text-dim)}
.empty-state h3{margin-bottom:.5rem;color:var(--text)}
.time-ago{color:var(--text-dim)}
@@ -114,6 +128,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
</div>
<div class="container">
<div class="router-bar">
<input type="text" id="router-input" placeholder="What do you need? Ask the router..." onkeydown="if(event.key==='Enter')askRouter()">
<button id="router-btn" onclick="askRouter()">Ask</button>
</div>
<div class="router-result" id="router-result"></div>
<div id="bridge-bar"></div>
<div class="section-header">
<h2>My Agents</h2>
@@ -361,6 +380,78 @@ async function enableAgent(catalogId,name){
else{const err=await res.json();alert(err.detail||'Failed to enable agent')}
}
// --- Router ---
async function askRouter(){
const input=document.getElementById('router-input');
const btn=document.getElementById('router-btn');
const resultDiv=document.getElementById('router-result');
const text=input.value.trim();
if(!text)return;
btn.disabled=true;btn.textContent='Thinking...';
resultDiv.style.display='none';
try{
const res=await fetch(API+'/api/router',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({request:text})});
if(res.status===401){location.href='/login';return}
const r=await res.json();
if(r.error){
resultDiv.innerHTML=`<div class="rr-action">Error</div><div class="rr-reasoning">${r.reasoning||r.error}</div>`;
resultDiv.style.display='block';
btn.disabled=false;btn.textContent='Ask';
return;
}
const action=r.action||'info';
const actionLabels={run_existing:'Run Agent',create_and_run:'Create & Run',configure:'Update Config',info:'Info',not_possible:'Not Available'};
let actionsHtml='';
if(action==='run_existing'||action==='create_and_run'||action==='configure'){
actionsHtml=`<div class="rr-actions">
<button class="btn-save" onclick="acceptRoute(${r.route_id})">Run It</button>
<button class="btn-secondary" onclick="rejectRoute(${r.route_id})">Dismiss</button>
</div>`;
} else if(action==='info'){
actionsHtml=`<div class="rr-actions"><button class="btn-secondary" onclick="dismissRouter()">OK</button></div>`;
} else {
actionsHtml=`<div class="rr-actions"><button class="btn-secondary" onclick="dismissRouter()">OK</button></div>`;
}
resultDiv.innerHTML=`
<div class="rr-action">${actionLabels[action]||action}</div>
<div class="rr-agent">${r.agent_name||''}</div>
<div class="rr-reasoning">${r.reasoning||''}</div>
${actionsHtml}`;
resultDiv.style.display='block';
}catch(e){
resultDiv.innerHTML=`<div class="rr-action">Error</div><div class="rr-reasoning">Connection error: ${e}</div>`;
resultDiv.style.display='block';
}
btn.disabled=false;btn.textContent='Ask';
}
async function acceptRoute(routeId){
const resultDiv=document.getElementById('router-result');
resultDiv.querySelector('.rr-actions').innerHTML='<span style="color:var(--green)">Running...</span>';
const res=await fetch(API+'/api/router/'+routeId+'/accept',{method:'POST'});
if(res.ok){
const data=await res.json();
resultDiv.querySelector('.rr-actions').innerHTML=`<span style="color:var(--green)">${data.message||'Done'}</span>`;
setTimeout(()=>{dismissRouter();refresh()},3000);
}
}
async function rejectRoute(routeId){
await fetch(API+'/api/router/'+routeId+'/reject',{method:'POST'});
dismissRouter();
}
function dismissRouter(){
document.getElementById('router-result').style.display='none';
document.getElementById('router-input').value='';
}
// --- LLM Settings ---
async function showLLMSettings(){
const res=await fetch(API+'/api/me/llm');