@ai2aim.ai/hivemind-sdk 1.0.6 → 1.0.7

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