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.
This commit is contained in:
Jino Jose committed 2026-07-09 11:11:38 +05:30
1 parent 7540da04a8
commit b3eec34158
57 files changed
+2832 -1354

No files matched your search

+121 -90
View File
@@ -4,7 +4,7 @@
<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=4">
<link rel="stylesheet" href="style.css?v=12">
<style>
/* ── Layout ── */
.ab-layout { display:grid; grid-template-columns:260px 1fr; min-height:calc(100vh - 64px); }
@@ -29,7 +29,8 @@
/* ── Empty state ── */
.ab-empty { text-align:center; padding:80px 20px; color:var(--lt); }
.ab-empty-icon { font-size:56px; margin-bottom:16px; }
.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; }
@@ -73,7 +74,6 @@
.ab-step-grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
@media(max-width:640px){ .ab-step-grid { grid-template-columns:1fr; } }
.ab-step-type-icons { prompt:'💬', summarise:'📝', extract:'🔍', classify:'🏷️', rag:'📚', format:'✏️' }
/* ── Add step dropdown ── */
.ab-add-step { position:relative; }
@@ -83,7 +83,8 @@
.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 { font-size:18px; }
.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; }
@@ -97,7 +98,8 @@
.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 { font-size:22px; margin-bottom:6px; }
.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; }
@@ -133,7 +135,8 @@
/* ── 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 { font-size:32px; margin-bottom:8px; }
.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; }
@@ -210,6 +213,7 @@
<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>
@@ -249,7 +253,11 @@
</div>
</div>
</nav>
<a href="notifications.html" style="position:relative">🔔</a>
<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>
@@ -260,10 +268,11 @@
<aside class="ab-sidebar">
<div class="ab-sidebar-header">
<h3>My Agents</h3>
<button class="ab-new-btn" onclick="newAgent()">+ New Agent</button>
<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 style="padding:16px;font-size:12px;color:var(--lt);text-align:center">No agents yet</div>
<div class="cezen-list-empty">No agents yet &mdash; <a onclick="newAgent()">create your first one</a>.</div>
</div>
</aside>
@@ -272,15 +281,15 @@
<!-- Empty state -->
<div class="ab-empty" id="ab-empty">
<div class="ab-empty-icon">🤖</div>
<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')">📄 Report Generator</button>
<button class="btn btn-ghost" onclick="loadTemplate('classify')">🏷️ Classify &amp; Summarise</button>
<button class="btn btn-ghost" onclick="loadTemplate('qa')">❓ Q&amp;A Extractor</button>
<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>
@@ -292,7 +301,7 @@
<!-- Agent meta -->
<div class="ab-card">
<div class="ab-card-title">🤖 Agent Details</div>
<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>
@@ -307,7 +316,7 @@
<!-- Governance card -->
<div class="ab-card">
<div class="ab-card-title">🛡️ Governance &amp; Approval
<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>
@@ -316,22 +325,22 @@
<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">🔍</div>
<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">💡</div>
<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">✋</div>
<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">⚡</div>
<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>
@@ -342,7 +351,7 @@
<div id="approval-settings">
<div class="ab-perm-grid" style="margin-bottom:16px">
<div class="ab-field">
<label>Approvers</label>
<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>
@@ -370,7 +379,7 @@
<input type="number" id="gov-max-runs" value="50" min="1" max="500">
</div>
<div class="ab-field">
<label>Cool-down Between Runs</label>
<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>
@@ -388,42 +397,42 @@
<label class="ab-toggle-row">
<input type="checkbox" id="perm-read-kb" checked>
<div>
<div class="ab-toggle-label">📚 Read Knowledge Base</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">📄 Read Uploaded Documents</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">📤 Export / Save Output</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">🔔 Send Notifications</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">🌐 Call External APIs</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">⏱️ Trigger Scheduled Jobs</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>
@@ -435,28 +444,28 @@
<label class="ab-toggle-row">
<input type="checkbox" id="gov-log-io" checked>
<div>
<div class="ab-toggle-label">📋 Log All Inputs &amp; Outputs</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">🔒 PII Detection</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">🛡️ Apply Guardrails Policy</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">⚠️ Mark as 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>
@@ -472,37 +481,37 @@
<!-- Steps builder -->
<div class="ab-card">
<div class="ab-steps-header">
<div class="ab-steps-title">⚡ Pipeline Steps</div>
<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()">+ Add Step</button>
<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">💬</span>
<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">📝</span>
<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">🔍</span>
<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">🏷️</span>
<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">📚</span>
<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">✏️</span>
<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>
@@ -510,15 +519,15 @@
<!-- Action bar -->
<div class="ab-actions" style="margin-top:20px">
<button class="btn btn-primary" onclick="saveAgent()">💾 Save Agent</button>
<button class="btn btn-ghost" onclick="deleteCurrentAgent()" id="delete-btn" style="display:none">🗑 Delete</button>
<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 style="font-size:20px">▶️</span>
<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>
@@ -527,18 +536,18 @@
<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()">▶ Run Pipeline</button>
<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">✋</div>
<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()">✓ Approve &amp; Finalise</button>
<button class="ab-reject-btn" onclick="rejectCurrentRun()">✕ Reject</button>
<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>
@@ -546,7 +555,7 @@
<!-- Recent runs -->
<div class="ab-card" style="margin-top:20px" id="runs-card">
<div class="ab-card-title">🕐 Recent Runs</div>
<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>
@@ -557,6 +566,7 @@
<script>
const _API = '/api';
let agents = [];
let agentFilterQuery = '';
let currentAgentId = null;
let steps = [];
let stepCounter = 0;
@@ -564,15 +574,41 @@ 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:'💬', summarise:'📝', extract:'🔍', classify:'🏷️', rag:'📚', format:'✏️' };
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:'🔍', label:'Observe', cls:'observe' },
advise: { icon:'💡', label:'Advise', cls:'advise' },
act_approval: { icon:'✋', label:'Act w/ Approval', cls:'approval' },
act_auto: { icon:'⚡', label:'Act Automatically', cls:'auto' },
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 ────────────────────────────────────────────────────────────────
@@ -646,22 +682,22 @@ function renderApprovalQueue() {
if (!pendingApprovals.length) { banner.classList.remove('has-items'); return; }
banner.classList.add('has-items');
banner.innerHTML = `
<div class="ab-queue-title">✋ ${pendingApprovals.length} Pending Approval${pendingApprovals.length>1?'s':''} — Review before output is finalised</div>
<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>🤖 <strong>${esc(a.agentName)}</strong></span>
<span>👤 ${esc(a.user)}</span>
<span>🕐 ${new Date(a.ts).toLocaleString()}</span>
${a.timeoutH ? `<span style="color:#D97706">⏱ Expires in ${a.timeoutH}h</span>` : ''}
<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})">✓ Approve &amp; Finalise</button>
<button class="ab-reject-btn" onclick="rejectQueued(${i})">✕ Reject</button>
<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('')}`;
}
@@ -671,7 +707,7 @@ function approveQueued(i) {
pendingApprovals.splice(i, 1);
saveApprovals();
renderApprovalQueue();
showToast(`✓ Approved: ${a.agentName}`);
showToast(`Approved: ${a.agentName}`);
}
function rejectQueued(i) {
@@ -679,18 +715,18 @@ function rejectQueued(i) {
pendingApprovals.splice(i, 1);
saveApprovals();
renderApprovalQueue();
showToast(`Rejected: ${a.agentName}`, true);
showToast(`Rejected: ${a.agentName}`, 'error');
}
function approveCurrentRun() {
document.getElementById('approval-waiting').style.display = 'none';
showToast('✓ Output approved and finalised');
showToast('Output approved and finalised');
loadRuns(currentAgentId);
}
function rejectCurrentRun() {
document.getElementById('approval-waiting').style.display = 'none';
showToast('Run rejected — output discarded', true);
showToast('Run rejected — output discarded', 'error');
// Remove from pending queue too
pendingApprovals = pendingApprovals.filter(a => a.runId !== currentRunId);
saveApprovals();
@@ -778,7 +814,7 @@ function renderSteps() {
}
function renderStep(s, idx) {
const icon = STEP_ICONS[s.type] || '⚙️';
const icon = STEP_ICONS[s.type] || agentIconSvg('robot');
const label = STEP_LABELS[s.type] || s.type;
// Build type-specific config fields
@@ -827,9 +863,9 @@ function renderStep(s, idx) {
<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)">↑</button>` : ''}
${idx < steps.length-1 ? `<button class="ab-step-btn" title="Move down" onclick="moveStep(${s._id},1)">↓</button>` : ''}
<button class="ab-step-btn" title="Remove" onclick="removeStep(${s._id})">✕</button>
${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">
@@ -885,8 +921,8 @@ document.addEventListener('click', e => {
// ── Save agent ────────────────────────────────────────────────────────────────
async function saveAgent() {
const name = document.getElementById('agent-name').value.trim();
if (!name) { alert('Please give the agent a name.'); return; }
if (!steps.length) { alert('Add at least one step.'); return; }
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 = {
@@ -919,17 +955,7 @@ async function saveAgent() {
document.getElementById('delete-btn').style.display = '';
await loadAgentList();
showToast('Agent saved');
} catch(e) { alert('Save failed: ' + e.message); }
}
function showToast(msg, isError = false) {
const t = document.createElement('div');
t.textContent = msg;
t.style.cssText = isError
? 'position:fixed;bottom:24px;right:24px;background:#FEF2F2;color:#B91C1C;border:1px solid #FECACA;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:600;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.1);'
: 'position:fixed;bottom:24px;right:24px;background:#F0FDFA;color:#0F766E;border:1px solid #99F6E4;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:600;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.1);';
document.body.appendChild(t);
setTimeout(() => t.remove(), 2800);
} catch(e) { showToast('Could not save this agent: ' + e.message, 'error'); }
}
// ── Delete agent ──────────────────────────────────────────────────────────────
@@ -954,10 +980,15 @@ async function loadAgentList() {
function renderAgentList() {
const el = document.getElementById('agent-list');
if (!agents.length) {
el.innerHTML = '<div style="padding:16px;font-size:12px;color:var(--lt);text-align:center">No agents yet</div>';
el.innerHTML = '<div class="cezen-list-empty">No agents yet &mdash; <a onclick="newAgent()">create your first one</a>.</div>';
return;
}
el.innerHTML = agents.map(a => {
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 `
@@ -996,9 +1027,9 @@ async function openAgent(id) {
// ── Run agent ─────────────────────────────────────────────────────────────────
async function runAgent() {
if (!currentAgentId) { alert('Save the agent first.'); return; }
if (!currentAgentId) { showToast('Save the agent before running it.', 'warn'); return; }
const input = document.getElementById('run-input').value.trim();
if (!input) { alert('Enter some input to process.'); return; }
if (!input) { showToast('Enter some input to process.', 'warn'); return; }
document.getElementById('run-btn').disabled = true;
const resultsEl = document.getElementById('step-results');
@@ -1006,9 +1037,9 @@ async function runAgent() {
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]||'⚙️'}</span>
<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">⚙️</span>
<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('');
@@ -1087,12 +1118,12 @@ function pollRun(runId) {
saveApprovals();
renderApprovalQueue();
document.getElementById('approval-waiting').style.display = '';
document.getElementById('run-status-badge').textContent = '⏳ Awaiting approval';
document.getElementById('run-status-badge').textContent = 'Awaiting approval';
} else if (run.status === 'done' && currentTier === 'observe') {
document.getElementById('run-status-badge').textContent = '🔍 Logged (Observe mode)';
document.getElementById('run-status-badge').textContent = 'Logged (Observe mode)';
} else {
document.getElementById('run-status-badge').textContent =
run.status === 'done' ? '✓ Complete' : '✗ Failed';
run.status === 'done' ? 'Complete' : 'Failed';
}
loadRuns(currentAgentId);
}
@@ -1151,6 +1182,6 @@ async function loadModels() {
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
<script src="branding.js?v=3"></script>
</body>
</html>