@swizzy_ai/kit 1.0.0 → 1.0.2

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.
Files changed (31) hide show
  1. package/README.md +835 -439
  2. package/dist/core/wizard/bungee/executor.d.ts +15 -0
  3. package/dist/core/wizard/bungee/executor.d.ts.map +1 -0
  4. package/dist/core/wizard/context-manager.d.ts +9 -0
  5. package/dist/core/wizard/context-manager.d.ts.map +1 -0
  6. package/dist/core/wizard/logger.d.ts +8 -0
  7. package/dist/core/wizard/logger.d.ts.map +1 -0
  8. package/dist/core/wizard/schema-utils.d.ts +16 -0
  9. package/dist/core/wizard/schema-utils.d.ts.map +1 -0
  10. package/dist/core/wizard/step-data-generator.d.ts +24 -0
  11. package/dist/core/wizard/step-data-generator.d.ts.map +1 -0
  12. package/dist/core/wizard/steps/base.d.ts +4 -3
  13. package/dist/core/wizard/steps/base.d.ts.map +1 -1
  14. package/dist/core/wizard/steps/compute.d.ts +2 -2
  15. package/dist/core/wizard/steps/compute.d.ts.map +1 -1
  16. package/dist/core/wizard/steps/text.d.ts +2 -2
  17. package/dist/core/wizard/steps/text.d.ts.map +1 -1
  18. package/dist/core/wizard/usage-tracker.d.ts +25 -0
  19. package/dist/core/wizard/usage-tracker.d.ts.map +1 -0
  20. package/dist/core/wizard/visualization-manager.d.ts +34 -0
  21. package/dist/core/wizard/visualization-manager.d.ts.map +1 -0
  22. package/dist/core/wizard/wizard.d.ts +39 -40
  23. package/dist/core/wizard/wizard.d.ts.map +1 -1
  24. package/dist/index.d.ts +46 -46
  25. package/dist/index.js +1207 -626
  26. package/dist/index.js.map +1 -1
  27. package/dist/services/client/index.d.ts +1 -0
  28. package/dist/services/client/index.d.ts.map +1 -1
  29. package/dist/services/client/providers.d.ts.map +1 -1
  30. package/dist/ui/wizard-visualizer.html +570 -385
  31. package/package.json +6 -2
@@ -3,479 +3,664 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Wizard Visualizer | Production</title>
6
+ <title>SimEnvironment | Production v5.5</title>
7
7
  <script src="https://cdn.tailwindcss.com"></script>
8
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
9
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
10
+
10
11
  <style>
11
12
  :root {
12
- --background: #000000;
13
- --foreground: #ffffff;
14
- --accent-primary: #e43de7;
15
- --accent-primary-rgb: 228, 61, 231;
16
- --accent-secondary: #00dfd8;
17
- --border: #333333;
18
- --surface: #111111;
19
- --success: #e43de7;
20
- --warning: #f5a623;
13
+ --bg-deep: #050505;
14
+ --bg-panel: #0a0a0a;
15
+ --border: #222;
16
+
17
+ /* Primary Color: Electric Violet */
18
+ --primary: #8b5cf6;
19
+ --primary-dim: rgba(139, 92, 246, 0.1);
20
+
21
+ --success: #10b981;
22
+ --warning: #eab308;
23
+ --text-main: #e5e5e5;
24
+ --text-muted: #737373;
21
25
  }
22
26
 
23
27
  body {
24
- background-color: var(--background);
25
- color: var(--foreground);
26
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
28
+ background-color: var(--bg-deep);
29
+ color: var(--text-main);
30
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
31
+ height: 100vh;
27
32
  overflow: hidden;
33
+ display: flex;
34
+ flex-direction: column;
28
35
  }
29
36
 
