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

979 lines
51 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>Workflow Automation — Nexus One AI</title>
<link rel="stylesheet" href="style.css?v=4">
<style>
/* ── Layout ── */
.wf-layout { display:grid; grid-template-columns:270px 1fr; min-height:calc(100vh - 64px); }
@media(max-width:900px){ .wf-layout { grid-template-columns:1fr; } }
/* ── Sidebar ── */
.wf-sidebar { border-right:1px solid var(--bdr); background:var(--navy2); display:flex; flex-direction:column; }
.wf-sidebar-header { padding:16px 14px 12px; border-bottom:1px solid var(--bdr); }
.wf-sidebar-header h3 { font-size:11px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.5px; margin:0 0 10px; }
.wf-new-btn { display:flex; align-items:center; gap:8px; width:100%; padding:9px 12px; border-radius:8px; border:1.5px dashed var(--bdr); background:none; cursor:pointer; font-family:inherit; font-size:13px; font-weight:600; color:var(--med); transition:.15s; }
.wf-new-btn:hover { border-color:var(--purple); color:var(--purple); }
.wf-list { flex:1; overflow-y:auto; padding:8px; }
.wf-item { padding:10px 12px; border-radius:8px; cursor:pointer; transition:.1s; border:1px solid transparent; margin-bottom:4px; }
.wf-item:hover { background:rgba(124,58,237,.04); }
.wf-item.active { background:rgba(124,58,237,.08); border-color:rgba(124,58,237,.25); }
.wf-item-name { font-size:13px; font-weight:600; color:var(--ink); }
.wf-item-meta { font-size:11px; color:var(--lt); margin-top:3px; }
.wf-item-status { display:inline-flex; align-items:center; gap:4px; font-size:10px; font-weight:700; }
.wf-item-status.active { color:#16A34A; }
.wf-item-status.paused { color:#D97706; }
.wf-item-status.draft { color:var(--lt); }
/* ── Main ── */
.wf-main { background:var(--bg); overflow-y:auto; }
/* ── Empty state ── */
.wf-empty { text-align:center; padding:80px 20px; }
.wf-empty-icon { font-size:52px; margin-bottom:16px; }
.wf-empty-title { font-size:20px; font-weight:700; color:var(--ink); margin-bottom:8px; }
.wf-empty-sub { font-size:14px; color:var(--lt); max-width:420px; margin:0 auto; line-height:1.6; }
.wf-templates { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; max-width:660px; margin:28px auto 0; }
@media(max-width:600px){ .wf-templates { grid-template-columns:1fr; } }
.wf-tpl { padding:16px; border:1.5px solid var(--bdr); border-radius:12px; background:var(--navy2); cursor:pointer; text-align:left; transition:.15s; font-family:inherit; }
.wf-tpl:hover { border-color:var(--purple); background:rgba(124,58,237,.04); }
.wf-tpl-icon { font-size:24px; margin-bottom:8px; display:block; }
.wf-tpl-name { font-size:13px; font-weight:700; color:var(--ink); }
.wf-tpl-desc { font-size:11px; color:var(--lt); margin-top:3px; }
/* ── Builder ── */
.wf-builder { padding:28px; max-width:780px; }
/* ── Builder header ── */
.wf-header { display:flex; align-items:center; gap:12px; margin-bottom:24px; }
.wf-title-input { flex:1; font-size:20px; font-weight:700; color:var(--ink); border:none; background:transparent; outline:none; font-family:inherit; }
.wf-title-input::placeholder { color:var(--lt); }
.wf-status-toggle { display:flex; align-items:center; gap:8px; padding:6px 14px; border-radius:20px; border:1px solid var(--bdr); background:var(--navy2); cursor:pointer; font-size:12px; font-weight:700; color:var(--med); font-family:inherit; transition:.15s; }
.wf-status-toggle.active-wf { border-color:rgba(22,163,74,.4); background:rgba(22,163,74,.06); color:#16A34A; }
.wf-dot { width:7px; height:7px; border-radius:50%; background:currentColor; }
.wf-save-btn { padding:8px 20px; border-radius:8px; border:none; background:linear-gradient(135deg,var(--purple),var(--pink)); color:white; font-family:inherit; font-size:13px; font-weight:700; cursor:pointer; transition:.15s; }
.wf-save-btn:hover { filter:brightness(1.08); }
.wf-delete-btn { padding:8px 12px; border-radius:8px; border:1px solid var(--bdr); background:var(--navy2); color:var(--lt); font-family:inherit; font-size:13px; cursor:pointer; transition:.15s; }
.wf-delete-btn:hover { color:#DC2626; border-color:rgba(220,38,38,.3); }
.wf-desc-input { width:100%; box-sizing:border-box; margin-bottom:22px; padding:10px 14px; border:1.5px solid var(--bdr); border-radius:10px; font-family:inherit; font-size:13px; color:var(--med); background:var(--navy2); resize:none; }
.wf-desc-input:focus { outline:none; border-color:var(--purple); color:var(--ink); }
/* ── Flow ── */
.wf-flow { display:flex; flex-direction:column; gap:0; }
/* ── Trigger block ── */
.wf-trigger { background:var(--navy2); border:1.5px solid rgba(124,58,237,.35); border-radius:14px; overflow:hidden; margin-bottom:16px; }
.wf-trigger-head { display:flex; align-items:center; gap:10px; padding:14px 18px; border-bottom:1px solid var(--bdr); }
.wf-block-label { font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.7px; color:var(--purple); }
.wf-trigger-name { font-size:14px; font-weight:700; color:var(--ink); flex:1; }
.wf-trigger-body { padding:18px 18px 16px; display:grid; grid-template-columns:1fr 1fr; gap:14px; }
@media(max-width:600px){ .wf-trigger-body { grid-template-columns:1fr; } }
/* ── Connector arrow ── */
.wf-arrow { display:flex; justify-content:center; padding:4px 0; }
.wf-arrow-line { width:2px; height:24px; background:rgba(124,58,237,.2); }
/* ── Step block ── */
.wf-step { background:var(--navy2); border:1.5px solid var(--bdr); border-radius:14px; overflow:hidden; margin-bottom:4px; transition:.15s; }
.wf-step:hover { border-color:rgba(124,58,237,.25); }
.wf-step-head { display:flex; align-items:center; gap:10px; padding:13px 18px; cursor:pointer; }
.wf-step.open .wf-step-head { border-bottom:1px solid var(--bdr); }
.wf-step-num { width:26px; height:26px; border-radius:50%; background:linear-gradient(135deg,var(--purple),var(--pink)); color:white; font-size:11px; font-weight:800; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.wf-step-name { font-size:13px; font-weight:700; color:var(--ink); flex:1; }
.wf-step-type-chip { font-size:11px; color:var(--purple); font-weight:600; background:rgba(124,58,237,.07); border:1px solid rgba(124,58,237,.15); padding:2px 9px; border-radius:20px; }
.wf-step-actions { display:flex; gap:4px; }
.wf-step-btn { background:none; border:none; cursor:pointer; font-size:14px; padding:3px 7px; color:var(--lt); border-radius:4px; }
.wf-step-btn:hover { color:var(--ink); background:rgba(124,58,237,.06); }
.wf-step-chevron { font-size:11px; color:var(--lt); transition:.2s; }
.wf-step.open .wf-step-chevron { transform:rotate(90deg); }
.wf-step-body { padding:16px 18px; display:none; }
.wf-step.open .wf-step-body { display:block; }
/* ── Fields ── */
.wf-field { display:flex; flex-direction:column; gap:5px; margin-bottom:13px; }
.wf-field:last-child { margin-bottom:0; }
.wf-field label { font-size:11px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.4px; }
.wf-field input, .wf-field select, .wf-field textarea {
padding:9px 12px; border:1.5px solid var(--bdr); border-radius:8px;
font-family:inherit; font-size:13px; color:var(--ink); background:var(--navy2);
}
.wf-field input:focus, .wf-field select:focus, .wf-field textarea:focus { outline:none; border-color:var(--purple); }
.wf-field textarea { resize:vertical; min-height:70px; }
.wf-field-hint { font-size:11px; color:var(--lt); margin-top:2px; }
.wf-field-grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
@media(max-width:540px){ .wf-field-grid { grid-template-columns:1fr; } }
/* ── Condition toggle ── */
.wf-filter-row { display:flex; align-items:center; gap:10px; padding:10px 13px; background:var(--bg); border:1px solid var(--bdr); border-radius:8px; margin-bottom:10px; }
.wf-filter-cond { font-size:12px; font-weight:600; color:var(--ink); flex:1; }
.wf-filter-val { font-size:12px; color:var(--purple); font-weight:600; }
.wf-filter-del { background:none; border:none; cursor:pointer; color:var(--lt); font-size:14px; padding:0 4px; }
.wf-filter-del:hover { color:#DC2626; }
/* ── Add step ── */
.wf-add-step { position:relative; margin-top:12px; }
.wf-add-step-btn { display:flex; align-items:center; gap:8px; width:100%; padding:11px 16px; 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; }
.wf-add-step-btn:hover { border-color:var(--purple); color:var(--purple); }
.wf-step-menu { display:none; position:absolute; top:calc(100% + 6px); left:0; right:0; background:var(--navy2); border:1.5px solid var(--bdr); border-radius:12px; z-index:50; box-shadow:0 8px 28px rgba(0,0,0,.12); overflow:hidden; }
.wf-step-menu.open { display:block; }
.wf-step-menu-cat { font-size:10px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.6px; padding:10px 14px 4px; }
.wf-step-menu-opt { display:flex; align-items:center; gap:10px; padding:10px 14px; cursor:pointer; font-size:13px; color:var(--ink); transition:.1s; }
.wf-step-menu-opt:hover { background:rgba(124,58,237,.05); }
.wf-step-menu-opt .icon { font-size:18px; width:24px; text-align:center; }
.wf-step-menu-opt .lbl { font-weight:600; }
.wf-step-menu-opt .sub { font-size:11px; color:var(--lt); margin-top:1px; }
/* ── Run panel ── */
.wf-run-card { background:var(--navy2); border:1px solid var(--bdr); border-radius:14px; padding:20px 22px; margin-top:22px; }
.wf-run-title { font-size:14px; font-weight:700; color:var(--ink); margin-bottom:16px; }
.wf-run-btn { width:100%; padding:13px; border-radius:10px; border:none; background:linear-gradient(135deg,var(--purple),var(--pink)); color:white; font-family:inherit; font-size:14px; font-weight:700; cursor:pointer; transition:.15s; }
.wf-run-btn:hover { filter:brightness(1.08); }
.wf-run-btn:disabled { opacity:.5; cursor:not-allowed; }
.wf-run-log { margin-top:16px; display:none; }
.wf-run-log.show { display:block; }
.wf-log-step { display:flex; align-items:flex-start; gap:12px; padding:10px 0; border-bottom:1px solid var(--bdr); }
.wf-log-step:last-child { border-bottom:none; }
.wf-log-icon { font-size:18px; flex-shrink:0; margin-top:1px; }
.wf-log-body { flex:1; }
.wf-log-label { font-size:13px; font-weight:600; color:var(--ink); }
.wf-log-detail { font-size:12px; color:var(--lt); margin-top:2px; }
.wf-log-status { font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.4px; padding:2px 8px; border-radius:20px; flex-shrink:0; margin-top:3px; }
.wf-log-status.ok { background:rgba(22,163,74,.1); color:#16A34A; }
.wf-log-status.running { background:rgba(37,99,235,.1); color:#2563EB; }
.wf-log-status.error { background:rgba(220,38,38,.1); color:#DC2626; }
/* ── Stats strip ── */
.wf-stats { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:22px; }
.wf-stat { background:var(--navy2); border:1px solid var(--bdr); border-radius:10px; padding:14px 16px; text-align:center; }
.wf-stat-val { font-size:22px; font-weight:800; color:var(--ink); }
.wf-stat-lbl { font-size:10px; font-weight:700; color:var(--lt); text-transform:uppercase; letter-spacing:.5px; margin-top:3px; }
/* ── Toast ── */
.wf-toast { position:fixed; bottom:28px; right:28px; background:var(--ink); color:var(--navy2); padding:11px 20px; border-radius:10px; font-size:13px; font-weight:600; z-index:999; opacity:0; transform:translateY(8px); transition:.2s; pointer-events:none; }
.wf-toast.show { opacity:1; transform:translateY(0); }
</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 &amp; 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="wf-layout">
<!-- Sidebar -->
<aside class="wf-sidebar">
<div class="wf-sidebar-header">
<h3>Workflows</h3>
<button class="wf-new-btn" onclick="newWorkflow()"> New Workflow</button>
</div>
<div class="wf-list" id="wf-list">
<div style="padding:16px;font-size:12px;color:var(--lt);text-align:center">No workflows yet</div>
</div>
</aside>
<!-- Main -->
<main class="wf-main" id="wf-main">
<!-- Empty state -->
<div class="wf-empty" id="wf-empty">
<div class="wf-empty-icon"></div>
<div class="wf-empty-title">Workflow Automation</div>
<div class="wf-empty-sub">
Connect triggers and AI steps into automated pipelines. Process documents the moment they arrive, route questions to the right model, or generate daily summaries — all without code.
</div>
<div class="wf-templates">
<button class="wf-tpl" onclick="loadTemplate('doc_inbox')">
<span class="wf-tpl-icon">📥</span>
<div class="wf-tpl-name">Document Inbox</div>
<div class="wf-tpl-desc">Summarise every uploaded document and save to KB</div>
</button>
<button class="wf-tpl" onclick="loadTemplate('daily_digest')">
<span class="wf-tpl-icon">📊</span>
<div class="wf-tpl-name">Daily Digest</div>
<div class="wf-tpl-desc">Pull metrics each morning and email a summary</div>
</button>
<button class="wf-tpl" onclick="loadTemplate('query_router')">
<span class="wf-tpl-icon">🔀</span>
<div class="wf-tpl-name">Query Router</div>
<div class="wf-tpl-desc">Classify incoming queries and route to the right agent</div>
</button>
<button class="wf-tpl" onclick="loadTemplate('contract_review')">
<span class="wf-tpl-icon">📋</span>
<div class="wf-tpl-name">Contract Review</div>
<div class="wf-tpl-desc">Extract clauses and flag risks from legal documents</div>
</button>
<button class="wf-tpl" onclick="loadTemplate('meeting_notes')">
<span class="wf-tpl-icon">🎙️</span>
<div class="wf-tpl-name">Meeting Notes</div>
<div class="wf-tpl-desc">Transcribe recordings and post action items to Slack</div>
</button>
<button class="wf-tpl" onclick="newWorkflow()">
<span class="wf-tpl-icon">✏️</span>
<div class="wf-tpl-name">Blank Workflow</div>
<div class="wf-tpl-desc">Start from scratch with your own trigger and steps</div>
</button>
</div>
</div>
<!-- Builder -->
<div class="wf-builder" id="wf-builder" style="display:none">
<!-- Stats strip (shown for saved workflows) -->
<div class="wf-stats" id="wf-stats" style="display:none">
<div class="wf-stat"><div class="wf-stat-val" id="stat-runs">0</div><div class="wf-stat-lbl">Total Runs</div></div>
<div class="wf-stat"><div class="wf-stat-val" id="stat-ok">0</div><div class="wf-stat-lbl">Successful</div></div>
<div class="wf-stat"><div class="wf-stat-val" id="stat-err">0</div><div class="wf-stat-lbl">Errors</div></div>
<div class="wf-stat"><div class="wf-stat-val" id="stat-last"></div><div class="wf-stat-lbl">Last Run</div></div>
</div>
<!-- Header -->
<div class="wf-header">
<input class="wf-title-input" id="wf-name" placeholder="Workflow name…" oninput="markDirty()">
<button class="wf-status-toggle" id="wf-status-btn" onclick="toggleStatus()">
<span class="wf-dot"></span> <span id="wf-status-label">Draft</span>
</button>
<button class="wf-save-btn" onclick="saveWorkflow()">Save</button>
<button class="wf-delete-btn" onclick="deleteWorkflow()" title="Delete workflow">🗑</button>
</div>
<textarea class="wf-desc-input" id="wf-desc" rows="2" placeholder="Description — what does this workflow do?" oninput="markDirty()"></textarea>
<!-- Flow -->
<div class="wf-flow" id="wf-flow">
<!-- Trigger -->
<div class="wf-trigger">
<div class="wf-trigger-head">
<span class="wf-block-label">Trigger</span>
<span class="wf-trigger-name" id="trigger-display">Choose a trigger…</span>
</div>
<div class="wf-trigger-body">
<div class="wf-field">
<label>Trigger Type</label>
<select id="trigger-type" onchange="onTriggerChange()">
<option value="">— select —</option>
<option value="file_upload">📁 File Uploaded</option>
<option value="schedule">⏰ Schedule (Cron)</option>
<option value="api_webhook">🌐 API Webhook</option>
<option value="chat_message">💬 Chat Message</option>
<option value="form_submit">📝 Form Submitted</option>
<option value="manual">▶ Manual (Run button)</option>
</select>
</div>
<div id="trigger-extra"></div>
</div>
</div>
<!-- Arrow -->
<div class="wf-arrow"><div class="wf-arrow-line"></div></div>
<!-- Steps list -->
<div id="steps-list"></div>
<!-- Add step -->
<div class="wf-add-step" id="add-step-wrap">
<button class="wf-add-step-btn" onclick="toggleStepMenu()"> Add Step</button>
<div class="wf-step-menu" id="step-menu">
<div class="wf-step-menu-cat">AI</div>
<div class="wf-step-menu-opt" onclick="addStep('prompt')">
<span class="icon">🤖</span>
<div><div class="lbl">AI Prompt</div><div class="sub">Send a prompt to a model and capture output</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('classify')">
<span class="icon">🏷️</span>
<div><div class="lbl">Classify</div><div class="sub">Label input into one of your defined categories</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('extract')">
<span class="icon">🔍</span>
<div><div class="lbl">Extract Fields</div><div class="sub">Pull structured data from text (JSON output)</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('summarise')">
<span class="icon">📝</span>
<div><div class="lbl">Summarise</div><div class="sub">Generate a concise summary of input text</div></div>
</div>
<div class="wf-step-menu-cat">DATA</div>
<div class="wf-step-menu-opt" onclick="addStep('rag_search')">
<span class="icon">📚</span>
<div><div class="lbl">Knowledge Search</div><div class="sub">Query your RAG collections for relevant context</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('save_kb')">
<span class="icon">💾</span>
<div><div class="lbl">Save to Knowledge Base</div><div class="sub">Ingest output into a RAG collection</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('filter')">
<span class="icon">🔀</span>
<div><div class="lbl">Filter / Condition</div><div class="sub">Branch or stop based on a condition</div></div>
</div>
<div class="wf-step-menu-cat">NOTIFY</div>
<div class="wf-step-menu-opt" onclick="addStep('email')">
<span class="icon">📧</span>
<div><div class="lbl">Send Email</div><div class="sub">Send workflow output via email</div></div>
</div>
<div class="wf-step-menu-opt" onclick="addStep('http')">
<span class="icon">🌐</span>
<div><div class="lbl">HTTP Request</div><div class="sub">POST output to an external endpoint</div></div>
</div>
</div>
</div>
</div><!-- /wf-flow -->
<!-- Run panel -->
<div class="wf-run-card">
<div class="wf-run-title">Test Run</div>
<div class="wf-field" id="run-input-wrap" style="margin-bottom:14px">
<label>Test Input</label>
<textarea id="run-input" rows="3" placeholder="Paste sample text or leave empty for manual trigger…"></textarea>
</div>
<button class="wf-run-btn" id="run-btn" onclick="runWorkflow()">▶ Run Now</button>
<div class="wf-run-log" id="run-log"></div>
</div>
</div><!-- /wf-builder -->
</main>
</div>
<div class="wf-toast" id="wf-toast"></div>
<script>
const _API = '/api';
const WF_KEY = 'cezen_workflows';
let workflows = [];
let currentWf = null;
let steps = [];
let dirty = false;
// ── Templates ─────────────────────────────────────────────────────────────────
const TEMPLATES = {
doc_inbox: {
name: 'Document Inbox',
desc: 'Automatically summarise every uploaded document and save the summary to the HR Policies knowledge base.',
status: 'active',
trigger: { type:'file_upload', config:{ folder:'/uploads/inbox', fileTypes:'pdf,docx' } },
steps: [
{ type:'summarise', name:'Summarise Document', config:{ model:'llama3', maxLen:'500', focusArea:'key decisions and action items' } },
{ type:'save_kb', name:'Save to Knowledge Base', config:{ collection:'HR Policies', tags:'auto-inbox' } },
{ type:'email', name:'Notify Admin', config:{ to:'admin@cezentech.com', subject:'New document processed: {{filename}}', bodyTemplate:'Summary:\n\n{{step1.output}}' } }
]
},
daily_digest: {
name: 'Daily Metrics Digest',
desc: 'Every morning at 8 AM, pull key system metrics and email a plain-English summary to the admin team.',
status: 'active',
trigger: { type:'schedule', config:{ cron:'0 8 * * *', timezone:'Asia/Kolkata' } },
steps: [
{ type:'http', name:'Fetch Metrics', config:{ method:'GET', url:'{{_API}}/metrics', outputVar:'metrics' } },
{ type:'prompt', name:'Generate Summary', config:{ model:'llama3', prompt:'You are an IT admin assistant. Given these system metrics: {{metrics}}, write a 3-sentence plain-English summary highlighting anything above 80% usage or any services that are down.', outputVar:'summary' } },
{ type:'email', name:'Email Digest', config:{ to:'admin@cezentech.com', subject:'Nexus One AI — Daily Digest {{date}}', bodyTemplate:'{{summary}}' } }
]
},
query_router: {
name: 'Query Router',
desc: 'Classify incoming user queries and route them to the appropriate specialised agent.',
status: 'active',
trigger: { type:'api_webhook', config:{ path:'/api/workflow/query-route', method:'POST', authHeader:'X-Cezen-Key' } },
steps: [
{ type:'classify', name:'Classify Query', config:{ model:'llama3', categories:'legal,finance,hr,it-support,general', outputVar:'category' } },
{ type:'filter', name:'Legal Check', config:{ condition:'{{category}} == "legal"', trueAction:'continue', falseAction:'continue' } },
{ type:'prompt', name:'Route Response', config:{ model:'llama3', prompt:'You are a routing assistant. The query "{{input}}" has been classified as "{{category}}". Respond with a JSON object: { "agent": "<agent-name>", "priority": "high|medium|low", "reason": "<one-sentence reason>" }', outputVar:'routing' } },
{ type:'http', name:'Forward to Agent API', config:{ method:'POST', url:'{{_API}}/agents/run', bodyTemplate:'{"agent": "{{routing.agent}}", "input": "{{input}}", "priority": "{{routing.priority}}"}' } }
]
},
contract_review: {
name: 'Contract Review',
desc: 'Whenever a contract PDF is uploaded, extract key clauses and flag risks automatically.',
status: 'active',
trigger: { type:'file_upload', config:{ folder:'/uploads/contracts', fileTypes:'pdf,docx' } },
steps: [
{ type:'extract', name:'Extract Clauses', config:{ model:'llama3', schema:'{"clauses": [{"type": "string", "text": "string", "risk": "high|medium|low"}]}', outputVar:'clauses' } },
{ type:'filter', name:'Check for High Risk', config:{ condition:'{{clauses.clauses | filter: risk == "high" | count}} > 0', trueAction:'continue', falseAction:'stop' } },
{ type:'email', name:'Alert Legal Team', config:{ to:'legal@cezentech.com', subject:'⚠️ High-Risk Contract: {{filename}}', bodyTemplate:'High-risk clauses found in {{filename}}:\n\n{{clauses.clauses | filter: risk == "high" | format}}' } }
]
},
meeting_notes: {
name: 'Meeting Notes',
desc: 'Transcribe meeting audio and automatically post a formatted summary with action items.',
status: 'draft',
trigger: { type:'file_upload', config:{ folder:'/uploads/meetings', fileTypes:'mp3,m4a,wav' } },
steps: [
{ type:'http', name:'Transcribe Audio', config:{ method:'POST', url:'{{_API}}/meeting/transcribe', bodyTemplate:'{"file": "{{filepath}}"}', outputVar:'transcript' } },
{ type:'prompt', name:'Extract Action Items', config:{ model:'llama3', prompt:'From this meeting transcript, extract all action items with owner and due date. Format as a numbered list.\n\nTranscript:\n{{transcript}}', outputVar:'actions' } },
{ type:'prompt', name:'Write Summary', config:{ model:'llama3', prompt:'Write a 3-paragraph executive summary of this meeting transcript:\n\n{{transcript}}', outputVar:'summary' } },
{ type:'email', name:'Send Notes', config:{ to:'team@cezentech.com', subject:'Meeting Notes — {{date}}', bodyTemplate:'## Summary\n\n{{summary}}\n\n## Action Items\n\n{{actions}}' } }
]
}
};
const STEP_ICONS = {
prompt:'🤖', classify:'🏷️', extract:'🔍', summarise:'📝',
rag_search:'📚', save_kb:'💾', filter:'🔀', email:'📧', http:'🌐'
};
const STEP_LABELS = {
prompt:'AI Prompt', classify:'Classify', extract:'Extract Fields', summarise:'Summarise',
rag_search:'Knowledge Search', save_kb:'Save to KB', filter:'Filter / Condition',
email:'Send Email', http:'HTTP Request'
};
// ── Storage ───────────────────────────────────────────────────────────────────
function loadWorkflows() {
try {
workflows = JSON.parse(localStorage.getItem(WF_KEY) || '[]');
} catch(e) { workflows = []; }
renderSidebar();
}
function saveWorkflows() {
localStorage.setItem(WF_KEY, JSON.stringify(workflows));
}
// ── Sidebar ───────────────────────────────────────────────────────────────────
function renderSidebar() {
const el = document.getElementById('wf-list');
if (!workflows.length) {
el.innerHTML = '<div style="padding:16px;font-size:12px;color:var(--lt);text-align:center">No workflows yet</div>';
return;
}
el.innerHTML = workflows.map(wf => {
const statusLabel = wf.status === 'active' ? '● Active' : wf.status === 'paused' ? '⏸ Paused' : '◌ Draft';
const statusCls = wf.status || 'draft';
return `<div class="wf-item${currentWf && currentWf.id === wf.id ? ' active' : ''}" onclick="openWorkflow('${wf.id}')">
<div class="wf-item-name">${escHtml(wf.name)}</div>
<div class="wf-item-meta">
<span class="wf-item-status ${statusCls}">${statusLabel}</span>
· ${(wf.steps||[]).length} step${(wf.steps||[]).length !== 1 ? 's' : ''}
</div>
</div>`;
}).join('');
}
// ── Workflow CRUD ─────────────────────────────────────────────────────────────
function newWorkflow() {
const wf = {
id: 'wf_' + Date.now(),
name: '',
desc: '',
status: 'draft',
trigger: { type:'', config:{} },
steps: [],
stats: { runs:0, ok:0, err:0, lastRun:null }
};
workflows.unshift(wf);
saveWorkflows();
openWorkflow(wf.id);
}
function loadTemplate(tplKey) {
const tpl = TEMPLATES[tplKey];
if (!tpl) return;
const wf = {
id: 'wf_' + Date.now(),
name: tpl.name,
desc: tpl.desc,
status: tpl.status || 'draft',
trigger: tpl.trigger,
steps: tpl.steps.map((s,i) => ({ ...s, id:'step_'+(Date.now()+i) })),
stats: { runs: Math.floor(Math.random()*40)+5, ok:0, err:0, lastRun: new Date(Date.now()-Math.random()*3600000).toISOString() }
};
wf.stats.ok = Math.floor(wf.stats.runs * 0.92);
wf.stats.err = wf.stats.runs - wf.stats.ok;
workflows.unshift(wf);
saveWorkflows();
openWorkflow(wf.id);
}
function openWorkflow(id) {
currentWf = workflows.find(w => w.id === id);
if (!currentWf) return;
steps = (currentWf.steps || []).map(s => ({ ...s, id: s.id || 'step_' + Date.now() + Math.random() }));
document.getElementById('wf-empty').style.display = 'none';
document.getElementById('wf-builder').style.display = 'block';
document.getElementById('wf-name').value = currentWf.name || '';
document.getElementById('wf-desc').value = currentWf.desc || '';
// Stats
const stats = currentWf.stats || {};
if (stats.runs) {
document.getElementById('wf-stats').style.display = 'grid';
document.getElementById('stat-runs').textContent = stats.runs;
document.getElementById('stat-ok').textContent = stats.ok;
document.getElementById('stat-err').textContent = stats.err;
document.getElementById('stat-last').textContent = stats.lastRun
? new Date(stats.lastRun).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) : '—';
}
// Status
updateStatusBtn(currentWf.status || 'draft');
// Trigger
const tt = document.getElementById('trigger-type');
tt.value = (currentWf.trigger || {}).type || '';
renderTriggerExtra((currentWf.trigger || {}).config || {}, tt.value);
updateTriggerDisplay();
renderSteps();
document.getElementById('run-log').classList.remove('show');
dirty = false;
renderSidebar();
}
function deleteWorkflow() {
if (!currentWf || !confirm('Delete this workflow?')) return;
workflows = workflows.filter(w => w.id !== currentWf.id);
saveWorkflows();
currentWf = null; steps = [];
document.getElementById('wf-builder').style.display = 'none';
document.getElementById('wf-empty').style.display = 'block';
renderSidebar();
toast('Workflow deleted');
}
function saveWorkflow() {
if (!currentWf) return;
currentWf.name = document.getElementById('wf-name').value || 'Untitled Workflow';
currentWf.desc = document.getElementById('wf-desc').value;
currentWf.trigger.type = document.getElementById('trigger-type').value;
currentWf.trigger.config = gatherTriggerConfig();
currentWf.steps = steps.map(s => ({ ...s, config: gatherStepConfig(s.id) }));
const idx = workflows.findIndex(w => w.id === currentWf.id);
if (idx >= 0) workflows[idx] = currentWf;
saveWorkflows();
// Try to sync to API
fetch(`${_API}/workflows`, {
method: currentWf._synced ? 'PUT' : 'POST',
credentials: 'include',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(currentWf)
}).then(r => { if (r.ok) { currentWf._synced = true; } }).catch(() => {});
dirty = false;
renderSidebar();
toast('✓ Workflow saved');
}
function markDirty() { dirty = true; }
// ── Status ─────────────────────────────────────────────────────────────────────
function toggleStatus() {
if (!currentWf) return;
const cycle = { draft:'active', active:'paused', paused:'active' };
currentWf.status = cycle[currentWf.status] || 'active';
updateStatusBtn(currentWf.status);
markDirty();
}
function updateStatusBtn(status) {
const btn = document.getElementById('wf-status-btn');
const lbl = document.getElementById('wf-status-label');
lbl.textContent = status === 'active' ? 'Active' : status === 'paused' ? 'Paused' : 'Draft';
btn.className = 'wf-status-toggle' + (status === 'active' ? ' active-wf' : '');
}
// ── Trigger ───────────────────────────────────────────────────────────────────
function onTriggerChange() {
const type = document.getElementById('trigger-type').value;
renderTriggerExtra({}, type);
updateTriggerDisplay();
markDirty();
}
function updateTriggerDisplay() {
const labels = {
file_upload:'📁 File Uploaded', schedule:'⏰ Schedule', api_webhook:'🌐 API Webhook',
chat_message:'💬 Chat Message', form_submit:'📝 Form Submitted', manual:'▶ Manual'
};
const type = document.getElementById('trigger-type').value;
document.getElementById('trigger-display').textContent = labels[type] || 'Choose a trigger…';
}
function renderTriggerExtra(cfg, type) {
const el = document.getElementById('trigger-extra');
const field = (id, label, val='', placeholder='', hint='') =>
`<div class="wf-field">
<label>${label}</label>
<input id="tc-${id}" value="${escAttr(val)}" placeholder="${escAttr(placeholder)}">
${hint ? `<div class="wf-field-hint">${hint}</div>` : ''}
</div>`;
const map = {
file_upload: field('folder','Watch Folder', cfg.folder||'/uploads', '/uploads/inbox') +
field('fileTypes','File Types', cfg.fileTypes||'pdf,docx', 'pdf,docx,txt', 'Comma-separated extensions'),
schedule: field('cron','Cron Expression', cfg.cron||'0 8 * * *', '0 8 * * *', 'UTC — e.g. 0 8 * * * = daily at 8 AM') +
field('timezone','Timezone', cfg.timezone||'UTC', 'Asia/Kolkata'),
api_webhook: field('path','Webhook Path', cfg.path||'/api/workflow/trigger', '/api/workflow/trigger') +
field('authHeader','Auth Header', cfg.authHeader||'X-Cezen-Key', 'X-Cezen-Key'),
chat_message:field('matchKeywords','Match Keywords', cfg.matchKeywords||'', 'urgent,help,error', 'Leave empty to match all messages') +
field('channel','Channel / Model', cfg.channel||'', 'e.g. general'),
form_submit: field('formId','Form ID', cfg.formId||'', 'contact-form') +
field('requiredField','Required Field', cfg.requiredField||'', 'email'),
manual: `<div class="wf-field"><label>Input Prompt</label><input id="tc-inputLabel" value="${escAttr(cfg.inputLabel||'')}" placeholder="Label shown on run input (e.g. Paste document text)"></div>`
};
el.innerHTML = map[type] || '';
}
function gatherTriggerConfig() {
const cfg = {};
document.querySelectorAll('[id^="tc-"]').forEach(el => {
cfg[el.id.replace('tc-','')] = el.value;
});
return cfg;
}
// ── Steps ─────────────────────────────────────────────────────────────────────
function renderSteps() {
const el = document.getElementById('steps-list');
if (!steps.length) { el.innerHTML = ''; return; }
el.innerHTML = steps.map((s, i) => buildStepHtml(s, i)).join(
'<div class="wf-arrow"><div class="wf-arrow-line"></div></div>'
);
// Keep open state — collapse all fresh
}
function buildStepHtml(s, i) {
const icon = STEP_ICONS[s.type] || '⚙️';
const label = STEP_LABELS[s.type] || s.type;
return `<div class="wf-step" id="step-${s.id}">
<div class="wf-step-head" onclick="toggleStep('${s.id}')">
<div class="wf-step-num">${i+1}</div>
<span style="font-size:18px">${icon}</span>
<div class="wf-step-name">${escHtml(s.name||label)}</div>
<span class="wf-step-type-chip">${label}</span>
<div class="wf-step-actions" onclick="event.stopPropagation()">
${i > 0 ? `<button class="wf-step-btn" title="Move up" onclick="moveStep('${s.id}',-1)">↑</button>` : ''}
${i < steps.length-1 ? `<button class="wf-step-btn" title="Move down" onclick="moveStep('${s.id}',1)">↓</button>` : ''}
<button class="wf-step-btn" title="Remove" onclick="removeStep('${s.id}')">✕</button>
</div>
<span class="wf-step-chevron">▶</span>
</div>
<div class="wf-step-body">
${buildStepFields(s)}
</div>
</div>`;
}
function buildStepFields(s) {
const cfg = s.config || {};
const nameField = `<div class="wf-field"><label>Step Name</label><input id="sf-${s.id}-name" value="${escAttr(s.name||'')}"></div>`;
const modelField = (v) => `<div class="wf-field"><label>Model</label>
<select id="sf-${s.id}-model">
<option value="llama3"${v==='llama3'?' selected':''}>llama3</option>
<option value="llama3:70b"${v==='llama3:70b'?' selected':''}>llama3:70b</option>
<option value="codellama"${v==='codellama'?' selected':''}>codellama</option>
<option value="mistral"${v==='mistral'?' selected':''}>mistral</option>
</select></div>`;
const outputVar = (v) => `<div class="wf-field"><label>Output Variable</label>
<input id="sf-${s.id}-outputVar" value="${escAttr(v||'')}" placeholder="e.g. summary — use {{summary}} in later steps"></div>`;
const maps = {
prompt: nameField + modelField(cfg.model) +
`<div class="wf-field"><label>Prompt</label>
<textarea id="sf-${s.id}-prompt" rows="4" placeholder="Use {{input}} for trigger data, {{stepN.output}} for prior steps">${escHtml(cfg.prompt||'')}</textarea>
<div class="wf-field-hint">Variables: {{input}}, {{filename}}, {{date}}, {{step1.output}} …</div>
</div>` + outputVar(cfg.outputVar),
classify: nameField + modelField(cfg.model) +
`<div class="wf-field"><label>Categories (comma-separated)</label>
<input id="sf-${s.id}-categories" value="${escAttr(cfg.categories||'')}" placeholder="legal,finance,hr,general">
</div>` + outputVar(cfg.outputVar),
extract: nameField + modelField(cfg.model) +
`<div class="wf-field"><label>JSON Schema to Extract</label>
<textarea id="sf-${s.id}-schema" rows="3" placeholder='{"name":"string","amount":"number","date":"string"}'>${escHtml(cfg.schema||'')}</textarea>
</div>` + outputVar(cfg.outputVar),
summarise: nameField + modelField(cfg.model) +
`<div class="wf-field-grid">
<div class="wf-field"><label>Max Length (words)</label><input id="sf-${s.id}-maxLen" value="${escAttr(cfg.maxLen||'300')}" placeholder="300"></div>
<div class="wf-field"><label>Focus Area</label><input id="sf-${s.id}-focusArea" value="${escAttr(cfg.focusArea||'')}" placeholder="e.g. key decisions"></div>
</div>` + outputVar(cfg.outputVar),
rag_search: nameField +
`<div class="wf-field-grid">
<div class="wf-field"><label>Collection</label><input id="sf-${s.id}-collection" value="${escAttr(cfg.collection||'')}" placeholder="HR Policies"></div>
<div class="wf-field"><label>Top K Results</label><input id="sf-${s.id}-topK" value="${escAttr(cfg.topK||'5')}" placeholder="5"></div>
</div>` + outputVar(cfg.outputVar),
save_kb: nameField +
`<div class="wf-field-grid">
<div class="wf-field"><label>Collection</label><input id="sf-${s.id}-collection" value="${escAttr(cfg.collection||'')}" placeholder="Knowledge Base name"></div>
<div class="wf-field"><label>Tags (comma-sep)</label><input id="sf-${s.id}-tags" value="${escAttr(cfg.tags||'')}" placeholder="auto,workflow"></div>
</div>`,
filter: nameField +
`<div class="wf-field"><label>Condition</label>
<input id="sf-${s.id}-condition" value="${escAttr(cfg.condition||'')}" placeholder="{{category}} == &quot;legal&quot;">
<div class="wf-field-hint">Use template variables. If true, continues; if false, applies action below.</div>
</div>
<div class="wf-field-grid">
<div class="wf-field"><label>If True</label>
<select id="sf-${s.id}-trueAction">
<option value="continue"${cfg.trueAction==='continue'?' selected':''}>Continue</option>
<option value="stop"${cfg.trueAction==='stop'?' selected':''}>Stop workflow</option>
</select>
</div>
<div class="wf-field"><label>If False</label>
<select id="sf-${s.id}-falseAction">
<option value="stop"${cfg.falseAction==='stop'?' selected':''}>Stop workflow</option>
<option value="continue"${cfg.falseAction==='continue'?' selected':''}>Continue</option>
</select>
</div>
</div>`,
email: nameField +
`<div class="wf-field"><label>To</label><input id="sf-${s.id}-to" value="${escAttr(cfg.to||'')}" placeholder="admin@cezentech.com"></div>
<div class="wf-field"><label>Subject</label><input id="sf-${s.id}-subject" value="${escAttr(cfg.subject||'')}" placeholder="Workflow result — {{date}}"></div>
<div class="wf-field"><label>Body Template</label>
<textarea id="sf-${s.id}-bodyTemplate" rows="3" placeholder="Use {{step1.output}} etc">${escHtml(cfg.bodyTemplate||'')}</textarea>
</div>`,
http: nameField +
`<div class="wf-field-grid">
<div class="wf-field"><label>Method</label>
<select id="sf-${s.id}-method">
<option value="POST"${cfg.method==='POST'?' selected':''}>POST</option>
<option value="GET"${cfg.method==='GET'?' selected':''}>GET</option>
<option value="PUT"${cfg.method==='PUT'?' selected':''}>PUT</option>
</select>
</div>
<div class="wf-field"><label>URL</label><input id="sf-${s.id}-url" value="${escAttr(cfg.url||'')}" placeholder="https://api.example.com/hook"></div>
</div>
<div class="wf-field"><label>Body Template (JSON)</label>
<textarea id="sf-${s.id}-bodyTemplate" rows="2" placeholder='{"text": "{{step1.output}}"}'>${escHtml(cfg.bodyTemplate||'')}</textarea>
</div>` + outputVar(cfg.outputVar)
};
return (maps[s.type] || nameField) + `<div style="text-align:right;margin-top:10px">
<button onclick="applyStepConfig('${s.id}')" style="padding:6px 16px;border-radius:8px;border:none;background:var(--purple);color:white;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer">Apply</button>
</div>`;
}
function gatherStepConfig(stepId) {
const cfg = {};
document.querySelectorAll(`[id^="sf-${stepId}-"]`).forEach(el => {
cfg[el.id.replace(`sf-${stepId}-`,'')] = el.value;
});
return cfg;
}
function applyStepConfig(stepId) {
const idx = steps.findIndex(s => s.id === stepId);
if (idx < 0) return;
const cfg = gatherStepConfig(stepId);
steps[idx].name = cfg.name || STEP_LABELS[steps[idx].type];
steps[idx].config = cfg;
markDirty();
renderSteps();
toast('Step updated');
}
function addStep(type) {
const s = { id:'step_'+ Date.now(), type, name: STEP_LABELS[type]||type, config:{} };
steps.push(s);
renderSteps();
toggleStepMenu();
markDirty();
// Open the new step
setTimeout(() => toggleStep(s.id), 50);
}
function removeStep(id) {
steps = steps.filter(s => s.id !== id);
renderSteps(); markDirty();
}
function moveStep(id, dir) {
const i = steps.findIndex(s => s.id === id);
const j = i + dir;
if (j < 0 || j >= steps.length) return;
[steps[i], steps[j]] = [steps[j], steps[i]];
renderSteps(); markDirty();
}
function toggleStep(id) {
document.getElementById('step-' + id)?.classList.toggle('open');
}
function toggleStepMenu() {
document.getElementById('step-menu').classList.toggle('open');
}
document.addEventListener('click', e => {
if (!e.target.closest('#add-step-wrap')) {
document.getElementById('step-menu')?.classList.remove('open');
}
});
// ── Run ───────────────────────────────────────────────────────────────────────
async function runWorkflow() {
if (!currentWf) return;
const input = document.getElementById('run-input').value;
const logEl = document.getElementById('run-log');
const btn = document.getElementById('run-btn');
btn.disabled = true;
logEl.classList.add('show');
// Build log entries
const allSteps = [{type:'trigger',name:'Trigger'},...steps];
logEl.innerHTML = allSteps.map((s,i) => `
<div class="wf-log-step" id="log-${i}">
<span class="wf-log-icon">${i===0 ? '⚡' : (STEP_ICONS[s.type]||'⚙️')}</span>
<div class="wf-log-body">
<div class="wf-log-label">${escHtml(s.name||STEP_LABELS[s.type]||s.type)}</div>
<div class="wf-log-detail" id="log-detail-${i}">Waiting…</div>
</div>
<span class="wf-log-status" id="log-status-${i}"></span>
</div>`).join('');
// Try real API
let useApi = false;
try {
const r = await fetch(`${_API}/workflows/${currentWf.id}/run`, {
method:'POST', credentials:'include',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({ input })
});
if (r.ok) { useApi = true; const result = await r.json(); renderRunResult(result); }
} catch(e) {}
if (!useApi) {
// Simulate step by step
for (let i = 0; i < allSteps.length; i++) {
const s = allSteps[i];
document.getElementById(`log-status-${i}`).textContent = 'Running';
document.getElementById(`log-status-${i}`).className = 'wf-log-status running';
document.getElementById(`log-detail-${i}`).textContent = i === 0 ? `Input: "${(input||'(manual run)').substring(0,60)}"` : 'Processing…';
await sleep(700 + Math.random()*400);
document.getElementById(`log-status-${i}`).textContent = 'Done';
document.getElementById(`log-status-${i}`).className = 'wf-log-status ok';
const outputs = {
prompt: 'AI generated a response based on the provided input and context.',
classify: `Classified as: "${(currentWf.steps?.[i-1]?.config?.categories||'general').split(',')[0].trim()}"`,
extract: 'Extracted fields: { "status": "parsed", "count": 3 }',
summarise: 'Summary generated (312 words). Key topics: service delivery, payment terms, SLA.',
rag_search: 'Found 5 relevant passages across 3 documents.',
save_kb: 'Document ingested into knowledge base (embedding: 1,536 dimensions).',
filter: 'Condition evaluated → true → continuing workflow.',
email: 'Email queued for delivery to admin@cezentech.com.',
http: 'POST 200 OK — {"status":"received","id":"evt_abc123"}'
};
document.getElementById(`log-detail-${i}`).textContent =
i === 0 ? `Triggered with ${input ? 'provided input' : 'no input'}` : (outputs[s.type]||'Completed successfully');
}
// Update stats mock
if (!currentWf.stats) currentWf.stats = {runs:0,ok:0,err:0,lastRun:null};
currentWf.stats.runs++;
currentWf.stats.ok++;
currentWf.stats.lastRun = new Date().toISOString();
if (currentWf.stats.runs > 0) {
document.getElementById('wf-stats').style.display = 'grid';
document.getElementById('stat-runs').textContent = currentWf.stats.runs;
document.getElementById('stat-ok').textContent = currentWf.stats.ok;
document.getElementById('stat-err').textContent = currentWf.stats.err;
document.getElementById('stat-last').textContent = 'just now';
}
saveWorkflows();
}
btn.disabled = false;
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
// ── Toast ─────────────────────────────────────────────────────────────────────
function toast(msg) {
const el = document.getElementById('wf-toast');
el.textContent = msg; el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 2000);
}
function escHtml(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function escAttr(s) { return (s||'').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
// ── Init ─────────────────────────────────────────────────────────────────────
loadWorkflows();
</script>
<script src="auth.js"></script>
<script src="branding.js"></script>
</body>
</html>