@ai2aim.ai/hivemind-sdk 1.0.15 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web.js CHANGED
@@ -1,1180 +1,650 @@
1
1
  "use strict";
2
- /**
3
- * Embedded web dashboard for the Hivemind SDK.
4
- *
5
- * `hivemind start-web` launches a local HTTP server that serves an
6
- * interactive single-page application built with Vue 3 + Tailwind (CDN).
7
- * The app is organized around **flows** — multi-step interactive wizards
8
- * that chain SDK methods together — rather than raw endpoint forms.
9
- */
10
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
11
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
12
4
  };
13
5
  Object.defineProperty(exports, "__esModule", { value: true });
14
6
  exports.startWebServer = startWebServer;
15
7
  const node_http_1 = __importDefault(require("node:http"));
16
- /* ------------------------------------------------------------------ */
17
- /* HTML template */
18
- /* ------------------------------------------------------------------ */
19
8
  function buildDashboardHtml(config) {
20
- const esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"');
21
- return /* html */ `<!DOCTYPE html>
9
+ const esc = (value) => value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"');
10
+ return `<!DOCTYPE html>
22
11
  <html lang="en">
23
12
  <head>
24
- <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
25
- <title>Hivemind Dashboard</title>
26
- <script src="https://unpkg.com/vue@3/dist/vue.global.js"><\/script>
27
- <link href="https://cdn.jsdelivr.net/npm/@mdi/font@7/css/materialdesignicons.min.css" rel="stylesheet">
28
- <script src="https://cdn.tailwindcss.com"><\/script>
29
- <script>tailwind.config={theme:{extend:{colors:{brand:'#6366f1',bg:'#0f1117',surface:'#1a1d25',border:'#2a2d35'}}}}<\/script>
30
- <style>
31
- *{scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.1) transparent}
32
- ::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.1);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.2)}
33
- @keyframes slideUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
34
- @keyframes scaleIn{from{opacity:0;transform:scale(0.95)}to{opacity:1;transform:scale(1)}}
35
- @keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
36
- @keyframes shimmer{from{background-position:-200% 0}to{background-position:200% 0}}
37
- @keyframes typing{0%,100%{opacity:.3}50%{opacity:1}}
38
- @keyframes confetti{0%{transform:translateY(-10px) rotate(0);opacity:1}100%{transform:translateY(100px) rotate(720deg);opacity:0}}
39
- @keyframes glow-pulse{0%,100%{box-shadow:0 0 4px rgba(99,102,241,0.3)}50%{box-shadow:0 0 16px rgba(99,102,241,0.6)}}
40
- .animate-slide-up{animation:slideUp .3s ease-out both}
41
- .animate-scale{animation:scaleIn .2s ease-out both}
42
- .animate-fade-up{animation:fadeUp .25s ease-out both}
43
- .stagger>*{animation:fadeUp .3s ease-out both}.stagger>:nth-child(1){animation-delay:0s}.stagger>:nth-child(2){animation-delay:.04s}.stagger>:nth-child(3){animation-delay:.08s}.stagger>:nth-child(4){animation-delay:.12s}.stagger>:nth-child(5){animation-delay:.16s}.stagger>:nth-child(6){animation-delay:.2s}.stagger>:nth-child(7){animation-delay:.24s}.stagger>:nth-child(8){animation-delay:.28s}.stagger>:nth-child(9){animation-delay:.32s}
44
- .skeleton{background:linear-gradient(90deg,#1e2025 25%,#2a2d35 50%,#1e2025 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:8px}
45
- .glass{background:rgba(26,29,37,0.92);backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.08)}
46
- .card-hover{transition:transform .2s,box-shadow .2s}.card-hover:hover{transform:translateY(-2px);box-shadow:0 8px 25px rgba(0,0,0,0.3)}
47
- .sidebar-full{width:260px;transition:width .3s cubic-bezier(.4,0,.2,1)}.sidebar-mini{width:64px;transition:width .3s cubic-bezier(.4,0,.2,1)}
48
- .cmd-backdrop{background:rgba(0,0,0,0.6);backdrop-filter:blur(4px)}
49
- .log-panel{transition:transform .3s cubic-bezier(.4,0,.2,1)}.log-open{transform:translateX(0)}.log-closed{transform:translateX(100%)}
50
- .ripple{position:relative;overflow:hidden}.ripple::after{content:'';position:absolute;inset:0;background:radial-gradient(circle,rgba(255,255,255,.15) 10%,transparent 10.01%);transform:scale(10);opacity:0;transition:transform .5s,opacity 1s}.ripple:active::after{transform:scale(0);opacity:.3;transition:0s}
51
- .glow-green{box-shadow:0 0 12px rgba(34,197,94,.3)}.glow-red{box-shadow:0 0 12px rgba(239,68,68,.3)}
52
- .toast-enter-active{animation:slideUp .3s ease-out}.toast-leave-active{transition:all .2s ease-in}.toast-leave-to{opacity:0;transform:translateX(40px)}
53
- .jt-key{color:#9cdcfe}.jt-str{color:#ce9178}.jt-num{color:#b5cea8}.jt-bool{color:#569cd6}.jt-null{color:#808080}
54
- .typing-dot{width:6px;height:6px;border-radius:50%;background:#6366f1;display:inline-block;animation:typing 1.2s infinite}.typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}
55
- .drag-active{border-color:#6366f1!important;background:rgba(99,102,241,0.05)!important}
56
- .poll-pulse{animation:glow-pulse 2s infinite}
57
- </style>
13
+ <meta charset="utf-8">
14
+ <meta name="viewport" content="width=device-width,initial-scale=1">
15
+ <title>Hivemind SDK Dashboard</title>
16
+ <link rel="preconnect" href="https://fonts.googleapis.com">
17
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
18
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
19
+ <style>
20
+ :root {
21
+ --bg: #07111f;
22
+ --bg2: #10243f;
23
+ --card: rgba(8, 18, 32, 0.82);
24
+ --line: rgba(150, 184, 214, 0.18);
25
+ --text: #edf4fb;
26
+ --muted: #9cb3c8;
27
+ --accent: #0ea5a8;
28
+ --accent2: #f59e0b;
29
+ --ok: #22c55e;
30
+ --bad: #ef4444;
31
+ }
32
+ * { box-sizing: border-box; }
33
+ body {
34
+ margin: 0;
35
+ font-family: "IBM Plex Sans", sans-serif;
36
+ color: var(--text);
37
+ background:
38
+ radial-gradient(circle at top left, rgba(14, 165, 168, 0.20), transparent 32%),
39
+ radial-gradient(circle at top right, rgba(245, 158, 11, 0.18), transparent 28%),
40
+ linear-gradient(160deg, var(--bg), var(--bg2));
41
+ min-height: 100vh;
42
+ }
43
+ .shell {
44
+ max-width: 1320px;
45
+ margin: 0 auto;
46
+ padding: 28px 20px 40px;
47
+ }
48
+ .hero {
49
+ display: grid;
50
+ gap: 12px;
51
+ margin-bottom: 22px;
52
+ }
53
+ .hero h1 {
54
+ margin: 0;
55
+ font-size: clamp(2rem, 4vw, 3rem);
56
+ line-height: 1;
57
+ }
58
+ .hero p {
59
+ margin: 0;
60
+ color: var(--muted);
61
+ max-width: 760px;
62
+ }
63
+ .grid {
64
+ display: grid;
65
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
66
+ gap: 16px;
67
+ }
68
+ .card {
69
+ background: var(--card);
70
+ border: 1px solid var(--line);
71
+ border-radius: 20px;
72
+ padding: 18px;
73
+ backdrop-filter: blur(12px);
74
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
75
+ }
76
+ .card h2 {
77
+ margin: 0 0 6px;
78
+ font-size: 1rem;
79
+ }
80
+ .sub {
81
+ margin: 0 0 14px;
82
+ color: var(--muted);
83
+ font-size: 0.92rem;
84
+ }
85
+ .form {
86
+ display: grid;
87
+ gap: 10px;
88
+ }
89
+ .row {
90
+ display: grid;
91
+ grid-template-columns: repeat(2, minmax(0, 1fr));
92
+ gap: 10px;
93
+ }
94
+ label {
95
+ display: grid;
96
+ gap: 6px;
97
+ font-size: 0.84rem;
98
+ color: var(--muted);
99
+ }
100
+ input, textarea, select, button {
101
+ font: inherit;
102
+ }
103
+ input, textarea, select {
104
+ width: 100%;
105
+ border: 1px solid var(--line);
106
+ background: rgba(4, 12, 22, 0.68);
107
+ color: var(--text);
108
+ border-radius: 14px;
109
+ padding: 11px 12px;
110
+ }
111
+ textarea {
112
+ min-height: 96px;
113
+ resize: vertical;
114
+ }
115
+ button {
116
+ border: 0;
117
+ border-radius: 14px;
118
+ padding: 11px 14px;
119
+ cursor: pointer;
120
+ font-weight: 600;
121
+ color: #04131d;
122
+ background: linear-gradient(135deg, #7dd3fc, #0ea5a8);
123
+ }
124
+ button.secondary {
125
+ color: var(--text);
126
+ background: rgba(255, 255, 255, 0.06);
127
+ border: 1px solid var(--line);
128
+ }
129
+ button.warn {
130
+ background: linear-gradient(135deg, #fbbf24, #f97316);
131
+ }
132
+ .actions {
133
+ display: flex;
134
+ flex-wrap: wrap;
135
+ gap: 10px;
136
+ }
137
+ .badge {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ gap: 8px;
141
+ padding: 8px 12px;
142
+ border: 1px solid var(--line);
143
+ border-radius: 999px;
144
+ background: rgba(255, 255, 255, 0.04);
145
+ color: var(--muted);
146
+ font-size: 0.86rem;
147
+ }
148
+ .dot {
149
+ width: 10px;
150
+ height: 10px;
151
+ border-radius: 50%;
152
+ background: var(--bad);
153
+ box-shadow: 0 0 18px rgba(239, 68, 68, 0.5);
154
+ }
155
+ .dot.ok {
156
+ background: var(--ok);
157
+ box-shadow: 0 0 18px rgba(34, 197, 94, 0.5);
158
+ }
159
+ .console {
160
+ margin-top: 18px;
161
+ display: grid;
162
+ gap: 12px;
163
+ }
164
+ pre {
165
+ margin: 0;
166
+ padding: 16px;
167
+ min-height: 240px;
168
+ border-radius: 18px;
169
+ background: rgba(3, 10, 18, 0.86);
170
+ border: 1px solid var(--line);
171
+ color: #cbe6ff;
172
+ overflow: auto;
173
+ font-family: "IBM Plex Mono", monospace;
174
+ font-size: 0.84rem;
175
+ line-height: 1.45;
176
+ white-space: pre-wrap;
177
+ word-break: break-word;
178
+ }
179
+ .history {
180
+ display: grid;
181
+ gap: 8px;
182
+ max-height: 220px;
183
+ overflow: auto;
184
+ }
185
+ .history-item {
186
+ padding: 10px 12px;
187
+ border-radius: 14px;
188
+ border: 1px solid var(--line);
189
+ background: rgba(255, 255, 255, 0.04);
190
+ font-size: 0.84rem;
191
+ color: var(--muted);
192
+ }
193
+ .history-item strong {
194
+ color: var(--text);
195
+ }
196
+ @media (max-width: 720px) {
197
+ .row { grid-template-columns: 1fr; }
198
+ .shell { padding: 20px 14px 30px; }
199
+ }
200
+ </style>
58
201
  </head>
59
- <body class="bg-bg text-white overflow-hidden h-screen">
60
- <div id="app"></div>
61
- <script type="text/html" id="app-tpl">
62
- <div class="flex h-screen">
63
-
64
- <!-- ═══════ SIDEBAR ═══════ -->
65
- <aside :class="sidebarOpen?'sidebar-full':'sidebar-mini'" class="bg-surface border-r border-border flex flex-col h-full overflow-hidden z-20 flex-shrink-0">
66
- <div class="p-4 flex items-center gap-3 border-b border-border min-h-[56px]">
67
- <div class="w-8 h-8 bg-brand rounded-lg flex items-center justify-center flex-shrink-0 cursor-pointer" @click="sidebarOpen=!sidebarOpen">
68
- <span class="mdi mdi-brain text-white text-lg"></span>
69
- </div>
70
- <transition name="toast"><span v-if="sidebarOpen" class="font-bold text-lg text-white whitespace-nowrap">Hivemind</span></transition>
71
- </div>
72
- <div class="px-4 py-2 border-b border-border" :class="sidebarOpen?'':'flex justify-center'">
73
- <div class="flex items-center gap-2 text-xs cursor-pointer" @click="checkHealth" title="Click to refresh">
74
- <span class="relative flex h-2.5 w-2.5">
75
- <span v-if="health.ok" class="absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75 animate-ping"></span>
76
- <span class="relative inline-flex rounded-full h-2.5 w-2.5" :class="health.ok?'bg-green-500':'bg-red-500'"></span>
77
- </span>
78
- <span v-if="sidebarOpen" class="text-gray-400 truncate">{{health.text}}<span v-if="health.ms" class="text-gray-600 ml-1">{{health.ms}}ms</span></span>
79
- </div>
80
- </div>
81
- <nav class="flex-1 overflow-y-auto py-2">
82
- <div v-for="sec in navSections" :key="sec.name" style="display:contents">
83
- <div v-if="sidebarOpen" class="px-4 pt-3 pb-1 text-[10px] uppercase tracking-wider text-gray-600 font-semibold">{{sec.name}}</div>
84
- <button v-for="item in sec.items" :key="item.view" @click="navigate(item.view)"
85
- class="w-full flex items-center gap-3 px-4 py-2 text-sm transition-all hover:bg-white/5 group relative"
86
- :class="currentView===item.view?'text-brand bg-brand/10 border-r-2 border-brand':'text-gray-400'">
87
- <span class="mdi text-lg flex-shrink-0" :class="item.icon"></span>
88
- <span v-if="sidebarOpen" class="truncate">{{item.label}}</span>
89
- <span v-if="!sidebarOpen" class="absolute left-full ml-2 px-2 py-1 bg-surface border border-border rounded text-xs whitespace-nowrap opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity z-50">{{item.label}}</span>
90
- </button>
91
- </div>
92
- </nav>
93
- <div class="border-t border-border p-3" :class="sidebarOpen?'':'flex justify-center'">
94
- <button @click="showShortcuts=true" class="text-gray-500 hover:text-white transition text-sm flex items-center gap-2 w-full" :title="sidebarOpen?'Keyboard shortcuts':'?'">
95
- <span class="mdi mdi-keyboard"></span>
96
- <span v-if="sidebarOpen" class="contents"><span class="text-xs">Shortcuts</span><kbd class="ml-auto text-[10px] bg-bg px-1.5 py-0.5 rounded border border-border text-gray-500">?</kbd></span>
97
- </button>
98
- </div>
99
- </aside>
100
-
101
- <!-- ═══════ MAIN AREA ═══════ -->
102
- <div class="flex-1 flex flex-col overflow-hidden">
103
- <!-- Header Bar -->
104
- <header class="h-14 border-b border-border flex items-center gap-3 px-4 bg-surface/50 backdrop-blur-sm flex-shrink-0 z-10">
105
- <nav class="flex items-center gap-1 text-sm">
106
- <button @click="navigate('home')" class="text-gray-500 hover:text-white transition"><span class="mdi mdi-home"></span></button>
107
- <span v-if="breadcrumb" class="contents"><span class="text-gray-600 mdi mdi-chevron-right text-xs"></span><span class="text-gray-300 text-xs">{{breadcrumb}}</span></span>
108
- </nav>
109
- <div class="flex-1"></div>
110
- <button @click="cmdOpen=true" class="flex items-center gap-2 bg-bg border border-border rounded-lg px-3 py-1.5 text-xs text-gray-500 hover:text-gray-300 hover:border-gray-500 transition min-w-[200px]">
111
- <span class="mdi mdi-magnify"></span><span>Search…</span><kbd class="ml-auto text-[10px] bg-surface px-1.5 py-0.5 rounded border border-border">Ctrl+K</kbd>
112
- </button>
113
- <button @click="logOpen=!logOpen" class="relative p-2 text-gray-500 hover:text-white transition rounded-lg hover:bg-white/5" title="Activity Log (Ctrl+L)">
114
- <span class="mdi mdi-history text-lg"></span>
115
- <span v-if="actLog.length" class="absolute -top-0.5 -right-0.5 w-4 h-4 bg-brand text-white text-[9px] rounded-full flex items-center justify-center font-bold">{{actLog.length>99?'…':actLog.length}}</span>
116
- </button>
117
- <button @click="navigate('quick')" class="p-2 text-gray-500 hover:text-white transition rounded-lg hover:bg-white/5" title="Quick Actions">
118
- <span class="mdi mdi-lightning-bolt text-lg"></span>
119
- </button>
120
- </header>
121
-
122
- <!-- Content Area -->
123
- <main class="flex-1 overflow-y-auto p-6" @drop.prevent="onDrop" @dragover.prevent="onDragOver" @dragleave="onDragLeave">
124
- <div :key="currentView" class="animate-slide-up max-w-5xl mx-auto">
125
-
126
- <!-- ▸ HOME ──────────────────────── -->
127
- <div v-if="currentView==='home'">
128
- <div class="mb-6"><h1 class="text-3xl font-bold text-white mb-1">Welcome to Hivemind</h1>
129
- <p class="text-gray-400">Choose a flow or press <kbd class="text-[10px] bg-surface px-1.5 py-0.5 rounded border border-border">Ctrl+K</kbd> to search.</p></div>
130
- <div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6 stagger">
131
- <div v-for="s in homeStats" :key="s.label" class="bg-surface border border-border rounded-xl p-4 card-hover">
132
- <div class="flex items-center gap-2 mb-2"><span class="mdi text-lg" :class="s.icon+' '+s.color"></span><span class="text-[10px] text-gray-500 uppercase">{{s.label}}</span></div>
133
- <div class="text-2xl font-bold text-white">{{s.value}}</div>
134
- </div>
135
- </div>
136
- <h2 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Flows</h2>
137
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 stagger">
138
- <button v-for="f in flows" :key="f.view" @click="navigate(f.view)"
139
- class="bg-surface border border-border rounded-xl p-5 text-left card-hover group transition-all hover:border-brand/30">
140
- <div class="flex items-center gap-3 mb-3">
141
- <div class="w-10 h-10 rounded-lg flex items-center justify-center" :class="f.color"><span class="mdi text-xl" :class="f.icon"></span></div>
142
- <div><div class="font-semibold text-white group-hover:text-brand transition">{{f.title}}</div><div class="text-[10px] text-gray-500">{{f.steps}} steps</div></div>
143
- <span class="mdi mdi-chevron-right text-gray-600 ml-auto group-hover:translate-x-1 transition-transform"></span>
144
- </div>
145
- <p class="text-xs text-gray-500 mb-3">{{f.desc}}</p>
146
- <div class="flex flex-wrap gap-1"><span v-for="t in f.tags" :key="t" class="text-[9px] bg-white/5 text-gray-500 rounded-full px-2 py-0.5">{{t}}</span></div>
147
- </button>
148
- </div>
202
+ <body>
203
+ <div class="shell">
204
+ <div class="hero">
205
+ <div class="badge"><span id="health-dot" class="dot"></span><span id="health-text">Checking API health...</span></div>
206
+ <h1>Hivemind SDK Dashboard</h1>
207
+ <p>API-key-scoped local dashboard for the current Hivemind surface. Admin, API key, webhook, and manual requests all use the same saved connection profile below.</p>
149
208
  </div>
150
209
 
151
- <!-- ▸ SETTINGS ──────────────────── -->
152
- <div v-if="currentView==='settings'">
153
- <div class="max-w-lg animate-slide-up">
154
- <h1 class="text-2xl font-bold text-white mb-1">Settings</h1><p class="text-gray-400 text-sm mb-6">Configure your API connection.</p>
155
- <div class="bg-surface border border-border rounded-xl p-6 card-hover">
156
- <div v-for="f in settingsFields" :key="f.key" class="mb-4">
157
- <label class="text-xs text-gray-500 uppercase tracking-wide">{{f.label}}</label>
158
- <input v-model="cfg[f.key]" :placeholder="f.placeholder" :type="f.type||'text'"
159
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 transition"/>
210
+ <div class="grid">
211
+ <section class="card">
212
+ <h2>Connection</h2>
213
+ <p class="sub">Saved in your browser so the dashboard can be reused between runs.</p>
214
+ <div class="form">
215
+ <div class="row">
216
+ <label>Base URL<input id="baseUrl" value="${esc(config.baseUrl)}"></label>
217
+ <label>API Prefix<input id="apiPrefix" value="${esc(config.apiPrefix)}"></label>
160
218
  </div>
161
- <div class="flex items-center gap-3 mt-4">
162
- <flow-btn variant="primary" @click="saveSettings" :loading="settingsLoading">Save & Test</flow-btn>
163
- <span v-if="settingsMsg" class="text-xs" :class="settingsMsgOk?'text-green-400':'text-red-400'">{{settingsMsg}}</span>
219
+ <label>API Key<input id="apiKey" value="${esc(config.apiKey)}" placeholder="vtx_..."></label>
220
+ <label>Admin Key<input id="adminKey" value="${esc(config.adminKey)}" placeholder="admin key"></label>
221
+ <label>Webhook Secret<input id="webhookSecret" value="${esc(config.webhookSecret)}" placeholder="secret"></label>
222
+ <div class="actions">
223
+ <button id="saveConfig">Save</button>
224
+ <button id="pingHealth" class="secondary">Health</button>
225
+ <button id="getScope" class="secondary">Current Scope</button>
226
+ <button id="adminConfig" class="secondary">Admin Config</button>
164
227
  </div>
165
228
  </div>
166
- </div>
167
- </div>
168
-
169
- <!-- ▸ QUICK ACTIONS ─────────────── -->
170
- <div v-if="currentView==='quick'">
171
- <div class="mb-4 flex items-center gap-3 flex-wrap">
172
- <h1 class="text-2xl font-bold text-white">Quick Actions</h1>
173
- <span class="text-xs text-gray-500 bg-surface border border-border rounded-full px-2 py-0.5">{{filteredQuickActions.length}} actions</span>
174
- </div>
175
- <div class="flex flex-wrap gap-2 mb-4">
176
- <button v-for="c in quickCategories" :key="c" @click="quickCat=c"
177
- class="text-xs px-3 py-1.5 rounded-full transition-all border"
178
- :class="quickCat===c?'bg-brand text-white border-brand':'bg-surface text-gray-400 border-border hover:border-gray-500'">{{c}}</button>
179
- </div>
180
- <input v-model="search" placeholder="Filter actions…" class="w-full bg-surface border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 mb-4 transition"/>
181
- <div class="space-y-2 stagger">
182
- <div v-for="a in filteredQuickActions" :key="a.id" class="bg-surface border border-border rounded-xl overflow-hidden transition-all" :class="a.open?'ring-1 ring-brand/30':''">
183
- <button @click="a.open=!a.open" class="w-full flex items-center gap-3 px-4 py-3 text-sm hover:bg-white/5 transition">
184
- <span class="text-[10px] font-mono font-bold rounded px-1.5 py-0.5" :class="methodColor(a.method)">{{a.method}}</span>
185
- <span class="font-medium text-white">{{a.title}}</span>
186
- <span class="text-[10px] text-gray-600 font-mono ml-1 hidden sm:inline">{{a.path}}</span>
187
- <span class="mdi ml-auto text-gray-500 transition-transform" :class="a.open?'mdi-chevron-up':'mdi-chevron-down'"></span>
188
- </button>
189
- <div v-if="a.open" class="px-4 pb-4 animate-fade-up">
190
- <div v-for="f in a.fields" :key="f.key" class="mt-2">
191
- <label class="text-[10px] text-gray-500 uppercase">{{f.label}}</label>
192
- <textarea v-if="f.type==='textarea'" v-model="a.values[f.key]" :placeholder="f.placeholder" rows="2"
193
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 resize-y"></textarea>
194
- <input v-else v-model="a.values[f.key]" :placeholder="f.placeholder" :type="f.type||'text'"
195
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50"/>
196
- </div>
197
- <div class="flex items-center gap-3 mt-3">
198
- <flow-btn variant="primary" size="sm" @click="runQuickAction(a)" :loading="a.loading">Execute</flow-btn>
199
- <span v-if="a.result" class="text-[10px] cursor-pointer hover:text-brand transition" :class="a.resultOk?'text-green-400':'text-red-400'" @click="openJsonViewer(tryJson(a.result)||a.result,a.timing)">
200
- {{a.resultOk?'Success':'Failed'}} <span class="text-gray-600">{{a.timing}}ms</span> — click to inspect
201
- </span>
202
- </div>
203
- <div v-if="a.result" class="mt-2 bg-bg rounded-lg p-3 border text-xs font-mono max-h-40 overflow-auto cursor-pointer hover:border-brand/30 transition"
204
- :class="a.resultOk?'border-green-900 text-green-300':'border-red-900 text-red-300'"
205
- @click="openJsonViewer(tryJson(a.result)||a.result,a.timing)">
206
- <div class="whitespace-pre-wrap">{{a.result.length>500?a.result.slice(0,500)+'\\n…click to expand':a.result}}</div>
207
- </div>
229
+ </section>
230
+
231
+ <section class="card">
232
+ <h2>API Keys</h2>
233
+ <p class="sub">Bootstrap new scopes with the admin key, then rotate the current key with the active bearer key.</p>
234
+ <div class="form">
235
+ <label>Scope Name<input id="scopeName" placeholder="Acme Marketing"></label>
236
+ <label>Settings JSON<textarea id="scopeSettings">{}</textarea></label>
237
+ <div class="actions">
238
+ <button id="issueApiKey">Issue API Key</button>
239
+ <button id="rotateApiKey" class="warn">Rotate Current Key</button>
208
240
  </div>
209
241
  </div>
210
- </div>
211
- </div>
212
-
213
- <!-- ORG SETUP FLOW ────────────── -->
214
- <div v-if="currentView==='flow-org'">
215
- <flow-header title="Organization Setup" desc="Create an organization, provision API keys, verify and rotate." :step="orgFlow.step" :steps="orgFlow.steps"></flow-header>
216
- <step-card v-if="orgFlow.step===0" title="Create Organization" subtitle="Provision a new tenant with API keys.">
217
- <text-field label="Org ID" v-model="orgFlow.orgId" placeholder="my-org-1"></text-field>
218
- <text-field label="Name" v-model="orgFlow.name" placeholder="Acme Corp"></text-field>
219
- <select-field label="Tier" v-model="orgFlow.tier" :options="['free','standard','premium','enterprise']"></select-field>
220
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="orgFlowCreate" :loading="orgFlow.loading">Create Organization</flow-btn></div>
221
- </step-card>
222
- <step-card v-if="orgFlow.step===1" title="API Key Generated" subtitle="Store this key securely — it won't be shown again.">
223
- <div class="flex items-center gap-2 bg-bg rounded-lg p-3 border border-green-900">
224
- <span class="mdi mdi-key text-green-400"></span>
225
- <code class="text-green-300 text-xs flex-1 break-all select-all">{{orgFlow.apiKey}}</code>
226
- <button @click="copyText(orgFlow.apiKey)" class="text-gray-500 hover:text-white transition" title="Copy"><span class="mdi mdi-content-copy"></span></button>
227
- </div>
228
- <kv-display v-if="orgFlow.orgData" :items="orgFlow.orgData" class="mt-3"></kv-display>
229
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="orgFlow.step=2">Verify →</flow-btn></div>
230
- </step-card>
231
- <step-card v-if="orgFlow.step===2" title="Verify Organization" subtitle="Confirm the org was created correctly.">
232
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="orgFlowVerify" :loading="orgFlow.loading">Verify</flow-btn></div>
233
- </step-card>
234
- <step-card v-if="orgFlow.step===3" title="Rotate API Key" subtitle="Generate a new key (the old one becomes invalid).">
235
- <kv-display v-if="orgFlow.verifyData" :items="orgFlow.verifyData" class="mb-3"></kv-display>
236
- <div v-if="orgFlow.newKey" class="flex items-center gap-2 bg-bg rounded-lg p-3 border border-amber-900 mt-2">
237
- <span class="mdi mdi-key text-amber-400"></span>
238
- <code class="text-amber-300 text-xs flex-1 break-all select-all">{{orgFlow.newKey}}</code>
239
- <button @click="copyText(orgFlow.newKey)" class="text-gray-500 hover:text-white transition"><span class="mdi mdi-content-copy"></span></button>
240
- </div>
241
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="orgFlowRotate" :loading="orgFlow.loading" variant="warn">Rotate Key</flow-btn></div>
242
- </step-card>
243
- <flow-result :result="orgFlow.lastResult"></flow-result>
244
- </div>
245
-
246
- <!-- ▸ DOCUMENT PIPELINE FLOW ────── -->
247
- <div v-if="currentView==='flow-docs'">
248
- <flow-header title="Document Pipeline" desc="Upload documents, query via RAG, chat with your data." :step="docFlow.step" :steps="docFlow.steps"></flow-header>
249
- <step-card v-if="docFlow.step===0" title="Select Organization" subtitle="Which org should own the documents?">
250
- <text-field label="Org ID" v-model="docFlow.orgId" placeholder="my-org-1"></text-field>
251
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="docFlow.step=1" :disabled="!docFlow.orgId">Next →</flow-btn></div>
252
- </step-card>
253
- <step-card v-if="docFlow.step===1" title="Upload Document" subtitle="Select a file or drag & drop anywhere on the page.">
254
- <div class="border-2 border-dashed rounded-xl p-8 text-center transition-all" :class="dragActive?'border-brand bg-brand/5':'border-border hover:border-gray-500'" @click="$refs.docFile&&$refs.docFile.click()">
255
- <span class="mdi mdi-cloud-upload text-4xl mb-2 block" :class="dragActive?'text-brand':'text-gray-500'"></span>
256
- <p class="text-sm text-gray-400">Click to browse or drag & drop</p>
257
- <p class="text-[10px] text-gray-600 mt-1">PDF, DOCX, TXT, CSV, JSON</p>
258
- </div>
259
- <input ref="docFile" id="docFileInput" type="file" class="hidden" @change="docFlowUpload">
260
- <kv-display v-if="docFlow.uploadResult" :items="docFlow.uploadResult" class="mt-3"></kv-display>
261
- <div class="flex flex-wrap gap-2 mt-4">
262
- <flow-btn @click="$refs.docFile&&$refs.docFile.click()" :loading="docFlow.loading">Upload</flow-btn>
263
- <flow-btn variant="secondary" @click="docFlow.step=2" v-if="docFlow.uploadResult">Query →</flow-btn>
264
- </div>
265
- </step-card>
266
- <step-card v-if="docFlow.step===2" title="RAG Query" subtitle="Ask questions about your uploaded documents.">
267
- <text-field label="Query" v-model="docFlow.query" placeholder="What are the key findings?"></text-field>
268
- <div class="grid grid-cols-2 gap-2">
269
- <select-field label="Model" v-model="docFlow.model" :options="['gemini-pro','gemini-pro-vision','text-bison']"></select-field>
270
- <select-field label="Strategy" v-model="docFlow.strategy" :options="['hybrid','semantic','keyword']"></select-field>
271
- </div>
272
- <div v-if="docFlow.answer" class="mt-3 bg-bg rounded-lg p-4 border border-border">
273
- <div class="text-xs text-gray-500 mb-1 flex items-center gap-2"><span class="mdi mdi-robot"></span> Answer</div>
274
- <div class="text-sm text-gray-300 whitespace-pre-wrap">{{docFlow.answer}}</div>
275
- </div>
276
- <div class="flex flex-wrap gap-2 mt-4">
277
- <flow-btn @click="docFlowQuery" :loading="docFlow.loading">Ask</flow-btn>
278
- <flow-btn variant="secondary" @click="docFlow.step=3">Data Chat →</flow-btn>
279
- </div>
280
- </step-card>
281
- <step-card v-if="docFlow.step===3" title="Data Chat" subtitle="Have a conversation with your documents.">
282
- <div class="bg-bg rounded-xl border border-border overflow-hidden">
283
- <div class="h-64 overflow-y-auto p-4 space-y-3" ref="chatScroll">
284
- <div v-if="!docFlow.chatHistory.length" class="text-center py-8 text-gray-600 text-sm">Start a conversation…</div>
285
- <div v-for="(m,i) in docFlow.chatHistory" :key="i" class="flex animate-fade-up" :class="m.role==='user'?'justify-end':'justify-start'">
286
- <div class="max-w-[80%] rounded-xl px-4 py-2 text-sm" :class="m.role==='user'?'bg-brand text-white':'bg-surface text-gray-300 border border-border'">{{m.text}}</div>
287
- </div>
288
- <div v-if="docFlow.loading" class="flex justify-start"><div class="bg-surface border border-border rounded-xl px-4 py-3"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div></div>
242
+ </section>
243
+
244
+ <section class="card">
245
+ <h2>Query And Search</h2>
246
+ <p class="sub">Use the active API key to run knowledge queries and discovery search.</p>
247
+ <div class="form">
248
+ <label>Query<textarea id="ragQuery">What is the latest summary of our indexed content?</textarea></label>
249
+ <div class="actions">
250
+ <button id="runQuery">Run Query</button>
289
251
  </div>
290
- <div class="border-t border-border p-3 flex gap-2">
291
- <input v-model="docFlow.chatInput" @keydown.enter="docFlowChat" placeholder="Ask a question…" class="flex-1 bg-transparent text-sm text-gray-300 outline-none placeholder-gray-600"/>
292
- <flow-btn size="sm" @click="docFlowChat" :loading="docFlow.loading" :disabled="!docFlow.chatInput.trim()"><span class="mdi mdi-send"></span></flow-btn>
252
+ <div class="row">
253
+ <label>Search Query<input id="searchQuery" value="content marketing trends 2026"></label>
254
+ <label>Search Type<input id="searchType" value="organic"></label>
293
255
  </div>
294
- </div>
295
- </step-card>
296
- <flow-result :result="docFlow.lastResult"></flow-result>
297
- </div>
298
-
299
- <!-- ▸ CONTENT GENERATION FLOW ───── -->
300
- <div v-if="currentView==='flow-gen'">
301
- <flow-header title="Content Generation" desc="Generate videos or images with AI, track job progress." :step="genFlow.step" :steps="genFlow.steps"></flow-header>
302
- <step-card v-if="genFlow.step===0" title="Select Type" subtitle="What do you want to generate?">
303
- <text-field label="Org ID" v-model="genFlow.orgId" placeholder="my-org-1"></text-field>
304
- <div class="grid grid-cols-2 gap-3 mt-3">
305
- <button v-for="t in [{v:'video',icon:'mdi-video',label:'Video'},{v:'image',icon:'mdi-image',label:'Image'}]" :key="t.v"
306
- @click="genFlow.type=t.v" class="p-4 rounded-xl border-2 text-center transition-all card-hover"
307
- :class="genFlow.type===t.v?'border-brand bg-brand/10 text-brand':'border-border text-gray-400 hover:border-gray-500'">
308
- <span class="mdi text-3xl block mb-1" :class="t.icon"></span><span class="text-sm font-medium">{{t.label}}</span>
309
- </button>
310
- </div>
311
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="genFlow.step=1" :disabled="!genFlow.type||!genFlow.orgId">Configure →</flow-btn></div>
312
- </step-card>
313
- <step-card v-if="genFlow.step===1" title="Configure" subtitle="Describe what you want to generate.">
314
- <text-area label="Prompt" v-model="genFlow.prompt" placeholder="A cinematic drone flyover of a futuristic city…"></text-area>
315
- <div class="grid grid-cols-2 gap-2">
316
- <select-field label="Aspect Ratio" v-model="genFlow.aspectRatio" :options="['16:9','9:16','1:1','4:3']"></select-field>
317
- <text-field v-if="genFlow.type==='video'" label="Duration (s)" v-model="genFlow.duration" placeholder="8"></text-field>
318
- <text-field v-if="genFlow.type==='image'" label="Count" v-model="genFlow.numImages" placeholder="1"></text-field>
319
- </div>
320
- <text-field label="Style (optional)" v-model="genFlow.style" placeholder="photorealistic, cinematic"></text-field>
321
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="genFlowSubmit" :loading="genFlow.loading">Generate</flow-btn></div>
322
- </step-card>
323
- <step-card v-if="genFlow.step===2" title="Track Job" subtitle="Monitor generation progress.">
324
- <div class="bg-bg rounded-lg p-4 border border-border mb-3">
325
- <div class="flex items-center gap-3 mb-2">
326
- <span class="text-xs text-gray-500">Job ID</span>
327
- <code class="text-xs text-gray-300 select-all">{{genFlow.jobId}}</code>
328
- <button @click="copyText(genFlow.jobId)" class="text-gray-600 hover:text-white transition"><span class="mdi mdi-content-copy text-xs"></span></button>
256
+ <div class="row">
257
+ <label>Max Results<input id="searchNum" type="number" min="1" max="25" value="5"></label>
258
+ <div></div>
329
259
  </div>
330
- <div class="flex items-center gap-3">
331
- <span class="text-xs text-gray-500">Status</span>
332
- <span class="text-xs font-bold rounded-full px-2 py-0.5" :class="genFlow.jobStatus==='completed'?'bg-green-900/40 text-green-400':genFlow.jobStatus==='failed'?'bg-red-900/40 text-red-400':'bg-amber-900/40 text-amber-400'">{{genFlow.jobStatus}}</span>
333
- <span v-if="isPolling('gen')" class="text-[10px] text-brand poll-pulse">● auto-polling</span>
260
+ <div class="actions">
261
+ <button id="runSearch" class="secondary">Run Search</button>
334
262
  </div>
335
263
  </div>
336
- <kv-display v-if="genFlow.jobResult" :items="genFlow.jobResult" class="mt-2"></kv-display>
337
- <div class="flex flex-wrap gap-2 mt-4">
338
- <flow-btn @click="genFlowPoll" :loading="genFlow.loading" size="sm">Poll Now</flow-btn>
339
- <flow-btn v-if="!isPolling('gen')" variant="secondary" size="sm" @click="startPoll('gen',genFlowPoll,3000)"><span class="mdi mdi-sync mr-1"></span>Auto-Poll</flow-btn>
340
- <flow-btn v-else variant="warn" size="sm" @click="stopPoll('gen')"><span class="mdi mdi-stop mr-1"></span>Stop</flow-btn>
341
- </div>
342
- </step-card>
343
- <flow-result :result="genFlow.lastResult"></flow-result>
344
- </div>
345
-
346
- <!-- ▸ BLUEPRINT→DOCUMENT FLOW ───── -->
347
- <div v-if="currentView==='flow-blueprint'">
348
- <flow-header title="Blueprint → Document" desc="Create blueprints, generate documents, AI-fill sections, manage workflow." :step="bpFlow.step" :steps="bpFlow.steps"></flow-header>
349
- <step-card v-if="bpFlow.step===0" title="Create Blueprint" subtitle="Define a reusable document template.">
350
- <text-field label="Org ID" v-model="bpFlow.orgId" placeholder="my-org-1"></text-field>
351
- <text-field label="Blueprint Name" v-model="bpFlow.name" placeholder="HR Policy Template"></text-field>
352
- <div class="grid grid-cols-2 gap-2">
353
- <text-field label="Category" v-model="bpFlow.category" placeholder="HR"></text-field>
354
- <text-field label="Description" v-model="bpFlow.description" placeholder="Standard HR policy doc"></text-field>
355
- </div>
356
- <text-area label="Sections JSON" v-model="bpFlow.sectionsJson" placeholder='[{"title":"Introduction","instructions":"Write an intro"}]'></text-area>
357
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="bpFlowCreate" :loading="bpFlow.loading">Create Blueprint</flow-btn></div>
358
- </step-card>
359
- <step-card v-if="bpFlow.step===1" title="Publish Blueprint" subtitle="Make the blueprint available for document creation.">
360
- <div class="bg-bg rounded-lg p-3 border border-border text-sm text-gray-400"><span class="mdi mdi-information text-brand mr-1"></span>Blueprint <strong class="text-white">{{bpFlow.blueprintId}}</strong> ready to publish.</div>
361
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="bpFlowPublish" :loading="bpFlow.loading">Publish</flow-btn></div>
362
- </step-card>
363
- <step-card v-if="bpFlow.step===2" title="Create Document" subtitle="Generate a new document from this blueprint.">
364
- <text-field label="Title" v-model="bpFlow.docTitle" placeholder="Q1 Report 2026"></text-field>
365
- <text-field label="Description" v-model="bpFlow.docDesc" placeholder="Quarterly report"></text-field>
366
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="bpFlowCreateDoc" :loading="bpFlow.loading">Create Document</flow-btn></div>
367
- </step-card>
368
- <step-card v-if="bpFlow.step===3" title="AI Generate Sections" subtitle="Fill sections with AI-generated content.">
369
- <div v-if="bpFlow.docSections.length" class="space-y-2 mb-3">
370
- <div v-for="sec in bpFlow.docSections" :key="sec.id" class="flex items-center gap-3 bg-bg rounded-lg p-3 border border-border">
371
- <span class="mdi" :class="sec.generated?'mdi-check-circle text-green-400':'mdi-file-outline text-gray-500'"></span>
372
- <span class="text-sm text-white flex-1">{{sec.title||sec.id}}</span>
373
- <flow-btn size="sm" @click="bpFlowGenSection(sec)" :loading="sec.loading" :disabled="sec.generated">{{sec.generated?'Done':'Generate'}}</flow-btn>
264
+ </section>
265
+
266
+ <section class="card">
267
+ <h2>Documents</h2>
268
+ <p class="sub">Upload knowledge files into the current API key scope.</p>
269
+ <div class="form">
270
+ <label>File<input id="documentFile" type="file"></label>
271
+ <div class="actions">
272
+ <button id="uploadDocument">Upload Document</button>
374
273
  </div>
375
274
  </div>
376
- <div class="border-t border-border pt-3 mt-3">
377
- <div class="text-xs text-gray-500 mb-2">Or generate by section ID manually:</div>
378
- <div class="flex gap-2">
379
- <text-field label="Section ID" v-model="bpFlow.manualSectionId" placeholder="sec-1" class="flex-1"></text-field>
380
- <text-field label="Context JSON" v-model="bpFlow.genContext" placeholder="{}" class="flex-1"></text-field>
275
+ </section>
276
+
277
+ <section class="card">
278
+ <h2>Projects</h2>
279
+ <p class="sub">Create projects, list them, and chat against a selected project.</p>
280
+ <div class="form">
281
+ <div class="row">
282
+ <label>Project Name<input id="projectName" value="Launch Campaign"></label>
283
+ <label>Project ID<input id="projectId" placeholder="Paste an existing project id"></label>
381
284
  </div>
382
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn size="sm" @click="bpFlowGenManual" :loading="bpFlow.loading">Generate</flow-btn><flow-btn variant="secondary" size="sm" @click="bpFlow.step=4">Workflow →</flow-btn></div>
383
- </div>
384
- </step-card>
385
- <step-card v-if="bpFlow.step===4" title="Workflow Action" subtitle="Submit the document for review or approval.">
386
- <select-field label="Action" v-model="bpFlow.wfAction" :options="['submit','approve','reject','request_changes','publish']"></select-field>
387
- <text-area label="Comment" v-model="bpFlow.wfComment" placeholder="Looks good, approved."></text-area>
388
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="bpFlowWorkflow" :loading="bpFlow.loading">Execute Workflow</flow-btn></div>
389
- </step-card>
390
- <flow-result :result="bpFlow.lastResult"></flow-result>
391
- </div>
392
-
393
- <!-- ▸ AI AGENTS FLOW ────────────── -->
394
- <div v-if="currentView==='flow-agents'">
395
- <flow-header title="AI Agents" desc="Create custom agents and chat in real-time." :step="agentFlow.step" :steps="agentFlow.steps"></flow-header>
396
- <step-card v-if="agentFlow.step===0" title="Create Agent" subtitle="Define a custom AI agent with specific tools and instructions.">
397
- <text-field label="Org ID" v-model="agentFlow.orgId" placeholder="my-org-1"></text-field>
398
- <text-field label="Agent Name" v-model="agentFlow.name" placeholder="HR Assistant"></text-field>
399
- <text-area label="Instructions" v-model="agentFlow.instructions" placeholder="You are an HR assistant…"></text-area>
400
- <text-field label="Tools (JSON)" v-model="agentFlow.tools" placeholder='["rag_search","web_search"]'></text-field>
401
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="agentFlowCreate" :loading="agentFlow.loading">Create Agent</flow-btn></div>
402
- </step-card>
403
- <step-card v-if="agentFlow.step===1" title="Chat with Agent" subtitle="Talk to your custom agent.">
404
- <select-field label="Agent Type" v-model="agentFlow.agentType" :options="['auto','custom','rag','data']"></select-field>
405
- <div class="bg-bg rounded-xl border border-border overflow-hidden mt-3">
406
- <div class="h-72 overflow-y-auto p-4 space-y-3" ref="agentChatScroll">
407
- <div v-if="!agentFlow.chatHistory.length" class="text-center py-12 text-gray-600 text-sm"><span class="mdi mdi-robot text-3xl block mb-2"></span>Start a conversation with your agent…</div>
408
- <div v-for="(m,i) in agentFlow.chatHistory" :key="i" class="flex animate-fade-up" :class="m.role==='user'?'justify-end':'justify-start'">
409
- <div class="max-w-[80%] rounded-xl px-4 py-2 text-sm" :class="m.role==='user'?'bg-brand text-white':'bg-surface text-gray-300 border border-border'">{{m.text}}</div>
410
- </div>
411
- <div v-if="agentFlow.loading" class="flex justify-start"><div class="bg-surface border border-border rounded-xl px-4 py-3"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div></div>
285
+ <label>Description<textarea id="projectDescription">Campaign workspace for API-key-scoped content operations.</textarea></label>
286
+ <div class="actions">
287
+ <button id="createProject">Create Project</button>
288
+ <button id="listProjects" class="secondary">List Projects</button>
412
289
  </div>
413
- <div class="border-t border-border p-3 flex gap-2">
414
- <input v-model="agentFlow.chatInput" @keydown.enter="agentFlowQuery" placeholder="Type a message…" class="flex-1 bg-transparent text-sm text-gray-300 outline-none placeholder-gray-600"/>
415
- <flow-btn size="sm" @click="agentFlowQuery" :loading="agentFlow.loading" :disabled="!agentFlow.chatInput.trim()"><span class="mdi mdi-send"></span></flow-btn>
290
+ <label>Chat Message<textarea id="projectMessage">Draft a short update using the saved project sources.</textarea></label>
291
+ <label>User ID<input id="projectUserId" value="sdk-dashboard"></label>
292
+ <div class="actions">
293
+ <button id="chatProject">Chat Project</button>
416
294
  </div>
417
295
  </div>
418
- </step-card>
419
- <flow-result :result="agentFlow.lastResult"></flow-result>
420
- </div>
421
-
422
- <!-- JIRA INTEGRATION FLOW ─────── -->
423
- <div v-if="currentView==='flow-jira'">
424
- <flow-header title="Jira Integration" desc="Connect, verify, sync projects and manage your Jira integration." :step="jiraFlow.step" :steps="jiraFlow.steps"></flow-header>
425
- <step-card v-if="jiraFlow.step===0" title="Connect Jira" subtitle="Enter your Atlassian credentials to link Jira.">
426
- <text-field label="Org ID" v-model="jiraFlow.orgId" placeholder="my-org-1"></text-field>
427
- <text-field label="Site URL" v-model="jiraFlow.siteUrl" placeholder="https://yoursite.atlassian.net"></text-field>
428
- <text-field label="Email" v-model="jiraFlow.email" placeholder="user@company.com"></text-field>
429
- <text-field label="API Token" v-model="jiraFlow.apiToken" placeholder="Your Jira API token" type="password"></text-field>
430
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="jiraFlowConnect" :loading="jiraFlow.loading">Connect</flow-btn></div>
431
- </step-card>
432
- <step-card v-if="jiraFlow.step===1" title="Connection Status" subtitle="Verify your Jira connection.">
433
- <kv-display v-if="jiraFlow.statusData" :items="jiraFlow.statusData"></kv-display>
434
- <div v-else class="skeleton h-20 w-full"></div>
435
- <div class="flex flex-wrap gap-2 mt-4">
436
- <flow-btn @click="jiraFlowStatus" :loading="jiraFlow.loading" size="sm">Refresh</flow-btn>
437
- <flow-btn variant="secondary" @click="jiraFlow.step=2">Sync →</flow-btn>
438
- </div>
439
- </step-card>
440
- <step-card v-if="jiraFlow.step===2" title="Sync Projects" subtitle="Choose which Jira projects to synchronize.">
441
- <text-field label="Project Keys (comma-separated)" v-model="jiraFlow.projectKeys" placeholder="PROJ, TEAM, OPS"></text-field>
442
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="jiraFlowSync" :loading="jiraFlow.loading">Start Sync</flow-btn></div>
443
- </step-card>
444
- <step-card v-if="jiraFlow.step===3" title="Manage" subtitle="Manage your Jira integration.">
445
- <div class="bg-bg rounded-lg p-3 border border-green-900 text-sm text-green-400 mb-3"><span class="mdi mdi-check-circle mr-1"></span>Jira is connected and syncing.</div>
446
- <div class="flex flex-wrap gap-2 mt-4">
447
- <flow-btn @click="jiraFlowStatus" :loading="jiraFlow.loading" size="sm">Refresh Status</flow-btn>
448
- <flow-btn variant="danger" size="sm" @click="confirmAction('Disconnect Jira','This will remove the Jira integration. Are you sure?',jiraFlowDisconnect)">Disconnect</flow-btn>
449
- </div>
450
- </step-card>
451
- <flow-result :result="jiraFlow.lastResult"></flow-result>
452
- </div>
453
-
454
- <!-- ▸ WEB SCRAPING FLOW ─────────── -->
455
- <div v-if="currentView==='flow-scrape'">
456
- <flow-header title="Web Scraping" desc="Submit URLs and monitor scraping progress." :step="scrapeFlow.step" :steps="scrapeFlow.steps"></flow-header>
457
- <step-card v-if="scrapeFlow.step===0" title="Submit URLs" subtitle="Enter URLs to scrape (one per line).">
458
- <div class="flex justify-end mb-1"><button @click="scrapeFlowConfig" class="text-[10px] text-gray-500 hover:text-brand transition"><span class="mdi mdi-cog mr-1"></span>View Config</button></div>
459
- <kv-display v-if="scrapeFlow.configData" :items="scrapeFlow.configData" class="mb-3"></kv-display>
460
- <text-area label="URLs (one per line)" v-model="scrapeFlow.urls" placeholder="https://example.com\nhttps://docs.example.com"></text-area>
461
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="scrapeFlowSubmit" :loading="scrapeFlow.loading">Submit</flow-btn></div>
462
- </step-card>
463
- <step-card v-if="scrapeFlow.step===1" title="Monitor Progress" subtitle="Track the scraping task.">
464
- <div class="bg-bg rounded-lg p-4 border border-border mb-3">
465
- <div class="flex items-center gap-3 mb-2">
466
- <span class="text-xs text-gray-500">Task ID</span><code class="text-xs text-gray-300 select-all">{{scrapeFlow.taskId}}</code>
467
- <button @click="copyText(scrapeFlow.taskId)" class="text-gray-600 hover:text-white transition"><span class="mdi mdi-content-copy text-xs"></span></button>
296
+ </section>
297
+
298
+ <section class="card">
299
+ <h2>Generation</h2>
300
+ <p class="sub">Start video, image, or music jobs and poll by job id.</p>
301
+ <div class="form">
302
+ <div class="row">
303
+ <label>Type
304
+ <select id="generationType">
305
+ <option value="video">video</option>
306
+ <option value="image">image</option>
307
+ <option value="music">music</option>
308
+ </select>
309
+ </label>
310
+ <label>Job ID<input id="jobId" placeholder="Returned job id"></label>
468
311
  </div>
469
- <div class="flex items-center gap-3">
470
- <span class="text-xs text-gray-500">Status</span>
471
- <span class="text-xs font-bold rounded-full px-2 py-0.5" :class="scrapeFlow.status==='completed'?'bg-green-900/40 text-green-400':scrapeFlow.status==='failed'?'bg-red-900/40 text-red-400':'bg-amber-900/40 text-amber-400'">{{scrapeFlow.status}}</span>
472
- <span v-if="isPolling('scrape')" class="text-[10px] text-brand poll-pulse">● auto-polling</span>
312
+ <label>Prompt Or Genre<textarea id="generationPrompt">A cinematic product launch teaser with crisp motion graphics.</textarea></label>
313
+ <div class="row">
314
+ <label>Mood / Style<input id="generationFlavor" value="uplifting"></label>
315
+ <label>Duration / Count<input id="generationAmount" value="8"></label>
316
+ </div>
317
+ <div class="actions">
318
+ <button id="startGeneration">Start Job</button>
319
+ <button id="getJob" class="secondary">Get Job</button>
473
320
  </div>
474
321
  </div>
475
- <kv-display v-if="scrapeFlow.taskResult" :items="scrapeFlow.taskResult"></kv-display>
476
- <div class="flex flex-wrap gap-2 mt-4">
477
- <flow-btn @click="scrapeFlowPoll" :loading="scrapeFlow.loading" size="sm">Poll Now</flow-btn>
478
- <flow-btn v-if="!isPolling('scrape')" variant="secondary" size="sm" @click="startPoll('scrape',scrapeFlowPoll,3000)"><span class="mdi mdi-sync mr-1"></span>Auto-Poll</flow-btn>
479
- <flow-btn v-else variant="warn" size="sm" @click="stopPoll('scrape')"><span class="mdi mdi-stop mr-1"></span>Stop</flow-btn>
480
- </div>
481
- </step-card>
482
- <flow-result :result="scrapeFlow.lastResult"></flow-result>
483
- </div>
484
-
485
- <!-- ANALYTICS & ML FLOW ─────── -->
486
- <div v-if="currentView==='flow-analytics'">
487
- <flow-header title="Analytics & ML" desc="Train models, make predictions, run forecasts." :step="mlFlow.step" :steps="mlFlow.steps"></flow-header>
488
- <step-card v-if="mlFlow.step===0" title="Train Model" subtitle="Provide training data and configure the model.">
489
- <text-field label="Org ID" v-model="mlFlow.orgId" placeholder="my-org-1"></text-field>
490
- <text-field label="Target Column" v-model="mlFlow.target" placeholder="churn"></text-field>
491
- <text-field label="Feature Columns (JSON)" v-model="mlFlow.features" placeholder='["tenure","monthly_charges"]'></text-field>
492
- <text-area label="Training Rows (JSON)" v-model="mlFlow.rows" placeholder='[{"tenure":12,"monthly_charges":50,"churn":0}]'></text-area>
493
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="mlFlowTrain" :loading="mlFlow.loading">Train</flow-btn></div>
494
- </step-card>
495
- <step-card v-if="mlFlow.step===1" title="Predict" subtitle="Run predictions using the trained model.">
496
- <div v-if="mlFlow.modelId" class="bg-bg rounded-lg p-3 border border-green-900 text-sm text-green-400 mb-3"><span class="mdi mdi-check-circle mr-1"></span>Model: <code>{{mlFlow.modelId}}</code></div>
497
- <text-area label="Instances (JSON)" v-model="mlFlow.instances" placeholder='[{"tenure":24,"monthly_charges":80}]'></text-area>
498
- <div class="flex flex-wrap gap-2 mt-4">
499
- <flow-btn @click="mlFlowPredict" :loading="mlFlow.loading">Predict</flow-btn>
500
- <flow-btn variant="secondary" @click="mlFlow.step=2">Forecast →</flow-btn>
501
- </div>
502
- </step-card>
503
- <step-card v-if="mlFlow.step===2" title="Forecast" subtitle="Generate time-series forecasts.">
504
- <text-field label="Time Series (JSON)" v-model="mlFlow.series" placeholder="[10,20,30,40,50]"></text-field>
505
- <text-field label="Horizon (days)" v-model="mlFlow.horizon" placeholder="30"></text-field>
506
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="mlFlowForecast" :loading="mlFlow.loading">Forecast</flow-btn></div>
507
- </step-card>
508
- <flow-result :result="mlFlow.lastResult"></flow-result>
509
- </div>
510
-
511
- <!-- ▸ AUDIO FLOW ────────────────── -->
512
- <div v-if="currentView==='flow-audio'">
513
- <flow-header title="Audio Processing" desc="Transcribe audio and synthesize speech." :step="audioFlow.step" :steps="audioFlow.steps"></flow-header>
514
- <step-card v-if="audioFlow.step===0" title="Transcribe" subtitle="Convert audio to text.">
515
- <text-field label="Org ID" v-model="audioFlow.orgId" placeholder="my-org-1"></text-field>
516
- <text-area label="Audio Text / Base64" v-model="audioFlow.audioText" placeholder="Audio content or base64…"></text-area>
517
- <select-field label="Language" v-model="audioFlow.lang" :options="['en-US','en-GB','es-ES','fr-FR','de-DE','ja-JP','zh-CN']"></select-field>
518
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="audioFlowTranscribe" :loading="audioFlow.loading">Transcribe</flow-btn></div>
519
- </step-card>
520
- <step-card v-if="audioFlow.step===1" title="Synthesize Speech" subtitle="Convert text to audio.">
521
- <text-area label="Text" v-model="audioFlow.synthText" placeholder="Hello, welcome to Hivemind."></text-area>
522
- <text-field label="Voice" v-model="audioFlow.voice" placeholder="en-US-Neural2-J"></text-field>
523
- <div class="flex flex-wrap gap-2 mt-4"><flow-btn @click="audioFlowSynthesize" :loading="audioFlow.loading">Synthesize</flow-btn></div>
524
- </step-card>
525
- <flow-result :result="audioFlow.lastResult"></flow-result>
526
- </div>
527
-
528
- <!-- ▸ BILLING & AUDIT ───────────── -->
529
- <div v-if="currentView==='flow-billing'">
530
- <h1 class="text-2xl font-bold text-white mb-1">Billing & Audit</h1><p class="text-gray-400 text-sm mb-6">View usage, invoices and audit logs.</p>
531
- <text-field label="Org ID" v-model="billingFlow.orgId" placeholder="my-org-1"></text-field>
532
- <text-field label="Month (optional)" v-model="billingFlow.month" placeholder="2026-03"></text-field>
533
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4">
534
- <div class="bg-surface border border-border rounded-xl p-4 card-hover">
535
- <div class="flex items-center gap-2 mb-3"><span class="mdi mdi-chart-bar text-blue-400"></span><span class="font-semibold text-white text-sm">Usage</span></div>
536
- <flow-btn size="sm" @click="billingFlowUsage" :loading="billingFlow.loadingU" class="mb-2">Fetch</flow-btn>
537
- <kv-display v-if="billingFlow.usageData" :items="billingFlow.usageData"></kv-display>
538
- <div v-else-if="billingFlow.loadingU" class="skeleton h-16 w-full mt-2"></div>
539
- </div>
540
- <div class="bg-surface border border-border rounded-xl p-4 card-hover">
541
- <div class="flex items-center gap-2 mb-3"><span class="mdi mdi-receipt text-green-400"></span><span class="font-semibold text-white text-sm">Invoice</span></div>
542
- <flow-btn size="sm" @click="billingFlowInvoice" :loading="billingFlow.loadingI" class="mb-2">Fetch</flow-btn>
543
- <kv-display v-if="billingFlow.invoiceData" :items="billingFlow.invoiceData"></kv-display>
544
- <div v-else-if="billingFlow.loadingI" class="skeleton h-16 w-full mt-2"></div>
322
+ </section>
323
+
324
+ <section class="card">
325
+ <h2>Scraping</h2>
326
+ <p class="sub">Submit public URLs for scraping and then inspect the task status.</p>
327
+ <div class="form">
328
+ <label>URLs<textarea id="scrapeUrls">https://example.com
329
+ https://www.openai.com</textarea></label>
330
+ <label>Task ID<input id="scrapeTaskId" placeholder="Returned task id"></label>
331
+ <div class="actions">
332
+ <button id="submitScrape">Submit Scrape</button>
333
+ <button id="getScrapeStatus" class="secondary">Get Status</button>
334
+ </div>
545
335
  </div>
546
- <div class="bg-surface border border-border rounded-xl p-4 card-hover">
547
- <div class="flex items-center gap-2 mb-3"><span class="mdi mdi-shield-check text-amber-400"></span><span class="font-semibold text-white text-sm">Audit Logs</span></div>
548
- <flow-btn size="sm" @click="billingFlowAudit" :loading="billingFlow.loadingA" class="mb-2">Fetch</flow-btn>
549
- <div v-if="billingFlow.auditData" class="bg-bg rounded-lg p-3 border border-border text-xs font-mono max-h-40 overflow-auto text-gray-300 cursor-pointer" @click="openJsonViewer(billingFlow.auditData)">{{JSON.stringify(billingFlow.auditData,null,2).slice(0,300)}}…</div>
550
- <div v-else-if="billingFlow.loadingA" class="skeleton h-16 w-full mt-2"></div>
336
+ </section>
337
+
338
+ <section class="card">
339
+ <h2>Admin Quick Actions</h2>
340
+ <p class="sub">Small operational shortcuts for the remaining admin surface.</p>
341
+ <div class="actions">
342
+ <button id="getRateLimits">Rate Limits</button>
343
+ <button id="getJobs" class="secondary">Jobs</button>
344
+ <button id="runSchedules" class="secondary">Run Due Schedules</button>
345
+ <button id="getAudit" class="secondary">Admin Audit</button>
551
346
  </div>
552
- </div>
553
- </div>
554
-
555
- <!-- ▸ ADMIN DASHBOARD ───────────── -->
556
- <div v-if="currentView==='flow-admin'">
557
- <div class="flex items-center gap-3 mb-6">
558
- <h1 class="text-2xl font-bold text-white">Admin Dashboard</h1>
559
- <flow-btn size="sm" variant="secondary" @click="adminWidgets.forEach(w=>adminFetch(w))"><span class="mdi mdi-refresh mr-1"></span>Refresh All</flow-btn>
560
- </div>
561
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-6 stagger">
562
- <div v-for="w in adminWidgets" :key="w.id" class="bg-surface border border-border rounded-xl p-4 card-hover">
563
- <div class="flex items-center gap-2 mb-3">
564
- <span class="mdi text-lg" :class="w.icon+' '+w.color"></span>
565
- <span class="font-semibold text-white text-sm">{{w.title}}</span>
566
- <button @click="adminFetch(w)" class="ml-auto text-gray-600 hover:text-white transition"><span class="mdi mdi-refresh text-xs" :class="w.loading?'mdi-spin':''"></span></button>
347
+ </section>
348
+
349
+ <section class="card">
350
+ <h2>Manual Request</h2>
351
+ <p class="sub">Fallback runner for any current endpoint when you want to inspect the raw envelope.</p>
352
+ <div class="form">
353
+ <div class="row">
354
+ <label>Method
355
+ <select id="manualMethod">
356
+ <option>GET</option>
357
+ <option>POST</option>
358
+ <option>PUT</option>
359
+ <option>PATCH</option>
360
+ <option>DELETE</option>
361
+ </select>
362
+ </label>
363
+ <label>Auth
364
+ <select id="manualAuth">
365
+ <option value="api">api</option>
366
+ <option value="admin">admin</option>
367
+ <option value="webhook">webhook</option>
368
+ <option value="none">none</option>
369
+ </select>
370
+ </label>
567
371
  </div>
568
- <div v-if="w.loading&&!w.data" class="skeleton h-16 w-full"></div>
569
- <div v-else-if="w.data" class="bg-bg rounded-lg p-3 border border-border text-xs font-mono max-h-32 overflow-auto text-gray-300 cursor-pointer hover:border-brand/30 transition" @click="openJsonViewer(w.data)">
570
- {{JSON.stringify(w.data,null,2).slice(0,200)}}
372
+ <label>Path<input id="manualPath" value="/query"></label>
373
+ <label>JSON Body<textarea id="manualBody">{"query":"Hello from the SDK dashboard"}</textarea></label>
374
+ <div class="actions">
375
+ <button id="sendManual">Send Request</button>
571
376
  </div>
572
- <div v-else class="text-xs text-gray-600">Click refresh to load</div>
573
377
  </div>
574
- </div>
575
- <h2 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Admin Actions</h2>
576
- <div class="space-y-2 stagger">
577
- <div v-for="act in adminActions" :key="act.id" class="bg-surface border border-border rounded-xl overflow-hidden transition-all" :class="act.open?'ring-1 ring-brand/30':''">
578
- <button @click="act.open=!act.open" class="w-full flex items-center gap-3 px-4 py-3 text-sm hover:bg-white/5 transition">
579
- <span class="mdi" :class="act.icon+' text-brand'"></span>
580
- <span class="font-medium text-white">{{act.title}}</span>
581
- <span class="mdi ml-auto text-gray-500 transition-transform" :class="act.open?'mdi-chevron-up':'mdi-chevron-down'"></span>
582
- </button>
583
- <div v-if="act.open" class="px-4 pb-4 animate-fade-up">
584
- <div v-for="f in act.fields" :key="f.key" class="mt-2">
585
- <label class="text-[10px] text-gray-500 uppercase">{{f.label}}</label>
586
- <input v-model="act.values[f.key]" :placeholder="f.placeholder" class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50"/>
587
- </div>
588
- <div class="flex items-center gap-3 mt-3">
589
- <flow-btn size="sm" @click="adminRunAction(act)" :loading="act.loading">Execute</flow-btn>
590
- <span v-if="act.result" class="text-[10px] cursor-pointer hover:text-brand transition" :class="act.resultOk?'text-green-400':'text-red-400'" @click="openJsonViewer(tryJson(act.result),act.timing)">
591
- {{act.resultOk?'Success':'Failed'}} — click to inspect
592
- </span>
593
- </div>
594
- <div v-if="act.result" class="mt-2 bg-bg rounded-lg p-3 border text-xs font-mono max-h-32 overflow-auto cursor-pointer hover:border-brand/30 transition"
595
- :class="act.resultOk?'border-green-900 text-green-300':'border-red-900 text-red-300'"
596
- @click="openJsonViewer(tryJson(act.result),act.timing)">{{act.result}}</div>
597
- </div>
598
- </div>
599
- </div>
600
- </div>
601
-
602
- </div><!-- /animate-slide-up -->
603
- </main>
604
- </div><!-- /main area -->
605
-
606
- <!-- ═══════ COMMAND PALETTE ═══════ -->
607
- <div v-if="cmdOpen" class="fixed inset-0 cmd-backdrop z-50 flex items-start justify-center pt-[12vh]" @mousedown.self="cmdOpen=false">
608
- <div class="w-full max-w-lg glass rounded-2xl shadow-2xl overflow-hidden animate-scale">
609
- <div class="flex items-center gap-3 px-4 py-3 border-b border-border">
610
- <span class="mdi mdi-magnify text-gray-500"></span>
611
- <input v-model="cmdQ" placeholder="Search flows, actions, settings…" autofocus
612
- class="flex-1 bg-transparent text-white placeholder-gray-500 outline-none text-sm"
613
- @keydown.down.prevent="cmdI=Math.min(cmdI+1,cmdResults.length-1)"
614
- @keydown.up.prevent="cmdI=Math.max(cmdI-1,0)"
615
- @keydown.enter.prevent="cmdExec()"
616
- @keydown.esc="cmdOpen=false">
617
- <kbd class="text-[10px] bg-bg px-1.5 py-0.5 rounded border border-border text-gray-500">ESC</kbd>
618
- </div>
619
- <div class="max-h-[320px] overflow-y-auto py-1">
620
- <div v-if="!cmdResults.length" class="px-4 py-8 text-center text-gray-500 text-sm">No results</div>
621
- <button v-for="(r,i) in cmdResults" :key="r.id" @click="cmdExec(r)" @mouseenter="cmdI=i"
622
- class="w-full flex items-center gap-3 px-4 py-2.5 text-sm transition-colors"
623
- :class="i===cmdI?'bg-brand/20 text-white':'text-gray-400 hover:bg-white/5'">
624
- <span class="mdi" :class="r.icon"></span>
625
- <div class="flex-1 text-left"><div class="text-sm">{{r.label}}</div><div class="text-[10px] text-gray-500">{{r.hint}}</div></div>
626
- <span class="text-[10px] text-gray-600 bg-bg px-1.5 py-0.5 rounded">{{r.type}}</span>
627
- </button>
628
- </div>
629
- </div>
630
- </div>
631
-
632
- <!-- ═══════ CONFIRM MODAL ═══════ -->
633
- <div v-if="cfmModal.open" class="fixed inset-0 cmd-backdrop z-50 flex items-center justify-center" @mousedown.self="cfmModal.open=false">
634
- <div class="glass rounded-2xl p-6 max-w-sm w-full animate-scale shadow-2xl">
635
- <div class="flex items-center gap-3 mb-4">
636
- <div class="w-10 h-10 rounded-full bg-red-500/20 flex items-center justify-center"><span class="mdi mdi-alert text-red-400 text-xl"></span></div>
637
- <h3 class="text-lg font-bold text-white">{{cfmModal.title}}</h3>
638
- </div>
639
- <p class="text-sm text-gray-400 mb-6">{{cfmModal.message}}</p>
640
- <div class="flex gap-3 justify-end">
641
- <flow-btn variant="secondary" @click="cfmModal.open=false">Cancel</flow-btn>
642
- <flow-btn variant="danger" @click="cfmModal.fn();cfmModal.open=false">{{cfmModal.btn}}</flow-btn>
643
- </div>
644
- </div>
645
- </div>
646
-
647
- <!-- ═══════ JSON VIEWER MODAL ═══════ -->
648
- <div v-if="jsonV.open" class="fixed inset-0 cmd-backdrop z-50 flex items-center justify-center p-8" @mousedown.self="jsonV.open=false">
649
- <div class="glass rounded-2xl w-full max-w-3xl max-h-[80vh] flex flex-col animate-scale shadow-2xl">
650
- <div class="flex items-center gap-3 px-4 py-3 border-b border-border flex-shrink-0">
651
- <span class="mdi mdi-code-json text-brand text-lg"></span>
652
- <h3 class="font-bold text-white">Response Viewer</h3>
653
- <span v-if="jsonV.ms" class="text-xs text-gray-500 ml-1">{{jsonV.ms}}ms</span>
654
- <div class="ml-auto flex items-center gap-2">
655
- <button @click="jsonV.pretty=!jsonV.pretty" class="text-xs text-gray-500 hover:text-white transition px-2 py-1 rounded bg-bg border border-border">{{jsonV.pretty?'Raw':'Pretty'}}</button>
656
- <button @click="copyText(jsonV.raw)" class="text-xs text-gray-500 hover:text-white transition px-2 py-1 rounded bg-bg border border-border"><span class="mdi mdi-content-copy mr-1"></span>Copy</button>
657
- <button @click="jsonV.open=false" class="text-gray-500 hover:text-white transition"><span class="mdi mdi-close"></span></button>
658
- </div>
659
- </div>
660
- <div class="flex-1 overflow-auto p-4">
661
- <pre v-if="!jsonV.pretty" class="text-xs font-mono text-gray-300 whitespace-pre-wrap select-all">{{jsonV.raw}}</pre>
662
- <div v-else><json-tree :data="jsonV.data" :depth="0"></json-tree></div>
663
- </div>
664
- </div>
665
- </div>
666
-
667
- <!-- ═══════ ACTIVITY LOG PANEL ═══════ -->
668
- <div class="fixed right-0 top-0 h-full w-[380px] bg-surface border-l border-border z-30 flex flex-col shadow-2xl log-panel"
669
- :class="logOpen?'log-open':'log-closed'">
670
- <div class="flex items-center gap-3 px-4 py-3 border-b border-border min-h-[56px]">
671
- <span class="mdi mdi-history text-brand text-lg"></span>
672
- <h3 class="font-bold text-white">Activity Log</h3>
673
- <span class="text-xs text-gray-500">{{actLog.length}} calls</span>
674
- <button v-if="actLog.length" @click="confirmAction('Clear Log','Clear all API call history?',()=>actLog.splice(0))" class="ml-auto text-xs text-gray-500 hover:text-white transition">Clear</button>
675
- <button @click="logOpen=false" class="text-gray-500 hover:text-white transition" :class="actLog.length?'ml-2':'ml-auto'"><span class="mdi mdi-close"></span></button>
676
- </div>
677
- <div class="flex-1 overflow-y-auto">
678
- <div v-if="!actLog.length" class="px-4 py-12 text-center text-gray-600 text-sm"><span class="mdi mdi-history text-3xl block mb-2"></span>No API calls yet</div>
679
- <div v-for="(log,i) in actLog" :key="log.ts" @click="openJsonViewer(log.response,log.timing)"
680
- class="px-4 py-3 border-b border-border/50 hover:bg-white/5 cursor-pointer transition group">
681
- <div class="flex items-center gap-2 mb-1">
682
- <span class="text-[10px] font-mono font-bold rounded px-1.5 py-0.5" :class="methodColor(log.method)">{{log.method}}</span>
683
- <span class="text-xs text-gray-400 truncate flex-1 font-mono">{{log.path}}</span>
684
- <span class="text-[10px] font-bold" :class="log.ok?'text-green-400':'text-red-400'">{{log.status}}</span>
685
- </div>
686
- <div class="flex items-center gap-3 text-[10px] text-gray-600">
687
- <span><span class="mdi mdi-timer-outline mr-0.5"></span>{{log.timing}}ms</span>
688
- <span>{{log.time}}</span>
689
- <span class="mdi mdi-eye ml-auto opacity-0 group-hover:opacity-100 transition text-gray-500"></span>
690
- </div>
378
+ </section>
691
379
  </div>
692
- </div>
693
- </div>
694
380
 
695
- <!-- ═══════ KEYBOARD SHORTCUTS ═══════ -->
696
- <div v-if="showShortcuts" class="fixed inset-0 cmd-backdrop z-50 flex items-center justify-center" @mousedown.self="showShortcuts=false">
697
- <div class="glass rounded-2xl p-6 max-w-md w-full animate-scale shadow-2xl">
698
- <h3 class="text-lg font-bold text-white mb-4 flex items-center gap-2"><span class="mdi mdi-keyboard"></span> Keyboard Shortcuts</h3>
699
- <div class="space-y-1">
700
- <div v-for="s in shortcuts" :key="s.keys" class="flex items-center justify-between py-2 border-b border-border/50 last:border-0">
701
- <span class="text-sm text-gray-400">{{s.desc}}</span>
702
- <div class="flex gap-1"><kbd v-for="k in s.keys.split(' + ')" :key="k" class="text-[10px] bg-bg px-2 py-1 rounded border border-border text-gray-400 font-mono">{{k}}</kbd></div>
703
- </div>
381
+ <div class="console">
382
+ <section class="card">
383
+ <h2>Response</h2>
384
+ <p class="sub">Latest request result.</p>
385
+ <pre id="output">No requests yet.</pre>
386
+ </section>
387
+ <section class="card">
388
+ <h2>History</h2>
389
+ <p class="sub">Recent actions from this page.</p>
390
+ <div id="history" class="history"></div>
391
+ </section>
704
392
  </div>
705
- <button @click="showShortcuts=false" class="mt-4 w-full text-center text-sm text-gray-500 hover:text-white transition">Close</button>
706
- </div>
707
- </div>
708
-
709
- <!-- ═══════ DRAG-DROP OVERLAY ═══════ -->
710
- <div v-if="dragActive" class="fixed inset-0 z-40 flex items-center justify-center pointer-events-none">
711
- <div class="bg-brand/10 border-2 border-dashed border-brand rounded-3xl p-16 animate-scale">
712
- <span class="mdi mdi-cloud-upload text-5xl text-brand block mb-3 text-center"></span>
713
- <p class="text-lg text-white font-bold text-center">Drop files to upload</p>
714
- <p class="text-xs text-gray-400 mt-1 text-center">Navigating to Document Pipeline</p>
715
393
  </div>
716
- </div>
717
-
718
- <!-- ═══════ TOAST STACK ═══════ -->
719
- <div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 items-end pointer-events-none">
720
- <transition-group name="toast">
721
- <div v-for="t in toasts" :key="t.id" class="glass rounded-xl px-4 py-3 flex items-center gap-3 min-w-[280px] shadow-xl pointer-events-auto animate-slide-up"
722
- :class="t.ok?'border-green-500/30':'border-red-500/30'">
723
- <span class="mdi text-lg" :class="t.ok?'mdi-check-circle text-green-400':'mdi-alert-circle text-red-400'"></span>
724
- <span class="text-sm text-gray-300 flex-1">{{t.msg}}</span>
725
- <button @click="rmToast(t.id)" class="text-gray-500 hover:text-white transition"><span class="mdi mdi-close text-sm"></span></button>
726
- </div>
727
- </transition-group>
728
- </div>
729
-
730
- </div>
731
- <\/script>
732
-
733
- <script>
734
- const{createApp,ref,reactive,computed,onMounted,watch,nextTick}=Vue;
735
- createApp({
736
- template:document.getElementById('app-tpl').innerHTML,
737
- setup(){
738
- const cfg=reactive({baseUrl:'${esc(config.baseUrl)}',apiPrefix:'${esc(config.apiPrefix)}',apiKey:'${esc(config.apiKey)}',adminKey:'${esc(config.adminKey)}',webhookSecret:'${esc(config.webhookSecret)}',employeeId:'${esc(config.employeeId)}'});
739
- const settingsFields=[{key:'baseUrl',label:'Base URL',placeholder:'http://localhost:8000'},{key:'apiPrefix',label:'API Prefix',placeholder:'/api/v1'},{key:'apiKey',label:'API Key',placeholder:'key-…',type:'password'},{key:'adminKey',label:'Admin Key',placeholder:'admin-…',type:'password'},{key:'webhookSecret',label:'Webhook Secret',placeholder:'whsec-…',type:'password'},{key:'employeeId',label:'Employee ID',placeholder:'EMP001'}];
740
394
 
741
- // ── Activity Log ──
742
- const actLog=reactive([]);
743
-
744
- // ── API helper with timing ──
745
- async function api(method,path,body){
746
- const url=cfg.baseUrl+cfg.apiPrefix+path;
747
- const headers={'X-API-Key':cfg.apiKey,'X-Admin-Key':cfg.adminKey,'X-Employee-ID':cfg.employeeId};
748
- const isForm=body instanceof FormData;if(!isForm)headers['Content-Type']='application/json';
749
- const t0=performance.now();
750
- try{
751
- const res=await fetch(url,{method,headers,body:isForm?body:body?JSON.stringify(body):undefined});
752
- const timing=Math.round(performance.now()-t0);
753
- let data;try{data=await res.json();}catch{data=null;}
754
- actLog.unshift({method,path,status:res.status,ok:res.ok,timing,time:new Date().toLocaleTimeString(),ts:Date.now(),response:data});
755
- if(actLog.length>200)actLog.length=200;
756
- return{ok:res.ok,status:res.status,data,timing};
757
- }catch(e){
758
- const timing=Math.round(performance.now()-t0);
759
- actLog.unshift({method,path,status:0,ok:false,timing,time:new Date().toLocaleTimeString(),ts:Date.now(),response:{error:e.message}});
760
- throw e;
395
+ <script>
396
+ const storeKey = "hivemind-sdk-dashboard-config";
397
+ const output = document.getElementById("output");
398
+ const historyEl = document.getElementById("history");
399
+ const healthDot = document.getElementById("health-dot");
400
+ const healthText = document.getElementById("health-text");
401
+
402
+ const inputs = {
403
+ baseUrl: document.getElementById("baseUrl"),
404
+ apiPrefix: document.getElementById("apiPrefix"),
405
+ apiKey: document.getElementById("apiKey"),
406
+ adminKey: document.getElementById("adminKey"),
407
+ webhookSecret: document.getElementById("webhookSecret"),
408
+ };
409
+
410
+ const saved = (() => {
411
+ try {
412
+ return JSON.parse(localStorage.getItem(storeKey) || "{}");
413
+ } catch {
414
+ return {};
761
415
  }
762
- }
763
- function tryJson(s){try{return JSON.parse(s);}catch{return null;}}
764
-
765
- // ── Toast stack ──
766
- let tid=0;const toasts=reactive([]);
767
- function showToast(msg,ok=true){const id=++tid;toasts.push({id,msg,ok});setTimeout(()=>rmToast(id),4000);}
768
- function rmToast(id){const i=toasts.findIndex(t=>t.id===id);if(i>=0)toasts.splice(i,1);}
769
- function copyText(t){navigator.clipboard.writeText(typeof t==='string'?t:JSON.stringify(t,null,2));showToast('Copied!');}
770
-
771
- // ── Health ──
772
- const health=reactive({ok:false,text:'Checking…',ms:0});
773
- async function checkHealth(){try{const r=await api('GET','/health');if(r.ok){health.ok=true;health.text=(r.data.service||'API')+' — '+r.data.status;health.ms=r.timing;}else{health.ok=false;health.text='Error '+r.status;health.ms=r.timing;}}catch{health.ok=false;health.text='Unreachable';health.ms=0;}}
774
-
775
- // ── Settings ──
776
- const settingsMsg=ref('');const settingsMsgOk=ref(false);const settingsLoading=ref(false);
777
- async function saveSettings(){settingsLoading.value=true;settingsMsg.value='Testing…';await checkHealth();if(health.ok){settingsMsg.value='Connected!';settingsMsgOk.value=true;showToast('Connected!');}else{settingsMsg.value='Failed: '+health.text;settingsMsgOk.value=false;showToast('Connection failed',false);}settingsLoading.value=false;}
416
+ })();
778
417
 
779
- // ── Navigation ──
780
- const currentView=ref('home');const search=ref('');const sidebarOpen=ref(true);
781
- const navSections=[
782
- {name:'Home',items:[{view:'home',label:'Dashboard',icon:'mdi-view-dashboard'},{view:'settings',label:'Settings',icon:'mdi-cog'},{view:'quick',label:'Quick Actions',icon:'mdi-lightning-bolt'}]},
783
- {name:'Flows',items:[
784
- {view:'flow-org',label:'Organization Setup',icon:'mdi-domain'},
785
- {view:'flow-docs',label:'Document Pipeline',icon:'mdi-file-document'},
786
- {view:'flow-gen',label:'Content Generation',icon:'mdi-creation'},
787
- {view:'flow-blueprint',label:'Blueprint → Document',icon:'mdi-file-tree'},
788
- {view:'flow-agents',label:'AI Agents',icon:'mdi-robot'},
789
- {view:'flow-jira',label:'Jira Integration',icon:'mdi-jira'},
790
- {view:'flow-scrape',label:'Web Scraping',icon:'mdi-web'},
791
- {view:'flow-analytics',label:'Analytics & ML',icon:'mdi-chart-line'},
792
- {view:'flow-audio',label:'Audio Processing',icon:'mdi-microphone'},
793
- ]},
794
- {name:'Management',items:[{view:'flow-billing',label:'Billing & Audit',icon:'mdi-cash-register'},{view:'flow-admin',label:'Admin Dashboard',icon:'mdi-shield-crown'}]},
795
- ];
796
- const allNavItems={};navSections.forEach(s=>s.items.forEach(i=>{allNavItems[i.view]=i.label;}));
797
- const breadcrumb=computed(()=>currentView.value==='home'?'':allNavItems[currentView.value]||'');
798
- function navigate(view){currentView.value=view;cmdOpen.value=false;}
799
-
800
- // ── Command Palette ──
801
- const cmdOpen=ref(false);const cmdQ=ref('');const cmdI=ref(0);
802
- const cmdResults=computed(()=>{
803
- const q=cmdQ.value.toLowerCase();
804
- let items=[
805
- ...navSections.flatMap(s=>s.items.map(i=>({id:'nav-'+i.view,label:i.label,icon:i.icon,hint:s.name,type:'Navigate',action:()=>navigate(i.view)}))),
806
- ...quickActions.map(a=>({id:a.id,label:a.title,icon:'mdi-lightning-bolt',hint:a.method+' '+a.path,type:a.cat,action:()=>{navigate('quick');setTimeout(()=>{a.open=true;},100);}})),
807
- ];
808
- if(q)items=items.filter(i=>i.label.toLowerCase().includes(q)||i.hint.toLowerCase().includes(q)||i.type.toLowerCase().includes(q));
809
- return items.slice(0,12);
418
+ Object.entries(saved).forEach(([key, value]) => {
419
+ if (inputs[key] && typeof value === "string") {
420
+ inputs[key].value = value;
421
+ }
810
422
  });
811
- watch(cmdQ,()=>{cmdI.value=0;});
812
- function cmdExec(item){const r=item||cmdResults.value[cmdI.value];if(r){r.action();cmdOpen.value=false;cmdQ.value='';}}
813
-
814
- // ── Confirm Modal ──
815
- const cfmModal=reactive({open:false,title:'',message:'',btn:'Confirm',fn:()=>{}});
816
- function confirmAction(title,message,fn,btn='Confirm'){cfmModal.title=title;cfmModal.message=message;cfmModal.fn=fn;cfmModal.btn=btn;cfmModal.open=true;}
817
423
 
818
- // ── JSON Viewer ──
819
- const jsonV=reactive({open:false,data:null,raw:'',ms:0,pretty:true});
820
- function openJsonViewer(data,ms){jsonV.data=data;jsonV.raw=typeof data==='string'?data:JSON.stringify(data,null,2);jsonV.ms=ms||0;jsonV.pretty=true;jsonV.open=true;}
424
+ function cfg() {
425
+ return {
426
+ baseUrl: inputs.baseUrl.value.replace(/\\/+$/, ""),
427
+ apiPrefix: inputs.apiPrefix.value || "/v1",
428
+ apiKey: inputs.apiKey.value.trim(),
429
+ adminKey: inputs.adminKey.value.trim(),
430
+ webhookSecret: inputs.webhookSecret.value.trim(),
431
+ };
432
+ }
821
433
 
822
- // ── Activity Log Panel ──
823
- const logOpen=ref(false);
434
+ function saveConfig() {
435
+ localStorage.setItem(storeKey, JSON.stringify(cfg()));
436
+ log("Saved connection profile.");
437
+ }
824
438
 
825
- // ── Keyboard Shortcuts ──
826
- const showShortcuts=ref(false);
827
- const shortcuts=[
828
- {keys:'Ctrl + K',desc:'Open command palette'},
829
- {keys:'?',desc:'Show keyboard shortcuts'},
830
- {keys:'Esc',desc:'Close modal / Go back'},
831
- {keys:'Ctrl + \\\\',desc:'Toggle sidebar'},
832
- {keys:'Ctrl + L',desc:'Toggle activity log'},
833
- {keys:'H',desc:'Go to home'},
834
- ];
439
+ function log(message) {
440
+ const item = document.createElement("div");
441
+ item.className = "history-item";
442
+ item.innerHTML = "<strong>" + new Date().toLocaleTimeString() + "</strong> " + message;
443
+ historyEl.prepend(item);
444
+ while (historyEl.children.length > 12) {
445
+ historyEl.removeChild(historyEl.lastChild);
446
+ }
447
+ }
835
448
 
836
- // ── Drag & Drop ──
837
- const dragActive=ref(false);let dragTimeout=null;
838
- function onDragOver(){dragActive.value=true;clearTimeout(dragTimeout);}
839
- function onDragLeave(){dragTimeout=setTimeout(()=>{dragActive.value=false;},100);}
840
- async function onDrop(e){
841
- dragActive.value=false;const files=e.dataTransfer?.files;if(!files?.length)return;
842
- if(currentView.value==='flow-docs'&&docFlow.orgId){
843
- const form=new FormData();form.append('file',files[0]);docFlow.loading=true;
844
- try{const r=await api('POST','/organizations/'+docFlow.orgId+'/documents/upload',form);docFlow.lastResult=r;if(r.ok){docFlow.uploadResult=r.data;docFlow.step=2;showToast('Uploaded via drag & drop!');}else showToast('Upload failed',false);}catch(e){showToast(e.message,false);}
845
- docFlow.loading=false;
846
- }else{navigate('flow-docs');showToast('Navigate to Document Pipeline and set Org ID first',false);}
449
+ function setOutput(label, data) {
450
+ output.textContent =
451
+ label + "\\n\\n" +
452
+ (typeof data === "string" ? data : JSON.stringify(data, null, 2));
847
453
  }
848
454
 
849
- // ── Auto-Poll ──
850
- const pollMap=reactive({});
851
- function startPoll(key,fn,ms=3000){stopPoll(key);fn();pollMap[key]=setInterval(fn,ms);}
852
- function stopPoll(key){if(pollMap[key]){clearInterval(pollMap[key]);delete pollMap[key];}}
853
- function isPolling(key){return!!pollMap[key];}
455
+ function parseJson(id, fallback) {
456
+ const raw = document.getElementById(id).value.trim();
457
+ if (!raw) return fallback;
458
+ return JSON.parse(raw);
459
+ }
854
460
 
855
- // ── Home Stats ──
856
- const homeStats=computed(()=>[
857
- {label:'Status',value:health.ok?'Online':'Offline',icon:'mdi-circle',color:health.ok?'text-green-400':'text-red-400'},
858
- {label:'API Calls',value:actLog.length,icon:'mdi-swap-vertical',color:'text-blue-400'},
859
- {label:'Avg Response',value:actLog.length?Math.round(actLog.reduce((s,l)=>s+l.timing,0)/actLog.length)+'ms':'—',icon:'mdi-timer',color:'text-amber-400'},
860
- {label:'Flows',value:9,icon:'mdi-sitemap',color:'text-purple-400'},
861
- ]);
461
+ async function apiRequest(method, path, options = {}) {
462
+ const settings = cfg();
463
+ const url = new URL((settings.apiPrefix || "/v1") + path, settings.baseUrl);
464
+ const headers = {};
465
+ const auth = options.auth || "api";
466
+
467
+ if (auth === "api" && settings.apiKey) headers.Authorization = "Bearer " + settings.apiKey;
468
+ if (auth === "admin" && settings.adminKey) headers["X-Admin-Key"] = settings.adminKey;
469
+ if (auth === "webhook" && settings.webhookSecret) headers["X-Webhook-Secret"] = settings.webhookSecret;
470
+ if (options.json !== undefined) headers["Content-Type"] = "application/json";
471
+
472
+ const res = await fetch(url.toString(), {
473
+ method,
474
+ headers,
475
+ body: options.formData ? options.formData : options.json !== undefined ? JSON.stringify(options.json) : undefined,
476
+ });
862
477
 
863
- const flows=[
864
- {view:'flow-org',title:'Organization Setup',icon:'mdi-domain',color:'bg-blue-500/20 text-blue-400',steps:4,desc:'Create org, provision API keys, configure settings.',tags:['create','api-key','verify','rotate']},
865
- {view:'flow-docs',title:'Document Pipeline',icon:'mdi-file-document',color:'bg-emerald-500/20 text-emerald-400',steps:4,desc:'Upload docs, RAG query, data chat.',tags:['upload','RAG','query','data-chat']},
866
- {view:'flow-gen',title:'Content Generation',icon:'mdi-creation',color:'bg-purple-500/20 text-purple-400',steps:3,desc:'Generate videos/images, track jobs.',tags:['video','image','job-tracking']},
867
- {view:'flow-blueprint',title:'Blueprint Document',icon:'mdi-file-tree',color:'bg-amber-500/20 text-amber-400',steps:5,desc:'Create blueprints, generate documents, AI-fill sections, workflow.',tags:['blueprint','sections','AI-generate','workflow']},
868
- {view:'flow-agents',title:'AI Agents',icon:'mdi-robot',color:'bg-pink-500/20 text-pink-400',steps:2,desc:'Create custom agents and chat in real-time.',tags:['custom-agent','chat','tools']},
869
- {view:'flow-jira',title:'Jira Integration',icon:'mdi-jira',color:'bg-sky-500/20 text-sky-400',steps:4,desc:'Connect, verify, sync projects, manage.',tags:['connect','sync','disconnect']},
870
- {view:'flow-scrape',title:'Web Scraping',icon:'mdi-web',color:'bg-teal-500/20 text-teal-400',steps:2,desc:'Submit URLs, monitor progress, collect results.',tags:['URLs','monitor','results']},
871
- {view:'flow-analytics',title:'Analytics & ML',icon:'mdi-chart-line',color:'bg-orange-500/20 text-orange-400',steps:3,desc:'Train ML models, predict, forecast.',tags:['train','predict','forecast']},
872
- {view:'flow-audio',title:'Audio Processing',icon:'mdi-microphone',color:'bg-rose-500/20 text-rose-400',steps:2,desc:'Transcribe audio, synthesize speech.',tags:['transcribe','synthesize']},
873
- ];
478
+ const text = await res.text();
479
+ let body = text;
480
+ try {
481
+ body = JSON.parse(text);
482
+ } catch {}
483
+
484
+ const payload = {
485
+ ok: res.ok,
486
+ status: res.status,
487
+ path,
488
+ method,
489
+ body,
490
+ };
491
+ setOutput(method + " " + path + " -> " + res.status, payload);
492
+ log(method + " " + path + " -> " + res.status);
493
+ return payload;
494
+ }
874
495
 
875
- // ── Quick Actions ──
876
- const quickCat=ref('All');
877
- const quickCategories=['All','Organization','Documents','Search','Generation','Content Creator','Agents','Audio','Analytics','Billing','Audit','Scraping','Webhooks','Jira','Blueprints','Managed Docs','Admin'];
878
- function qf(key,label,placeholder,type){return{key,label,placeholder,type:type||'text'};}
879
- const orgF=qf('orgId','Org ID','my-org-1');
496
+ async function healthCheck() {
497
+ try {
498
+ const settings = cfg();
499
+ const url = new URL((settings.apiPrefix || "/v1") + "/health", settings.baseUrl);
500
+ const res = await fetch(url.toString());
501
+ if (!res.ok) throw new Error("HTTP " + res.status);
502
+ const body = await res.json();
503
+ healthDot.classList.add("ok");
504
+ healthText.textContent = "API reachable";
505
+ setOutput("Health", body);
506
+ } catch (err) {
507
+ healthDot.classList.remove("ok");
508
+ healthText.textContent = "API unavailable";
509
+ setOutput("Health", String(err));
510
+ }
511
+ }
880
512
 
881
- const quickActions=reactive([
882
- {id:'q0',cat:'Organization',method:'GET',title:'Health Check',path:'/health',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/health')},
883
- {id:'q1',cat:'Organization',method:'POST',title:'Create Organization',path:'/organizations',fields:[orgF,qf('name','Name','Acme'),qf('tier','Tier','standard')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations',{org_id:v.orgId,name:v.name,tier:v.tier||'standard',settings:{}})},
884
- {id:'q2',cat:'Organization',method:'GET',title:'Get Organization',path:'/organizations/{orgId}',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId)},
885
- {id:'q3',cat:'Organization',method:'POST',title:'Rotate API Key',path:'/organizations/{orgId}/api-keys/rotate',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/api-keys/rotate')},
886
- {id:'q4',cat:'Organization',method:'POST',title:'Suspend Org',path:'/organizations/{orgId}/suspend',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/suspend')},
887
- {id:'q5',cat:'Organization',method:'POST',title:'Reactivate Org',path:'/organizations/{orgId}/reactivate',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/reactivate')},
888
- {id:'q6',cat:'Organization',method:'DELETE',title:'Delete Organization',path:'/organizations/{orgId}',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('DELETE','/organizations/'+v.orgId)},
889
- {id:'q7',cat:'Documents',method:'POST',title:'RAG Query',path:'/organizations/{orgId}/query',fields:[orgF,qf('query','Query','key findings?'),qf('model','Model','gemini-pro'),qf('strategy','Strategy','hybrid')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/query',{query:v.query,model_type:v.model||'gemini-pro',retrieval_strategy:v.strategy||'hybrid'})},
890
- {id:'q8',cat:'Documents',method:'POST',title:'Data Chat',path:'/organizations/{orgId}/data-chat',fields:[orgF,qf('question','Question','active users?')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/data-chat',{question:v.question})},
891
- {id:'q8a',cat:'Search',method:'POST',title:'Search Sources',path:'/organizations/{orgId}/search',fields:[orgF,qf('query','Query','content marketing 2026'),qf('type','Type','news'),qf('num','Num','10')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/search',{query:v.query,type:v.type||undefined,num:parseInt(v.num||'10')})},
892
- {id:'q9',cat:'Generation',method:'POST',title:'Generate Video',path:'/organizations/{orgId}/generate/video',fields:[orgF,qf('prompt','Prompt','Drone flyover'),qf('duration','Duration','8'),qf('aspect','Aspect','16:9')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/generate/video',{prompt:v.prompt,duration:parseInt(v.duration||'8'),aspect_ratio:v.aspect||'16:9'})},
893
- {id:'q10',cat:'Generation',method:'POST',title:'Generate Image',path:'/organizations/{orgId}/generate/image',fields:[orgF,qf('prompt','Prompt','Futuristic city'),qf('aspect','Aspect','1:1'),qf('num','Num','1')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/generate/image',{prompt:v.prompt,aspect_ratio:v.aspect||'1:1',num_images:parseInt(v.num||'1')})},
894
- {id:'q10a',cat:'Generation',method:'POST',title:'Generate Music',path:'/organizations/{orgId}/generate/music',fields:[orgF,qf('genre','Genre','ambient'),qf('mood','Mood','uplifting'),qf('duration','Duration','30')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/generate/music',{genre:v.genre,mood:v.mood,duration:parseInt(v.duration||'30')})},
895
- {id:'q11',cat:'Generation',method:'GET',title:'Job Status',path:'/organizations/{orgId}/jobs/{jobId}',fields:[orgF,qf('jobId','Job ID','job-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/jobs/'+v.jobId)},
896
- {id:'q12',cat:'Agents',method:'POST',title:'Create Agent',path:'/organizations/{orgId}/agents/custom',fields:[orgF,qf('name','Name','Bot'),qf('instructions','Instructions','','textarea'),qf('tools','Tools','["rag_search"]')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/agents/custom',{name:v.name,instructions:v.instructions,tools:tryJson(v.tools)||[]})},
897
- {id:'q13',cat:'Agents',method:'POST',title:'Query Agent',path:'/organizations/{orgId}/agents/query',fields:[orgF,qf('query','Query','Help me'),qf('agent_type','Type','auto')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/agents/query',{query:v.query,agent_type:v.agent_type||'auto'})},
898
- {id:'q14',cat:'Audio',method:'POST',title:'Transcribe',path:'/organizations/{orgId}/audio/transcribe',fields:[orgF,qf('audio','Audio','...'),qf('lang','Language','en-US')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/audio/transcribe',{audio_text:v.audio,language_code:v.lang||'en-US'})},
899
- {id:'q15',cat:'Audio',method:'POST',title:'Synthesize',path:'/organizations/{orgId}/audio/synthesize',fields:[orgF,qf('text','Text','Hello'),qf('voice','Voice','en-US-Neural2-J')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/audio/synthesize',{text:v.text,voice_name:v.voice||'en-US-Neural2-J'})},
900
- {id:'q16',cat:'Analytics',method:'POST',title:'Train Model',path:'/organizations/{orgId}/analytics/train',fields:[orgF,qf('target','Target','churn'),qf('features','Features','["tenure"]'),qf('rows','Rows','[]','textarea')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/analytics/train',{target_column:v.target,feature_columns:tryJson(v.features)||[],rows:tryJson(v.rows)||[]})},
901
- {id:'q17',cat:'Analytics',method:'POST',title:'Predict',path:'/organizations/{orgId}/analytics/predict',fields:[orgF,qf('model_id','Model ID','model-xxx'),qf('instances','Instances','[]','textarea')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/analytics/predict',{model_id:v.model_id,instances:tryJson(v.instances)||[]})},
902
- {id:'q18',cat:'Analytics',method:'POST',title:'Forecast',path:'/organizations/{orgId}/analytics/forecast',fields:[orgF,qf('series','Series','[1,2,3]'),qf('horizon','Horizon','30')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/analytics/forecast',{series:tryJson(v.series)||[],horizon:parseInt(v.horizon||'30')})},
903
- {id:'q19',cat:'Billing',method:'GET',title:'Usage',path:'/organizations/{orgId}/usage',fields:[orgF,qf('month','Month','2026-03')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/usage'+(v.month?'?month='+v.month:''))},
904
- {id:'q20',cat:'Billing',method:'GET',title:'Invoice',path:'/organizations/{orgId}/invoice',fields:[orgF,qf('month','Month','')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/invoice'+(v.month?'?month='+v.month:''))},
905
- {id:'q21',cat:'Audit',method:'GET',title:'Audit Logs',path:'/organizations/{orgId}/audit',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/audit')},
906
- {id:'q22',cat:'Scraping',method:'GET',title:'Scraping Config',path:'/scraping/config',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/scraping/config')},
907
- {id:'q23',cat:'Scraping',method:'POST',title:'Submit Scrape',path:'/scraping/scrape',fields:[qf('urls','URLs (comma)','https://example.com')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/scraping/scrape',{urls:(v.urls||'').split(',').map(u=>u.trim()).filter(Boolean)})},
908
- {id:'q24',cat:'Scraping',method:'GET',title:'Scrape Status',path:'/scraping/status/{taskId}',fields:[qf('taskId','Task ID','task-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/scraping/status/'+v.taskId)},
909
- {id:'q25',cat:'Webhooks',method:'POST',title:'Video Webhook',path:'/webhook/video-complete',fields:[qf('job_id','Job ID','job-1'),orgF,qf('status','Status','completed')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/webhook/video-complete',{job_id:v.job_id,org_id:v.orgId,status:v.status})},
910
- {id:'q26',cat:'Webhooks',method:'POST',title:'Document Webhook',path:'/webhook/document-processed',fields:[qf('document_id','Doc ID','doc-1'),orgF,qf('status','Status','processed')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/webhook/document-processed',{document_id:v.document_id,org_id:v.orgId,status:v.status})},
911
- {id:'q27',cat:'Webhooks',method:'POST',title:'Jira Webhook',path:'/webhook/jira',fields:[qf('payload','Payload','{}','textarea')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/webhook/jira',tryJson(v.payload)||{})},
912
- {id:'q28',cat:'Jira',method:'POST',title:'Connect Jira',path:'/organizations/{orgId}/integrations/jira/connect',fields:[orgF,qf('site_url','Site URL','https://x.atlassian.net'),qf('email','Email','user@co.com'),qf('api_token','Token','','password')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/integrations/jira/connect',{site_url:v.site_url,email:v.email,api_token:v.api_token})},
913
- {id:'q29',cat:'Jira',method:'GET',title:'Jira Status',path:'/organizations/{orgId}/integrations/jira/status',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/integrations/jira/status')},
914
- {id:'q30',cat:'Jira',method:'DELETE',title:'Disconnect Jira',path:'/organizations/{orgId}/integrations/jira',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('DELETE','/organizations/'+v.orgId+'/integrations/jira')},
915
- {id:'q31',cat:'Jira',method:'POST',title:'Sync Jira',path:'/organizations/{orgId}/integrations/jira/sync',fields:[orgF,qf('keys','Project Keys','["PROJ"]')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/integrations/jira/sync',{project_keys:tryJson(v.keys)||[]})},
916
- {id:'q32',cat:'Blueprints',method:'POST',title:'Create Blueprint',path:'/organizations/{orgId}/blueprints',fields:[orgF,qf('name','Name','Template'),qf('category','Category','HR'),qf('sections','Sections','[]','textarea')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/blueprints',{name:v.name,category:v.category,sections:tryJson(v.sections)||[],default_workflow:'standard_approval'})},
917
- {id:'q33',cat:'Blueprints',method:'GET',title:'List Blueprints',path:'/organizations/{orgId}/blueprints',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/blueprints')},
918
- {id:'q34',cat:'Blueprints',method:'GET',title:'Get Blueprint',path:'/organizations/{orgId}/blueprints/{bpId}',fields:[orgF,qf('bpId','Blueprint ID','bp-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/blueprints/'+v.bpId)},
919
- {id:'q35',cat:'Blueprints',method:'POST',title:'Publish Blueprint',path:'/organizations/{orgId}/blueprints/{bpId}/publish',fields:[orgF,qf('bpId','BP ID','bp-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/blueprints/'+v.bpId+'/publish',{})},
920
- {id:'q36',cat:'Blueprints',method:'POST',title:'Archive Blueprint',path:'/organizations/{orgId}/blueprints/{bpId}/archive',fields:[orgF,qf('bpId','BP ID','bp-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/blueprints/'+v.bpId+'/archive',{})},
921
- {id:'q37',cat:'Blueprints',method:'DELETE',title:'Delete Blueprint',path:'/organizations/{orgId}/blueprints/{bpId}',fields:[orgF,qf('bpId','BP ID','bp-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('DELETE','/organizations/'+v.orgId+'/blueprints/'+v.bpId)},
922
- {id:'q38',cat:'Managed Docs',method:'POST',title:'Create from Blueprint',path:'/organizations/{orgId}/documents/create',fields:[orgF,qf('bpId','BP ID','bp-xxx'),qf('title','Title','Q1 Report')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/documents/create',{blueprint_id:v.bpId,title:v.title})},
923
- {id:'q39',cat:'Managed Docs',method:'GET',title:'List Managed Docs',path:'/organizations/{orgId}/documents/managed',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/documents/managed')},
924
- {id:'q40',cat:'Managed Docs',method:'GET',title:'Get Managed Doc',path:'/organizations/{orgId}/documents/managed/{docId}',fields:[orgF,qf('docId','Doc ID','doc-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/documents/managed/'+v.docId)},
925
- {id:'q41',cat:'Managed Docs',method:'PATCH',title:'Update Section',path:'/organizations/{orgId}/documents/managed/{docId}/sections',fields:[orgF,qf('docId','Doc ID','doc-xxx'),qf('section_id','Section','sec-1'),qf('content','Content','','textarea')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('PATCH','/organizations/'+v.orgId+'/documents/managed/'+v.docId+'/sections',{section_id:v.section_id,content:v.content})},
926
- {id:'q42',cat:'Managed Docs',method:'POST',title:'AI Generate Section',path:'/organizations/{orgId}/documents/managed/{docId}/generate',fields:[orgF,qf('docId','Doc ID','doc-xxx'),qf('section_id','Section','sec-1'),qf('context','Context','{}')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/documents/managed/'+v.docId+'/generate',{section_id:v.section_id,context:tryJson(v.context)||{}})},
927
- {id:'q43',cat:'Managed Docs',method:'POST',title:'Workflow Action',path:'/organizations/{orgId}/documents/managed/{docId}/workflow',fields:[orgF,qf('docId','Doc ID','doc-xxx'),qf('action','Action','submit'),qf('comment','Comment','')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/documents/managed/'+v.docId+'/workflow',{action:v.action,comment:v.comment})},
928
- {id:'q44',cat:'Managed Docs',method:'DELETE',title:'Delete Document',path:'/organizations/{orgId}/documents/managed/{docId}',fields:[orgF,qf('docId','Doc ID','doc-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('DELETE','/organizations/'+v.orgId+'/documents/managed/'+v.docId)},
929
- {id:'q45',cat:'Admin',method:'POST',title:'Admin Login',path:'/admin/login',fields:[qf('key','Admin Key','','password')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/admin/login',{admin_key:v.key})},
930
- {id:'q46',cat:'Admin',method:'GET',title:'Config',path:'/admin/config',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/config')},
931
- {id:'q47',cat:'Admin',method:'GET',title:'System Info',path:'/admin/system',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/system')},
932
- {id:'q48',cat:'Admin',method:'GET',title:'All Orgs',path:'/admin/organizations',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/organizations')},
933
- {id:'q49',cat:'Admin',method:'GET',title:'All Jobs',path:'/admin/jobs',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/jobs')},
934
- {id:'q50',cat:'Admin',method:'GET',title:'Feature Flags',path:'/admin/feature-flags',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/feature-flags')},
935
- {id:'q51',cat:'Admin',method:'GET',title:'Rate Limits',path:'/admin/rate-limits',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/rate-limits')},
936
- {id:'q52',cat:'Admin',method:'GET',title:'Cache Stats',path:'/admin/cache',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/cache')},
937
- {id:'q53',cat:'Admin',method:'GET',title:'Tiers',path:'/admin/tiers',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/tiers')},
938
- {id:'q54',cat:'Admin',method:'GET',title:'Pricing',path:'/admin/pricing',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/pricing')},
939
- {id:'q55',cat:'Admin',method:'GET',title:'CORS',path:'/admin/cors',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/cors')},
940
- {id:'q56',cat:'Admin',method:'GET',title:'Traffic Inbound',path:'/admin/traffic/inbound',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/traffic/inbound')},
941
- {id:'q57',cat:'Admin',method:'GET',title:'Traffic Outbound',path:'/admin/traffic/outbound',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/traffic/outbound')},
942
- {id:'q58',cat:'Admin',method:'GET',title:'Log Level',path:'/admin/logging/level',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/logging/level')},
943
- {id:'q59',cat:'Admin',method:'GET',title:'Loggers',path:'/admin/logging/loggers',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/logging/loggers')},
944
- {id:'q60',cat:'Admin',method:'GET',title:'Maintenance',path:'/admin/maintenance',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/maintenance')},
945
- {id:'q61',cat:'Admin',method:'GET',title:'Admin Audit',path:'/admin/audit',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('GET','/admin/audit')},
946
- {id:'q62',cat:'Admin',method:'POST',title:'Flush Cache',path:'/admin/cache/flush',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('POST','/admin/cache/flush')},
947
- {id:'q63',cat:'Admin',method:'PUT',title:'Update Setting',path:'/admin/settings',fields:[qf('key','Key','log_level'),qf('value','Value','DEBUG')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('PUT','/admin/settings',{key:v.key,value:v.value})},
948
- {id:'q64',cat:'Content Creator',method:'POST',title:'Create Project',path:'/organizations/{orgId}/projects',fields:[orgF,qf('name','Name','Launch Campaign'),qf('description','Description','Q4 rollout')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/projects',{name:v.name,description:v.description})},
949
- {id:'q65',cat:'Content Creator',method:'GET',title:'List Projects',path:'/organizations/{orgId}/projects',fields:[orgF],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/projects')},
950
- {id:'q66',cat:'Content Creator',method:'POST',title:'Create Publish Schedule',path:'/organizations/{orgId}/projects/{projectId}/schedules',fields:[orgF,qf('projectId','Project ID','proj-xxx'),qf('contentId','Content ID','cnt-xxx'),qf('platforms','Platforms (comma)','linkedin,x'),qf('publishAt','Publish At','2026-03-17T12:00:00Z')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('POST','/organizations/'+v.orgId+'/projects/'+v.projectId+'/schedules',{content_id:v.contentId,target_platforms:(v.platforms||'').split(',').map(p=>p.trim()).filter(Boolean),publish_at:v.publishAt})},
951
- {id:'q67',cat:'Content Creator',method:'GET',title:'List Publish Schedules',path:'/organizations/{orgId}/projects/{projectId}/schedules',fields:[orgF,qf('projectId','Project ID','proj-xxx')],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:v=>api('GET','/organizations/'+v.orgId+'/projects/'+v.projectId+'/schedules')},
952
- {id:'q68',cat:'Content Creator',method:'POST',title:'Run Due Schedules',path:'/admin/content-creator/schedules/run-due',fields:[],values:{},open:false,loading:false,result:null,resultOk:false,timing:0,fn:()=>api('POST','/admin/content-creator/schedules/run-due')},
953
- ]);
513
+ document.getElementById("saveConfig").onclick = saveConfig;
514
+ document.getElementById("pingHealth").onclick = healthCheck;
515
+ document.getElementById("getScope").onclick = () => apiRequest("GET", "/api-key", { auth: "api" });
516
+ document.getElementById("adminConfig").onclick = () => apiRequest("GET", "/admin/config", { auth: "admin" });
517
+
518
+ document.getElementById("issueApiKey").onclick = () => {
519
+ const name = document.getElementById("scopeName").value.trim() || undefined;
520
+ return apiRequest("POST", "/api-keys", {
521
+ auth: "admin",
522
+ json: {
523
+ name,
524
+ settings: parseJson("scopeSettings", {}),
525
+ },
526
+ });
527
+ };
954
528
 
955
- const filteredQuickActions=computed(()=>{
956
- let l=quickActions;
957
- if(quickCat.value!=='All')l=l.filter(a=>a.cat===quickCat.value);
958
- if(search.value){const q=search.value.toLowerCase();l=l.filter(a=>a.title.toLowerCase().includes(q)||a.path.toLowerCase().includes(q)||a.cat.toLowerCase().includes(q));}
959
- return l;
529
+ document.getElementById("rotateApiKey").onclick = () => apiRequest("POST", "/api-key/rotate", { auth: "api" });
530
+ document.getElementById("runQuery").onclick = () => apiRequest("POST", "/query", {
531
+ auth: "api",
532
+ json: { query: document.getElementById("ragQuery").value },
960
533
  });
961
- function methodColor(m){return{GET:'bg-green-900/40 text-green-400',POST:'bg-blue-900/40 text-blue-400',PUT:'bg-yellow-900/40 text-yellow-400',PATCH:'bg-orange-900/40 text-orange-400',DELETE:'bg-red-900/40 text-red-400'}[m]||'bg-gray-900/40 text-gray-400';}
962
- async function runQuickAction(a){a.loading=true;a.result=null;try{const r=await a.fn(a.values);a.resultOk=r.ok;a.timing=r.timing||0;a.result=JSON.stringify(r.data,null,2);}catch(e){a.resultOk=false;a.result='Error: '+e.message;}a.loading=false;}
963
-
964
- // ── Org Flow ──
965
- const orgFlow=reactive({step:0,steps:['Create','API Key','Verify','Rotate'],orgId:'',name:'',tier:'standard',loading:false,apiKey:'',orgData:null,verifyData:null,newKey:'',lastResult:null});
966
- async function orgFlowCreate(){orgFlow.loading=true;try{const r=await api('POST','/organizations',{org_id:orgFlow.orgId,name:orgFlow.name,tier:orgFlow.tier,settings:{}});orgFlow.lastResult=r;if(r.ok){orgFlow.apiKey=r.data.api_key||'';orgFlow.orgData=r.data.organization||r.data;orgFlow.step=1;showToast('Org created!');}else showToast('Failed: '+(r.data.detail||r.status),false);}catch(e){showToast(e.message,false);}orgFlow.loading=false;}
967
- async function orgFlowVerify(){orgFlow.loading=true;try{const r=await api('GET','/organizations/'+orgFlow.orgId);orgFlow.lastResult=r;if(r.ok){orgFlow.verifyData=r.data;orgFlow.step=3;showToast('Verified!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}orgFlow.loading=false;}
968
- async function orgFlowRotate(){orgFlow.loading=true;try{const r=await api('POST','/organizations/'+orgFlow.orgId+'/api-keys/rotate');orgFlow.lastResult=r;if(r.ok){orgFlow.newKey=r.data.api_key||JSON.stringify(r.data);showToast('Rotated!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}orgFlow.loading=false;}
969
-
970
- // ── Doc Flow ──
971
- const docFlow=reactive({step:0,steps:['Select Org','Upload','Query','Data Chat'],orgId:'',loading:false,uploadResult:null,query:'',model:'gemini-pro',strategy:'hybrid',answer:'',chatHistory:[],chatInput:'',lastResult:null});
972
- async function docFlowUpload(){const el=document.getElementById('docFileInput');if(!el||!el.files.length){showToast('Select a file',false);return;}docFlow.loading=true;try{const form=new FormData();form.append('file',el.files[0]);const r=await api('POST','/organizations/'+docFlow.orgId+'/documents/upload',form);docFlow.lastResult=r;if(r.ok){docFlow.uploadResult=r.data;docFlow.step=2;showToast('Uploaded!');}else showToast('Upload failed',false);}catch(e){showToast(e.message,false);}docFlow.loading=false;}
973
- async function docFlowQuery(){docFlow.loading=true;try{const r=await api('POST','/organizations/'+docFlow.orgId+'/query',{query:docFlow.query,model_type:docFlow.model,retrieval_strategy:docFlow.strategy});docFlow.lastResult=r;if(r.ok){docFlow.answer=r.data.answer||JSON.stringify(r.data,null,2);showToast('Answer received!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}docFlow.loading=false;}
974
- async function docFlowChat(){if(!docFlow.chatInput.trim())return;const msg=docFlow.chatInput;docFlow.chatInput='';docFlow.chatHistory.push({role:'user',text:msg});docFlow.loading=true;try{const r=await api('POST','/organizations/'+docFlow.orgId+'/data-chat',{question:msg});docFlow.lastResult=r;docFlow.chatHistory.push({role:'ai',text:r.ok?(r.data.answer||JSON.stringify(r.data)):'Error: '+r.status});}catch(e){docFlow.chatHistory.push({role:'ai',text:'Error: '+e.message});}docFlow.loading=false;await nextTick();const el=document.querySelector('[ref=chatScroll]')||document.querySelector('.h-64.overflow-y-auto');if(el)el.scrollTop=el.scrollHeight;}
975
-
976
- // ── Gen Flow ──
977
- const genFlow=reactive({step:0,steps:['Type','Configure','Track'],orgId:'',type:'',prompt:'',aspectRatio:'16:9',duration:'8',numImages:'1',style:'',loading:false,jobId:'',jobStatus:'',jobResult:null,lastResult:null});
978
- async function genFlowSubmit(){genFlow.loading=true;try{const body=genFlow.type==='video'?{prompt:genFlow.prompt,duration:parseInt(genFlow.duration||'8'),aspect_ratio:genFlow.aspectRatio,style:genFlow.style||undefined}:{prompt:genFlow.prompt,aspect_ratio:genFlow.aspectRatio,num_images:parseInt(genFlow.numImages||'1'),style:genFlow.style||undefined};const r=await api('POST','/organizations/'+genFlow.orgId+'/generate/'+genFlow.type,body);genFlow.lastResult=r;if(r.ok){genFlow.jobId=r.data.job_id||'';genFlow.jobStatus=r.data.status||'queued';genFlow.step=2;showToast('Submitted!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}genFlow.loading=false;}
979
- async function genFlowPoll(){genFlow.loading=true;try{const r=await api('GET','/organizations/'+genFlow.orgId+'/jobs/'+genFlow.jobId);genFlow.lastResult=r;if(r.ok){genFlow.jobStatus=r.data.status||'unknown';genFlow.jobResult=r.data;if(genFlow.jobStatus==='completed'||genFlow.jobStatus==='failed'){stopPoll('gen');showToast(genFlow.jobStatus==='completed'?'Job completed!':'Job failed',genFlow.jobStatus==='completed');}}}catch(e){showToast(e.message,false);}genFlow.loading=false;}
980
-
981
- // ── Blueprint Flow ──
982
- const bpFlow=reactive({step:0,steps:['Create BP','Publish','Create Doc','AI Generate','Workflow'],orgId:'',name:'',category:'',description:'',sectionsJson:'',loading:false,blueprintId:'',docTitle:'',docDesc:'',docId:'',docSections:[],manualSectionId:'',genContext:'{}',wfAction:'submit',wfComment:'',lastResult:null});
983
- async function bpFlowCreate(){bpFlow.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/blueprints',{name:bpFlow.name,category:bpFlow.category,description:bpFlow.description,sections:tryJson(bpFlow.sectionsJson)||[],default_workflow:'standard_approval'});bpFlow.lastResult=r;if(r.ok){bpFlow.blueprintId=r.data.id||r.data.blueprint_id||'';bpFlow.step=1;showToast('Blueprint created!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}bpFlow.loading=false;}
984
- async function bpFlowPublish(){bpFlow.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/blueprints/'+bpFlow.blueprintId+'/publish',{});bpFlow.lastResult=r;if(r.ok){bpFlow.step=2;showToast('Published!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}bpFlow.loading=false;}
985
- async function bpFlowCreateDoc(){bpFlow.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/documents/create',{blueprint_id:bpFlow.blueprintId,title:bpFlow.docTitle,description:bpFlow.docDesc});bpFlow.lastResult=r;if(r.ok){bpFlow.docId=r.data.id||r.data.document_id||'';bpFlow.docSections=(r.data.sections||[]).map(s=>({...s,id:s.id||s.section_id,generated:false,loading:false}));bpFlow.step=3;showToast('Document created!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}bpFlow.loading=false;}
986
- async function bpFlowGenSection(sec){sec.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/documents/managed/'+bpFlow.docId+'/generate',{section_id:sec.id,context:tryJson(bpFlow.genContext)||{}});bpFlow.lastResult=r;if(r.ok){sec.generated=true;showToast('Generated!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}sec.loading=false;}
987
- async function bpFlowGenManual(){bpFlow.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/documents/managed/'+bpFlow.docId+'/generate',{section_id:bpFlow.manualSectionId,context:tryJson(bpFlow.genContext)||{}});bpFlow.lastResult=r;if(r.ok)showToast('Generated!');else showToast('Failed',false);}catch(e){showToast(e.message,false);}bpFlow.loading=false;}
988
- async function bpFlowWorkflow(){bpFlow.loading=true;try{const r=await api('POST','/organizations/'+bpFlow.orgId+'/documents/managed/'+bpFlow.docId+'/workflow',{action:bpFlow.wfAction,comment:bpFlow.wfComment});bpFlow.lastResult=r;if(r.ok)showToast('Workflow executed!');else showToast('Failed',false);}catch(e){showToast(e.message,false);}bpFlow.loading=false;}
989
-
990
- // ── Jira Flow ──
991
- const jiraFlow=reactive({step:0,steps:['Connect','Status','Sync','Manage'],orgId:'',siteUrl:'',email:'',apiToken:'',projectKeys:'',loading:false,statusData:null,lastResult:null});
992
- async function jiraFlowConnect(){jiraFlow.loading=true;try{const r=await api('POST','/organizations/'+jiraFlow.orgId+'/integrations/jira/connect',{site_url:jiraFlow.siteUrl,email:jiraFlow.email,api_token:jiraFlow.apiToken});jiraFlow.lastResult=r;if(r.ok){jiraFlow.step=1;await jiraFlowStatus();showToast('Connected!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}jiraFlow.loading=false;}
993
- async function jiraFlowStatus(){jiraFlow.loading=true;try{const r=await api('GET','/organizations/'+jiraFlow.orgId+'/integrations/jira/status');jiraFlow.lastResult=r;if(r.ok)jiraFlow.statusData=r.data;}catch(e){showToast(e.message,false);}jiraFlow.loading=false;}
994
- async function jiraFlowSync(){jiraFlow.loading=true;try{const keys=jiraFlow.projectKeys.split(',').map(k=>k.trim()).filter(Boolean);const r=await api('POST','/organizations/'+jiraFlow.orgId+'/integrations/jira/sync',{project_keys:keys});jiraFlow.lastResult=r;if(r.ok){jiraFlow.step=3;showToast('Sync started!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}jiraFlow.loading=false;}
995
- async function jiraFlowDisconnect(){jiraFlow.loading=true;try{const r=await api('DELETE','/organizations/'+jiraFlow.orgId+'/integrations/jira');jiraFlow.lastResult=r;if(r.ok){jiraFlow.step=0;showToast('Disconnected');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}jiraFlow.loading=false;}
996
-
997
- // ── Scrape Flow ──
998
- const scrapeFlow=reactive({step:0,steps:['Submit','Monitor'],urls:'',loading:false,loading2:false,configData:null,taskId:'',status:'',taskResult:null,lastResult:null});
999
- async function scrapeFlowConfig(){scrapeFlow.loading2=true;try{const r=await api('GET','/scraping/config');scrapeFlow.configData=r.data;}catch{}scrapeFlow.loading2=false;}
1000
- async function scrapeFlowSubmit(){scrapeFlow.loading=true;try{const urls=scrapeFlow.urls.split('\\n').map(u=>u.trim()).filter(Boolean);const r=await api('POST','/scraping/scrape',{urls});scrapeFlow.lastResult=r;if(r.ok){scrapeFlow.taskId=r.data.task_id||'';scrapeFlow.status=r.data.status||'queued';scrapeFlow.step=1;showToast('Submitted!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}scrapeFlow.loading=false;}
1001
- async function scrapeFlowPoll(){scrapeFlow.loading=true;try{const r=await api('GET','/scraping/status/'+scrapeFlow.taskId);scrapeFlow.lastResult=r;if(r.ok){scrapeFlow.status=r.data.status||'unknown';scrapeFlow.taskResult=r.data;if(scrapeFlow.status==='completed'||scrapeFlow.status==='failed'){stopPoll('scrape');showToast(scrapeFlow.status==='completed'?'Scraping completed!':'Scraping failed',scrapeFlow.status==='completed');}}}catch(e){showToast(e.message,false);}scrapeFlow.loading=false;}
1002
-
1003
- // ── Agent Flow ──
1004
- const agentFlow=reactive({step:0,steps:['Create','Chat'],orgId:'',name:'',instructions:'',tools:'',agentType:'auto',chatHistory:[],chatInput:'',loading:false,lastResult:null});
1005
- async function agentFlowCreate(){agentFlow.loading=true;try{const r=await api('POST','/organizations/'+agentFlow.orgId+'/agents/custom',{name:agentFlow.name,instructions:agentFlow.instructions,tools:tryJson(agentFlow.tools)||[]});agentFlow.lastResult=r;if(r.ok){agentFlow.step=1;showToast('Agent created!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}agentFlow.loading=false;}
1006
- async function agentFlowQuery(){if(!agentFlow.chatInput.trim())return;const msg=agentFlow.chatInput;agentFlow.chatInput='';agentFlow.chatHistory.push({role:'user',text:msg});agentFlow.loading=true;try{const r=await api('POST','/organizations/'+agentFlow.orgId+'/agents/query',{query:msg,agent_type:agentFlow.agentType||'auto'});agentFlow.lastResult=r;agentFlow.chatHistory.push({role:'ai',text:r.ok?(r.data.response||r.data.answer||JSON.stringify(r.data)):'Error: '+r.status});}catch(e){agentFlow.chatHistory.push({role:'ai',text:'Error: '+e.message});}agentFlow.loading=false;await nextTick();const el=document.querySelector('.h-72.overflow-y-auto');if(el)el.scrollTop=el.scrollHeight;}
1007
-
1008
- // ── ML Flow ──
1009
- const mlFlow=reactive({step:0,steps:['Train','Predict','Forecast'],orgId:'',target:'',features:'',rows:'',modelId:'',instances:'',series:'',horizon:'30',loading:false,lastResult:null});
1010
- async function mlFlowTrain(){mlFlow.loading=true;try{const r=await api('POST','/organizations/'+mlFlow.orgId+'/analytics/train',{target_column:mlFlow.target,feature_columns:tryJson(mlFlow.features)||[],rows:tryJson(mlFlow.rows)||[]});mlFlow.lastResult=r;if(r.ok){mlFlow.modelId=r.data.model_id||'';mlFlow.step=1;showToast('Trained!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}mlFlow.loading=false;}
1011
- async function mlFlowPredict(){mlFlow.loading=true;try{const r=await api('POST','/organizations/'+mlFlow.orgId+'/analytics/predict',{model_id:mlFlow.modelId,instances:tryJson(mlFlow.instances)||[]});mlFlow.lastResult=r;if(r.ok)showToast('Predicted!');else showToast('Failed',false);}catch(e){showToast(e.message,false);}mlFlow.loading=false;}
1012
- async function mlFlowForecast(){mlFlow.loading=true;try{const r=await api('POST','/organizations/'+mlFlow.orgId+'/analytics/forecast',{series:tryJson(mlFlow.series)||[],horizon:parseInt(mlFlow.horizon||'30')});mlFlow.lastResult=r;if(r.ok)showToast('Forecast ready!');else showToast('Failed',false);}catch(e){showToast(e.message,false);}mlFlow.loading=false;}
1013
-
1014
- // ── Audio Flow ──
1015
- const audioFlow=reactive({step:0,steps:['Transcribe','Synthesize'],orgId:'',audioText:'',lang:'en-US',synthText:'',voice:'en-US-Neural2-J',loading:false,lastResult:null});
1016
- async function audioFlowTranscribe(){audioFlow.loading=true;try{const r=await api('POST','/organizations/'+audioFlow.orgId+'/audio/transcribe',{audio_text:audioFlow.audioText,language_code:audioFlow.lang});audioFlow.lastResult=r;if(r.ok){audioFlow.step=1;showToast('Transcribed!');}else showToast('Failed',false);}catch(e){showToast(e.message,false);}audioFlow.loading=false;}
1017
- async function audioFlowSynthesize(){audioFlow.loading=true;try{const r=await api('POST','/organizations/'+audioFlow.orgId+'/audio/synthesize',{text:audioFlow.synthText,voice_name:audioFlow.voice});audioFlow.lastResult=r;if(r.ok)showToast('Synthesized!');else showToast('Failed',false);}catch(e){showToast(e.message,false);}audioFlow.loading=false;}
1018
-
1019
- // ── Billing Flow ──
1020
- const billingFlow=reactive({orgId:'',month:'',loadingU:false,loadingI:false,loadingA:false,usageData:null,invoiceData:null,auditData:null});
1021
- async function billingFlowUsage(){billingFlow.loadingU=true;try{const r=await api('GET','/organizations/'+billingFlow.orgId+'/usage'+(billingFlow.month?'?month='+billingFlow.month:''));billingFlow.usageData=r.data;}catch{}billingFlow.loadingU=false;}
1022
- async function billingFlowInvoice(){billingFlow.loadingI=true;try{const r=await api('GET','/organizations/'+billingFlow.orgId+'/invoice'+(billingFlow.month?'?month='+billingFlow.month:''));billingFlow.invoiceData=r.data;}catch{}billingFlow.loadingI=false;}
1023
- async function billingFlowAudit(){billingFlow.loadingA=true;try{const r=await api('GET','/organizations/'+billingFlow.orgId+'/audit');billingFlow.auditData=r.data;}catch{}billingFlow.loadingA=false;}
1024
-
1025
- // ── Admin ──
1026
- const adminWidgets=reactive([
1027
- {id:'sys',title:'System',icon:'mdi-server',color:'text-blue-400',endpoint:'/admin/system',loading:false,data:null},
1028
- {id:'cfg',title:'Config',icon:'mdi-cog',color:'text-purple-400',endpoint:'/admin/config',loading:false,data:null},
1029
- {id:'trf',title:'Traffic',icon:'mdi-swap-vertical',color:'text-green-400',endpoint:'/admin/traffic/inbound',loading:false,data:null},
1030
- {id:'cch',title:'Cache',icon:'mdi-database',color:'text-amber-400',endpoint:'/admin/cache',loading:false,data:null},
1031
- {id:'flg',title:'Feature Flags',icon:'mdi-flag',color:'text-pink-400',endpoint:'/admin/feature-flags',loading:false,data:null},
1032
- {id:'mnt',title:'Maintenance',icon:'mdi-wrench',color:'text-orange-400',endpoint:'/admin/maintenance',loading:false,data:null},
1033
- {id:'trs',title:'Tiers',icon:'mdi-layers',color:'text-cyan-400',endpoint:'/admin/tiers',loading:false,data:null},
1034
- {id:'prc',title:'Pricing',icon:'mdi-tag',color:'text-emerald-400',endpoint:'/admin/pricing',loading:false,data:null},
1035
- {id:'rts',title:'Rate Limits',icon:'mdi-speedometer',color:'text-red-400',endpoint:'/admin/rate-limits',loading:false,data:null},
1036
- ]);
1037
- async function adminFetch(w){w.loading=true;try{const r=await api('GET',w.endpoint);w.data=r.data;}catch(e){w.data={error:e.message};}w.loading=false;}
1038
-
1039
- const adminActions=reactive([
1040
- {id:'a1',title:'Update Setting',icon:'mdi-pencil',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[qf('key','Key','log_level'),qf('value','Value','DEBUG')],values:{},fn:v=>api('PUT','/admin/settings',{key:v.key,value:v.value})},
1041
- {id:'a2',title:'Set Log Level',icon:'mdi-format-list-bulleted',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[qf('level','Level','DEBUG')],values:{},fn:v=>api('PUT','/admin/logging/level',{level:v.level})},
1042
- {id:'a3',title:'Update CORS',icon:'mdi-web',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[qf('origins','Allow Origins','*')],values:{},fn:v=>api('PUT','/admin/cors',{allow_origins:v.origins})},
1043
- {id:'a4',title:'Maintenance Mode',icon:'mdi-wrench',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[qf('enabled','Enabled','true'),qf('message','Message','')],values:{},fn:v=>api('PUT','/admin/maintenance',{enabled:v.enabled==='true',message:v.message||undefined})},
1044
- {id:'a5',title:'Flush Cache',icon:'mdi-delete-sweep',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[],values:{},fn:()=>api('POST','/admin/cache/flush')},
1045
- {id:'a6',title:'Set Feature Flag',icon:'mdi-flag',open:false,loading:false,result:null,resultOk:false,timing:0,fields:[qf('key','Flag Key','new_feature'),qf('enabled','Enabled','true')],values:{},fn:v=>api('PUT','/admin/feature-flags',{key:v.key,enabled:v.enabled==='true'})},
1046
- ]);
1047
- async function adminRunAction(act){act.loading=true;act.result=null;try{const r=await act.fn(act.values);act.resultOk=r.ok;act.timing=r.timing||0;act.result=JSON.stringify(r.data,null,2);}catch(e){act.resultOk=false;act.result='Error: '+e.message;}act.loading=false;}
1048
-
1049
- // ── Keyboard Shortcuts (global) ──
1050
- onMounted(()=>{
1051
- checkHealth();setInterval(checkHealth,30000);
1052
- document.addEventListener('keydown',e=>{
1053
- if(['INPUT','TEXTAREA','SELECT'].includes(e.target.tagName)){if(e.key==='Escape')e.target.blur();return;}
1054
- if(e.key==='k'&&(e.ctrlKey||e.metaKey)){e.preventDefault();cmdOpen.value=!cmdOpen.value;}
1055
- else if(e.key==='?'){showShortcuts.value=!showShortcuts.value;}
1056
- else if(e.key==='Escape'){if(cmdOpen.value)cmdOpen.value=false;else if(jsonV.open)jsonV.open=false;else if(cfmModal.open)cfmModal.open=false;else if(showShortcuts.value)showShortcuts.value=false;else if(logOpen.value)logOpen.value=false;else if(currentView.value!=='home')navigate('home');}
1057
- else if(e.key==='\\\\'&&e.ctrlKey){e.preventDefault();sidebarOpen.value=!sidebarOpen.value;}
1058
- else if(e.key==='l'&&e.ctrlKey){e.preventDefault();logOpen.value=!logOpen.value;}
1059
- else if(e.key==='h'&&!e.ctrlKey&&!e.metaKey){navigate('home');}
1060
- });
534
+ document.getElementById("runSearch").onclick = () => apiRequest("POST", "/search", {
535
+ auth: "api",
536
+ json: {
537
+ query: document.getElementById("searchQuery").value,
538
+ type: document.getElementById("searchType").value || undefined,
539
+ num: Number(document.getElementById("searchNum").value || 5),
540
+ },
1061
541
  });
1062
542
 
1063
- return{cfg,settingsFields,settingsMsg,settingsMsgOk,settingsLoading,saveSettings,health,toasts,showToast,rmToast,copyText,
1064
- currentView,search,sidebarOpen,breadcrumb,navigate,navSections,flows,homeStats,
1065
- cmdOpen,cmdQ,cmdI,cmdResults,cmdExec,
1066
- cfmModal,confirmAction,jsonV,openJsonViewer,tryJson,
1067
- logOpen,actLog,showShortcuts,shortcuts,
1068
- dragActive,onDragOver,onDragLeave,onDrop,
1069
- pollMap,startPoll,stopPoll,isPolling,
1070
- quickCat,quickCategories,quickActions,filteredQuickActions,methodColor,runQuickAction,
1071
- orgFlow,orgFlowCreate,orgFlowVerify,orgFlowRotate,
1072
- docFlow,docFlowUpload,docFlowQuery,docFlowChat,
1073
- genFlow,genFlowSubmit,genFlowPoll,
1074
- bpFlow,bpFlowCreate,bpFlowPublish,bpFlowCreateDoc,bpFlowGenSection,bpFlowGenManual,bpFlowWorkflow,
1075
- jiraFlow,jiraFlowConnect,jiraFlowStatus,jiraFlowSync,jiraFlowDisconnect,
1076
- scrapeFlow,scrapeFlowConfig,scrapeFlowSubmit,scrapeFlowPoll,
1077
- agentFlow,agentFlowCreate,agentFlowQuery,
1078
- mlFlow,mlFlowTrain,mlFlowPredict,mlFlowForecast,
1079
- audioFlow,audioFlowTranscribe,audioFlowSynthesize,
1080
- billingFlow,billingFlowUsage,billingFlowInvoice,billingFlowAudit,
1081
- adminWidgets,adminFetch,adminActions,adminRunAction};
1082
- }
1083
- })
1084
- .component('flow-header',{props:['title','desc','step','steps'],template:\`
1085
- <div class="mb-6">
1086
- <div class="flex items-center gap-3 mb-1">
1087
- <button @click="$root.navigate('home')" class="text-gray-500 hover:text-white transition"><span class="mdi mdi-arrow-left"></span></button>
1088
- <h1 class="text-2xl font-bold text-white">{{title}}</h1>
1089
- </div>
1090
- <p class="text-gray-400 text-sm mb-4 ml-8">{{desc}}</p>
1091
- <div class="flex items-center gap-1 mb-2 ml-8">
1092
- <div v-for="(s,i) in steps" :key="i" style="display:contents">
1093
- <div class="flex items-center gap-1 cursor-pointer" @click="$emit('jump',i)">
1094
- <div class="w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold transition-all"
1095
- :class="i<step?'bg-green-500 text-white scale-90':i===step?'bg-brand text-white ring-2 ring-brand/30 scale-110':'bg-gray-700 text-gray-500'">
1096
- <span v-if="i<step" class="mdi mdi-check"></span><span v-else>{{i+1}}</span>
1097
- </div>
1098
- <span class="text-xs hidden sm:inline transition-colors" :class="i===step?'text-white font-semibold':'text-gray-500'">{{s}}</span>
1099
- </div>
1100
- <div v-if="i<steps.length-1" class="flex-1 h-0.5 mx-1 rounded transition-all" :class="i<step?'bg-green-500':'bg-gray-700'"></div>
1101
- </div>
1102
- </div>
1103
- </div>\`})
1104
- .component('step-card',{props:['title','subtitle'],template:\`
1105
- <div class="bg-surface border border-border rounded-xl p-6 mb-4 animate-fade-up card-hover">
1106
- <h3 class="text-lg font-semibold text-white mb-1">{{title}}</h3>
1107
- <p class="text-xs text-gray-500 mb-4">{{subtitle}}</p>
1108
- <slot></slot>
1109
- </div>\`})
1110
- .component('text-field',{props:['label','modelValue','placeholder','disabled','type'],emits:['update:modelValue'],template:\`
1111
- <div class="mt-2">
1112
- <label class="text-xs text-gray-500 uppercase tracking-wide">{{label}}</label>
1113
- <input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" :type="type||'text'" :placeholder="placeholder" :disabled="disabled"
1114
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 focus:ring-1 focus:ring-brand/20 disabled:opacity-50 transition"/>
1115
- </div>\`})
1116
- .component('text-area',{props:['label','modelValue','placeholder'],emits:['update:modelValue'],template:\`
1117
- <div class="mt-2">
1118
- <label class="text-xs text-gray-500 uppercase tracking-wide">{{label}}</label>
1119
- <textarea :value="modelValue" @input="$emit('update:modelValue',$event.target.value)" :placeholder="placeholder" rows="3"
1120
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 focus:ring-1 focus:ring-brand/20 resize-y transition"></textarea>
1121
- </div>\`})
1122
- .component('select-field',{props:['label','modelValue','options'],emits:['update:modelValue'],template:\`
1123
- <div class="mt-2">
1124
- <label class="text-xs text-gray-500 uppercase tracking-wide">{{label}}</label>
1125
- <select :value="modelValue" @change="$emit('update:modelValue',$event.target.value)"
1126
- class="mt-1 w-full bg-bg border border-border rounded-lg px-3 py-2 text-sm text-gray-300 focus:outline-none focus:border-brand/50 transition">
1127
- <option v-for="o in options" :key="o" :value="o">{{o}}</option>
1128
- </select>
1129
- </div>\`})
1130
- .component('flow-btn',{props:{variant:{default:'primary'},loading:{default:false},disabled:{default:false},size:{default:'md'}},template:\`
1131
- <button @click="$emit('click')" :disabled="loading||disabled"
1132
- class="rounded-lg font-semibold transition-all active:scale-95 disabled:opacity-50 ripple"
1133
- :class="[variant==='primary'?'bg-brand text-white hover:bg-brand/80 hover:shadow-lg hover:shadow-brand/20':variant==='secondary'?'bg-white/5 text-gray-300 border border-border hover:bg-white/10':variant==='danger'?'bg-red-600 text-white hover:bg-red-700 hover:shadow-lg hover:shadow-red/20':variant==='warn'?'bg-amber-600 text-white hover:bg-amber-700':'',size==='sm'?'px-3 py-1.5 text-xs':'px-5 py-2 text-sm']">
1134
- <span v-if="loading" class="mdi mdi-loading mdi-spin mr-1"></span><slot></slot>
1135
- </button>\`})
1136
- .component('kv-display',{props:['items'],template:\`
1137
- <div class="bg-bg rounded-lg p-3 border border-border">
1138
- <div v-for="(v,k) in items" :key="k" class="flex justify-between py-1.5 text-xs border-b border-border/50 last:border-0 hover:bg-white/5 rounded px-1 -mx-1 transition cursor-default group">
1139
- <span class="text-gray-500">{{k}}</span>
1140
- <span class="text-gray-300 font-mono text-right max-w-[60%] truncate group-hover:text-white transition" :title="String(v)">{{typeof v==='object'?JSON.stringify(v):v}}</span>
1141
- </div>
1142
- </div>\`})
1143
- .component('flow-result',{props:['result'],template:\`
1144
- <div v-if="result" class="mt-4">
1145
- <button @click="$root.openJsonViewer(result.data,result.timing)" class="text-xs text-gray-500 hover:text-brand transition flex items-center gap-1">
1146
- <span class="mdi mdi-code-json"></span>
1147
- View full response
1148
- <span v-if="result.timing" class="text-gray-600 ml-1">{{result.timing}}ms</span>
1149
- <span class="w-2 h-2 rounded-full ml-1" :class="result.ok?'bg-green-500':'bg-red-500'"></span>
1150
- </button>
1151
- </div>\`})
1152
- .component('json-tree',{props:['data','depth'],template:\`
1153
- <div :style="{paddingLeft:(depth*16)+'px'}" class="text-xs font-mono">
1154
- <div v-if="typeof data==='object'&&data!==null">
1155
- <div v-for="(v,k) in data" :key="k" class="py-0.5">
1156
- <div v-if="typeof v==='object'&&v!==null">
1157
- <details :open="depth<2">
1158
- <summary class="cursor-pointer hover:bg-white/5 rounded px-1 -mx-1 transition select-none">
1159
- <span class="jt-key">"{{k}}"</span><span class="text-gray-600">: {{Array.isArray(v)?'['+v.length+']':'{'+Object.keys(v).length+'}'}}</span>
1160
- </summary>
1161
- <json-tree :data="v" :depth="depth+1"></json-tree>
1162
- </details>
1163
- </div>
1164
- <div v-else>
1165
- <div class="hover:bg-white/5 rounded px-1 -mx-1 transition cursor-default">
1166
- <span class="jt-key">"{{k}}"</span><span class="text-gray-600">: </span>
1167
- <span :class="typeof v==='string'?'jt-str':typeof v==='number'?'jt-num':typeof v==='boolean'?'jt-bool':'jt-null'">{{typeof v==='string'?'"'+v+'"':String(v)}}</span>
1168
- </div>
1169
- </div>
1170
- </div>
1171
- </div>
1172
- <div v-else>
1173
- <span :class="typeof data==='string'?'jt-str':typeof data==='number'?'jt-num':typeof data==='boolean'?'jt-bool':'jt-null'">{{typeof data==='string'?'"'+data+'"':String(data)}}</span>
1174
- </div>
1175
- </div>\`})
1176
- .mount('#app');
1177
- <\/script>
543
+ document.getElementById("uploadDocument").onclick = async () => {
544
+ const fileInput = document.getElementById("documentFile");
545
+ const file = fileInput.files && fileInput.files[0];
546
+ if (!file) {
547
+ setOutput("Upload Document", "Select a file first.");
548
+ return;
549
+ }
550
+ const formData = new FormData();
551
+ formData.append("file", file);
552
+ await apiRequest("POST", "/documents/upload", { auth: "api", formData });
553
+ };
554
+
555
+ document.getElementById("createProject").onclick = () => apiRequest("POST", "/projects", {
556
+ auth: "api",
557
+ json: {
558
+ name: document.getElementById("projectName").value,
559
+ description: document.getElementById("projectDescription").value,
560
+ },
561
+ });
562
+ document.getElementById("listProjects").onclick = () => apiRequest("GET", "/projects", { auth: "api" });
563
+ document.getElementById("chatProject").onclick = () => {
564
+ const projectId = document.getElementById("projectId").value.trim();
565
+ if (!projectId) {
566
+ setOutput("Chat Project", "Provide a project id first.");
567
+ return;
568
+ }
569
+ return apiRequest("POST", "/projects/" + encodeURIComponent(projectId) + "/chat", {
570
+ auth: "api",
571
+ json: {
572
+ message: document.getElementById("projectMessage").value,
573
+ user_id: document.getElementById("projectUserId").value || "sdk-dashboard",
574
+ },
575
+ });
576
+ };
577
+
578
+ document.getElementById("startGeneration").onclick = () => {
579
+ const type = document.getElementById("generationType").value;
580
+ const prompt = document.getElementById("generationPrompt").value;
581
+ const flavor = document.getElementById("generationFlavor").value;
582
+ const amount = document.getElementById("generationAmount").value;
583
+ let body;
584
+ if (type === "music") {
585
+ body = { genre: prompt, mood: flavor || "uplifting", duration: Number(amount || 30) };
586
+ } else if (type === "image") {
587
+ body = { prompt, style: flavor || undefined, num_images: Number(amount || 1) };
588
+ } else {
589
+ body = { prompt, style: flavor || undefined, duration: Number(amount || 8) };
590
+ }
591
+ return apiRequest("POST", "/generate/" + type, { auth: "api", json: body });
592
+ };
593
+
594
+ document.getElementById("getJob").onclick = () => {
595
+ const jobId = document.getElementById("jobId").value.trim();
596
+ if (!jobId) {
597
+ setOutput("Get Job", "Provide a job id first.");
598
+ return;
599
+ }
600
+ return apiRequest("GET", "/jobs/" + encodeURIComponent(jobId), { auth: "api" });
601
+ };
602
+
603
+ document.getElementById("submitScrape").onclick = () => {
604
+ const urls = document.getElementById("scrapeUrls").value
605
+ .split("\\n")
606
+ .map((item) => item.trim())
607
+ .filter(Boolean);
608
+ return apiRequest("POST", "/scraping/scrape", { auth: "none", json: { urls } });
609
+ };
610
+
611
+ document.getElementById("getScrapeStatus").onclick = () => {
612
+ const taskId = document.getElementById("scrapeTaskId").value.trim();
613
+ if (!taskId) {
614
+ setOutput("Scrape Status", "Provide a task id first.");
615
+ return;
616
+ }
617
+ return apiRequest("GET", "/scraping/status/" + encodeURIComponent(taskId), { auth: "none" });
618
+ };
619
+
620
+ document.getElementById("getRateLimits").onclick = () => apiRequest("GET", "/admin/rate-limits", { auth: "admin" });
621
+ document.getElementById("getJobs").onclick = () => apiRequest("GET", "/admin/jobs", { auth: "admin" });
622
+ document.getElementById("runSchedules").onclick = () => apiRequest("POST", "/admin/content-creator/schedules/run-due", { auth: "admin" });
623
+ document.getElementById("getAudit").onclick = () => apiRequest("GET", "/admin/audit", { auth: "admin" });
624
+
625
+ document.getElementById("sendManual").onclick = () => {
626
+ let body;
627
+ const raw = document.getElementById("manualBody").value.trim();
628
+ if (raw) {
629
+ try {
630
+ body = JSON.parse(raw);
631
+ } catch (err) {
632
+ setOutput("Manual Request", "Body must be valid JSON: " + err.message);
633
+ return;
634
+ }
635
+ }
636
+ return apiRequest(
637
+ document.getElementById("manualMethod").value,
638
+ document.getElementById("manualPath").value,
639
+ {
640
+ auth: document.getElementById("manualAuth").value,
641
+ json: body,
642
+ },
643
+ );
644
+ };
645
+
646
+ healthCheck();
647
+ </script>
1178
648
  </body>
1179
649
  </html>`;
1180
650
  }
@@ -1185,7 +655,6 @@ function startWebServer(opts) {
1185
655
  apiKey: opts.apiKey,
1186
656
  adminKey: opts.adminKey,
1187
657
  webhookSecret: opts.webhookSecret,
1188
- employeeId: opts.employeeId,
1189
658
  });
1190
659
  const server = node_http_1.default.createServer((_req, res) => {
1191
660
  res.writeHead(200, {
@@ -1197,7 +666,7 @@ function startWebServer(opts) {
1197
666
  server.listen(opts.port, "127.0.0.1", () => {
1198
667
  const addr = server.address();
1199
668
  const url = `http://127.0.0.1:${addr.port}`;
1200
- console.log(`\n Hivemind Dashboard running at ${url}\n`);
669
+ console.log(`\n Hivemind SDK dashboard running at ${url}\n`);
1201
670
  console.log(" Press Ctrl+C to stop.\n");
1202
671
  if (opts.open) {
1203
672
  const { exec } = require("node:child_process");