aipackage/cezen-portal/agents.html
Jino Jose b3eec34158 Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)
Brings the ISO-packaged portal copy current with the live cezen-portal/
working directory, which had drifted across the entire V1.0 GA polish
initiative (Phases 1-10) plus this session's topnav icon-order bugfix.

- 52 existing files updated, 2 new pages added (about.html, health.html)
- cgit/cezen-portal/ is now byte-identical to cezen-portal/
- Backend files (rag_ingest.py, cezen_license.py) already in sync;
  main.py intentionally left as-is (cgit is ahead there with
  workstation-category commercial-fit logic not yet backported to the
  live working copy)

Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
2026-07-09 11:11:38 +05:30

1188 lines
69 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Studio — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=12">
<style>
/* ── Layout ── */
.ab-layout { display:grid; grid-template-columns:260px 1fr; min-height:calc(100vh - 64px); }
@media(max-width:900px){ .ab-layout { grid-template-columns:1fr; } }
/* ── Sidebar ── */
.ab-sidebar { border-right:1px solid var(--bdr); background:var(--navy2); display:flex; flex-direction:column; }
.ab-sidebar-header { padding:16px 14px 10px; border-bottom:1px solid var(--bdr); }
.ab-sidebar-header h3 { font-size:12px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.5px; margin:0 0 10px; }
.ab-new-btn { display:flex; align-items:center; gap:8px; width:100%; padding:9px 12px; border-radius:8px; border:1.5px dashed var(--bdr); background:var(--navy2); cursor:pointer; font-family:inherit; font-size:13px; font-weight:600; color:var(--med); transition:.15s; }
.ab-new-btn:hover { border-color:var(--teal); color:var(--teal); }
.ab-agent-list { flex:1; overflow-y:auto; padding:8px; }
.ab-agent-item { padding:10px 12px; border-radius:8px; cursor:pointer; transition:.1s; border:1px solid transparent; margin-bottom:4px; }
.ab-agent-item:hover { background:rgba(255,255,255,.03); }
.ab-agent-item.active { background:rgba(13,148,136,.12); border-color:rgba(13,148,136,.4); }
.ab-agent-name { font-size:13px; font-weight:600; color:var(--ink); }
.ab-agent-desc { font-size:11px; color:var(--lt); margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ab-agent-meta { font-size:10px; color:var(--lt); margin-top:4px; }
/* ── Main area ── */
.ab-main { background:rgba(255,255,255,.03); padding:28px; overflow-y:auto; }
/* ── Empty state ── */
.ab-empty { text-align:center; padding:80px 20px; color:var(--lt); }
.ab-empty-icon { width:56px; height:56px; margin:0 auto 16px; color:var(--brand); }
.ab-empty-icon svg { width:100%; height:100%; }
.ab-empty-title { font-size:20px; font-weight:700; color:var(--ink); margin-bottom:8px; }
.ab-empty-sub { font-size:14px; max-width:400px; margin:0 auto; line-height:1.6; }
/* ── Builder card ── */
.ab-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:24px 28px; margin-bottom:20px; }
.ab-card-title { font-size:15px; font-weight:700; color:var(--ink); margin-bottom:18px; display:flex; align-items:center; gap:10px; }
.ab-meta-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:0; }
.ab-field { display:flex; flex-direction:column; gap:5px; margin-bottom:14px; }
.ab-field label { font-size:11px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.4px; }
.ab-field input, .ab-field textarea, .ab-field select {
padding:9px 12px; border:1.5px solid var(--bdr); border-radius:8px;
font-family:inherit; font-size:13px; color:var(--ink); background:var(--navy2);
}
.ab-field input:focus, .ab-field textarea:focus, .ab-field select:focus { outline:none; border-color:var(--teal); }
.ab-field textarea { resize:vertical; min-height:60px; }
/* ── Steps ── */
.ab-steps-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }
.ab-steps-title { font-size:14px; font-weight:700; color:var(--ink); }
.ab-steps-list { display:flex; flex-direction:column; gap:12px; margin-bottom:14px; }
.ab-step {
border:1.5px solid var(--bdr); border-radius:12px; background:rgba(255,255,255,.03);
overflow:hidden; transition:.15s;
}
.ab-step:hover { border-color:var(--teal); }
.ab-step-header {
display:flex; align-items:center; gap:10px; padding:12px 14px;
cursor:pointer; user-select:none; background:var(--navy2);
border-bottom:1px solid transparent;
}
.ab-step.open .ab-step-header { border-bottom-color:var(--bdr); }
.ab-step-num { width:24px; height:24px; border-radius:50%; background:var(--teal); color:var(--ink); font-size:11px; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.ab-step-name-text { flex:1; font-size:13px; font-weight:600; color:var(--ink); }
.ab-step-type-badge { font-size:11px; background:rgba(255,255,255,.03); border:1px solid var(--bdr); border-radius:20px; padding:2px 9px; color:var(--med); }
.ab-step-actions { display:flex; gap:6px; }
.ab-step-btn { background:none; border:none; cursor:pointer; font-size:14px; padding:2px 6px; color:var(--lt); border-radius:4px; }
.ab-step-btn:hover { background:rgba(255,255,255,.03); color:var(--ink); }
.ab-step-body { padding:14px; display:none; }
.ab-step.open .ab-step-body { display:block; }
.ab-step-grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
@media(max-width:640px){ .ab-step-grid { grid-template-columns:1fr; } }
/* ── Add step dropdown ── */
.ab-add-step { position:relative; }
.ab-add-step-btn { display:flex; align-items:center; gap:8px; width:100%; padding:10px 14px; border-radius:10px; border:1.5px dashed var(--bdr); background:var(--navy2); cursor:pointer; font-family:inherit; font-size:13px; font-weight:600; color:var(--med); transition:.15s; justify-content:center; }
.ab-add-step-btn:hover { border-color:var(--teal); color:var(--teal); }
.ab-step-types { display:none; position:absolute; top:calc(100% + 6px); left:0; right:0; background:var(--navy2); border:1.5px solid var(--bdr); border-radius:10px; z-index:50; box-shadow:0 8px 24px rgba(0,0,0,.1); overflow:hidden; }
.ab-step-types.open { display:block; }
.ab-step-type-opt { display:flex; align-items:center; gap:10px; padding:11px 14px; cursor:pointer; font-size:13px; color:var(--ink); transition:.1s; }
.ab-step-type-opt:hover { background:rgba(255,255,255,.03); }
.ab-step-type-opt .icon { width:18px; height:18px; color:currentColor; }
.ab-step-type-opt .icon svg { width:100%; height:100%; }
.ab-step-type-opt .label { font-weight:600; }
.ab-step-type-opt .sublabel { font-size:11px; color:var(--lt); margin-top:1px; }
/* ── Autonomy tier selector ── */
.ab-autonomy-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:4px; }
@media(max-width:700px){ .ab-autonomy-grid { grid-template-columns:repeat(2,1fr); } }
.ab-tier-opt { border:1.5px solid var(--bdr); border-radius:12px; padding:12px 14px; cursor:pointer; transition:.15s; background:var(--navy2); text-align:left; }
.ab-tier-opt:hover { border-color:var(--purple); }
.ab-tier-opt.selected { border-color:var(--purple); background:rgba(124,58,237,.06); }
.ab-tier-opt.selected.observe { border-color:#6B7280; background:rgba(107,114,128,.06); }
.ab-tier-opt.selected.advise { border-color:#2563EB; background:rgba(37,99,235,.06); }
.ab-tier-opt.selected.approval { border-color:#D97706; background:rgba(217,119,6,.06); }
.ab-tier-opt.selected.auto { border-color:#16A34A; background:rgba(22,163,74,.06); }
.ab-tier-icon { width:22px; height:22px; margin:0 auto 6px; color:currentColor; }
.ab-tier-icon svg { width:100%; height:100%; }
.ab-tier-name { font-size:12px; font-weight:700; color:var(--ink); margin-bottom:2px; }
.ab-tier-desc { font-size:11px; color:var(--lt); line-height:1.4; }
/* ── Tier badge (sidebar) ── */
.tier-badge { display:inline-block; font-size:10px; font-weight:700; border-radius:20px; padding:2px 8px; text-transform:uppercase; letter-spacing:.4px; }
.tier-badge.observe { background:rgba(107,114,128,.1); color:#6B7280; }
.tier-badge.advise { background:rgba(37,99,235,.1); color:#2563EB; }
.tier-badge.approval { background:rgba(217,119,6,.1); color:#D97706; }
.tier-badge.auto { background:rgba(22,163,74,.1); color:#16A34A; }
/* ── Permissions card ── */
.ab-perm-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
@media(max-width:640px){ .ab-perm-grid { grid-template-columns:1fr; } }
.ab-toggle-row { display:flex; align-items:center; gap:10px; padding:10px 14px; border:1px solid var(--bdr); border-radius:8px; background:var(--bg); cursor:pointer; transition:.1s; }
.ab-toggle-row:hover { border-color:var(--purple); }
.ab-toggle-row input[type=checkbox] { accent-color:var(--purple); width:15px; height:15px; flex-shrink:0; }
.ab-toggle-label { font-size:13px; color:var(--ink); font-weight:500; flex:1; }
.ab-toggle-sub { font-size:11px; color:var(--lt); }
/* ── Approval queue ── */
.ab-queue-banner { background:#FFFBEB; border:1.5px solid #D97706; border-radius:14px; padding:16px 20px; margin-bottom:20px; display:none; }
.ab-queue-banner.has-items { display:block; }
.ab-queue-title { font-size:13px; font-weight:700; color:#92400E; margin-bottom:12px; display:flex; align-items:center; gap:8px; }
.ab-queue-item { background:#FFFFFF; border:1px solid #FDE68A; border-radius:10px; padding:14px; margin-bottom:10px; }
.ab-queue-item:last-child { margin-bottom:0; }
.ab-queue-meta { font-size:11px; color:#92400E; margin-bottom:8px; display:flex; gap:12px; }
.ab-queue-preview { font-size:12px; color:var(--ink); background:var(--bg); border-radius:6px; padding:8px 10px; margin-bottom:10px; line-height:1.5; max-height:80px; overflow:hidden; }
.ab-queue-actions { display:flex; gap:8px; }
.ab-approve-btn { padding:6px 16px; border-radius:20px; border:none; font-family:inherit; font-size:12px; font-weight:700; cursor:pointer; background:#16A34A; color:white; transition:.15s; }
.ab-approve-btn:hover { background:#15803D; }
.ab-reject-btn { padding:6px 16px; border-radius:20px; border:1px solid #FCA5A5; font-family:inherit; font-size:12px; font-weight:700; cursor:pointer; background:#FEF2F2; color:#B91C1C; transition:.15s; }
.ab-reject-btn:hover { background:#FEE2E2; }
/* ── Approval waiting state ── */
.ab-awaiting-approval { background:#FFFBEB; border:1.5px solid #D97706; border-radius:12px; padding:20px 24px; text-align:center; margin-top:16px; }
.ab-awaiting-icon { width:32px; height:32px; margin:0 auto 8px; color:#92400E; }
.ab-awaiting-icon svg { width:100%; height:100%; }
.ab-awaiting-title { font-size:15px; font-weight:700; color:#92400E; margin-bottom:6px; }
.ab-awaiting-sub { font-size:13px; color:#A16207; }
/* ── Action bar ── */
.ab-actions { display:flex; gap:10px; flex-wrap:wrap; }
/* ── Run panel ── */
.ab-run-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; overflow:hidden; }
.ab-run-header { padding:16px 20px; border-bottom:1px solid var(--bdr); display:flex; align-items:center; gap:12px; }
.ab-run-title { font-size:14px; font-weight:700; color:var(--ink); flex:1; }
.ab-run-body { padding:20px; }
.ab-run-input textarea { width:100%; box-sizing:border-box; padding:12px 14px; border:1.5px solid var(--bdr); border-radius:10px; font-family:inherit; font-size:13px; color:var(--ink); resize:vertical; min-height:80px; }
.ab-run-input textarea:focus { outline:none; border-color:var(--teal); }
#run-btn { margin-top:12px; padding:11px 24px; background:var(--teal); color:var(--ink); border:none; border-radius:8px; font-family:inherit; font-size:14px; font-weight:700; cursor:pointer; transition:.15s; }
#run-btn:hover { filter:brightness(1.08); }
#run-btn:disabled { opacity:.5; cursor:not-allowed; }
/* ── Step results ── */
.ab-step-results { margin-top:20px; display:flex; flex-direction:column; gap:12px; }
.ab-step-result { border:1px solid var(--bdr); border-radius:10px; overflow:hidden; }
.ab-step-result-header { padding:10px 14px; background:rgba(255,255,255,.03); display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--bdr); }
.ab-step-result-label { font-size:12px; font-weight:700; color:var(--ink); flex:1; }
.ab-step-result-body { padding:14px; font-size:13px; color:var(--ink); line-height:1.65; white-space:pre-wrap; max-height:300px; overflow-y:auto; }
.ab-step-result.ok .ab-step-result-header { background:rgba(13,148,136,.12); border-bottom-color:#99F6E4; }
.ab-step-result.error .ab-step-result-header { background:rgba(185,28,28,.08); border-bottom-color:#FECACA; }
.ab-spinner { display:inline-block; animation:spin 1s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* ── Runs history ── */
.ab-hist-table-wrap { border:1px solid var(--bdr); border-radius:10px; overflow:hidden; }
table.ab-hist { width:100%; border-collapse:collapse; }
.ab-hist th { background:rgba(255,255,255,.03); padding:9px 14px; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.4px; color:var(--lt); border-bottom:1px solid var(--bdr); text-align:left; }
.ab-hist td { padding:10px 14px; border-bottom:1px solid var(--bdr); font-size:13px; color:var(--ink); }
.ab-hist tr:last-child td { border-bottom:none; }
.ab-hist tr:hover td { background:rgba(255,255,255,.03); cursor:pointer; }
.ab-status { display:inline-block; padding:2px 9px; border-radius:20px; font-size:11px; font-weight:700; }
.ab-status.done { background:rgba(34,197,94,.15); color:#15803D; }
.ab-status.running { background:#DBEAFE; color:#1D4ED8; }
.ab-status.error { background:#FEE2E2; color:#B91C1C; }
.ab-status.pending { background:rgba(234,179,8,.15); color:#A16207; }
</style>
</head>
<body>
<header class="topnav">
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
<nav>
<a href="index.html">Home</a>
<a href="quickstart.html">Quick Start</a>
<a href="prompts.html">Prompt Library</a>
<a href="usecases.html">Use Cases</a>
<span class="nav-sep"></span>
<div class="nav-dropdown">
<button class="nav-drop-btn">Help ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">LEARN /</span>
<a href="quickstart.html">Quick Start</a>
<a href="models.html">Models</a>
<span class="nav-drop-cat">SUPPORT /</span>
<a href="troubleshooting.html">Troubleshoot</a>
<a href="faq.html">FAQ</a>
<span class="nav-drop-cat">MORE /</span>
<a href="glossary.html">Glossary</a>
<a href="whats-new.html">What's New</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn">Admin ▾</button>
<div class="nav-drop-menu nav-drop-menu-wide">
<span class="nav-drop-cat">DOCS /</span>
<a href="security.html">Security & Privacy</a>
<a href="admin.html">Admin Guide</a>
<span class="nav-drop-cat">MONITOR /</span>
<a href="dashboard.html">Dashboard</a>
<a href="analytics.html">Usage Analytics</a>
<a href="audit.html">Governance & Audit</a>
<a href="health.html">Health Center</a>
<a href="feedback.html">Feedback &amp; Ratings</a>
<span class="nav-drop-cat">MANAGE /</span>
<a href="users.html">Users</a>
<a href="teams.html">Teams</a>
<a href="models-admin.html">Model Catalog</a>
<a href="training.html">Training</a>
<a href="knowledge.html">Knowledge Base</a>
<span class="nav-drop-cat">TOOLS /</span>
<a href="apikeys.html">API Keys</a>
<a href="benchmark.html">Benchmarking</a>
<a href="model-compare.html">Model Compare</a>
<a href="api-playground.html">API Playground</a>
<a href="guardrails.html">Guardrails</a>
<a href="rag-quality.html">RAG Quality</a>
<a href="router.html">Model Router</a>
<a href="connectors.html">MCP & Enterprise Connectors</a>
<span class="nav-drop-cat">SYSTEM /</span>
<a href="console.html">Console</a>
<a href="settings.html">Settings</a>
</div>
</div>
<div class="nav-dropdown">
<button class="nav-drop-btn active">AI Tools ▾</button>
<div class="nav-drop-menu">
<span class="nav-drop-cat">INTELLIGENCE /</span>
<a href="documents.html">Document Intelligence</a>
<a href="chat-multi.html">AI Workspace</a>
<a href="prompt-studio.html">Prompt Studio</a>
<a href="meeting.html">Meeting Assistant</a>
<span class="nav-drop-cat">AUTOMATION /</span>
<a href="agents.html">Agent Studio</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Designer</a>
<span class="nav-drop-cat">QUALITY /</span>
<a href="evals.html">AI Eval Suite</a>
<a href="chatrooms.html">Chat Rooms</a>
</div>
</div>
</nav>
<a href="notifications.html" class="nav-icon-btn" style="position:relative" title="Notifications">
<span class="icon-svg icon-md" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
</span>
</a>
<span class="badge" data-brand="tier">Basic Tier</span>
<div id="nav-org-logo" class="nav-org-logo"></div>
</header>
<div class="ab-layout">
<!-- Sidebar -->
<aside class="ab-sidebar">
<div class="ab-sidebar-header">
<h3>My Agents</h3>
<button class="ab-new-btn" onclick="newAgent()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span><span>New Agent</span></span></button>
</div>
<input type="text" id="agent-filter" placeholder="Filter agents…" oninput="agentFilterQuery=this.value.toLowerCase();renderAgentList();" style="width:100%;box-sizing:border-box;margin:0 0 10px;padding:8px 10px;border:1px solid var(--bdr);border-radius:7px;font-size:12.5px;font-family:inherit">
<div class="ab-agent-list" id="agent-list">
<div class="cezen-list-empty">No agents yet &mdash; <a onclick="newAgent()">create your first one</a>.</div>
</div>
</aside>
<!-- Main -->
<main class="ab-main" id="ab-main">
<!-- Empty state -->
<div class="ab-empty" id="ab-empty">
<div class="ab-empty-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="7" y="8" width="10" height="8" rx="2"/><path d="M12 4v4M9 16v2M15 16v2M6 10H4M20 10h-2"/><circle cx="10" cy="12" r="1"/><circle cx="14" cy="12" r="1"/></svg></div>
<div class="ab-empty-title">Agent Studio</div>
<div class="ab-empty-sub">
Chain prompts and AI steps into automated pipelines. Each step's output feeds the next — build multi-step workflows that run with one click.
</div>
<div style="margin-top:24px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap">
<button class="btn btn-primary" onclick="loadTemplate('report')"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>Report Generator</span></button>
<button class="btn btn-ghost" onclick="loadTemplate('classify')"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 7h8"/><path d="M8 12h5"/><path d="M8 17h8"/><path d="M16 7a3 3 0 0 1 0 6"/></svg></span>Classify &amp; Summarise</span></button>
<button class="btn btn-ghost" onclick="loadTemplate('qa')"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9.09 9a3 3 0 1 1 5.82 1c0 2-3 3-3 3"/><path d="M12 17h.01"/><circle cx="12" cy="12" r="9"/></svg></span>Q&amp;A Extractor</span></button>
</div>
</div>
<!-- Approval Queue (shown when pending approvals exist) -->
<div class="ab-queue-banner" id="approval-queue"></div>
<!-- Builder (hidden until agent selected/created) -->
<div id="ab-builder" style="display:none">
<!-- Agent meta -->
<div class="ab-card">
<div class="ab-card-title"><span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="7" y="8" width="10" height="8" rx="2"/><path d="M12 4v4M9 16v2M15 16v2M6 10H4M20 10h-2"/><circle cx="10" cy="12" r="1"/><circle cx="14" cy="12" r="1"/></svg></span>Agent Details</div>
<div class="ab-meta-row">
<div class="ab-field">
<label>Agent Name</label>
<input type="text" id="agent-name" placeholder="e.g. Technical Report Summariser">
</div>
<div class="ab-field">
<label>Description</label>
<input type="text" id="agent-desc" placeholder="What does this agent do?">
</div>
</div>
</div>
<!-- Governance card -->
<div class="ab-card">
<div class="ab-card-title"><span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>Governance &amp; Approval
<span style="font-size:11px;font-weight:500;color:var(--lt);margin-left:auto">Controls how autonomously this agent can act</span>
</div>
<!-- Autonomy tier -->
<div class="ab-field" style="margin-bottom:18px">
<label>Autonomy Level</label>
<div class="ab-autonomy-grid" id="autonomy-grid">
<button class="ab-tier-opt observe" data-tier="observe" onclick="setTier('observe')">
<div class="ab-tier-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.35-4.35"/></svg></div>
<div class="ab-tier-name">Observe</div>
<div class="ab-tier-desc">Runs in background, logs only — no output surfaced to users</div>
</button>
<button class="ab-tier-opt advise" data-tier="advise" onclick="setTier('advise')">
<div class="ab-tier-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.74c.62.46 1 1.17 1 1.93V18h6v-1.33c0-.76.38-1.47 1-1.93A7 7 0 0 0 12 2z"/></svg></div>
<div class="ab-tier-name">Advise</div>
<div class="ab-tier-desc">Shows recommendations — human decides whether to act</div>
</button>
<button class="ab-tier-opt approval selected" data-tier="act_approval" onclick="setTier('act_approval')">
<div class="ab-tier-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 12V7a1 1 0 0 1 2 0v5"/><path d="M10 12V5a1 1 0 0 1 2 0v7"/><path d="M14 12V6a1 1 0 0 1 2 0v6"/><path d="M18 12V8a1 1 0 0 1 2 0v8a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4v-2a2 2 0 0 1 2-2h11"/></svg></div>
<div class="ab-tier-name">Act with Approval</div>
<div class="ab-tier-desc">Runs pipeline, waits for human sign-off before finalising</div>
</button>
<button class="ab-tier-opt auto" data-tier="act_auto" onclick="setTier('act_auto')">
<div class="ab-tier-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg></div>
<div class="ab-tier-name">Act Automatically</div>
<div class="ab-tier-desc">Runs and applies output without confirmation — use with care</div>
</button>
</div>
</div>
<!-- Approval settings (shown for act_approval tier) -->
<div id="approval-settings">
<div class="ab-perm-grid" style="margin-bottom:16px">
<div class="ab-field">
<label>Approvers <span class="cezen-help" tabindex="0" data-tip="Who can approve this agent's proposed actions before it runs them. Applies only at the Act with Approval autonomy level.">?</span></label>
<select id="gov-approvers">
<option value="admin">Admins only</option>
<option value="any">Any logged-in user</option>
<option value="owner">Agent owner only</option>
</select>
</div>
<div class="ab-field">
<label>Approval Timeout</label>
<select id="gov-timeout">
<option value="1">1 hour — then auto-reject</option>
<option value="4">4 hours</option>
<option value="24" selected>24 hours</option>
<option value="72">3 days</option>
<option value="0">No timeout</option>
</select>
</div>
</div>
</div>
<!-- Auto-run limit (shown for act_auto tier) -->
<div id="auto-settings" style="display:none">
<div class="ab-perm-grid" style="margin-bottom:16px">
<div class="ab-field">
<label>Max Auto-Runs Per Day</label>
<input type="number" id="gov-max-runs" value="50" min="1" max="500">
</div>
<div class="ab-field">
<label>Cool-down Between Runs <span class="cezen-help" tabindex="0" data-tip="Minimum wait time before this agent can run again after finishing, even if triggered again. Prevents runaway loops when running fully autonomously.">?</span></label>
<select id="gov-cooldown">
<option value="0">None</option>
<option value="60">1 minute</option>
<option value="300" selected>5 minutes</option>
<option value="3600">1 hour</option>
</select>
</div>
</div>
</div>
<!-- Permissions -->
<div class="ab-field" style="margin-bottom:8px">
<label>Permissions — Tools This Agent May Use</label>
<div class="ab-perm-grid" style="margin-top:6px">
<label class="ab-toggle-row">
<input type="checkbox" id="perm-read-kb" checked>
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg></span><span>Read Knowledge Base</span></div>
<div class="ab-toggle-sub">RAG queries against allowed collections</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="perm-read-docs">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span><span>Read Uploaded Documents</span></div>
<div class="ab-toggle-sub">Access files uploaded by users</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="perm-export">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg></span><span>Export / Save Output</span></div>
<div class="ab-toggle-sub">Write results to files or exports</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="perm-notify">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg></span><span>Send Notifications</span></div>
<div class="ab-toggle-sub">In-portal alerts and email notices</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="perm-api">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18"/></svg></span><span>Call External APIs</span></div>
<div class="ab-toggle-sub">HTTP requests to configured connectors</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="perm-schedule">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l3 2"/><path d="M8 3h8"/></svg></span><span>Trigger Scheduled Jobs</span></div>
<div class="ab-toggle-sub">Chain into the scheduler</div>
</div>
</label>
</div>
</div>
<!-- Safety toggles -->
<div class="ab-perm-grid" style="margin-top:12px">
<label class="ab-toggle-row">
<input type="checkbox" id="gov-log-io" checked>
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9 3h6"/><path d="M10 7h4"/><rect x="5" y="5" width="14" height="16" rx="2"/></svg></span><span>Log All Inputs &amp; Outputs</span></div>
<div class="ab-toggle-sub">Every run recorded in audit log</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="gov-pii" checked>
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span><span>PII Detection</span></div>
<div class="ab-toggle-sub">Warn before processing personal data</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="gov-guardrails" checked>
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span><span>Apply Guardrails Policy</span></div>
<div class="ab-toggle-sub">Content filter rules from Guardrails page</div>
</div>
</label>
<label class="ab-toggle-row">
<input type="checkbox" id="gov-sensitive">
<div>
<div class="ab-toggle-label icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg></span><span>Mark as Sensitive</span></div>
<div class="ab-toggle-sub">Outputs redacted from non-admin audit views</div>
</div>
</label>
</div>
<!-- Allowed knowledge base collections -->
<div class="ab-field" style="margin-top:16px">
<label>Allowed Knowledge Base Collections <span style="color:var(--lt);font-weight:400">(comma-separated, or * for all)</span></label>
<input type="text" id="gov-collections" value="*" placeholder="e.g. hr-policy, finance-docs, *">
</div>
</div>
<!-- Steps builder -->
<div class="ab-card">
<div class="ab-steps-header">
<div class="ab-steps-title icon-label"><span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg></span><span>Pipeline Steps</span></div>
<span style="font-size:12px;color:var(--lt)">Each step receives the previous step's output as <code>{{prev}}</code></span>
</div>
<div class="ab-steps-list" id="steps-list"></div>
<!-- Add step -->
<div class="ab-add-step" id="add-step-wrap">
<button class="ab-add-step-btn" onclick="toggleStepMenu()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span><span>Add Step</span></span></button>
<div class="ab-step-types" id="step-types-menu">
<div class="ab-step-type-opt" onclick="addStep('prompt')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
<div><div class="label">Prompt</div><div class="sublabel">Send a custom prompt to the LLM</div></div>
</div>
<div class="ab-step-type-opt" onclick="addStep('summarise')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M8 7h8M8 11h8M8 15h5"/></svg></span>
<div><div class="label">Summarise</div><div class="sublabel">Shorten the previous output</div></div>
</div>
<div class="ab-step-type-opt" onclick="addStep('extract')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.35-4.35"/></svg></span>
<div><div class="label">Extract</div><div class="sublabel">Pull out specific fields as JSON</div></div>
</div>
<div class="ab-step-type-opt" onclick="addStep('classify')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m20 7-8.5 8.5a2.12 2.12 0 0 1-3 0L4 11V4h7l9 9a2.12 2.12 0 0 1 0 3z"/><circle cx="7.5" cy="7.5" r=".5"/></svg></span>
<div><div class="label">Classify</div><div class="sublabel">Assign a category label</div></div>
</div>
<div class="ab-step-type-opt" onclick="addStep('rag')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg></span>
<div><div class="label">RAG Query</div><div class="sublabel">Search knowledge base and inject context</div></div>
</div>
<div class="ab-step-type-opt" onclick="addStep('format')">
<span class="icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg></span>
<div><div class="label">Format</div><div class="sublabel">Reformat output using a template</div></div>
</div>
</div>
</div>
<!-- Action bar -->
<div class="ab-actions" style="margin-top:20px">
<button class="btn btn-primary" onclick="saveAgent()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 21h14"/><path d="M19 21V7l-3-3H5v17"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span>Save Agent</span></button>
<button class="btn btn-ghost" onclick="deleteCurrentAgent()" id="delete-btn" style="display:none"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/></svg></span>Delete</span></button>
</div>
</div>
<!-- Run panel -->
<div class="ab-run-card">
<div class="ab-run-header">
<span class="icon-svg icon-xl" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span>
<div class="ab-run-title">Run Agent</div>
<span style="font-size:12px;color:var(--lt)" id="run-status-badge"></span>
</div>
<div class="ab-run-body">
<div class="ab-run-input">
<label style="font-size:11px;font-weight:700;color:var(--lt);text-transform:uppercase;letter-spacing:.4px;display:block;margin-bottom:6px">Input (available as {{input}} in all steps)</label>
<textarea id="run-input" placeholder="Paste your input text here — a document, question, data, or anything the pipeline should process…"></textarea>
</div>
<button id="run-btn" onclick="runAgent()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span>Run Pipeline</span></button>
<div class="ab-step-results" id="step-results" style="display:none"></div>
<!-- Approval waiting state (shown for act_approval agents after run) -->
<div class="ab-awaiting-approval" id="approval-waiting" style="display:none">
<div class="ab-awaiting-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 12V7a1 1 0 0 1 2 0v5"/><path d="M10 12V5a1 1 0 0 1 2 0v7"/><path d="M14 12V6a1 1 0 0 1 2 0v6"/><path d="M18 12V8a1 1 0 0 1 2 0v8a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4v-2a2 2 0 0 1 2-2h11"/></svg></div>
<div class="ab-awaiting-title">Waiting for Approval</div>
<div class="ab-awaiting-sub">This agent requires human sign-off before the output is finalised. A pending approval has been added to the queue above.</div>
<div style="margin-top:14px;display:flex;gap:10px;justify-content:center">
<button class="ab-approve-btn" onclick="approveCurrentRun()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m20 6-11 11-5-5"/></svg></span>Approve &amp; Finalise</span></button>
<button class="ab-reject-btn" onclick="rejectCurrentRun()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg></span>Reject</span></button>
</div>
</div>
</div>
</div>
<!-- Recent runs -->
<div class="ab-card" style="margin-top:20px" id="runs-card">
<div class="ab-card-title"><span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg></span>Recent Runs</div>
<div id="runs-table"><div style="color:var(--lt);font-size:13px">No runs yet</div></div>
</div>
</div><!-- /#ab-builder -->
</main>
</div>
<script>
const _API = '/api';
let agents = [];
let agentFilterQuery = '';
let currentAgentId = null;
let steps = [];
let stepCounter = 0;
let models = [];
let pollTimer = null;
let currentRunId = null;
let pendingApprovals = JSON.parse(localStorage.getItem('cezen_approvals') || '[]');
function agentIconSvg(name, sizeClass = 'icon-md') {
const icons = {
chat:'<svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
doc:'<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>',
search:'<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.35-4.35"/></svg>',
tag:'<svg viewBox="0 0 24 24"><path d="m20 7-8.5 8.5a2.12 2.12 0 0 1-3 0L4 11V4h7l9 9a2.12 2.12 0 0 1 0 3z"/><circle cx="7.5" cy="7.5" r=".5"/></svg>',
kb:'<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>',
edit:'<svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>',
observe:'<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.35-4.35"/></svg>',
advise:'<svg viewBox="0 0 24 24"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.74c.62.46 1 1.17 1 1.93V18h6v-1.33c0-.76.38-1.47 1-1.93A7 7 0 0 0 12 2z"/></svg>',
approval:'<svg viewBox="0 0 24 24"><path d="M6 12V7a1 1 0 0 1 2 0v5"/><path d="M10 12V5a1 1 0 0 1 2 0v7"/><path d="M14 12V6a1 1 0 0 1 2 0v6"/><path d="M18 12V8a1 1 0 0 1 2 0v8a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4v-2a2 2 0 0 1 2-2h11"/></svg>',
auto:'<svg viewBox="0 0 24 24"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg>',
robot:'<svg viewBox="0 0 24 24"><rect x="7" y="8" width="10" height="8" rx="2"/><path d="M12 4v4M9 16v2M15 16v2M6 10H4M20 10h-2"/><circle cx="10" cy="12" r="1"/><circle cx="14" cy="12" r="1"/></svg>',
user:'<svg viewBox="0 0 24 24"><path d="M20 21a8 8 0 0 0-16 0"/><circle cx="12" cy="7" r="4"/></svg>',
clock:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>',
check:'<svg viewBox="0 0 24 24"><path d="m20 6-11 11-5-5"/></svg>',
close:'<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg>',
spinner:'<svg viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-2.64-6.36"/></svg>',
shield:'<svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>',
plus:'<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>',
up:'<svg viewBox="0 0 24 24"><path d="m18 15-6-6-6 6"/></svg>',
down:'<svg viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>',
trash:'<svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/></svg>'
};
return `<span class="icon-svg ${sizeClass}" aria-hidden="true">${icons[name] || icons.robot}</span>`;
}
const STEP_ICONS = { prompt:agentIconSvg('chat'), summarise:agentIconSvg('kb'), extract:agentIconSvg('search'), classify:agentIconSvg('tag'), rag:agentIconSvg('kb'), format:agentIconSvg('edit') };
const STEP_LABELS = { prompt:'Prompt', summarise:'Summarise', extract:'Extract', classify:'Classify', rag:'RAG Query', format:'Format' };
const TIER_META = {
observe: { icon:agentIconSvg('observe'), label:'Observe', cls:'observe' },
advise: { icon:agentIconSvg('advise'), label:'Advise', cls:'advise' },
act_approval: { icon:agentIconSvg('approval'), label:'Act w/ Approval', cls:'approval' },
act_auto: { icon:agentIconSvg('auto'), label:'Act Automatically', cls:'auto' },
};
// ── Governance ────────────────────────────────────────────────────────────────
let currentTier = 'act_approval';
function setTier(tier) {
currentTier = tier;
document.querySelectorAll('.ab-tier-opt').forEach(el => {
const t = el.dataset.tier;
el.classList.toggle('selected', t === tier);
if (t === tier) {
el.classList.add(TIER_META[tier]?.cls || '');
} else {
['observe','advise','approval','auto'].forEach(c => el.classList.remove(c));
}
});
document.getElementById('approval-settings').style.display = tier === 'act_approval' ? '' : 'none';
document.getElementById('auto-settings').style.display = tier === 'act_auto' ? '' : 'none';
}
function getGovernance() {
return {
autonomy: currentTier,
approvers: document.getElementById('gov-approvers')?.value || 'admin',
timeout_h: parseInt(document.getElementById('gov-timeout')?.value || '24'),
max_runs_day: parseInt(document.getElementById('gov-max-runs')?.value || '50'),
cooldown_s: parseInt(document.getElementById('gov-cooldown')?.value || '300'),
perm_read_kb: document.getElementById('perm-read-kb')?.checked ?? true,
perm_read_docs:document.getElementById('perm-read-docs')?.checked ?? false,
perm_export: document.getElementById('perm-export')?.checked ?? false,
perm_notify: document.getElementById('perm-notify')?.checked ?? false,
perm_api: document.getElementById('perm-api')?.checked ?? false,
perm_schedule:document.getElementById('perm-schedule')?.checked ?? false,
log_io: document.getElementById('gov-log-io')?.checked ?? true,
pii_detect: document.getElementById('gov-pii')?.checked ?? true,
guardrails: document.getElementById('gov-guardrails')?.checked ?? true,
sensitive: document.getElementById('gov-sensitive')?.checked ?? false,
collections: document.getElementById('gov-collections')?.value || '*',
};
}
function applyGovernance(gov) {
if (!gov) return;
setTier(gov.autonomy || 'act_approval');
const set = (id, val) => { const el = document.getElementById(id); if (el) el.value = val; };
const chk = (id, val) => { const el = document.getElementById(id); if (el) el.checked = !!val; };
set('gov-approvers', gov.approvers || 'admin');
set('gov-timeout', gov.timeout_h || '24');
set('gov-max-runs', gov.max_runs_day || '50');
set('gov-cooldown', gov.cooldown_s || '300');
set('gov-collections', gov.collections || '*');
chk('perm-read-kb', gov.perm_read_kb ?? true);
chk('perm-read-docs', gov.perm_read_docs ?? false);
chk('perm-export', gov.perm_export ?? false);
chk('perm-notify', gov.perm_notify ?? false);
chk('perm-api', gov.perm_api ?? false);
chk('perm-schedule', gov.perm_schedule ?? false);
chk('gov-log-io', gov.log_io ?? true);
chk('gov-pii', gov.pii_detect ?? true);
chk('gov-guardrails', gov.guardrails ?? true);
chk('gov-sensitive', gov.sensitive ?? false);
}
// ── Approval queue ─────────────────────────────────────────────────────────────
function saveApprovals() {
localStorage.setItem('cezen_approvals', JSON.stringify(pendingApprovals));
}
function renderApprovalQueue() {
const banner = document.getElementById('approval-queue');
if (!pendingApprovals.length) { banner.classList.remove('has-items'); return; }
banner.classList.add('has-items');
banner.innerHTML = `
<div class="ab-queue-title">${agentIconSvg('approval')} ${pendingApprovals.length} Pending Approval${pendingApprovals.length>1?'s':''} — Review before output is finalised</div>
${pendingApprovals.map((a,i) => `
<div class="ab-queue-item">
<div class="ab-queue-meta">
<span class="icon-label">${agentIconSvg('robot', 'icon-sm')}<strong>${esc(a.agentName)}</strong></span>
<span class="icon-label">${agentIconSvg('user', 'icon-sm')}<span>${esc(a.user)}</span></span>
<span class="icon-label">${agentIconSvg('clock', 'icon-sm')}<span>${new Date(a.ts).toLocaleString()}</span></span>
${a.timeoutH ? `<span style="color:#D97706" class="icon-label">${agentIconSvg('clock', 'icon-sm')}<span>Expires in ${a.timeoutH}h</span></span>` : ''}
</div>
<div style="font-size:11px;font-weight:700;color:var(--lt);margin-bottom:4px;text-transform:uppercase;letter-spacing:.4px">Input</div>
<div class="ab-queue-preview">${esc(a.input.slice(0,200))}${a.input.length>200?'…':''}</div>
<div style="font-size:11px;font-weight:700;color:var(--lt);margin-bottom:4px;text-transform:uppercase;letter-spacing:.4px">Agent Output</div>
<div class="ab-queue-preview">${esc(a.output.slice(0,200))}${a.output.length>200?'…':''}</div>
<div class="ab-queue-actions">
<button class="ab-approve-btn" onclick="approveQueued(${i})"><span class="icon-label">${agentIconSvg('check', 'icon-sm')}<span>Approve &amp; Finalise</span></span></button>
<button class="ab-reject-btn" onclick="rejectQueued(${i})"><span class="icon-label">${agentIconSvg('close', 'icon-sm')}<span>Reject</span></span></button>
</div>
</div>`).join('')}`;
}
function approveQueued(i) {
const a = pendingApprovals[i];
pendingApprovals.splice(i, 1);
saveApprovals();
renderApprovalQueue();
showToast(`Approved: ${a.agentName}`);
}
function rejectQueued(i) {
const a = pendingApprovals[i];
pendingApprovals.splice(i, 1);
saveApprovals();
renderApprovalQueue();
showToast(`Rejected: ${a.agentName}`, 'error');
}
function approveCurrentRun() {
document.getElementById('approval-waiting').style.display = 'none';
showToast('Output approved and finalised');
loadRuns(currentAgentId);
}
function rejectCurrentRun() {
document.getElementById('approval-waiting').style.display = 'none';
showToast('Run rejected — output discarded', 'error');
// Remove from pending queue too
pendingApprovals = pendingApprovals.filter(a => a.runId !== currentRunId);
saveApprovals();
renderApprovalQueue();
}
// ── Templates ─────────────────────────────────────────────────────────────────
const TEMPLATES = {
report: {
name: 'Technical Report Summariser',
description: 'Extracts key points then writes an executive summary',
steps: [
{ type:'prompt', name:'Extract Key Points', model:'llama3',
prompt:'Read the following document and extract the top 10 key points as a numbered list:\n\n{{input}}' },
{ type:'summarise', name:'Condense Points', model:'llama3', target_words:'100' },
{ type:'format', name:'Write Executive Summary', model:'llama3',
prompt:'Write a professional executive summary based on these points. Use formal language suitable for a PSU context:\n\n{{prev}}' }
]
},
classify: {
name: 'Classify & Summarise',
description: 'Classifies the input then provides a category-specific summary',
steps: [
{ type:'classify', name:'Classify Document', model:'llama3',
categories:'Technical Report, Policy Document, Research Paper, Meeting Minutes, Other' },
{ type:'prompt', name:'Category-Aware Summary', model:'llama3',
prompt:'The document has been classified as: {{prev}}\n\nNow provide a concise summary of the following document, highlighting aspects most relevant to a {{prev}}:\n\n{{input}}' }
]
},
qa: {
name: 'Q&A Extractor',
description: 'Generates questions and answers from a document',
steps: [
{ type:'prompt', name:'Generate Questions', model:'llama3',
prompt:'Read the following document and generate 8 important questions a reader might ask:\n\n{{input}}' },
{ type:'prompt', name:'Answer Questions', model:'llama3',
prompt:'Using the original document below, answer each of these questions:\n\nQuestions:\n{{prev}}\n\nOriginal Document:\n{{input}}' },
{ type:'format', name:'Format as Q&A', model:'llama3',
prompt:'Format the following as a clean Q&A document with bold questions and clear answers:\n\n{{prev}}' }
]
}
};
function loadTemplate(key) {
const t = TEMPLATES[key];
if (!t) return;
currentAgentId = null;
document.getElementById('agent-name').value = t.name;
document.getElementById('agent-desc').value = t.description;
steps = t.steps.map((s, i) => ({ ...s, _id: ++stepCounter }));
showBuilder();
renderSteps();
}
// ── New agent ─────────────────────────────────────────────────────────────────
function newAgent() {
currentAgentId = null;
document.getElementById('agent-name').value = '';
document.getElementById('agent-desc').value = '';
steps = [];
stepCounter = 0;
showBuilder();
renderSteps();
document.getElementById('delete-btn').style.display = 'none';
document.getElementById('step-results').style.display = 'none';
document.getElementById('approval-waiting').style.display = 'none';
document.getElementById('runs-table').innerHTML = '<div style="color:var(--lt);font-size:13px">No runs yet</div>';
document.querySelectorAll('.ab-agent-item').forEach(i => i.classList.remove('active'));
setTier('act_approval');
}
function showBuilder() {
document.getElementById('ab-empty').style.display = 'none';
document.getElementById('ab-builder').style.display = '';
}
// ── Steps rendering ───────────────────────────────────────────────────────────
function renderSteps() {
const list = document.getElementById('steps-list');
if (!steps.length) {
list.innerHTML = '<div style="text-align:center;padding:24px 0;color:var(--lt);font-size:13px">No steps yet — add a step below to build your pipeline</div>';
return;
}
list.innerHTML = steps.map((s, i) => renderStep(s, i)).join('');
}
function renderStep(s, idx) {
const icon = STEP_ICONS[s.type] || agentIconSvg('robot');
const label = STEP_LABELS[s.type] || s.type;
// Build type-specific config fields
let fields = '';
const modelOpts = models.map(m => `<option value="${esc(m)}" ${s.model===m?'selected':''}>${esc(m)}</option>`).join('');
const modelSel = `<div class="ab-field"><label>Model</label><select onchange="updateStep(${s._id},'model',this.value)">${modelOpts || `<option value="llama3">llama3</option>`}</select></div>`;
if (s.type === 'prompt' || s.type === 'format') {
fields = `
<div class="ab-field"><label>Step Name</label><input type="text" value="${esc(s.name||'')}" onchange="updateStep(${s._id},'name',this.value)" placeholder="e.g. Write Summary"></div>
${modelSel}
<div class="ab-field" style="grid-column:1/-1"><label>Prompt Template <span style="color:var(--lt);font-weight:400">(use {{input}} for pipeline input, {{prev}} for previous step output)</span></label>
<textarea rows="3" onchange="updateStep(${s._id},'prompt',this.value)">${esc(s.prompt||'')}</textarea></div>`;
} else if (s.type === 'summarise') {
fields = `
<div class="ab-field"><label>Step Name</label><input type="text" value="${esc(s.name||'')}" onchange="updateStep(${s._id},'name',this.value)" placeholder="Summarise"></div>
${modelSel}
<div class="ab-field"><label>Target Words</label><input type="number" value="${esc(s.target_words||'150')}" min="30" max="1000" onchange="updateStep(${s._id},'target_words',this.value)"></div>`;
} else if (s.type === 'extract') {
fields = `
<div class="ab-field"><label>Step Name</label><input type="text" value="${esc(s.name||'')}" onchange="updateStep(${s._id},'name',this.value)" placeholder="Extract Fields"></div>
${modelSel}
<div class="ab-field" style="grid-column:1/-1"><label>Fields to Extract <span style="color:var(--lt);font-weight:400">(comma-separated)</span></label>
<input type="text" value="${esc(s.fields||'')}" onchange="updateStep(${s._id},'fields',this.value)" placeholder="title, date, author, summary, recommendations"></div>`;
} else if (s.type === 'classify') {
fields = `
<div class="ab-field"><label>Step Name</label><input type="text" value="${esc(s.name||'')}" onchange="updateStep(${s._id},'name',this.value)" placeholder="Classify"></div>
${modelSel}
<div class="ab-field" style="grid-column:1/-1"><label>Categories <span style="color:var(--lt);font-weight:400">(comma-separated)</span></label>
<input type="text" value="${esc(s.categories||'')}" onchange="updateStep(${s._id},'categories',this.value)" placeholder="Technical, Financial, Legal, HR, Other"></div>`;
} else if (s.type === 'rag') {
fields = `
<div class="ab-field"><label>Step Name</label><input type="text" value="${esc(s.name||'')}" onchange="updateStep(${s._id},'name',this.value)" placeholder="RAG Query"></div>
${modelSel}
<div class="ab-field"><label>Knowledge Base Collection</label>
<input type="text" value="${esc(s.collection||'')}" onchange="updateStep(${s._id},'collection',this.value)" placeholder="collection name"></div>
<div class="ab-field"><label>Results to Fetch</label>
<input type="number" value="${esc(s.n_results||'3')}" min="1" max="10" onchange="updateStep(${s._id},'n_results',this.value)"></div>`;
}
return `
<div class="ab-step" id="step-${s._id}">
<div class="ab-step-header" onclick="toggleStep(${s._id})">
<div class="ab-step-num">${idx+1}</div>
<span style="font-size:16px">${icon}</span>
<div class="ab-step-name-text">${esc(s.name || label)}</div>
<span class="ab-step-type-badge">${label}</span>
<div class="ab-step-actions" onclick="event.stopPropagation()">
${idx > 0 ? `<button class="ab-step-btn" title="Move up" onclick="moveStep(${s._id},-1)">${agentIconSvg('up', 'icon-sm')}</button>` : ''}
${idx < steps.length-1 ? `<button class="ab-step-btn" title="Move down" onclick="moveStep(${s._id},1)">${agentIconSvg('down', 'icon-sm')}</button>` : ''}
<button class="ab-step-btn" title="Remove" onclick="removeStep(${s._id})">${agentIconSvg('trash', 'icon-sm')}</button>
</div>
</div>
<div class="ab-step-body">
<div class="ab-step-grid">${fields}</div>
</div>
</div>`;
}
function toggleStep(id) {
document.getElementById(`step-${id}`).classList.toggle('open');
}
function updateStep(id, key, value) {
const s = steps.find(s => s._id === id);
if (s) s[key] = value;
}
function addStep(type) {
const defaultNames = { prompt:'Prompt Step', summarise:'Summarise', extract:'Extract Fields', classify:'Classify', rag:'RAG Query', format:'Format Output' };
steps.push({ type, name: defaultNames[type]||type, model: models[0]||'llama3', _id: ++stepCounter });
toggleStepMenu();
renderSteps();
// Auto-open the new step
setTimeout(() => {
const el = document.getElementById(`step-${stepCounter}`);
if (el) { el.classList.add('open'); el.scrollIntoView({ behavior:'smooth', block:'nearest' }); }
}, 50);
}
function removeStep(id) {
steps = steps.filter(s => s._id !== id);
renderSteps();
}
function moveStep(id, dir) {
const idx = steps.findIndex(s => s._id === id);
if (idx < 0) return;
const newIdx = idx + dir;
if (newIdx < 0 || newIdx >= steps.length) return;
[steps[idx], steps[newIdx]] = [steps[newIdx], steps[idx]];
renderSteps();
}
function toggleStepMenu() {
document.getElementById('step-types-menu').classList.toggle('open');
}
document.addEventListener('click', e => {
if (!e.target.closest('.ab-add-step')) {
document.getElementById('step-types-menu').classList.remove('open');
}
});
// ── Save agent ────────────────────────────────────────────────────────────────
async function saveAgent() {
const name = document.getElementById('agent-name').value.trim();
if (!name) { showToast('Please give the agent a name before saving.', 'warn'); return; }
if (!steps.length) { showToast('Add at least one step before saving.', 'warn'); return; }
const cleanSteps = steps.map(({ _id, ...rest }) => rest);
const payload = {
name,
description: document.getElementById('agent-desc').value.trim(),
steps: cleanSteps,
governance: getGovernance(),
};
try {
let res;
if (currentAgentId) {
res = await fetch(`${_API}/agents/${currentAgentId}`, {
method:'PUT', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
});
} else {
res = await fetch(`${_API}/agents`, {
method:'POST', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
});
if (res.ok) {
const d = await res.json();
currentAgentId = d.id;
}
}
if (!res.ok) throw new Error((await res.json()).detail || 'Save failed');
document.getElementById('delete-btn').style.display = '';
await loadAgentList();
showToast('Agent saved');
} catch(e) { showToast('Could not save this agent: ' + e.message, 'error'); }
}
// ── Delete agent ──────────────────────────────────────────────────────────────
async function deleteCurrentAgent() {
if (!currentAgentId || !confirm('Delete this agent?')) return;
await fetch(`${_API}/agents/${currentAgentId}`, { method:'DELETE', credentials:'include' });
currentAgentId = null;
document.getElementById('ab-empty').style.display = '';
document.getElementById('ab-builder').style.display = 'none';
loadAgentList();
}
// ── Load agent list ───────────────────────────────────────────────────────────
async function loadAgentList() {
try {
const res = await fetch(`${_API}/agents`, { credentials:'include' });
agents = await res.json();
renderAgentList();
} catch(e) {}
}
function renderAgentList() {
const el = document.getElementById('agent-list');
if (!agents.length) {
el.innerHTML = '<div class="cezen-list-empty">No agents yet &mdash; <a onclick="newAgent()">create your first one</a>.</div>';
return;
}
const filtered = agentFilterQuery ? agents.filter(a => (a.name || '').toLowerCase().includes(agentFilterQuery)) : agents;
if (!filtered.length) {
el.innerHTML = '<div class="cezen-list-empty">No agents match your filter.</div>';
return;
}
el.innerHTML = filtered.map(a => {
const tier = a.governance?.autonomy || 'act_approval';
const tm = TIER_META[tier] || TIER_META.act_approval;
return `
<div class="ab-agent-item ${a.id === currentAgentId ? 'active' : ''}" onclick="openAgent(${a.id})">
<div class="ab-agent-name" style="display:flex;align-items:center;gap:6px">
<span>${tm.icon}</span> ${esc(a.name)}
</div>
${a.description ? `<div class="ab-agent-desc">${esc(a.description)}</div>` : ''}
<div class="ab-agent-meta" style="display:flex;align-items:center;gap:6px;margin-top:5px">
<span class="tier-badge ${tm.cls}">${tm.label}</span>
<span>${a.username} · ${new Date(a.updated_at).toLocaleDateString()}</span>
</div>
</div>`;
}).join('');
}
async function openAgent(id) {
try {
const res = await fetch(`${_API}/agents/${id}`, { credentials:'include' });
const agent = await res.json();
currentAgentId = id;
document.getElementById('agent-name').value = agent.name;
document.getElementById('agent-desc').value = agent.description || '';
stepCounter = 0;
steps = agent.steps.map(s => ({ ...s, _id: ++stepCounter }));
applyGovernance(agent.governance);
showBuilder();
renderSteps();
document.getElementById('delete-btn').style.display = '';
document.getElementById('step-results').style.display = 'none';
document.getElementById('approval-waiting').style.display = 'none';
renderAgentList();
loadRuns(id);
} catch(e) {}
}
// ── Run agent ─────────────────────────────────────────────────────────────────
async function runAgent() {
if (!currentAgentId) { showToast('Save the agent before running it.', 'warn'); return; }
const input = document.getElementById('run-input').value.trim();
if (!input) { showToast('Enter some input to process.', 'warn'); return; }
document.getElementById('run-btn').disabled = true;
const resultsEl = document.getElementById('step-results');
resultsEl.style.display = '';
resultsEl.innerHTML = steps.map((s, i) => `
<div class="ab-step-result pending" id="run-step-${i+1}">
<div class="ab-step-result-header">
<span>${STEP_ICONS[s.type]||agentIconSvg('robot')}</span>
<div class="ab-step-result-label">Step ${i+1}: ${esc(s.name||s.type)}</div>
<span class="ab-spinner" id="spin-${i+1}" style="display:none">${agentIconSvg('spinner')}</span>
</div>
<div class="ab-step-result-body" id="run-body-${i+1}" style="color:var(--lt)">Waiting…</div>
</div>`).join('');
// Show first spinner
if (steps.length) document.getElementById('spin-1').style.display = '';
document.getElementById('approval-waiting').style.display = 'none';
try {
const res = await fetch(`${_API}/agents/${currentAgentId}/run`, {
method:'POST', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ input })
});
if (!res.ok) throw new Error((await res.json()).detail || 'Run failed');
const { run_id } = await res.json();
currentRunId = run_id;
pollRun(run_id);
} catch(e) {
document.getElementById('run-btn').disabled = false;
resultsEl.innerHTML = `<div style="color:#B91C1C;font-size:13px">Run failed: ${esc(e.message)}</div>`;
}
}
function pollRun(runId) {
if (pollTimer) clearInterval(pollTimer);
let lastDone = 0;
pollTimer = setInterval(async () => {
try {
const res = await fetch(`${_API}/agents/runs/${runId}`, { credentials:'include' });
const run = await res.json();
// Update completed steps
(run.steps_log || []).forEach((sl, i) => {
const bodyEl = document.getElementById(`run-body-${sl.step}`);
const cardEl = document.getElementById(`run-step-${sl.step}`);
const spinEl = document.getElementById(`spin-${sl.step}`);
if (bodyEl && cardEl) {
if (sl.status === 'ok') {
bodyEl.textContent = sl.output;
bodyEl.style.color = '';
cardEl.className = 'ab-step-result ok';
if (spinEl) spinEl.style.display = 'none';
// Show spinner for next step
const nextSpin = document.getElementById(`spin-${sl.step+1}`);
if (nextSpin && run.status === 'running') nextSpin.style.display = '';
} else if (sl.status === 'error') {
bodyEl.textContent = 'Error: ' + sl.error;
bodyEl.style.color = '#B91C1C';
cardEl.className = 'ab-step-result error';
if (spinEl) spinEl.style.display = 'none';
}
}
});
if (run.status === 'done' || run.status === 'error') {
clearInterval(pollTimer);
document.getElementById('run-btn').disabled = false;
if (run.status === 'done' && currentTier === 'act_approval') {
// Queue for approval
const lastStep = (run.steps_log || []).slice(-1)[0];
const outputPreview = lastStep?.output || '(no output)';
const approval = {
runId: runId,
agentId: currentAgentId,
agentName: document.getElementById('agent-name').value,
input: document.getElementById('run-input').value,
output: outputPreview,
user: window.cezenUser?.username || 'unknown',
ts: new Date().toISOString(),
timeoutH: parseInt(document.getElementById('gov-timeout')?.value || '24'),
};
pendingApprovals.unshift(approval);
saveApprovals();
renderApprovalQueue();
document.getElementById('approval-waiting').style.display = '';
document.getElementById('run-status-badge').textContent = 'Awaiting approval';
} else if (run.status === 'done' && currentTier === 'observe') {
document.getElementById('run-status-badge').textContent = 'Logged (Observe mode)';
} else {
document.getElementById('run-status-badge').textContent =
run.status === 'done' ? 'Complete' : 'Failed';
}
loadRuns(currentAgentId);
}
} catch(e) {}
}, 2000);
}
// ── Runs table ────────────────────────────────────────────────────────────────
async function loadRuns(agentId) {
try {
const res = await fetch(`${_API}/agents/runs/list`, { credentials:'include' });
const runs = await res.json();
const myRuns = runs.filter(r => r.agent_id === agentId);
const el = document.getElementById('runs-table');
if (!myRuns.length) {
el.innerHTML = '<div style="color:var(--lt);font-size:13px">No runs yet</div>';
return;
}
el.innerHTML = `<div class="ab-hist-table-wrap"><table class="ab-hist">
<tr><th>When</th><th>Input</th><th>Status</th><th>Duration</th></tr>
${myRuns.slice(0,20).map(r => {
const dur = r.finished_at
? Math.round((new Date(r.finished_at)-new Date(r.created_at))/1000) + 's'
: '—';
return `<tr>
<td style="color:var(--lt);font-size:12px">${new Date(r.created_at).toLocaleString()}</td>
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(r.input.slice(0,60))}${r.input.length>60?'…':''}</td>
<td><span class="ab-status ${r.status}">${r.status}</span></td>
<td style="color:var(--lt)">${dur}</td>
</tr>`;
}).join('')}
</table></div>`;
} catch(e) {}
}
// ── Helpers ───────────────────────────────────────────────────────────────────
function esc(s) {
return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
async function loadModels() {
try {
const res = await fetch(`${_API}/models/list`, { credentials:'include' });
const data = await res.json();
models = (data.models || []).map(m => m.name);
} catch(e) { models = ['llama3']; }
}
// ── Boot ──────────────────────────────────────────────────────────────────────
(async () => {
await loadModels();
await loadAgentList();
setTier('act_approval'); // default governance tier
renderApprovalQueue(); // show any stored pending approvals
})();
</script>
<script src="auth.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>