30
- /* Custom Scrollbar */
31
- ::-webkit-scrollbar { width: 4px; height: 4px; }
32
- ::-webkit-scrollbar-track { background: transparent; }
33
- ::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
34
- ::-webkit-scrollbar-thumb:hover { background: #444; }
37
+ /* --- LAYOUT GRID --- */
38
+ .app-grid {
39
+ display: grid;
40
+ grid-template-columns: 320px 1fr 320px;
41
+ flex: 1;
42
+ min-height: 0;
43
+ overflow: hidden;
44
+ width: 100%;
45
+ min-width: 1024px;
46
+ }
35
47
 
36
- .glass-header {
37
- background: rgba(0, 0, 0, 0.8);
38
- backdrop-filter: blur(12px);
48
+ /* --- SIDEBARS --- */
49
+ .sidebar {
50
+ background: var(--bg-panel);
51
+ display: flex;
52
+ flex-direction: column;
53
+ height: 100%;
54
+ overflow: hidden;
55
+ z-index: 10;
56
+ }
57
+ .sidebar-left { border-right: 1px solid var(--border); }
58
+ .sidebar-right { border-left: 1px solid var(--border); }
59
+
60
+ .sidebar-header {
61
+ height: 50px;
62
+ min-height: 50px;
39
63
  border-bottom: 1px solid var(--border);
64
+ display: flex;
65
+ align-items: center;
66
+ padding: 0 16px;
67
+ justify-content: space-between;
68
+ background: #080808;
69
+ }
70
+
71
+ /* --- STAGE --- */
72
+ .stage-container {
73
+ position: relative;
74
+ background-image:
75
+ linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
76
+ linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
77
+ background-size: 40px 40px;
78
+ display: flex;
79
+ flex-direction: column;
80
+ height: 100%;
81
+ overflow: hidden;
40
82
  }
41
83
 
42
- .sidebar-border { border-right: 1px solid var(--border); }
43
- .right-sidebar-border { border-left: 1px solid var(--border); }
84
+ .steps-scroll-area {
85
+ flex: 1;
86
+ overflow-y: auto;
87
+ scroll-behavior: smooth;
88
+ padding: 40px 0 160px 0;
89
+ position: relative;
90
+ }
91
+ .steps-scroll-area::-webkit-scrollbar { display: none; }
92
+
93
+ .timeline-guide {
94
+ position: absolute;
95
+ left: 50%;
96
+ top: 0;
97
+ bottom: 0;
98
+ width: 1px;
99
+ background: linear-gradient(180deg, transparent, #222 10%, #222 90%, transparent);
100
+ z-index: 0;
101
+ transform: translateX(-50%);
102
+ }
44
103
 
104
+ /* --- STEP CARD --- */
45
105
  .step-card {
46
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
106
+ width: 90%;
107
+ max-width: 650px;
108
+ margin: 0 auto 30px auto;
109
+ background: #080808;
47
110
  border: 1px solid var(--border);
48
- background: #0a0a0a;
111
+ border-radius: 12px;
112
+ position: relative;
113
+ z-index: 10;
114
+ transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
115
+ opacity: 0.5;
116
+ filter: grayscale(1);
117
+ transform: scale(0.98);
49
118
  }
50
-
51
119
  .step-card.active {
52
- border-color: var(--accent-primary);
53
- box-shadow: 0 0 20px rgba(0, 112, 243, 0.1);
54
- transform: scale(1.01);
120
+ opacity: 1;
121
+ filter: grayscale(0);
122
+ transform: scale(1);
123
+ border-color: var(--primary);
124
+ box-shadow: 0 0 0 1px var(--primary-dim), 0 20px 60px -10px rgba(0,0,0,0.8);
55
125
  }
56
-
57
126
  .step-card.completed {
58
- border-color: rgba(0, 112, 243, 0.3);
127
+ opacity: 0.6;
128
+ border-color: #333;
129
+ filter: grayscale(0.5);
59
130
  }
60
131
 
61
- .pulse {
62
- animation: pulse 2s infinite;
132
+ .step-badge {
133
+ font-size: 10px;
134
+ font-weight: 700;
135
+ letter-spacing: 0.1em;
136
+ padding: 4px 8px;
137
+ border-radius: 4px;
138
+ text-transform: uppercase;
139
+ background: #111;
140
+ border: 1px solid #222;
141
+ color: #666;
63
142
  }
64
-
65
- @keyframes pulse {
66
- 0% { opacity: 1; }
67
- 50% { opacity: 0.5; }
68
- 100% { opacity: 1; }
143
+ .active .step-badge {
144
+ background: var(--primary-dim);
145
+ color: var(--primary);
146
+ border-color: rgba(139, 92, 246, 0.3);
69
147
  }
70
148
 
71
- .terminal-line {
72
- border-left: 2px solid var(--accent-primary);
73
- padding-left: 12px;
74
- margin-bottom: 8px;
75
- animation: slideIn 0.3s ease-out;
149
+ /* --- MARKDOWN & OUTPUT STYLING --- */
150
+ .markdown-body {
151
+ font-size: 13px;
152
+ line-height: 1.6;
153
+ color: #d4d4d4;
76
154
  }
77
-
78
- @keyframes slideIn {
79
- from { opacity: 0; transform: translateX(-10px); }
80
- to { opacity: 1; transform: translateX(0); }
155
+ .markdown-body h1, .markdown-body h2, .markdown-body h3 {
156
+ color: #fff;
157
+ margin-top: 16px;
158
+ margin-bottom: 8px;
159
+ font-weight: 600;
81
160
  }
82
-
83
- .badge {
84
- font-size: 10px;
85
- text-transform: uppercase;
86
- letter-spacing: 0.05em;
87
- padding: 2px 6px;
161
+ .markdown-body h1 { font-size: 1.2em; border-bottom: 1px solid #333; padding-bottom: 4px; }
162
+ .markdown-body h2 { font-size: 1.1em; }
163
+ .markdown-body p { margin-bottom: 12px; }
164
+ .markdown-body ul, .markdown-body ol { padding-left: 20px; margin-bottom: 12px; }
165
+ .markdown-body li { margin-bottom: 4px; }
166
+ .markdown-body code {
167
+ font-family: 'JetBrains Mono', monospace;
168
+ background: rgba(255, 255, 255, 0.1);
169
+ padding: 2px 4px;
88
170
  border-radius: 4px;
89
- font-weight: 600;
171
+ font-size: 0.9em;
172
+ color: #eab308;
173
+ }
174
+ .markdown-body pre {
175
+ background: #111;
176
+ padding: 12px;
177
+ border-radius: 6px;
178
+ overflow-x: auto;
179
+ margin-bottom: 12px;
180
+ border: 1px solid #222;
181
+ }
182
+ .markdown-body pre code {
183
+ background: transparent;
184
+ padding: 0;
185
+ color: #a5f3fc;
186
+ font-size: 12px;
187
+ }
188
+ .markdown-body blockquote {
189
+ border-left: 3px solid var(--primary);
190
+ padding-left: 12px;
191
+ color: #999;
192
+ font-style: italic;
90
193
  }
91
-
92
- .badge-blue { background: rgba(0, 112, 243, 0.1); color: #0070f3; border: 1px solid rgba(0, 112, 243, 0.2); }
93
- .badge-green { background: rgba(0, 223, 216, 0.1); color: #00dfd8; border: 1px solid rgba(0, 223, 216, 0.2); }
94
194
 
95
- .instruction-panel {
96
- background: #050505;
97
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
195
+ /* JSON Output specifically */
196
+ .json-output {
197
+ font-family: 'JetBrains Mono', monospace;
198
+ font-size: 11px;
199
+ color: #a7f3d0;
200
+ white-space: pre-wrap;
201
+ word-break: break-all;
98
202
  }
99
203
 
100
- .loader {
101
- width: 14px;
102
- height: 14px;
103
- border: 2px solid rgba(255, 255, 255, 0.1);
104
- border-top-color: var(--accent-primary);
105
- border-radius: 50%;
106
- animation: spin 0.8s linear infinite;
204
+ /* --- SIM INPUTS --- */
205
+ .sim-container {
206
+ margin-top: 16px;
207
+ padding-top: 16px;
208
+ border-top: 1px dashed #222;
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 12px;
107
212
  }
108
-
109
- @keyframes spin {
110
- to { transform: rotate(360deg); }
213
+ .sim-group { opacity: 0; animation: fadeIn 0.3s forwards; position: relative; }
214
+ @keyframes fadeIn { to { opacity: 1; } }
215
+ .sim-label {
216
+ font-size: 9px;
217
+ text-transform: uppercase;
218
+ color: #555;
219
+ font-weight: 700;
220
+ letter-spacing: 0.05em;
221
+ margin-bottom: 4px;
222
+ display: block;
223
+ }
224
+ .sim-input {
225
+ background: #000;
226
+ border: 1px solid #333;
227
+ border-radius: 4px;
228
+ padding: 8px 12px;
229
+ font-family: 'Courier New', monospace;
230
+ font-size: 12px;
231
+ color: var(--primary);
232
+ min-height: 34px;
233
+ display: flex;
234
+ align-items: center;
235
+ position: relative;
236
+ }
237
+ .sim-input::after { content: 'AI Generated'; position: absolute; right: 8px; font-size: 9px; color: #333; }
238
+ .cursor-blink { display: inline-block; width: 6px; height: 14px; background-color: var(--primary); margin-left: 2px; animation: blink 1s step-end infinite; }
239
+ @keyframes blink { 50% { opacity: 0; } }
240
+
241
+ /* --- CONTROL DECK --- */
242
+ .control-deck {
243
+ position: fixed;
244
+ bottom: 40px;
245
+ left: 50%;
246
+ transform: translateX(-50%);
247
+ background: rgba(15, 15, 15, 0.95);
248
+ backdrop-filter: blur(16px);
249
+ border: 1px solid var(--border);
250
+ border-radius: 100px;
251
+ padding: 8px 16px;
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 16px;
255
+ z-index: 9999;
256
+ box-shadow: 0 20px 50px rgba(0,0,0,0.5);
111
257
  }
258
+ .deck-btn { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #666; background: transparent; border: none; cursor: pointer; transition: all 0.2s; }
259
+ .deck-btn:hover { color: #fff; background: rgba(255,255,255,0.05); }
260
+ .play-btn { width: 52px; height: 52px; border-radius: 50%; background: #fff; color: #000; display: flex; align-items: center; justify-content: center; font-size: 16px; transition: transform 0.2s; border: none; cursor: pointer; }
261
+ .play-btn:hover { transform: scale(1.05); }
262
+
263
+ /* --- SCROLLBARS --- */
264
+ .custom-scroll::-webkit-scrollbar { width: 4px; }
265
+ .custom-scroll::-webkit-scrollbar-track { background: transparent; }
266
+ .custom-scroll::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
112
267
  </style>
113
268
  </head>
114
- <body class="h-screen flex flex-col">
115
-
116
- <!-- Header -->
117
- <header class="glass-header h-14 flex items-center justify-between px-6 z-50">
118
- <div class="flex items-center gap-4">
119
- <div class="flex items-center gap-2">
120
- <div class="w-6 h-6 bg-white rounded flex items-center justify-center">
121
- <div class="w-3 h-3 bg-black transform rotate-45"></div>
122
- </div>
123
- <span class="font-bold text-sm tracking-tight">WIZARD_CORE</span>
269
+
270
+ <body>
271
+ <!-- HEADER -->
272
+ <header class="h-14 border-b border-[#222] bg-[#050505] flex items-center justify-between px-6 shrink-0 z-30 relative">
273
+ <div class="flex items-center gap-3">
274
+ <div class="w-8 h-8 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center">
275
+ <i class="fas fa-hat-wizard text-violet-400 text-xs"></i>
124
276
  </div>
125
- <div class="h-4 w-[1px] bg-neutral-800 mx-2"></div>
126
- <div class="flex gap-6 text-[11px] font-medium text-neutral-400">
127
- <div class="flex flex-col">
128
- <span class="text-[9px] text-neutral-600 uppercase">Tokens</span>
129
- <span class="text-white" id="total-tokens">0</span>
130
- </div>
131
- <div class="flex flex-col">
132
- <span class="text-[9px] text-neutral-600 uppercase">Rate</span>
133
- <span class="text-white" id="token-rate">0/s</span>
277
+ <div>
278
+ <h1 class="text-sm font-bold text-white tracking-tight">WIZARD_CORE <span class="text-xs text-violet-500 font-mono">v5.5</span></h1>
279
+ <div class="flex items-center gap-2">
280
+ <div id="status-dot" class="w-1.5 h-1.5 rounded-full bg-neutral-600"></div>
281
+ <span id="header-status" class="text-[9px] font-bold text-neutral-500 uppercase tracking-widest">READY</span>
134
282
  </div>
135
283
  </div>
136
284
  </div>
137
-
138
- <div class="flex items-center gap-4">
139
- <div id="status-badge" class="badge badge-blue flex items-center gap-2">
140
- <div class="w-1.5 h-1.5 rounded-full bg-current"></div>
141
- <span id="status-text">Ready</span>
285
+ <div class="flex items-center gap-6">
286
+ <div class="flex flex-col items-end">
287
+ <span class="text-[9px] text-neutral-600 uppercase font-bold">Tokens</span>
288
+ <span id="header-tokens" class="text-xs font-mono text-white">0</span>
142
289
  </div>
143
- <div class="h-8 w-8 rounded-full bg-neutral-900 border border-neutral-800 flex items-center justify-center overflow-hidden">
144
- <img src="/placeholder.svg?height=32&width=32" alt="user" />
290
+ <div class="h-6 w-[1px] bg-neutral-800"></div>
291
+ <div class="flex flex-col items-end">
292
+ <span class="text-[9px] text-neutral-600 uppercase font-bold">Rate/s</span>
293
+ <span id="header-rate" class="text-xs font-mono text-violet-400">0.0</span>
145
294
  </div>
146
295
  </div>
147
296
  </header>
148
297
 
149
- <div class="flex-1 flex overflow-hidden">
150
- <!-- Left Sidebar: Context -->
151
- <aside class="w-72 sidebar-border flex flex-col bg-[#050505]">
152
- <div class="p-4 border-bottom border-neutral-900 flex items-center justify-between">
153
- <span class="text-[10px] uppercase font-bold text-neutral-500 tracking-widest">Context Data</span>
154
- <i class="fas fa-database text-[10px] text-neutral-600"></i>
298
+ <!-- GRID -->
299
+ <div class="app-grid">
300
+
301
+ <!-- LEFT: CONTEXT -->
302
+ <aside class="sidebar sidebar-left">
303
+ <div class="sidebar-header">
304
+ <span class="text-[10px] font-bold uppercase tracking-widest text-neutral-400">Context Memory</span>
305
+ <i class="fas fa-database text-neutral-600 text-xs"></i>
155
306
  </div>
156
- <div id="context-list" class="flex-1 overflow-y-auto p-4 space-y-3">
157
- <!-- Context Items -->
307
+ <div id="context-list" class="flex-1 overflow-y-auto p-4 custom-scroll space-y-3">
308
+ <!-- Context injected here -->
158
309
  </div>
159
310
  </aside>
160
-
161
- <!-- Main Content: Step Flow -->
162
- <main class="flex-1 overflow-y-auto bg-black scroll-smooth">
163
- <div class="max-w-3xl mx-auto py-12 px-6">
164
- <div class="flex flex-col gap-8 relative" id="steps-container">
165
- <!-- Timeline Line -->
166
- <div class="absolute left-6 top-8 bottom-8 w-[1px] bg-neutral-800 -z-10"></div>
167
-
168
- <!-- Steps dynamic -->
169
- </div>
311
+
312
+ <!-- CENTER: STAGE -->
313
+ <main class="stage-container">
314
+ <div id="steps-container" class="steps-scroll-area">
315
+ <div class="timeline-guide"></div>
316
+ </div>
317
+ <div id="control-deck" class="control-deck">
318
+ <button id="stop-btn" class="deck-btn hover:text-red-500"><i class="fas fa-stop"></i></button>
319
+ <button id="play-pause-btn" class="play-btn shadow-lg shadow-violet-500/20 text-violet-900"><i class="fas fa-play ml-1"></i></button>
320
+ <button id="replay-btn" class="deck-btn"><i class="fas fa-redo"></i></button>
170
321
  </div>
171
322
  </main>
172
323
 
173
- <!-- Right Sidebar: Instructions & Live Feed -->
174
- <aside class="w-80 right-sidebar-border flex flex-col instruction-panel">
175
- <div class="p-4 border-b border-neutral-900 flex items-center justify-between">
176
- <span class="text-[10px] uppercase font-bold text-neutral-500 tracking-widest">Execution Log</span>
177
- <div class="flex gap-2">
178
- <div class="w-2 h-2 rounded-full bg-neutral-800"></div>
179
- <div class="w-2 h-2 rounded-full bg-neutral-800"></div>
324
+ <!-- RIGHT: LOGS/INSTRUCT -->
325
+ <aside class="sidebar sidebar-right">
326
+ <div class="instruction-panel">
327
+ <div class="sidebar-header">
328
+ <span class="text-[10px] font-bold uppercase tracking-widest text-neutral-400">Current Instruction</span>
329
+ <i class="fas fa-code text-neutral-600 text-xs"></i>
330
+ </div>
331
+ <div id="current-instruction" class="instruction-content custom-scroll">
332
+ <p class="text-neutral-600 text-[11px]">Waiting for workflow...</p>
180
333
  </div>
181
334
  </div>
182
- <div id="instruction-feed" class="flex-1 p-4 overflow-y-auto text-[12px] leading-relaxed text-neutral-300">
183
- <div class="terminal-line text-neutral-500">System initialized. Waiting for command...</div>
184
- </div>
185
-
186
- <!-- Controls -->
187
- <div class="p-6 border-t border-neutral-900 bg-black">
188
- <div class="flex gap-2">
189
- <button id="start-btn" class="flex-1 h-10 bg-white text-black text-[12px] font-bold rounded flex items-center justify-center gap-2 hover:bg-neutral-200 transition-colors">
190
- <i class="fas fa-play text-[10px]"></i> START
191
- </button>
192
- <button id="pause-btn" class="hidden flex-1 h-10 bg-neutral-900 text-white text-[12px] font-bold rounded border border-neutral-800 flex items-center justify-center gap-2 hover:bg-neutral-800 transition-colors">
193
- <i class="fas fa-pause text-[10px]"></i> PAUSE
194
- </button>
195
- <button id="stop-btn" class="w-10 h-10 bg-neutral-900 text-red-500 text-[12px] rounded border border-neutral-800 flex items-center justify-center hover:bg-red-500/10 transition-colors">
196
- <i class="fas fa-stop"></i>
197
- </button>
198
- <button id="replay-btn" class="w-10 h-10 bg-neutral-900 text-neutral-400 text-[12px] rounded border border-neutral-800 flex items-center justify-center hover:bg-neutral-800 transition-colors">
199
- <i class="fas fa-redo"></i>
200
- </button>
335
+ <div class="flex-1 flex flex-col border-t border-[#222] bg-[#030303] overflow-hidden">
336
+ <div class="sidebar-header">
337
+ <span class="text-[10px] font-bold uppercase tracking-widest text-neutral-400">System Logs</span>
338
+ <div class="flex gap-1"><div class="w-1.5 h-1.5 rounded-full bg-[#222]"></div><div class="w-1.5 h-1.5 rounded-full bg-[#222]"></div></div>
201
339
  </div>
340
+ <div id="log-feed" class="flex-1 p-3 overflow-y-auto custom-scroll flex flex-col"></div>
202
341
  </div>
203
342
  </aside>
343
+
204
344
  </div>
205
345
 
206
- <script>
207
- class WizardEngine {
208
- constructor() {
209
- this.ws = null;
210
- this.steps = new Map();
211
- this.context = new Map();
212
- this.state = {
213
- isRunning: false,
214
- isPaused: false,
215
- totalTokens: 0,
216
- currentStepId: null
217
- };
218
-
219
- this.init();
220
- }
346
+ <script>
347
+ class WizardEngine {
348
+ constructor() {
349
+ this.ws = null;
350
+ this.steps = new Map();
351
+ this.context = new Map();
352
+ this.state = { isRunning: false, isPaused: false, totalTokens: 0, currentStepId: null, tokenRate: 0 };
353
+ this.init();
354
+ }
355
+
356
+ init() {
357
+ this.setupWebSocket();
358
+ this.setupListeners();
359
+ this.log("Wizard Engine V5.5 initialized.");
360
+ }
361
+
362
+ setupWebSocket() {
363
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
364
+ const wsUrl = `${protocol}//${location.host}`;
365
+ this.ws = new WebSocket(wsUrl);
366
+
367
+ this.ws.onopen = () => { this.log("Connected."); this.updateConnectionUI(true); };
368
+ this.ws.onmessage = (e) => this.handleMessage(JSON.parse(e.data));
369
+ this.ws.onerror = () => this.log("Connection failed.", "error");
370
+ this.ws.onclose = () => {
371
+ this.log("Connection lost. Retrying...", "error");
372
+ this.updateConnectionUI(false);
373
+ setTimeout(() => this.setupWebSocket(), 5000);
374
+ };
375
+ }
221
376
 
222
- init() {
223
- this.setupWebSocket();
224
- this.setupListeners();
225
- this.log("Wizard Engine V4.0 ready for deployment.");
377
+ handleMessage(data) {
378
+ if (data.type === 'batch') { data.messages.forEach(m => this.handleMessage(m)); return; }
379
+ switch(data.type) {
380
+ case 'wizard_start': this.initSteps(data.steps); break;
381
+ case 'step_update': this.updateStep(data); break;
382
+ case 'token_update': this.updateTokens(data); break;
383
+ case 'context_update': this.updateContext(data); break;
384
+ case 'status_update': this.updateStatus(data.status); break;
226
385
  }
386
+ }
227
387
 
228
- setupWebSocket() {
229
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
230
- const wsUrl = `${protocol}//${location.host}`;
231
-
232
- this.ws = new WebSocket(wsUrl);
233
-
234
- this.ws.onopen = () => {
235
- this.log("Connection established with production node.");
236
- this.updateConnectionUI(true);
237
- };
388
+ initSteps(steps) {
389
+ const container = document.getElementById('steps-container');
390
+ container.innerHTML = '<div class="timeline-guide"></div><div style="height:40px"></div>';
391
+ this.steps.clear();
392
+ const flatSteps = Array.isArray(steps[0]) ? steps[0] : steps;
393
+ flatSteps.forEach((step, index) => {
394
+ const el = this.createStepElement(step, index + 1);
395
+ container.appendChild(el);
396
+ this.steps.set(step.id, { el, data: step });
397
+ });
398
+ const spacer = document.createElement('div');
399
+ spacer.style.height = "120px";
400
+ container.appendChild(spacer);
401
+ }
238
402
 
239
- this.ws.onmessage = (e) => {
240
- const data = JSON.parse(e.data);
241
- this.handleMessage(data);
242
- };
403
+ createStepElement(step, index) {
404
+ const div = document.createElement('div');
405
+ div.id = `step-${step.id}`;
406
+ div.className = `step-card`;
407
+ div.innerHTML = `
408
+ <div class="px-6 py-4 border-b border-[#222] bg-white/[0.02] flex items-center justify-between">
409
+ <div class="flex items-center gap-3">
410
+ <div class="text-[10px] font-mono text-neutral-500 border border-[#333] px-1.5 rounded">${index.toString().padStart(2,'0')}</div>
411
+ <span class="text-xs font-bold text-neutral-300 tracking-wide">${step.id}</span>
412
+ </div>
413
+ <div class="step-badge">QUEUED</div>
414
+ </div>
415
+ <div class="p-6">
416
+ <div class="text-sm text-neutral-500 font-medium leading-relaxed instruction-text mb-4">
417
+ ${step.instruction || 'Ready for processing...'}
418
+ </div>
419
+ <div class="step-content"></div>
420
+ </div>
421
+ `;
422
+ return div;
423
+ }
243
424
 
244
- this.ws.onclose = () => {
245
- this.log("Connection lost. Retrying in 5s...", "error");
246
- this.updateConnectionUI(false);
247
- setTimeout(() => this.setupWebSocket(), 5000);
248
- };
249
- }
425
+ updateStep(data) {
426
+ const step = this.steps.get(data.stepId);
427
+ if (!step) return;
250
428
 
251
- handleMessage(data) {
252
- if (data.type === 'batch') {
253
- data.messages.forEach(m => this.handleMessage(m));
254
- return;
255
- }
429
+ const { el } = step;
430
+ const badge = el.querySelector('.step-badge');
431
+ const content = el.querySelector('.step-content');
432
+
433
+ document.querySelectorAll('.step-card').forEach(c => c.classList.remove('active'));
256
434
 
257
- switch(data.type) {
258
- case 'wizard_start': this.initSteps(data.steps); break;
259
- case 'step_update': this.updateStep(data); break;
260
- case 'token_update': this.updateTokens(data); break;
261
- case 'context_update': this.updateContext(data); break;
262
- case 'status_update': this.updateStatus(data.status); break;
435
+ if (data.status === 'current') {
436
+ el.classList.add('active');
437
+ el.classList.remove('completed');
438
+ badge.textContent = 'RUNNING';
439
+
440
+ document.getElementById('current-instruction').innerHTML = `
441
+ <h3>${data.stepId}</h3>
442
+ <p>${data.instruction || 'Processing...'}</p>
443
+ `;
444
+
445
+ if (data.fields && data.fields.length > 0) {
446
+ content.innerHTML = '<div class="sim-container"></div>';
447
+ this.runSimulationSequence(content.querySelector('.sim-container'), data.fields);
263
448
  }
264
- }
449
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
450
+
451
+ } else if (data.status === 'completed') {
452
+ el.classList.remove('active');
453
+ el.classList.add('completed');
454
+ badge.textContent = 'DONE';
455
+ el.querySelectorAll('.cursor-blink').forEach(c => c.style.display = 'none');
456
+
457
+ if (data.data) {
458
+ let outputHtml = '';
459
+ let rawTextForCopy = '';
460
+
461
+ // DETERMINE FORMAT
462
+ if (typeof data.data === 'object') {
463
+ // It's an Object -> JSON Pretty Print
464
+ rawTextForCopy = JSON.stringify(data.data, null, 2);
465
+ outputHtml = `<pre class="json-output">${rawTextForCopy}</pre>`;
466
+ } else {
467
+ // It's a String
468
+ const strData = String(data.data).trim();
469
+ // Try Parsing as JSON
470
+ try {
471
+ const parsed = JSON.parse(strData);
472
+ rawTextForCopy = JSON.stringify(parsed, null, 2);
473
+ outputHtml = `<pre class="json-output">${rawTextForCopy}</pre>`;
474
+ } catch (e) {
475
+ // Not JSON -> Render as Markdown
476
+ rawTextForCopy = strData;
477
+ outputHtml = `<div class="markdown-body">${marked.parse(strData)}</div>`;
478
+ }
479
+ }
265
480
 
266
- initSteps(steps) {
267
- const container = document.getElementById('steps-container');
268
- container.innerHTML = '<div class="absolute left-6 top-8 bottom-8 w-[1px] bg-neutral-800 -z-10"></div>';
269
- this.steps.clear();
481
+ const outputWrapper = document.createElement('div');
482
+ outputWrapper.className = 'bg-neutral-900/50 p-4 rounded mt-4 border border-neutral-800 relative';
483
+ outputWrapper.innerHTML = outputHtml;
484
+
485
+ // Add Copy Button
486
+ const copyBtn = document.createElement('button');
487
+ copyBtn.className = 'absolute top-3 right-3 text-neutral-500 hover:text-white text-xs bg-[#111] p-1.5 rounded border border-[#222]';
488
+ copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
489
+ copyBtn.onclick = () => {
490
+ navigator.clipboard.writeText(rawTextForCopy);
491
+ copyBtn.innerHTML = '<i class="fas fa-check text-green-400"></i>';
492
+ setTimeout(() => copyBtn.innerHTML = '<i class="fas fa-copy"></i>', 1000);
493
+ };
494
+ outputWrapper.appendChild(copyBtn);
270
495
 
271
- const flatSteps = Array.isArray(steps[0]) ? steps[0] : steps;
272
-
273
- flatSteps.forEach(step => {
274
- const el = this.createStepElement(step);
275
- container.appendChild(el);
276
- this.steps.set(step.id, { el, data: step });
277
- });
496
+ content.innerHTML = '';
497
+ content.appendChild(outputWrapper);
498
+ }
278
499
  }
500
+ }
279
501
 
280
- createStepElement(step) {
281
- const div = document.createElement('div');
282
- div.id = `step-${step.id}`;
283
- div.className = `step-card p-6 rounded-xl flex gap-6 relative transition-all duration-500 opacity-40`;
284
- div.innerHTML = `
285
- <div class="flex-shrink-0 w-12 h-12 rounded-full border-2 border-neutral-800 bg-black flex items-center justify-center z-10 step-icon-container">
286
- <span class="text-neutral-500 font-mono text-xs">${step.id.split('-').pop() || '01'}</span>
287
- </div>
288
- <div class="flex-1">
289
- <div class="flex items-center justify-between mb-2">
290
- <h3 class="text-sm font-bold text-white">${step.id}</h3>
291
- <div class="flex items-center">
292
- <span class="text-[10px] text-neutral-600 font-mono uppercase status-label">Waiting</span>
293
- <button class="replay-btn hidden text-neutral-500 hover:text-white text-xs ml-2" onclick="engine.sendMessage({ type: 'goto', stepId: '${step.id}' })"><i class="fas fa-redo"></i></button>
294
- </div>
295
- </div>
296
- <p class="text-[12px] text-neutral-500 leading-relaxed mb-4">${step.instruction || 'Processing...'}</p>
297
- <div class="step-content hidden border-t border-neutral-900 pt-4 mt-4">
298
- <!-- Dynamic form fields -->
299
- </div>
502
+ runSimulationSequence(container, fields) {
503
+ let delay = 0;
504
+ fields.forEach(field => {
505
+ const group = document.createElement('div');
506
+ group.className = 'sim-group';
507
+ group.style.animationDelay = `${delay}ms`;
508
+
509
+ group.innerHTML = `
510
+ <span class="sim-label">${field.key}</span>
511
+ <div class="sim-input">
512
+ <span class="type-target"></span><span class="cursor-blink"></span>
300
513
  </div>
301
514
  `;
302
- return div;
303
- }
515
+ container.appendChild(group);
304
516
 
305
- updateStep(data) {
306
- const step = this.steps.get(data.stepId);
307
- if (!step) return;
308
-
309
- const { el } = step;
310
- el.className = `step-card p-6 rounded-xl flex gap-6 relative transition-all duration-500 ${data.status}`;
311
-
312
- const iconContainer = el.querySelector('.step-icon-container');
313
- const statusLabel = el.querySelector('.status-label');
314
- const content = el.querySelector('.step-content');
315
-
316
- if (data.status === 'current') {
317
- el.classList.add('active', 'opacity-100');
318
- iconContainer.innerHTML = '<div class="loader"></div>';
319
- iconContainer.style.borderColor = 'var(--accent-primary)';
320
- statusLabel.textContent = 'Running';
321
- statusLabel.className = 'text-[10px] font-mono uppercase status-label pulse';
322
- statusLabel.style.color = 'var(--accent-primary)';
323
- el.querySelector('.replay-btn').classList.add('hidden');
324
- this.log(`Executing: ${data.stepId} - ${data.instruction || 'Processing...'}`);
325
-
326
- if (data.fields && data.fields.length > 0) {
327
- this.createStepForm(content, data.stepId, data.fields);
328
- content.classList.remove('hidden');
329
- } else if (data.instruction) {
330
- const instEl = document.createElement('div');
331
- instEl.className = 'text-[11px] text-neutral-400 bg-neutral-900/50 p-3 rounded mt-2 border border-neutral-800';
332
- instEl.innerHTML = marked.parse(data.instruction);
333
- content.innerHTML = '';
334
- content.appendChild(instEl);
335
- content.classList.remove('hidden');
336
- }
337
-
338
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
339
- } else if (data.status === 'completed') {
340
- el.classList.add('completed', 'opacity-100');
341
- el.classList.remove('active');
342
- iconContainer.innerHTML = '<i class="fas fa-check text-[12px]"></i>';
343
- iconContainer.style.borderColor = 'rgba(var(--accent-primary-rgb), 0.3)';
344
- iconContainer.querySelector('i').style.color = 'var(--accent-primary)';
345
- statusLabel.textContent = 'Done';
346
- statusLabel.className = 'text-[10px] text-neutral-500 font-mono uppercase status-label';
347
- el.querySelector('.replay-btn').classList.remove('hidden');
348
-
349
- if (data.data) {
350
- const outputEl = document.createElement('div');
351
- outputEl.className = 'text-[11px] text-neutral-300 bg-neutral-900/50 p-3 rounded mt-2 border border-neutral-800 font-mono relative';
352
- const preEl = document.createElement('pre');
353
- preEl.className = 'whitespace-pre-wrap break-words max-h-64 overflow-y-auto';
354
- preEl.textContent = typeof data.data === 'string' ? data.data : JSON.stringify(data.data, null, 2);
355
- const copyBtn = document.createElement('button');
356
- copyBtn.className = 'absolute top-2 right-2 text-neutral-500 hover:text-white text-xs';
357
- copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
358
- copyBtn.onclick = () => {
359
- navigator.clipboard.writeText(preEl.textContent);
360
- copyBtn.innerHTML = '<i class="fas fa-check"></i>';
361
- setTimeout(() => copyBtn.innerHTML = '<i class="fas fa-copy"></i>', 1000);
362
- };
363
- outputEl.appendChild(preEl);
364
- outputEl.appendChild(copyBtn);
365
- content.innerHTML = '';
366
- content.appendChild(outputEl);
367
- content.classList.remove('hidden');
368
- }
517
+ setTimeout(() => {
518
+ this.typeWriter(group.querySelector('.type-target'), field.value || "sample_data");
519
+ }, delay + 300);
520
+
521
+ delay += (field.value ? field.value.length * 30 : 500) + 500;
522
+ });
523
+ }
524
+
525
+ typeWriter(element, text) {
526
+ let i = 0;
527
+ const type = () => {
528
+ if (i < text.length) {
529
+ element.textContent += text.charAt(i);
530
+ i++;
531
+ setTimeout(type, 20);
369
532
  }
370
- }
533
+ };
534
+ type();
535
+ }
371
536
 
372
- updateContext(data) {
373
- this.context.clear();
374
- Object.entries(data.context).forEach(([key, value]) => {
375
- this.context.set(key, value);
376
- });
377
-
378
- const list = document.getElementById('context-list');
379
- list.innerHTML = '';
380
-
381
- Object.entries(data.context).forEach(([key, value]) => {
382
- const item = document.createElement('div');
383
- item.className = 'p-3 bg-neutral-900 border border-neutral-800 rounded-lg group cursor-pointer hover:border-neutral-700 transition-all';
384
-
385
- const type = Array.isArray(value) ? 'ARRAY' : typeof value;
386
- const displayContent = typeof value === 'string' ? value :
387
- Array.isArray(value) ? JSON.stringify(value, null, 2) :
388
- JSON.stringify(value, null, 2);
389
-
390
- item.innerHTML = `
391
- <div class="flex items-center justify-between mb-1">
392
- <span class="text-[11px] font-bold text-neutral-300">${key}</span>
393
- <div class="flex items-center gap-1">
394
- <span class="text-[9px] text-neutral-600 uppercase font-mono">${type.toUpperCase()}</span>
395
- <button class="text-neutral-500 hover:text-white text-xs copy-btn" data-key="${key}">
396
- <i class="fas fa-copy"></i>
397
- </button>
398
- </div>
399
- </div>
400
- <pre class="text-[10px] text-neutral-500 whitespace-pre-wrap break-words max-h-32 overflow-y-auto">${displayContent}</pre>
401
- `;
402
-
403
- item.querySelector('.copy-btn').onclick = (e) => {
404
- e.stopPropagation();
405
- const btn = e.target.closest('.copy-btn');
406
- const key = btn.dataset.key;
407
- const value = this.context.get(key);
408
- const copyText = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
409
- navigator.clipboard.writeText(copyText);
410
- btn.innerHTML = '<i class="fas fa-check"></i>';
411
- setTimeout(() => btn.innerHTML = '<i class="fas fa-copy"></i>', 1000);
412
- };
537
+ updateContext(data) {
538
+ this.context.clear();
539
+ Object.entries(data.context).forEach(([key, value]) => this.context.set(key, value));
413
540
 
414
- list.appendChild(item);
415
- });
416
- }
541
+ const list = document.getElementById('context-list');
542
+ list.innerHTML = '';
417
543
 
418
- updateStatus(status) {
419
- this.state = { ...this.state, ...status };
420
- const startBtn = document.getElementById('start-btn');
421
- const pauseBtn = document.getElementById('pause-btn');
422
- const statusBadge = document.getElementById('status-badge');
423
- const statusText = document.getElementById('status-text');
424
-
425
- if (status.isRunning && !status.isPaused) {
426
- startBtn.classList.add('hidden');
427
- pauseBtn.classList.remove('hidden');
428
- statusBadge.className = 'badge badge-green flex items-center gap-2';
429
- statusText.textContent = 'Running';
430
- } else if (status.isPaused) {
431
- if (status.isStepMode) {
432
- pauseBtn.innerHTML = '<i class="fas fa-step-forward text-[10px]"></i> NEXT STEP';
433
- pauseBtn.onclick = () => this.sendMessage({ type: 'step_forward' });
434
- } else {
435
- pauseBtn.innerHTML = '<i class="fas fa-play text-[10px]"></i> RESUME';
436
- pauseBtn.onclick = () => this.sendMessage({ type: 'resume' });
437
- }
438
- statusText.textContent = 'Paused';
544
+ Object.entries(data.context).forEach(([key, value]) => {
545
+ const item = document.createElement('div');
546
+ item.className = 'bg-[#0a0a0a] border border-[#222] rounded p-3 hover:border-violet-900 transition-colors group';
547
+
548
+ let typeLabel = 'OBJ';
549
+ let displayContent = '';
550
+
551
+ if (Array.isArray(value)) {
552
+ typeLabel = 'ARRAY';
553
+ displayContent = JSON.stringify(value, null, 2);
554
+ } else if (typeof value === 'object' && value !== null) {
555
+ typeLabel = 'JSON';
556
+ displayContent = JSON.stringify(value, null, 2);
439
557
  } else {
440
- startBtn.classList.remove('hidden');
441
- pauseBtn.classList.add('hidden');
558
+ typeLabel = typeof value;
559
+ displayContent = String(value);
442
560
  }
443
- }
444
561
 
445
- log(msg, type = "info") {
446
- const feed = document.getElementById('instruction-feed');
447
- const line = document.createElement('div');
448
- line.className = `terminal-line ${type === 'error' ? 'text-red-500 border-red-500' : 'text-neutral-300'}`;
449
- line.innerHTML = `<span class="text-neutral-600 mr-2">[${new Date().toLocaleTimeString([], {hour12: false})}]</span> ${msg}`;
450
- feed.appendChild(line);
451
- feed.scrollTop = feed.scrollHeight;
452
- }
562
+ item.innerHTML = `
563
+ <div class="flex items-center justify-between mb-2">
564
+ <span class="text-[11px] font-bold text-neutral-300 font-mono">${key}</span>
565
+ <div class="flex items-center gap-2">
566
+ <span class="text-[9px] text-neutral-600 uppercase font-bold bg-[#111] px-1 rounded">${typeLabel}</span>
567
+ <button class="text-neutral-500 hover:text-white text-xs opacity-0 group-hover:opacity-100 transition-opacity copy-btn">
568
+ <i class="fas fa-copy"></i>
569
+ </button>
570
+ </div>
571
+ </div>
572
+ <pre class="text-[10px] text-neutral-500 whitespace-pre-wrap break-all font-mono leading-relaxed max-h-40 overflow-y-auto custom-scroll">${displayContent}</pre>
573
+ `;
453
574
 
454
- setupListeners() {
455
- document.getElementById('start-btn').onclick = () => this.sendMessage({ type: 'run' });
456
- document.getElementById('pause-btn').onclick = () => this.sendMessage({ type: 'pause' });
457
- document.getElementById('stop-btn').onclick = () => this.sendMessage({ type: 'stop' });
458
- document.getElementById('replay-btn').onclick = () => this.sendMessage({ type: 'resume' });
459
- }
575
+ item.querySelector('.copy-btn').onclick = function() {
576
+ navigator.clipboard.writeText(displayContent);
577
+ this.innerHTML = '<i class="fas fa-check text-green-400"></i>';
578
+ setTimeout(() => this.innerHTML = '<i class="fas fa-copy"></i>', 1000);
579
+ };
460
580
 
461
- sendMessage(message) {
462
- if (this.ws?.readyState === WebSocket.OPEN) {
463
- this.ws.send(JSON.stringify(message));
464
- this.log(`Manual trigger: ${message.type.toUpperCase()}`);
465
- }
581
+ list.appendChild(item);
582
+ });
583
+ }
584
+
585
+ updateStatus(status) {
586
+ this.state = { ...this.state, ...status };
587
+ const deck = document.getElementById('control-deck');
588
+ const headerStatus = document.getElementById('header-status');
589
+ const dot = document.getElementById('status-dot');
590
+ const playBtn = document.getElementById('play-pause-btn');
591
+
592
+ if (status.isRunning && !status.isPaused) {
593
+ deck.style.borderColor = 'var(--success)';
594
+ headerStatus.textContent = "RUNNING";
595
+ headerStatus.className = "text-[9px] font-bold text-green-500 uppercase tracking-widest";
596
+ dot.className = "w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse";
597
+ playBtn.innerHTML = '<i class="fas fa-pause"></i>';
598
+ } else if (status.isPaused) {
599
+ deck.style.borderColor = 'var(--warning)';
600
+ headerStatus.textContent = "PAUSED";
601
+ headerStatus.className = "text-[9px] font-bold text-yellow-500 uppercase tracking-widest";
602
+ dot.className = "w-1.5 h-1.5 rounded-full bg-yellow-500";
603
+ playBtn.innerHTML = '<i class="fas fa-play ml-1"></i>';
604
+ } else {
605
+ deck.style.borderColor = 'var(--border)';
606
+ headerStatus.textContent = "READY";
607
+ headerStatus.className = "text-[9px] font-bold text-neutral-500 uppercase tracking-widest";
608
+ dot.className = "w-1.5 h-1.5 rounded-full bg-neutral-600";
609
+ playBtn.innerHTML = '<i class="fas fa-play ml-1"></i>';
466
610
  }
611
+ }
612
+
613
+ log(msg, type = "info") {
614
+ const feed = document.getElementById('log-feed');
615
+ const line = document.createElement('div');
616
+ line.className = 'log-line';
617
+ const time = new Date().toLocaleTimeString('en-GB', { hour12: false });
618
+ const color = type === 'error' ? 'text-red-500' : 'text-neutral-400';
619
+ line.innerHTML = `<span class="log-time text-[9px] font-mono opacity-50">[${time}]</span><span class="${color}">${msg}</span>`;
620
+ feed.appendChild(line);
621
+ feed.scrollTop = feed.scrollHeight;
622
+ }
467
623
 
468
- updateConnectionUI(connected) {
469
- const badge = document.getElementById('status-badge');
470
- badge.classList.toggle('opacity-50', !connected);
624
+ setupListeners() {
625
+ document.getElementById('play-pause-btn').onclick = () => {
626
+ if (this.state.isRunning && !this.state.isPaused) this.sendMessage({ type: 'pause' });
627
+ else if (this.state.isPaused) this.sendMessage({ type: 'resume' });
628
+ else this.sendMessage({ type: 'run' });
629
+ };
630
+ document.getElementById('stop-btn').onclick = () => this.sendMessage({ type: 'stop' });
631
+ document.getElementById('replay-btn').onclick = () => this.sendMessage({ type: 'resume' });
632
+ }
633
+
634
+ sendMessage(message) {
635
+ if (this.ws?.readyState === WebSocket.OPEN) {
636
+ this.ws.send(JSON.stringify(message));
637
+ this.log(`Action: ${message.type.toUpperCase()}`);
471
638
  }
472
-
473
- updateTokens(data) {
474
- document.getElementById('total-tokens').textContent = data.totalTokens.toLocaleString();
639
+ }
640
+
641
+ updateConnectionUI(connected) {
642
+ const dot = document.getElementById('status-dot');
643
+ const headerStatus = document.getElementById('header-status');
644
+ if (connected) {
645
+ headerStatus.textContent = "READY";
646
+ headerStatus.className = "text-[9px] font-bold text-green-500 uppercase tracking-widest";
647
+ dot.className = "w-1.5 h-1.5 rounded-full bg-green-500";
648
+ } else {
649
+ headerStatus.textContent = "OFFLINE";
650
+ headerStatus.className = "text-[9px] font-bold text-red-500 uppercase tracking-widest";
651
+ dot.className = "w-1.5 h-1.5 rounded-full bg-red-500";
475
652
  }
476
653
  }
654
+
655
+ updateTokens(data) {
656
+ this.state.totalTokens = data.totalTokens || 0;
657
+ this.state.tokenRate = data.rate || 0;
658
+ document.getElementById('header-tokens').textContent = this.state.totalTokens.toLocaleString();
659
+ document.getElementById('header-rate').textContent = this.state.tokenRate.toFixed(1);
660
+ }
661
+ }
477
662
 
478
- const engine = new WizardEngine();
479
- </script>
663
+ const engine = new WizardEngine();
664
+ </script>
480
665
  </body>
481
- </html>
666
+ </html>