Intelligent Agent Router: LLM-powered natural language routing with suggestion UI
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Agent Router
|
||||||
|
LLM-powered router that reads a natural language request, examines the agent
|
||||||
|
catalog and user's instances, and recommends the best agent to handle it.
|
||||||
|
Supports: run_existing, create_and_run, configure, info, not_possible.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from shared import DASHBOARD_API, api_request
|
||||||
|
from llm_client import complete as llm_complete
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """You are the Agent Router for the Agent Command Center — a personal automation platform.
|
||||||
|
|
||||||
|
Your job: Given a user's natural language request, decide which agent (or combination of agents) should handle it.
|
||||||
|
|
||||||
|
You have access to:
|
||||||
|
1. The AGENT CATALOG — available agent types that can be enabled
|
||||||
|
2. The user's EXISTING INSTANCES — agents they've already enabled and configured
|
||||||
|
|
||||||
|
ACTIONS you can recommend:
|
||||||
|
- "run_existing" — Run one of the user's existing agent instances. Include the instance_id.
|
||||||
|
- "create_and_run" — The user doesn't have this agent yet. Enable it from the catalog with suggested config, then run it. Include catalog_id and suggested config.
|
||||||
|
- "configure" — Modify an existing instance's settings. Include instance_id and the config changes.
|
||||||
|
- "info" — The user is asking a question, not requesting an action. Answer it directly.
|
||||||
|
- "not_possible" — No agent can handle this request. Explain what's missing.
|
||||||
|
|
||||||
|
RULES:
|
||||||
|
- Always prefer running an existing instance over creating a new one
|
||||||
|
- Be specific about WHY you chose this agent
|
||||||
|
- For "configure" actions, specify exactly what config fields to change
|
||||||
|
- For "info" actions, answer the question directly in your reasoning
|
||||||
|
- If the request is ambiguous, pick the most likely interpretation and explain your reasoning
|
||||||
|
- Keep reasoning concise — 1-3 sentences
|
||||||
|
|
||||||
|
Respond with ONLY valid JSON (no markdown, no code fences):
|
||||||
|
{
|
||||||
|
"action": "run_existing|create_and_run|configure|info|not_possible",
|
||||||
|
"instance_id": null or integer,
|
||||||
|
"catalog_id": null or string,
|
||||||
|
"instance_name": null or string (for create_and_run),
|
||||||
|
"config": null or object,
|
||||||
|
"reasoning": "string explaining the decision"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def build_context(catalog, instances):
|
||||||
|
"""Build the context string for the LLM prompt."""
|
||||||
|
ctx = "=== AGENT CATALOG (available agent types) ===\n"
|
||||||
|
for c in catalog:
|
||||||
|
ctx += f"\n**{c['name']}** (id: {c['id']}, category: {c['category']})"
|
||||||
|
ctx += f"\n {c['description']}"
|
||||||
|
if c.get('requires_llm'):
|
||||||
|
ctx += "\n [Requires LLM]"
|
||||||
|
if c.get('is_sub_agent'):
|
||||||
|
ctx += "\n [Sub-agent — called by other agents]"
|
||||||
|
ctx += "\n"
|
||||||
|
|
||||||
|
ctx += "\n=== YOUR EXISTING AGENT INSTANCES ===\n"
|
||||||
|
if not instances:
|
||||||
|
ctx += "\nNo agents enabled yet.\n"
|
||||||
|
else:
|
||||||
|
for i in instances:
|
||||||
|
config_summary = json.dumps(i.get('config', {}))[:200]
|
||||||
|
ctx += f"\n**{i['name']}** (instance_id: {i['id']}, type: {i['catalog_id']}, status: {i.get('status', '?')})"
|
||||||
|
ctx += f"\n Config: {config_summary}"
|
||||||
|
ctx += "\n"
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
def route(user_id, request_text):
|
||||||
|
"""Route a natural language request to the best agent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: Dashboard user ID
|
||||||
|
request_text: The user's natural language request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with: action, instance_id, catalog_id, config, reasoning
|
||||||
|
"""
|
||||||
|
# Fetch catalog and user's instances
|
||||||
|
catalog = api_request(f"{DASHBOARD_API}/api/catalog/all", retries=1)
|
||||||
|
instances = api_request(f"{DASHBOARD_API}/api/instances/by-user/{user_id}", retries=1)
|
||||||
|
|
||||||
|
# Build the prompt
|
||||||
|
context = build_context(catalog, instances)
|
||||||
|
prompt = f"{context}\n=== USER REQUEST ===\n{request_text}"
|
||||||
|
|
||||||
|
# Call LLM
|
||||||
|
result = llm_complete(user_id, prompt, system=SYSTEM_PROMPT, max_tokens=500)
|
||||||
|
response_text = result["text"].strip()
|
||||||
|
|
||||||
|
# Parse JSON response
|
||||||
|
try:
|
||||||
|
# Handle potential markdown code fences
|
||||||
|
if response_text.startswith("```"):
|
||||||
|
response_text = response_text.split("```")[1]
|
||||||
|
if response_text.startswith("json"):
|
||||||
|
response_text = response_text[4:]
|
||||||
|
recommendation = json.loads(response_text)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
recommendation = {
|
||||||
|
"action": "info",
|
||||||
|
"reasoning": response_text,
|
||||||
|
"instance_id": None,
|
||||||
|
"catalog_id": None,
|
||||||
|
"config": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendation["model"] = result.get("model", "")
|
||||||
|
recommendation["tokens_in"] = result.get("input_tokens", 0)
|
||||||
|
recommendation["tokens_out"] = result.get("output_tokens", 0)
|
||||||
|
|
||||||
|
# Resolve agent name for display
|
||||||
|
if recommendation.get("instance_id"):
|
||||||
|
for i in instances:
|
||||||
|
if i["id"] == recommendation["instance_id"]:
|
||||||
|
recommendation["agent_name"] = i["name"]
|
||||||
|
break
|
||||||
|
elif recommendation.get("catalog_id"):
|
||||||
|
for c in catalog:
|
||||||
|
if c["id"] == recommendation["catalog_id"]:
|
||||||
|
recommendation["agent_name"] = c["name"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if "agent_name" not in recommendation:
|
||||||
|
recommendation["agent_name"] = recommendation.get("catalog_id") or "Unknown"
|
||||||
|
|
||||||
|
return recommendation
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--user-id", type=int, required=True)
|
||||||
|
parser.add_argument("request", nargs="+")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = route(args.user_id, " ".join(args.request))
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
+166
-1
@@ -14,7 +14,7 @@ from email.mime.text import MIMEText
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
|
||||||
from database import get_db, init_db
|
from database import get_db, init_db
|
||||||
from models import User, AgentCatalog, AgentInstance, Run, LLMProvider, Bridge
|
from models import User, AgentCatalog, AgentInstance, Run, LLMProvider, Bridge, RouteLog
|
||||||
|
|
||||||
app = FastAPI(title="Agent Command Center", version="2026.04.12.01")
|
app = FastAPI(title="Agent Command Center", version="2026.04.12.01")
|
||||||
|
|
||||||
@@ -724,6 +724,171 @@ def admin_delete_catalog(catalog_id: str, admin: dict = Depends(require_admin),
|
|||||||
return {"status": "deleted"}
|
return {"status": "deleted"}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Agent Router ---
|
||||||
|
|
||||||
|
class RouterRequest(BaseModel):
|
||||||
|
request: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/catalog/all")
|
||||||
|
def list_catalog_all(db: Session = Depends(get_db)):
|
||||||
|
"""Internal: full catalog for the router (no auth)."""
|
||||||
|
entries = db.query(AgentCatalog).all()
|
||||||
|
return [{
|
||||||
|
"id": e.id, "name": e.name, "description": e.description,
|
||||||
|
"category": e.category, "supports_schedule": e.supports_schedule,
|
||||||
|
"is_sub_agent": e.is_sub_agent, "requires_llm": e.requires_llm,
|
||||||
|
} for e in entries]
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/router")
|
||||||
|
def ask_router(data: RouterRequest, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||||
|
"""Route a natural language request to the best agent."""
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Call the router agent
|
||||||
|
agent_dir = "/app/agents"
|
||||||
|
config_path = f"/tmp/router_{user['user_id']}_{secrets.token_hex(4)}.json"
|
||||||
|
with open(config_path, "w") as f:
|
||||||
|
json.dump({"user_id": user["user_id"], "request": data.request}, f)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["python3", "-c",
|
||||||
|
f"import sys, json; sys.path.insert(0, '{agent_dir}'); "
|
||||||
|
f"d = json.load(open('{config_path}')); "
|
||||||
|
f"from agent_router import route; "
|
||||||
|
f"r = route(d['user_id'], d['request']); "
|
||||||
|
f"print(json.dumps(r))"],
|
||||||
|
capture_output=True, text=True, timeout=120, cwd=agent_dir,
|
||||||
|
env={**dict(os.environ), "PYTHONPATH": agent_dir},
|
||||||
|
)
|
||||||
|
os.remove(config_path)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(result.stderr[:500])
|
||||||
|
|
||||||
|
recommendation = json.loads(result.stdout.strip())
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e), "action": "not_possible", "reasoning": f"Router error: {e}"}
|
||||||
|
|
||||||
|
# Log the route
|
||||||
|
log = RouteLog(
|
||||||
|
user_id=user["user_id"],
|
||||||
|
request_text=data.request,
|
||||||
|
recommended_agent=recommendation.get("agent_name", ""),
|
||||||
|
action=recommendation.get("action", ""),
|
||||||
|
reasoning=recommendation.get("reasoning", ""),
|
||||||
|
outcome="pending",
|
||||||
|
metadata_={
|
||||||
|
"instance_id": recommendation.get("instance_id"),
|
||||||
|
"catalog_id": recommendation.get("catalog_id"),
|
||||||
|
"config": recommendation.get("config"),
|
||||||
|
"model": recommendation.get("model", ""),
|
||||||
|
"tokens_in": recommendation.get("tokens_in", 0),
|
||||||
|
"tokens_out": recommendation.get("tokens_out", 0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
db.add(log)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
recommendation["route_id"] = log.id
|
||||||
|
return recommendation
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/router/{route_id}/accept")
|
||||||
|
def accept_route(route_id: int, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||||
|
"""Accept a router suggestion and execute it."""
|
||||||
|
log = db.query(RouteLog).filter(RouteLog.id == route_id, RouteLog.user_id == user["user_id"]).first()
|
||||||
|
if not log:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
log.outcome = "accepted"
|
||||||
|
meta = log.metadata_ or {}
|
||||||
|
action = log.action
|
||||||
|
instance_id = meta.get("instance_id")
|
||||||
|
|
||||||
|
# Execute the recommended action
|
||||||
|
if action == "run_existing" and instance_id:
|
||||||
|
# Trigger the instance
|
||||||
|
import subprocess
|
||||||
|
inst = db.query(AgentInstance).filter(AgentInstance.id == instance_id).first()
|
||||||
|
if inst:
|
||||||
|
agent_dir = "/app/agents"
|
||||||
|
catalog_id = inst.catalog_id
|
||||||
|
u = db.query(User).filter(User.id == user["user_id"]).first()
|
||||||
|
env = {**dict(os.environ), "PYTHONPATH": agent_dir}
|
||||||
|
|
||||||
|
if catalog_id == "daily-briefing":
|
||||||
|
script_map = {"eric": "eric_briefing.py", "angela": "angela_briefing.py"}
|
||||||
|
script = script_map.get(u.username)
|
||||||
|
if script:
|
||||||
|
env_key = f"{u.username.upper().replace('.', '_')}_INSTANCE_ID"
|
||||||
|
env[env_key] = str(instance_id)
|
||||||
|
subprocess.Popen(["python3", f"{agent_dir}/{script}"], env=env, cwd=agent_dir)
|
||||||
|
elif catalog_id == "project-monitor":
|
||||||
|
config_path = f"/tmp/pm_config_{instance_id}.json"
|
||||||
|
with open(config_path, "w") as f:
|
||||||
|
json.dump({"config": inst.config or {}, "user_id": user["user_id"], "instance_id": instance_id}, f)
|
||||||
|
subprocess.Popen(
|
||||||
|
["python3", "-c",
|
||||||
|
f"import sys, json; sys.path.insert(0, '{agent_dir}'); "
|
||||||
|
f"d = json.load(open('{config_path}')); "
|
||||||
|
f"from project_monitor import run; "
|
||||||
|
f"run(d['config'], user_id=d['user_id'], instance_id=d['instance_id'])"],
|
||||||
|
env=env, cwd=agent_dir,
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return {"status": "executing", "message": f"Running {inst.name}"}
|
||||||
|
|
||||||
|
elif action == "configure" and instance_id:
|
||||||
|
inst = db.query(AgentInstance).filter(AgentInstance.id == instance_id).first()
|
||||||
|
if inst and meta.get("config"):
|
||||||
|
new_config = {**(inst.config or {}), **meta["config"]}
|
||||||
|
inst.config = new_config
|
||||||
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
flag_modified(inst, "config")
|
||||||
|
if new_config.get("project_name") and inst.catalog_id == "project-monitor":
|
||||||
|
inst.name = f"Project Monitor - {new_config['project_name']}"
|
||||||
|
db.commit()
|
||||||
|
return {"status": "configured", "message": f"Updated {inst.name}"}
|
||||||
|
|
||||||
|
elif action == "create_and_run":
|
||||||
|
catalog_id = meta.get("catalog_id")
|
||||||
|
catalog = db.query(AgentCatalog).filter(AgentCatalog.id == catalog_id).first()
|
||||||
|
if catalog:
|
||||||
|
config = {**(catalog.default_config or {}), **(meta.get("config") or {})}
|
||||||
|
inst = AgentInstance(
|
||||||
|
user_id=user["user_id"],
|
||||||
|
catalog_id=catalog_id,
|
||||||
|
name=meta.get("instance_name") or catalog.name,
|
||||||
|
config=config,
|
||||||
|
schedule="manual",
|
||||||
|
)
|
||||||
|
db.add(inst)
|
||||||
|
db.commit()
|
||||||
|
return {"status": "created", "message": f"Created and ready: {inst.name}", "instance_id": inst.id}
|
||||||
|
|
||||||
|
elif action == "info":
|
||||||
|
db.commit()
|
||||||
|
return {"status": "info", "message": log.reasoning}
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return {"status": "accepted"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/router/{route_id}/reject")
|
||||||
|
def reject_route(route_id: int, user: dict = Depends(require_auth), db: Session = Depends(get_db)):
|
||||||
|
"""Reject a router suggestion."""
|
||||||
|
log = db.query(RouteLog).filter(RouteLog.id == route_id, RouteLog.user_id == user["user_id"]).first()
|
||||||
|
if not log:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
log.outcome = "rejected"
|
||||||
|
db.commit()
|
||||||
|
return {"status": "rejected"}
|
||||||
|
|
||||||
|
|
||||||
# --- Bridge Management ---
|
# --- Bridge Management ---
|
||||||
|
|
||||||
class BridgeRegister(BaseModel):
|
class BridgeRegister(BaseModel):
|
||||||
|
|||||||
@@ -85,6 +85,20 @@ class Bridge(Base):
|
|||||||
user = relationship("User")
|
user = relationship("User")
|
||||||
|
|
||||||
|
|
||||||
|
class RouteLog(Base):
|
||||||
|
__tablename__ = "route_log"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||||
|
request_text = Column(Text, nullable=False)
|
||||||
|
recommended_agent = Column(String, default="")
|
||||||
|
action = Column(String, default="")
|
||||||
|
reasoning = Column(Text, default="")
|
||||||
|
outcome = Column(String, default="pending") # pending, accepted, rejected, success, failed
|
||||||
|
metadata_ = Column("metadata", JSON, default=dict)
|
||||||
|
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
class LLMProvider(Base):
|
class LLMProvider(Base):
|
||||||
__tablename__ = "llm_providers"
|
__tablename__ = "llm_providers"
|
||||||
|
|
||||||
|
|||||||
@@ -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-dot.offline{background:var(--red)}
|
||||||
.bridge-bar .bridge-actions{display:flex;gap:.5rem}
|
.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{text-align:center;padding:3rem;color:var(--text-dim)}
|
||||||
.empty-state h3{margin-bottom:.5rem;color:var(--text)}
|
.empty-state h3{margin-bottom:.5rem;color:var(--text)}
|
||||||
.time-ago{color:var(--text-dim)}
|
.time-ago{color:var(--text-dim)}
|
||||||
@@ -114,6 +128,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<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 id="bridge-bar"></div>
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>My Agents</h2>
|
<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')}
|
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 ---
|
// --- LLM Settings ---
|
||||||
async function showLLMSettings(){
|
async function showLLMSettings(){
|
||||||
const res=await fetch(API+'/api/me/llm');
|
const res=await fetch(API+'/api/me/llm');
|
||||||
|
|||||||
Reference in New Issue
Block a user