@ai2aim.ai/hivemind-sdk 1.0.15 → 3.1.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/README.md +175 -803
- package/dist/cli-config.d.ts.map +1 -1
- package/dist/cli-config.js +0 -3
- package/dist/cli-config.js.map +1 -1
- package/dist/cli.js +191 -204
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +52 -1166
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +260 -1598
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +139 -833
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/web.d.ts +0 -9
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +714 -1119
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
- package/dist/ws.d.ts +0 -33
- package/dist/ws.d.ts.map +0 -1
- package/dist/ws.js +0 -78
- package/dist/ws.js.map +0 -1
package/dist/web.js
CHANGED
|
@@ -1,1180 +1,776 @@
|
|
|
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 = (
|
|
21
|
-
return
|
|
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"
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<link href="https://
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
<style>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
60
|
-
<div
|
|
61
|
-
<
|
|
62
|
-
<div class="
|
|
63
|
-
|
|
64
|
-
|
|
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>
|
|
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>
|
|
79
208
|
</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
209
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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>
|
|
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>
|
|
144
218
|
</div>
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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"/>
|
|
160
|
-
</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
|
-
</
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
</
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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>Search</h2>
|
|
246
|
+
<p class="sub">Use the active API key to run discovery search across the current scope.</p>
|
|
247
|
+
<div class="form">
|
|
248
|
+
<div class="row">
|
|
249
|
+
<label>Search Query<input id="searchQuery" value="content marketing trends 2026"></label>
|
|
250
|
+
<label>Search Type<input id="searchType" value="organic"></label>
|
|
289
251
|
</div>
|
|
290
|
-
<div class="
|
|
291
|
-
<input
|
|
292
|
-
<
|
|
252
|
+
<div class="row">
|
|
253
|
+
<label>Max Results<input id="searchNum" type="number" min="1" max="25" value="5"></label>
|
|
254
|
+
<div></div>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="actions">
|
|
257
|
+
<button id="runSearch">Run Search</button>
|
|
293
258
|
</div>
|
|
294
259
|
</div>
|
|
295
|
-
</
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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>
|
|
260
|
+
</section>
|
|
261
|
+
|
|
262
|
+
<section class="card">
|
|
263
|
+
<h2>Documents</h2>
|
|
264
|
+
<p class="sub">Upload knowledge files into the current API key scope.</p>
|
|
265
|
+
<div class="form">
|
|
266
|
+
<label>File<input id="documentFile" type="file"></label>
|
|
267
|
+
<div class="actions">
|
|
268
|
+
<button id="uploadDocument">Upload Document</button>
|
|
269
|
+
</div>
|
|
319
270
|
</div>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
<
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<
|
|
328
|
-
|
|
271
|
+
</section>
|
|
272
|
+
|
|
273
|
+
<section class="card">
|
|
274
|
+
<h2>Generation</h2>
|
|
275
|
+
<p class="sub">Start video, image, or music jobs and poll by job id.</p>
|
|
276
|
+
<div class="form">
|
|
277
|
+
<div class="row">
|
|
278
|
+
<label>Type
|
|
279
|
+
<select id="generationType">
|
|
280
|
+
<option value="video">video</option>
|
|
281
|
+
<option value="image">image</option>
|
|
282
|
+
<option value="music">music</option>
|
|
283
|
+
</select>
|
|
284
|
+
</label>
|
|
285
|
+
<label>Job ID<input id="jobId" placeholder="Returned job id"></label>
|
|
329
286
|
</div>
|
|
330
|
-
<
|
|
331
|
-
|
|
332
|
-
<
|
|
333
|
-
<
|
|
287
|
+
<label>Prompt Or Genre<textarea id="generationPrompt">A cinematic product launch teaser with crisp motion graphics.</textarea></label>
|
|
288
|
+
<div class="row">
|
|
289
|
+
<label>Mood / Style<input id="generationFlavor" value="uplifting"></label>
|
|
290
|
+
<label>Duration / Count<input id="generationAmount" value="8"></label>
|
|
334
291
|
</div>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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>
|
|
292
|
+
<div class="actions">
|
|
293
|
+
<button id="startGeneration">Start Job</button>
|
|
294
|
+
<button id="getJob" class="secondary">Get Job</button>
|
|
374
295
|
</div>
|
|
375
296
|
</div>
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
297
|
+
</section>
|
|
298
|
+
|
|
299
|
+
<section class="card">
|
|
300
|
+
<h2>Social Content</h2>
|
|
301
|
+
<p class="sub">Generate platform-tailored content with tone, writing style, SEO review, and guardrail checks.</p>
|
|
302
|
+
<div class="form">
|
|
303
|
+
<label>Prompt<textarea id="socialPrompt">Launch our AI writing assistant for startup marketing teams that need faster campaign drafts and better collaboration.</textarea></label>
|
|
304
|
+
<div class="row">
|
|
305
|
+
<label>Platforms<input id="socialPlatforms" value="x,linkedin,instagram"></label>
|
|
306
|
+
<label>Objective
|
|
307
|
+
<select id="socialObjective">
|
|
308
|
+
<option value="engagement">engagement</option>
|
|
309
|
+
<option value="awareness">awareness</option>
|
|
310
|
+
<option value="clicks">clicks</option>
|
|
311
|
+
<option value="leads">leads</option>
|
|
312
|
+
<option value="conversions">conversions</option>
|
|
313
|
+
<option value="education">education</option>
|
|
314
|
+
<option value="community">community</option>
|
|
315
|
+
<option value="launch">launch</option>
|
|
316
|
+
</select>
|
|
317
|
+
</label>
|
|
381
318
|
</div>
|
|
382
|
-
<div class="
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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>
|
|
319
|
+
<div class="row">
|
|
320
|
+
<label>Tone
|
|
321
|
+
<select id="socialTone">
|
|
322
|
+
<option value="professional">professional</option>
|
|
323
|
+
<option value="friendly">friendly</option>
|
|
324
|
+
<option value="playful">playful</option>
|
|
325
|
+
<option value="bold">bold</option>
|
|
326
|
+
<option value="authoritative">authoritative</option>
|
|
327
|
+
<option value="educational">educational</option>
|
|
328
|
+
<option value="witty">witty</option>
|
|
329
|
+
</select>
|
|
330
|
+
</label>
|
|
331
|
+
<label>Writing Style
|
|
332
|
+
<select id="socialStyle">
|
|
333
|
+
<option value="conversational">conversational</option>
|
|
334
|
+
<option value="storytelling">storytelling</option>
|
|
335
|
+
<option value="direct">direct</option>
|
|
336
|
+
<option value="thought_leadership">thought_leadership</option>
|
|
337
|
+
<option value="persuasive">persuasive</option>
|
|
338
|
+
<option value="community_led">community_led</option>
|
|
339
|
+
<option value="scripted">scripted</option>
|
|
340
|
+
</select>
|
|
341
|
+
</label>
|
|
412
342
|
</div>
|
|
413
|
-
<div class="
|
|
414
|
-
<
|
|
415
|
-
|
|
343
|
+
<div class="row">
|
|
344
|
+
<label>Length
|
|
345
|
+
<select id="socialLength">
|
|
346
|
+
<option value="short">short</option>
|
|
347
|
+
<option value="medium">medium</option>
|
|
348
|
+
<option value="long">long</option>
|
|
349
|
+
</select>
|
|
350
|
+
</label>
|
|
351
|
+
<label>Variations<input id="socialVariations" type="number" min="1" max="5" value="2"></label>
|
|
416
352
|
</div>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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>
|
|
353
|
+
<div class="row">
|
|
354
|
+
<label>Audience<input id="socialAudience" value="startup marketing teams"></label>
|
|
355
|
+
<label>Brand<input id="socialBrand" value="LaunchPilot"></label>
|
|
468
356
|
</div>
|
|
469
|
-
<div class="
|
|
470
|
-
<
|
|
471
|
-
<
|
|
472
|
-
|
|
357
|
+
<div class="row">
|
|
358
|
+
<label>Call To Action<input id="socialCta" value="Book a demo this week."></label>
|
|
359
|
+
<label>Primary Keyword<input id="socialKeyword" value="AI writing assistant"></label>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="row">
|
|
362
|
+
<label>Secondary Keywords<input id="socialSecondary" value="startup marketing,content workflow"></label>
|
|
363
|
+
<label>Hashtags<input id="socialHashtags" value="AIWriting,StartupMarketing"></label>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="row">
|
|
366
|
+
<label>Landing Page URL<input id="socialUrl" value="https://example.com/demo"></label>
|
|
367
|
+
<label>Language<input id="socialLanguage" value="en"></label>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="row">
|
|
370
|
+
<label>Emoji Density
|
|
371
|
+
<select id="socialEmojiDensity">
|
|
372
|
+
<option value="none">none</option>
|
|
373
|
+
<option value="light">light</option>
|
|
374
|
+
<option value="medium">medium</option>
|
|
375
|
+
<option value="heavy">heavy</option>
|
|
376
|
+
</select>
|
|
377
|
+
</label>
|
|
378
|
+
<label>Hashtag Count<input id="socialHashtagCount" type="number" min="0" max="12" value="3"></label>
|
|
379
|
+
</div>
|
|
380
|
+
<div class="row">
|
|
381
|
+
<label>Must Include<input id="socialMustInclude" value="free trial"></label>
|
|
382
|
+
<label>Avoid Terms<input id="socialAvoid" value="guaranteed results"></label>
|
|
383
|
+
</div>
|
|
384
|
+
<label>Compliance Notes<textarea id="socialCompliance">Avoid unverifiable performance claims.</textarea></label>
|
|
385
|
+
<div class="row">
|
|
386
|
+
<label>Include Emojis
|
|
387
|
+
<select id="socialIncludeEmojis">
|
|
388
|
+
<option value="false">false</option>
|
|
389
|
+
<option value="true">true</option>
|
|
390
|
+
</select>
|
|
391
|
+
</label>
|
|
392
|
+
<label>Include Script
|
|
393
|
+
<select id="socialIncludeScript">
|
|
394
|
+
<option value="false">false</option>
|
|
395
|
+
<option value="true">true</option>
|
|
396
|
+
</select>
|
|
397
|
+
</label>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="row">
|
|
400
|
+
<label>Include Title
|
|
401
|
+
<select id="socialIncludeTitle">
|
|
402
|
+
<option value="true">true</option>
|
|
403
|
+
<option value="false">false</option>
|
|
404
|
+
</select>
|
|
405
|
+
</label>
|
|
406
|
+
<label>Include Hook
|
|
407
|
+
<select id="socialIncludeHook">
|
|
408
|
+
<option value="true">true</option>
|
|
409
|
+
<option value="false">false</option>
|
|
410
|
+
</select>
|
|
411
|
+
</label>
|
|
412
|
+
</div>
|
|
413
|
+
<div class="row">
|
|
414
|
+
<label>Include Short Description
|
|
415
|
+
<select id="socialIncludeShortDescription">
|
|
416
|
+
<option value="true">true</option>
|
|
417
|
+
<option value="false">false</option>
|
|
418
|
+
</select>
|
|
419
|
+
</label>
|
|
420
|
+
<div></div>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="actions">
|
|
423
|
+
<button id="generateSocial">Generate Social Content</button>
|
|
473
424
|
</div>
|
|
474
425
|
</div>
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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>
|
|
426
|
+
</section>
|
|
427
|
+
|
|
428
|
+
<section class="card">
|
|
429
|
+
<h2>Scraping</h2>
|
|
430
|
+
<p class="sub">Submit public URLs for scraping and then inspect the task status.</p>
|
|
431
|
+
<div class="form">
|
|
432
|
+
<label>URLs<textarea id="scrapeUrls">https://example.com
|
|
433
|
+
https://www.openai.com</textarea></label>
|
|
434
|
+
<label>Task ID<input id="scrapeTaskId" placeholder="Returned task id"></label>
|
|
435
|
+
<div class="actions">
|
|
436
|
+
<button id="submitScrape">Submit Scrape</button>
|
|
437
|
+
<button id="getScrapeStatus" class="secondary">Get Status</button>
|
|
438
|
+
</div>
|
|
545
439
|
</div>
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
440
|
+
</section>
|
|
441
|
+
|
|
442
|
+
<section class="card">
|
|
443
|
+
<h2>Admin Quick Actions</h2>
|
|
444
|
+
<p class="sub">Small operational shortcuts for the remaining admin surface.</p>
|
|
445
|
+
<div class="actions">
|
|
446
|
+
<button id="getRateLimits">Rate Limits</button>
|
|
447
|
+
<button id="getJobs" class="secondary">Jobs</button>
|
|
448
|
+
<button id="getAudit" class="secondary">Admin Audit</button>
|
|
551
449
|
</div>
|
|
552
|
-
</
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
450
|
+
</section>
|
|
451
|
+
|
|
452
|
+
<section class="card">
|
|
453
|
+
<h2>Manual Request</h2>
|
|
454
|
+
<p class="sub">Fallback runner for any current endpoint when you want to inspect the raw envelope.</p>
|
|
455
|
+
<div class="form">
|
|
456
|
+
<div class="row">
|
|
457
|
+
<label>Method
|
|
458
|
+
<select id="manualMethod">
|
|
459
|
+
<option>GET</option>
|
|
460
|
+
<option>POST</option>
|
|
461
|
+
<option>PUT</option>
|
|
462
|
+
<option>PATCH</option>
|
|
463
|
+
<option>DELETE</option>
|
|
464
|
+
</select>
|
|
465
|
+
</label>
|
|
466
|
+
<label>Auth
|
|
467
|
+
<select id="manualAuth">
|
|
468
|
+
<option value="api">api</option>
|
|
469
|
+
<option value="admin">admin</option>
|
|
470
|
+
<option value="webhook">webhook</option>
|
|
471
|
+
<option value="none">none</option>
|
|
472
|
+
</select>
|
|
473
|
+
</label>
|
|
567
474
|
</div>
|
|
568
|
-
<
|
|
569
|
-
<
|
|
570
|
-
|
|
475
|
+
<label>Path<input id="manualPath" value="/search"></label>
|
|
476
|
+
<label>JSON Body<textarea id="manualBody">{"query":"content marketing trends 2026","num":5}</textarea></label>
|
|
477
|
+
<div class="actions">
|
|
478
|
+
<button id="sendManual">Send Request</button>
|
|
571
479
|
</div>
|
|
572
|
-
<div v-else class="text-xs text-gray-600">Click refresh to load</div>
|
|
573
480
|
</div>
|
|
574
|
-
</
|
|
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>
|
|
481
|
+
</section>
|
|
663
482
|
</div>
|
|
664
|
-
</div>
|
|
665
|
-
</div>
|
|
666
483
|
|
|
667
|
-
|
|
668
|
-
<
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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>
|
|
484
|
+
<div class="console">
|
|
485
|
+
<section class="card">
|
|
486
|
+
<h2>Response</h2>
|
|
487
|
+
<p class="sub">Latest request result.</p>
|
|
488
|
+
<pre id="output">No requests yet.</pre>
|
|
489
|
+
</section>
|
|
490
|
+
<section class="card">
|
|
491
|
+
<h2>History</h2>
|
|
492
|
+
<p class="sub">Recent actions from this page.</p>
|
|
493
|
+
<div id="history" class="history"></div>
|
|
494
|
+
</section>
|
|
691
495
|
</div>
|
|
692
496
|
</div>
|
|
693
|
-
</div>
|
|
694
497
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
</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
|
-
|
|
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;
|
|
498
|
+
<script>
|
|
499
|
+
const storeKey = "hivemind-sdk-dashboard-config";
|
|
500
|
+
const output = document.getElementById("output");
|
|
501
|
+
const historyEl = document.getElementById("history");
|
|
502
|
+
const healthDot = document.getElementById("health-dot");
|
|
503
|
+
const healthText = document.getElementById("health-text");
|
|
504
|
+
|
|
505
|
+
const inputs = {
|
|
506
|
+
baseUrl: document.getElementById("baseUrl"),
|
|
507
|
+
apiPrefix: document.getElementById("apiPrefix"),
|
|
508
|
+
apiKey: document.getElementById("apiKey"),
|
|
509
|
+
adminKey: document.getElementById("adminKey"),
|
|
510
|
+
webhookSecret: document.getElementById("webhookSecret"),
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const saved = (() => {
|
|
514
|
+
try {
|
|
515
|
+
return JSON.parse(localStorage.getItem(storeKey) || "{}");
|
|
516
|
+
} catch {
|
|
517
|
+
return {};
|
|
761
518
|
}
|
|
762
|
-
}
|
|
763
|
-
function tryJson(s){try{return JSON.parse(s);}catch{return null;}}
|
|
519
|
+
})();
|
|
764
520
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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;}
|
|
778
|
-
|
|
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);
|
|
521
|
+
Object.entries(saved).forEach(([key, value]) => {
|
|
522
|
+
if (inputs[key] && typeof value === "string") {
|
|
523
|
+
inputs[key].value = value;
|
|
524
|
+
}
|
|
810
525
|
});
|
|
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
526
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
527
|
+
function cfg() {
|
|
528
|
+
return {
|
|
529
|
+
baseUrl: inputs.baseUrl.value.replace(/\\/+$/, ""),
|
|
530
|
+
apiPrefix: inputs.apiPrefix.value || "/v1",
|
|
531
|
+
apiKey: inputs.apiKey.value.trim(),
|
|
532
|
+
adminKey: inputs.adminKey.value.trim(),
|
|
533
|
+
webhookSecret: inputs.webhookSecret.value.trim(),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
821
536
|
|
|
822
|
-
|
|
823
|
-
|
|
537
|
+
function saveConfig() {
|
|
538
|
+
localStorage.setItem(storeKey, JSON.stringify(cfg()));
|
|
539
|
+
log("Saved connection profile.");
|
|
540
|
+
}
|
|
824
541
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
];
|
|
542
|
+
function log(message) {
|
|
543
|
+
const item = document.createElement("div");
|
|
544
|
+
item.className = "history-item";
|
|
545
|
+
item.innerHTML = "<strong>" + new Date().toLocaleTimeString() + "</strong> " + message;
|
|
546
|
+
historyEl.prepend(item);
|
|
547
|
+
while (historyEl.children.length > 12) {
|
|
548
|
+
historyEl.removeChild(historyEl.lastChild);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
835
551
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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);}
|
|
552
|
+
function setOutput(label, data) {
|
|
553
|
+
output.textContent =
|
|
554
|
+
label + "\\n\\n" +
|
|
555
|
+
(typeof data === "string" ? data : JSON.stringify(data, null, 2));
|
|
847
556
|
}
|
|
848
557
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
558
|
+
function parseJson(id, fallback) {
|
|
559
|
+
const raw = document.getElementById(id).value.trim();
|
|
560
|
+
if (!raw) return fallback;
|
|
561
|
+
return JSON.parse(raw);
|
|
562
|
+
}
|
|
854
563
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
564
|
+
function parseList(raw, stripHashes = false) {
|
|
565
|
+
if (!raw) return [];
|
|
566
|
+
const seen = new Set();
|
|
567
|
+
return raw
|
|
568
|
+
.split(",")
|
|
569
|
+
.map((item) => item.trim())
|
|
570
|
+
.filter(Boolean)
|
|
571
|
+
.map((item) => stripHashes ? item.replace(/^#+/, "") : item)
|
|
572
|
+
.filter((item) => {
|
|
573
|
+
const key = item.toLowerCase();
|
|
574
|
+
if (seen.has(key)) return false;
|
|
575
|
+
seen.add(key);
|
|
576
|
+
return true;
|
|
577
|
+
});
|
|
578
|
+
}
|
|
862
579
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
580
|
+
async function apiRequest(method, path, options = {}) {
|
|
581
|
+
const settings = cfg();
|
|
582
|
+
const url = new URL((settings.apiPrefix || "/v1") + path, settings.baseUrl);
|
|
583
|
+
const headers = {};
|
|
584
|
+
const auth = options.auth || "api";
|
|
585
|
+
|
|
586
|
+
if (auth === "api" && settings.apiKey) headers.Authorization = "Bearer " + settings.apiKey;
|
|
587
|
+
if (auth === "admin" && settings.adminKey) headers["X-Admin-Key"] = settings.adminKey;
|
|
588
|
+
if (auth === "webhook" && settings.webhookSecret) headers["X-Webhook-Secret"] = settings.webhookSecret;
|
|
589
|
+
if (options.json !== undefined) headers["Content-Type"] = "application/json";
|
|
590
|
+
|
|
591
|
+
const res = await fetch(url.toString(), {
|
|
592
|
+
method,
|
|
593
|
+
headers,
|
|
594
|
+
body: options.formData ? options.formData : options.json !== undefined ? JSON.stringify(options.json) : undefined,
|
|
595
|
+
});
|
|
874
596
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
597
|
+
const text = await res.text();
|
|
598
|
+
let body = text;
|
|
599
|
+
try {
|
|
600
|
+
body = JSON.parse(text);
|
|
601
|
+
} catch {}
|
|
602
|
+
|
|
603
|
+
const payload = {
|
|
604
|
+
ok: res.ok,
|
|
605
|
+
status: res.status,
|
|
606
|
+
path,
|
|
607
|
+
method,
|
|
608
|
+
body,
|
|
609
|
+
};
|
|
610
|
+
setOutput(method + " " + path + " -> " + res.status, payload);
|
|
611
|
+
log(method + " " + path + " -> " + res.status);
|
|
612
|
+
return payload;
|
|
613
|
+
}
|
|
880
614
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
]);
|
|
615
|
+
async function healthCheck() {
|
|
616
|
+
try {
|
|
617
|
+
const settings = cfg();
|
|
618
|
+
const url = new URL((settings.apiPrefix || "/v1") + "/health", settings.baseUrl);
|
|
619
|
+
const res = await fetch(url.toString());
|
|
620
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
621
|
+
const body = await res.json();
|
|
622
|
+
healthDot.classList.add("ok");
|
|
623
|
+
healthText.textContent = "API reachable";
|
|
624
|
+
setOutput("Health", body);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
healthDot.classList.remove("ok");
|
|
627
|
+
healthText.textContent = "API unavailable";
|
|
628
|
+
setOutput("Health", String(err));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
954
631
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
632
|
+
document.getElementById("saveConfig").onclick = saveConfig;
|
|
633
|
+
document.getElementById("pingHealth").onclick = healthCheck;
|
|
634
|
+
document.getElementById("getScope").onclick = () => apiRequest("GET", "/api-key", { auth: "api" });
|
|
635
|
+
document.getElementById("adminConfig").onclick = () => apiRequest("GET", "/admin/config", { auth: "admin" });
|
|
636
|
+
|
|
637
|
+
document.getElementById("issueApiKey").onclick = () => {
|
|
638
|
+
const name = document.getElementById("scopeName").value.trim() || undefined;
|
|
639
|
+
return apiRequest("POST", "/api-keys", {
|
|
640
|
+
auth: "admin",
|
|
641
|
+
json: {
|
|
642
|
+
name,
|
|
643
|
+
settings: parseJson("scopeSettings", {}),
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
document.getElementById("rotateApiKey").onclick = () => apiRequest("POST", "/api-key/rotate", { auth: "api" });
|
|
649
|
+
document.getElementById("runSearch").onclick = () => apiRequest("POST", "/search", {
|
|
650
|
+
auth: "api",
|
|
651
|
+
json: {
|
|
652
|
+
query: document.getElementById("searchQuery").value,
|
|
653
|
+
type: document.getElementById("searchType").value || undefined,
|
|
654
|
+
num: Number(document.getElementById("searchNum").value || 5),
|
|
655
|
+
},
|
|
960
656
|
});
|
|
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
657
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
{
|
|
1033
|
-
|
|
1034
|
-
{
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
658
|
+
document.getElementById("uploadDocument").onclick = async () => {
|
|
659
|
+
const fileInput = document.getElementById("documentFile");
|
|
660
|
+
const file = fileInput.files && fileInput.files[0];
|
|
661
|
+
if (!file) {
|
|
662
|
+
setOutput("Upload Document", "Select a file first.");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const formData = new FormData();
|
|
666
|
+
formData.append("file", file);
|
|
667
|
+
await apiRequest("POST", "/documents/upload", { auth: "api", formData });
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
document.getElementById("startGeneration").onclick = () => {
|
|
671
|
+
const type = document.getElementById("generationType").value;
|
|
672
|
+
const prompt = document.getElementById("generationPrompt").value;
|
|
673
|
+
const flavor = document.getElementById("generationFlavor").value;
|
|
674
|
+
const amount = document.getElementById("generationAmount").value;
|
|
675
|
+
let body;
|
|
676
|
+
if (type === "music") {
|
|
677
|
+
body = { genre: prompt, mood: flavor || "uplifting", duration: Number(amount || 30) };
|
|
678
|
+
} else if (type === "image") {
|
|
679
|
+
body = { prompt, style: flavor || undefined, num_images: Number(amount || 1) };
|
|
680
|
+
} else {
|
|
681
|
+
body = { prompt, style: flavor || undefined, duration: Number(amount || 8) };
|
|
682
|
+
}
|
|
683
|
+
return apiRequest("POST", "/generate/" + type, { auth: "api", json: body });
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
document.getElementById("generateSocial").onclick = () => apiRequest("POST", "/generate/social", {
|
|
687
|
+
auth: "api",
|
|
688
|
+
json: {
|
|
689
|
+
prompt: document.getElementById("socialPrompt").value,
|
|
690
|
+
platforms: parseList(document.getElementById("socialPlatforms").value).map((item) => item.toLowerCase()),
|
|
691
|
+
objective: document.getElementById("socialObjective").value,
|
|
692
|
+
target_audience: document.getElementById("socialAudience").value || undefined,
|
|
693
|
+
brand_name: document.getElementById("socialBrand").value || undefined,
|
|
694
|
+
tone: document.getElementById("socialTone").value,
|
|
695
|
+
writing_style: document.getElementById("socialStyle").value,
|
|
696
|
+
length: document.getElementById("socialLength").value,
|
|
697
|
+
call_to_action: document.getElementById("socialCta").value || undefined,
|
|
698
|
+
primary_keyword: document.getElementById("socialKeyword").value || undefined,
|
|
699
|
+
secondary_keywords: parseList(document.getElementById("socialSecondary").value),
|
|
700
|
+
hashtags: parseList(document.getElementById("socialHashtags").value, true),
|
|
701
|
+
hashtag_count: Number(document.getElementById("socialHashtagCount").value || 3),
|
|
702
|
+
include_hashtags: true,
|
|
703
|
+
include_emojis: document.getElementById("socialIncludeEmojis").value === "true",
|
|
704
|
+
emoji_density: document.getElementById("socialEmojiDensity").value,
|
|
705
|
+
variation_count: Number(document.getElementById("socialVariations").value || 1),
|
|
706
|
+
include_title: document.getElementById("socialIncludeTitle").value === "true",
|
|
707
|
+
include_hook: document.getElementById("socialIncludeHook").value === "true",
|
|
708
|
+
include_short_description: document.getElementById("socialIncludeShortDescription").value === "true",
|
|
709
|
+
include_script: document.getElementById("socialIncludeScript").value === "true",
|
|
710
|
+
must_include: parseList(document.getElementById("socialMustInclude").value),
|
|
711
|
+
avoid_terms: parseList(document.getElementById("socialAvoid").value),
|
|
712
|
+
compliance_notes: document.getElementById("socialCompliance").value
|
|
713
|
+
.split("\\n")
|
|
714
|
+
.map((item) => item.trim())
|
|
715
|
+
.filter(Boolean),
|
|
716
|
+
landing_page_url: document.getElementById("socialUrl").value || undefined,
|
|
717
|
+
language: document.getElementById("socialLanguage").value || "en",
|
|
718
|
+
},
|
|
1061
719
|
});
|
|
1062
720
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
</
|
|
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>
|
|
721
|
+
document.getElementById("getJob").onclick = () => {
|
|
722
|
+
const jobId = document.getElementById("jobId").value.trim();
|
|
723
|
+
if (!jobId) {
|
|
724
|
+
setOutput("Get Job", "Provide a job id first.");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
return apiRequest("GET", "/jobs/" + encodeURIComponent(jobId), { auth: "api" });
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
document.getElementById("submitScrape").onclick = () => {
|
|
731
|
+
const urls = document.getElementById("scrapeUrls").value
|
|
732
|
+
.split("\\n")
|
|
733
|
+
.map((item) => item.trim())
|
|
734
|
+
.filter(Boolean);
|
|
735
|
+
return apiRequest("POST", "/scraping/scrape", { auth: "none", json: { urls } });
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
document.getElementById("getScrapeStatus").onclick = () => {
|
|
739
|
+
const taskId = document.getElementById("scrapeTaskId").value.trim();
|
|
740
|
+
if (!taskId) {
|
|
741
|
+
setOutput("Scrape Status", "Provide a task id first.");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
return apiRequest("GET", "/scraping/status/" + encodeURIComponent(taskId), { auth: "none" });
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
document.getElementById("getRateLimits").onclick = () => apiRequest("GET", "/admin/rate-limits", { auth: "admin" });
|
|
748
|
+
document.getElementById("getJobs").onclick = () => apiRequest("GET", "/admin/jobs", { auth: "admin" });
|
|
749
|
+
document.getElementById("getAudit").onclick = () => apiRequest("GET", "/admin/audit", { auth: "admin" });
|
|
750
|
+
|
|
751
|
+
document.getElementById("sendManual").onclick = () => {
|
|
752
|
+
let body;
|
|
753
|
+
const raw = document.getElementById("manualBody").value.trim();
|
|
754
|
+
if (raw) {
|
|
755
|
+
try {
|
|
756
|
+
body = JSON.parse(raw);
|
|
757
|
+
} catch (err) {
|
|
758
|
+
setOutput("Manual Request", "Body must be valid JSON: " + err.message);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return apiRequest(
|
|
763
|
+
document.getElementById("manualMethod").value,
|
|
764
|
+
document.getElementById("manualPath").value,
|
|
765
|
+
{
|
|
766
|
+
auth: document.getElementById("manualAuth").value,
|
|
767
|
+
json: body,
|
|
768
|
+
},
|
|
769
|
+
);
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
healthCheck();
|
|
773
|
+
</script>
|
|
1178
774
|
</body>
|
|
1179
775
|
</html>`;
|
|
1180
776
|
}
|
|
@@ -1185,7 +781,6 @@ function startWebServer(opts) {
|
|
|
1185
781
|
apiKey: opts.apiKey,
|
|
1186
782
|
adminKey: opts.adminKey,
|
|
1187
783
|
webhookSecret: opts.webhookSecret,
|
|
1188
|
-
employeeId: opts.employeeId,
|
|
1189
784
|
});
|
|
1190
785
|
const server = node_http_1.default.createServer((_req, res) => {
|
|
1191
786
|
res.writeHead(200, {
|
|
@@ -1197,7 +792,7 @@ function startWebServer(opts) {
|
|
|
1197
792
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
1198
793
|
const addr = server.address();
|
|
1199
794
|
const url = `http://127.0.0.1:${addr.port}`;
|
|
1200
|
-
console.log(`\n Hivemind
|
|
795
|
+
console.log(`\n Hivemind SDK dashboard running at ${url}\n`);
|
|
1201
796
|
console.log(" Press Ctrl+C to stop.\n");
|
|
1202
797
|
if (opts.open) {
|
|
1203
798
|
const { exec } = require("node:child_process");
|