aipackage/cezen-portal/agents.html
2026-06-30 10:51:41 +05:30

1157 lines
58 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Builder — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<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 { font-size:56px; margin-bottom:16px; }
.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; } }
.ab-step-type-icons { prompt:'💬', summarise:'📝', extract:'🔍', classify:'🏷️', rag:'📚', format:'✏️' }
/* ── 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 { font-size:18px; }
.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 { font-size:22px; margin-bottom:6px; }
.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 { font-size:32px; margin-bottom:8px; }
.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">Audit Log</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 Manager</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">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">Multimodal Chat</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 Builder</a>
<a href="schedules.html">Scheduled Jobs</a>
<a href="workflows.html">Workflow Automation</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" style="position:relative">🔔</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()"> New Agent</button>
</div>
<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>
</aside>
<!-- Main -->
<main class="ab-main" id="ab-main">
<!-- Empty state -->
<div class="ab-empty" id="ab-empty">
<div class="ab-empty-icon">🤖</div>
<div class="ab-empty-title">Agent Builder</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>
</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">🤖 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">🛡️ 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">🔍</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-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-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-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</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</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">📚 Read Knowledge Base</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-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-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-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-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-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">📋 Log All Inputs &amp; Outputs</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-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-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-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">⚡ Pipeline Steps</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>
<div class="ab-step-types" id="step-types-menu">
<div class="ab-step-type-opt" onclick="addStep('prompt')">
<span class="icon">💬</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>
<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>
<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>
<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>
<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>
<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()">💾 Save Agent</button>
<button class="btn btn-ghost" onclick="deleteCurrentAgent()" id="delete-btn" style="display:none">🗑 Delete</button>
</div>
</div>
<!-- Run panel -->
<div class="ab-run-card">
<div class="ab-run-header">
<span style="font-size:20px">▶️</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()">▶ Run Pipeline</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-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>
</div>
</div>
</div>
</div>
<!-- Recent runs -->
<div class="ab-card" style="margin-top:20px" id="runs-card">
<div class="ab-card-title">🕐 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 currentAgentId = null;
let steps = [];
let stepCounter = 0;
let models = [];
let pollTimer = null;
let currentRunId = null;
let pendingApprovals = JSON.parse(localStorage.getItem('cezen_approvals') || '[]');
const STEP_ICONS = { prompt:'💬', summarise:'📝', extract:'🔍', classify:'🏷️', rag:'📚', format:'✏️' };
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' },
};
// ── 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">✋ ${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>` : ''}
</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>
</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}`, true);
}
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', true);
// 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] || '⚙️';
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)">↑</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>
</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) { alert('Please give the agent a name.'); return; }
if (!steps.length) { alert('Add at least one step.'); 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) { 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);
}
// ── 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 style="padding:16px;font-size:12px;color:var(--lt);text-align:center">No agents yet</div>';
return;
}
el.innerHTML = agents.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) { alert('Save the agent first.'); return; }
const input = document.getElementById('run-input').value.trim();
if (!input) { alert('Enter some input to process.'); 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]||'⚙️'}</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>
</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"></script>
</body>
</html>