pixelcrew 0.2.3 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +389 -315
- package/bin/pixel-agents.js +306 -149
- package/package.json +3 -1
- package/skill/SKILL.md +185 -0
- package/skill/references/anti-ai-rubric.md +30 -0
- package/skill/references/anti-ai-slop-encyclopedia.md +181 -0
- package/skill/references/commands-reference.md +135 -0
- package/skill/references/testing-and-qa-strategy.md +25 -0
- package/skill/references/token-efficiency.md +20 -0
- package/src/adapters/adapter.interface.js +77 -0
- package/src/adapters/antigravity.js +138 -0
- package/src/adapters/claude-code.js +149 -0
- package/src/adapters/codex.js +131 -0
- package/src/adapters/cursor.js +133 -0
- package/src/adapters/generic.js +63 -0
- package/src/adapters/index.js +15 -0
- package/src/adapters/kiro.js +134 -0
- package/src/adapters/registry.js +150 -0
- package/src/backend/analyzer/complexity-analyzer.js +104 -0
- package/src/backend/analyzer/requirement-analyzer.js +213 -0
- package/src/backend/analyzer/threat-analyzer.js +122 -0
- package/src/backend/api/api-designer.js +90 -0
- package/src/backend/api/error-contract.js +132 -0
- package/src/backend/api/route-generator.js +92 -0
- package/src/backend/api/schema-generator.js +64 -0
- package/src/backend/architecture/adr-generator.js +62 -0
- package/src/backend/architecture/architecture-reasoner.js +119 -0
- package/src/backend/architecture/architecture-selector.js +57 -0
- package/src/backend/architecture/tradeoff-analyzer.js +92 -0
- package/src/backend/data/data-modeler.js +114 -0
- package/src/backend/data/index-analyzer.js +93 -0
- package/src/backend/data/query-analyzer.js +41 -0
- package/src/backend/data/schema-generator.js +92 -0
- package/src/backend/data/transaction-analyzer.js +49 -0
- package/src/backend/index.js +67 -0
- package/src/backend/observability/logging-engine.js +80 -0
- package/src/backend/observability/metrics-engine.js +31 -0
- package/src/backend/observability/tracing-engine.js +16 -0
- package/src/backend/patterns/pattern-registry.js +74 -0
- package/src/backend/patterns/pattern-selector.js +39 -0
- package/src/backend/patterns/tradeoff-engine.js +17 -0
- package/src/backend/performance/cache-analyzer.js +31 -0
- package/src/backend/performance/concurrency-analyzer.js +34 -0
- package/src/backend/performance/performance-engine.js +98 -0
- package/src/backend/performance/query-optimizer.js +16 -0
- package/src/backend/reliability/failure-analyzer.js +22 -0
- package/src/backend/reliability/idempotency-engine.js +56 -0
- package/src/backend/reliability/resilience-engine.js +141 -0
- package/src/backend/reliability/retry-analyzer.js +15 -0
- package/src/backend/security/auth-selector.js +33 -0
- package/src/backend/security/authorization-engine.js +90 -0
- package/src/backend/security/policy-engine.js +49 -0
- package/src/backend/security/secret-analyzer.js +39 -0
- package/src/backend/security/security-engine.js +122 -0
- package/src/backend/security/threat-model.js +59 -0
- package/src/backend/synthesis/backend-synthesizer.js +98 -0
- package/src/backend/synthesis/infrastructure-synthesizer.js +108 -0
- package/src/backend/synthesis/module-synthesizer.js +262 -0
- package/src/backend/validation/architecture-validator.js +70 -0
- package/src/backend/validation/backend-quality-validator.js +71 -0
- package/src/backend/validation/dependency-validator.js +20 -0
- package/src/backend/validation/performance-validator.js +38 -0
- package/src/backend/validation/security-validator.js +73 -0
- package/src/commands/adapt.js +105 -0
- package/src/commands/add.js +78 -0
- package/src/commands/animate.js +48 -0
- package/src/commands/assemble.js +57 -0
- package/src/commands/audit.js +93 -0
- package/src/commands/backend.js +125 -0
- package/src/commands/bento.js +25 -0
- package/src/commands/blueprint.js +65 -0
- package/src/commands/bolder.js +48 -0
- package/src/commands/boss-fight.js +60 -0
- package/src/commands/build.js +39 -0
- package/src/commands/calibrate.js +25 -0
- package/src/commands/chromatic.js +25 -0
- package/src/commands/clarify.js +52 -0
- package/src/commands/colorize.js +57 -0
- package/src/commands/command.interface.js +28 -0
- package/src/commands/commands.js +84 -0
- package/src/commands/craft.js +57 -0
- package/src/commands/crew.js +66 -0
- package/src/commands/critique.js +25 -0
- package/src/commands/de-slop.js +25 -0
- package/src/commands/delight.js +46 -0
- package/src/commands/deploy.js +42 -0
- package/src/commands/distill.js +48 -0
- package/src/commands/doctor.js +89 -0
- package/src/commands/document.js +83 -0
- package/src/commands/eight-bit.js +25 -0
- package/src/commands/extract.js +75 -0
- package/src/commands/fix.js +46 -0
- package/src/commands/harden.js +85 -0
- package/src/commands/index.js +67 -0
- package/src/commands/install.js +75 -0
- package/src/commands/layout.js +48 -0
- package/src/commands/live.js +44 -0
- package/src/commands/manifest.js +25 -0
- package/src/commands/office.js +25 -0
- package/src/commands/onboard.js +46 -0
- package/src/commands/oneshot.js +60 -0
- package/src/commands/optimize.js +46 -0
- package/src/commands/overdrive.js +46 -0
- package/src/commands/parser.js +143 -0
- package/src/commands/pixelcrew.js +96 -0
- package/src/commands/plan.js +66 -0
- package/src/commands/polish.js +45 -0
- package/src/commands/quieter.js +48 -0
- package/src/commands/recap.js +358 -0
- package/src/commands/registry.js +353 -0
- package/src/commands/render.js +26 -0
- package/src/commands/retrofit.js +25 -0
- package/src/commands/review.js +118 -0
- package/src/commands/roster.js +25 -0
- package/src/commands/sentinel.js +25 -0
- package/src/commands/shape.js +66 -0
- package/src/commands/skills.js +57 -0
- package/src/commands/status.js +48 -0
- package/src/commands/stop.js +50 -0
- package/src/commands/sync.js +58 -0
- package/src/commands/typeset.js +58 -0
- package/src/commands/warp.js +25 -0
- package/src/core/agent-runtime.js +94 -0
- package/src/core/event-bus.js +98 -0
- package/src/core/index.js +9 -0
- package/src/core/orchestrator.js +234 -0
- package/src/core/scheduler.js +124 -0
- package/src/core/task-graph.js +185 -0
- package/src/dashboard/app.js +1172 -30
- package/src/dashboard/index.html +223 -23
- package/src/dashboard/styles.css +870 -0
- package/src/index.js +16 -0
- package/src/orchestrator/artifact-graph.js +104 -0
- package/src/orchestrator/ast-normalizer.js +147 -0
- package/src/orchestrator/code-generator.js +632 -780
- package/src/orchestrator/design-engine.js +203 -0
- package/src/orchestrator/engine.js +307 -38
- package/src/orchestrator/oneshot.js +367 -380
- package/src/orchestrator/ontology.js +394 -0
- package/src/orchestrator/planner.js +231 -258
- package/src/orchestrator/repair-planner.js +67 -0
- package/src/orchestrator/requirement-contract.js +177 -0
- package/src/orchestrator/requirement-validator.js +39 -0
- package/src/orchestrator/semantic-engine.js +582 -0
- package/src/orchestrator/semantic-reviewer.js +179 -0
- package/src/orchestrator/task-planner.js +111 -0
- package/src/orchestrator/telemetry.js +209 -0
- package/src/orchestrator/ui-synthesizer.js +245 -0
- package/src/orchestrator/visual-critic.js +106 -0
- package/src/policies/architecture/layering.policy.js +14 -0
- package/src/policies/performance/indexing.policy.js +13 -0
- package/src/policies/reliability/resilience.policy.js +13 -0
- package/src/policies/security/auth.policy.js +31 -0
- package/src/protocol/agent.js +124 -0
- package/src/protocol/event.js +78 -0
- package/src/protocol/index.js +8 -0
- package/src/protocol/skill.js +54 -0
- package/src/protocol/task.js +100 -0
- package/src/scaffold/claude-generator.js +60 -0
- package/src/scaffold/commands-catalog.js +321 -0
- package/src/scaffold/cursor-generator.js +74 -0
- package/src/scaffold/ide-rules.js +133 -0
- package/src/scaffold/init.js +415 -91
- package/src/scaffold/installer.js +571 -0
- package/src/scaffold/kiro-generator.js +86 -0
- package/src/scaffold/skills-bundle.js +520 -0
- package/src/scaffold/templates.js +57 -4
- package/src/scaffold/workstations.js +364 -0
- package/src/server/server.js +283 -40
- package/src/utils/fs-safe.js +168 -0
- package/src/utils/markdown-report.js +175 -0
package/src/dashboard/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PIXEL
|
|
2
|
+
* PIXEL CREW HQ — Autonomous Multi-Agent Office & Realtime Telemetry Engine
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// Global State
|
|
@@ -8,9 +8,15 @@ const appState = {
|
|
|
8
8
|
state: null,
|
|
9
9
|
events: [],
|
|
10
10
|
activeFilter: 'all',
|
|
11
|
+
streamMode: 'sidechat', // 'sidechat' | 'slack'
|
|
12
|
+
autoScrollEnabled: true,
|
|
11
13
|
audioEnabled: true,
|
|
12
14
|
crtEnabled: true,
|
|
13
15
|
nightMode: false,
|
|
16
|
+
activeProvider: 'antigravity',
|
|
17
|
+
activeProviderName: 'Google Antigravity',
|
|
18
|
+
activeProviderIcon: '🪐',
|
|
19
|
+
providersData: null,
|
|
14
20
|
missionStartTime: null,
|
|
15
21
|
missionTimerInterval: null,
|
|
16
22
|
hoveredDesk: null
|
|
@@ -104,7 +110,303 @@ const WORKSTATIONS = {
|
|
|
104
110
|
}
|
|
105
111
|
};
|
|
106
112
|
|
|
107
|
-
//
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// PROCEDURAL LO-FI MUSIC SYNTHESIZER & AMBIENT RADIO
|
|
115
|
+
// ============================================================================
|
|
116
|
+
class LofiMusicEngine {
|
|
117
|
+
constructor() {
|
|
118
|
+
this.ctx = null;
|
|
119
|
+
this.isPlaying = false;
|
|
120
|
+
this.currentTrack = 'floor42';
|
|
121
|
+
this.volume = 0.7;
|
|
122
|
+
this.loopTimer = null;
|
|
123
|
+
this.noiseNode = null;
|
|
124
|
+
this.noiseGain = null;
|
|
125
|
+
this.chordStep = 0;
|
|
126
|
+
this.bpm = 75;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
initContext() {
|
|
130
|
+
if (!this.ctx) {
|
|
131
|
+
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
|
132
|
+
if (AudioCtx) {
|
|
133
|
+
this.ctx = new AudioCtx();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (this.ctx && this.ctx.state === 'suspended') {
|
|
137
|
+
this.ctx.resume();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setVolume(val) {
|
|
142
|
+
this.volume = Math.max(0, Math.min(1, val));
|
|
143
|
+
if (this.masterGain && this.ctx) {
|
|
144
|
+
this.masterGain.gain.setValueAtTime(this.volume * 0.8, this.ctx.currentTime);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
setTrack(trackId) {
|
|
149
|
+
this.currentTrack = trackId;
|
|
150
|
+
this.chordStep = 0;
|
|
151
|
+
if (this.isPlaying) {
|
|
152
|
+
this.stop();
|
|
153
|
+
this.start();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
start() {
|
|
158
|
+
this.initContext();
|
|
159
|
+
if (!this.ctx) return;
|
|
160
|
+
if (this.isPlaying) return;
|
|
161
|
+
this.isPlaying = true;
|
|
162
|
+
|
|
163
|
+
this.masterGain = this.ctx.createGain();
|
|
164
|
+
this.masterGain.gain.setValueAtTime(this.volume * 0.8, this.ctx.currentTime);
|
|
165
|
+
this.masterGain.connect(this.ctx.destination);
|
|
166
|
+
|
|
167
|
+
this.startVinylNoise();
|
|
168
|
+
this.scheduleNextBeat();
|
|
169
|
+
|
|
170
|
+
const btn = document.getElementById('btnLofiToggle');
|
|
171
|
+
const visualizer = document.getElementById('lofiVisualizer');
|
|
172
|
+
const playIcon = document.getElementById('lofiPlayIcon');
|
|
173
|
+
if (btn) btn.classList.add('playing');
|
|
174
|
+
if (visualizer) visualizer.classList.add('playing');
|
|
175
|
+
if (playIcon) playIcon.textContent = '⏸';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
stop() {
|
|
179
|
+
this.isPlaying = false;
|
|
180
|
+
if (this.loopTimer) {
|
|
181
|
+
clearTimeout(this.loopTimer);
|
|
182
|
+
this.loopTimer = null;
|
|
183
|
+
}
|
|
184
|
+
this.stopVinylNoise();
|
|
185
|
+
|
|
186
|
+
const btn = document.getElementById('btnLofiToggle');
|
|
187
|
+
const visualizer = document.getElementById('lofiVisualizer');
|
|
188
|
+
const playIcon = document.getElementById('lofiPlayIcon');
|
|
189
|
+
if (btn) btn.classList.remove('playing');
|
|
190
|
+
if (visualizer) visualizer.classList.remove('playing');
|
|
191
|
+
if (playIcon) playIcon.textContent = '▶';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
toggle() {
|
|
195
|
+
if (this.isPlaying) this.stop();
|
|
196
|
+
else this.start();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
startVinylNoise() {
|
|
200
|
+
if (!this.ctx || this.noiseNode) return;
|
|
201
|
+
try {
|
|
202
|
+
const bufferSize = this.ctx.sampleRate * 2;
|
|
203
|
+
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
|
|
204
|
+
const data = buffer.getChannelData(0);
|
|
205
|
+
for (let i = 0; i < bufferSize; i++) {
|
|
206
|
+
const white = Math.random() * 2 - 1;
|
|
207
|
+
const isPop = Math.random() < 0.0008;
|
|
208
|
+
data[i] = (white * 0.035) + (isPop ? (Math.random() * 0.35 - 0.17) : 0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.noiseNode = this.ctx.createBufferSource();
|
|
212
|
+
this.noiseNode.buffer = buffer;
|
|
213
|
+
this.noiseNode.loop = true;
|
|
214
|
+
|
|
215
|
+
const filter = this.ctx.createBiquadFilter();
|
|
216
|
+
filter.type = 'lowpass';
|
|
217
|
+
filter.frequency.value = 1600;
|
|
218
|
+
|
|
219
|
+
this.noiseGain = this.ctx.createGain();
|
|
220
|
+
this.noiseGain.gain.setValueAtTime(0.06, this.ctx.currentTime);
|
|
221
|
+
|
|
222
|
+
this.noiseNode.connect(filter);
|
|
223
|
+
filter.connect(this.noiseGain);
|
|
224
|
+
this.noiseGain.connect(this.masterGain);
|
|
225
|
+
|
|
226
|
+
this.noiseNode.start();
|
|
227
|
+
} catch {}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
stopVinylNoise() {
|
|
231
|
+
if (this.noiseNode) {
|
|
232
|
+
try { this.noiseNode.stop(); } catch {}
|
|
233
|
+
this.noiseNode = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
playWarmChord(freqs, duration = 2.4, gainLevel = 0.07) {
|
|
238
|
+
if (!this.ctx || !this.isPlaying) return;
|
|
239
|
+
const now = this.ctx.currentTime;
|
|
240
|
+
|
|
241
|
+
freqs.forEach((freq, i) => {
|
|
242
|
+
try {
|
|
243
|
+
const osc = this.ctx.createOscillator();
|
|
244
|
+
const subOsc = this.ctx.createOscillator();
|
|
245
|
+
const filter = this.ctx.createBiquadFilter();
|
|
246
|
+
const gain = this.ctx.createGain();
|
|
247
|
+
|
|
248
|
+
osc.type = 'triangle';
|
|
249
|
+
osc.frequency.setValueAtTime(freq, now);
|
|
250
|
+
|
|
251
|
+
subOsc.type = 'sine';
|
|
252
|
+
subOsc.frequency.setValueAtTime(freq * 0.5, now);
|
|
253
|
+
|
|
254
|
+
filter.type = 'lowpass';
|
|
255
|
+
filter.frequency.setValueAtTime(1400, now);
|
|
256
|
+
filter.frequency.exponentialRampToValueAtTime(500, now + duration);
|
|
257
|
+
|
|
258
|
+
const stagger = i * 0.022;
|
|
259
|
+
gain.gain.setValueAtTime(0.0001, now + stagger);
|
|
260
|
+
gain.gain.exponentialRampToValueAtTime(gainLevel / freqs.length, now + stagger + 0.08);
|
|
261
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, now + duration);
|
|
262
|
+
|
|
263
|
+
osc.connect(filter);
|
|
264
|
+
subOsc.connect(filter);
|
|
265
|
+
filter.connect(gain);
|
|
266
|
+
gain.connect(this.masterGain);
|
|
267
|
+
|
|
268
|
+
osc.start(now + stagger);
|
|
269
|
+
subOsc.start(now + stagger);
|
|
270
|
+
osc.stop(now + duration);
|
|
271
|
+
subOsc.stop(now + duration);
|
|
272
|
+
} catch {}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
playLofiKick() {
|
|
277
|
+
if (!this.ctx || !this.isPlaying) return;
|
|
278
|
+
try {
|
|
279
|
+
const now = this.ctx.currentTime;
|
|
280
|
+
const osc = this.ctx.createOscillator();
|
|
281
|
+
const gain = this.ctx.createGain();
|
|
282
|
+
|
|
283
|
+
osc.frequency.setValueAtTime(110, now);
|
|
284
|
+
osc.frequency.exponentialRampToValueAtTime(42, now + 0.16);
|
|
285
|
+
|
|
286
|
+
gain.gain.setValueAtTime(0.1, now);
|
|
287
|
+
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
|
|
288
|
+
|
|
289
|
+
osc.connect(gain);
|
|
290
|
+
gain.connect(this.masterGain);
|
|
291
|
+
|
|
292
|
+
osc.start(now);
|
|
293
|
+
osc.stop(now + 0.18);
|
|
294
|
+
} catch {}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
playLofiSnare() {
|
|
298
|
+
if (!this.ctx || !this.isPlaying) return;
|
|
299
|
+
try {
|
|
300
|
+
const now = this.ctx.currentTime;
|
|
301
|
+
const buffer = this.ctx.createBuffer(1, this.ctx.sampleRate * 0.14, this.ctx.sampleRate);
|
|
302
|
+
const data = buffer.getChannelData(0);
|
|
303
|
+
for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
|
|
304
|
+
|
|
305
|
+
const noise = this.ctx.createBufferSource();
|
|
306
|
+
noise.buffer = buffer;
|
|
307
|
+
|
|
308
|
+
const filter = this.ctx.createBiquadFilter();
|
|
309
|
+
filter.type = 'bandpass';
|
|
310
|
+
filter.frequency.setValueAtTime(1100, now);
|
|
311
|
+
|
|
312
|
+
const gain = this.ctx.createGain();
|
|
313
|
+
gain.gain.setValueAtTime(0.05, now);
|
|
314
|
+
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.14);
|
|
315
|
+
|
|
316
|
+
noise.connect(filter);
|
|
317
|
+
filter.connect(gain);
|
|
318
|
+
gain.connect(this.masterGain);
|
|
319
|
+
|
|
320
|
+
noise.start(now);
|
|
321
|
+
noise.stop(now + 0.14);
|
|
322
|
+
} catch {}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
playLofiHat() {
|
|
326
|
+
if (!this.ctx || !this.isPlaying) return;
|
|
327
|
+
try {
|
|
328
|
+
const now = this.ctx.currentTime;
|
|
329
|
+
const buffer = this.ctx.createBuffer(1, this.ctx.sampleRate * 0.04, this.ctx.sampleRate);
|
|
330
|
+
const data = buffer.getChannelData(0);
|
|
331
|
+
for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
|
|
332
|
+
|
|
333
|
+
const noise = this.ctx.createBufferSource();
|
|
334
|
+
noise.buffer = buffer;
|
|
335
|
+
|
|
336
|
+
const filter = this.ctx.createBiquadFilter();
|
|
337
|
+
filter.type = 'highpass';
|
|
338
|
+
filter.frequency.setValueAtTime(6500, now);
|
|
339
|
+
|
|
340
|
+
const gain = this.ctx.createGain();
|
|
341
|
+
gain.gain.setValueAtTime(0.018, now);
|
|
342
|
+
gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.04);
|
|
343
|
+
|
|
344
|
+
noise.connect(filter);
|
|
345
|
+
filter.connect(gain);
|
|
346
|
+
gain.connect(this.masterGain);
|
|
347
|
+
|
|
348
|
+
noise.start(now);
|
|
349
|
+
noise.stop(now + 0.04);
|
|
350
|
+
} catch {}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
scheduleNextBeat() {
|
|
354
|
+
if (!this.isPlaying) return;
|
|
355
|
+
|
|
356
|
+
const CHORD_PROGRESSIONS = {
|
|
357
|
+
floor42: [
|
|
358
|
+
[293.66, 349.23, 440.00, 523.25, 659.25], // Dm9
|
|
359
|
+
[392.00, 493.88, 587.33, 698.46, 880.00], // G13
|
|
360
|
+
[261.63, 329.63, 392.00, 493.88, 587.33], // Cmaj9
|
|
361
|
+
[220.00, 277.18, 329.63, 440.00, 554.37] // A7b13
|
|
362
|
+
],
|
|
363
|
+
rainy: [
|
|
364
|
+
[349.23, 440.00, 523.25, 659.25, 783.99], // Fmaj9
|
|
365
|
+
[329.63, 392.00, 493.88, 587.33], // Em7
|
|
366
|
+
[293.66, 349.23, 440.00, 523.25], // Dm7
|
|
367
|
+
[220.00, 261.63, 329.63, 392.00, 493.88] // Am9
|
|
368
|
+
],
|
|
369
|
+
antigravity: [
|
|
370
|
+
[261.63, 329.63, 392.00, 523.25, 659.25], // Cmaj7/9
|
|
371
|
+
[220.00, 261.63, 329.63, 440.00, 587.33], // Am9
|
|
372
|
+
[349.23, 440.00, 523.25, 698.46], // Fmaj7
|
|
373
|
+
[196.00, 246.94, 293.66, 392.00, 493.88] // G6
|
|
374
|
+
],
|
|
375
|
+
retro8bit: [
|
|
376
|
+
[261.63, 329.63, 392.00, 523.25], // C
|
|
377
|
+
[220.00, 261.63, 329.63, 440.00], // Am
|
|
378
|
+
[174.61, 220.00, 261.63, 349.23], // F
|
|
379
|
+
[196.00, 246.94, 293.66, 392.00] // G
|
|
380
|
+
]
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const chords = CHORD_PROGRESSIONS[this.currentTrack] || CHORD_PROGRESSIONS.floor42;
|
|
384
|
+
const currentChord = chords[this.chordStep % chords.length];
|
|
385
|
+
|
|
386
|
+
this.playWarmChord(currentChord, 2.4, 0.08);
|
|
387
|
+
|
|
388
|
+
const beatInterval = (60 / this.bpm) * 1000;
|
|
389
|
+
|
|
390
|
+
this.playLofiKick();
|
|
391
|
+
this.playLofiHat();
|
|
392
|
+
|
|
393
|
+
setTimeout(() => { if (this.isPlaying) { this.playLofiSnare(); this.playLofiHat(); } }, beatInterval);
|
|
394
|
+
setTimeout(() => { if (this.isPlaying) { this.playLofiKick(); this.playLofiHat(); } }, beatInterval * 2);
|
|
395
|
+
setTimeout(() => { if (this.isPlaying) { this.playLofiHat(); } }, beatInterval * 2.5);
|
|
396
|
+
setTimeout(() => { if (this.isPlaying) { this.playLofiSnare(); this.playLofiHat(); } }, beatInterval * 3);
|
|
397
|
+
|
|
398
|
+
this.chordStep++;
|
|
399
|
+
this.loopTimer = setTimeout(() => {
|
|
400
|
+
this.scheduleNextBeat();
|
|
401
|
+
}, beatInterval * 4);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const lofiEngine = new LofiMusicEngine();
|
|
406
|
+
|
|
407
|
+
// ============================================================================
|
|
408
|
+
// RETRO SFX AUDIO SYNTHESIZER
|
|
409
|
+
// ============================================================================
|
|
108
410
|
class RetroAudioSynth {
|
|
109
411
|
constructor() {
|
|
110
412
|
this.ctx = null;
|
|
@@ -122,7 +424,7 @@ class RetroAudioSynth {
|
|
|
122
424
|
}
|
|
123
425
|
}
|
|
124
426
|
|
|
125
|
-
playTone(freq, type = 'square', duration = 0.08, gainVal = 0.
|
|
427
|
+
playTone(freq, type = 'square', duration = 0.08, gainVal = 0.04) {
|
|
126
428
|
if (!appState.audioEnabled) return;
|
|
127
429
|
try {
|
|
128
430
|
this.initContext();
|
|
@@ -140,36 +442,40 @@ class RetroAudioSynth {
|
|
|
140
442
|
|
|
141
443
|
osc.start();
|
|
142
444
|
osc.stop(this.ctx.currentTime + duration);
|
|
143
|
-
} catch
|
|
144
|
-
// Audio autoplay policy
|
|
145
|
-
}
|
|
445
|
+
} catch {}
|
|
146
446
|
}
|
|
147
447
|
|
|
148
448
|
playSpawn() {
|
|
149
449
|
if (!appState.audioEnabled) return;
|
|
150
450
|
this.playTone(330, 'triangle', 0.06);
|
|
151
|
-
setTimeout(() => this.playTone(440, 'triangle', 0.06),
|
|
152
|
-
setTimeout(() => this.playTone(660, 'square', 0.
|
|
451
|
+
setTimeout(() => this.playTone(440, 'triangle', 0.06), 55);
|
|
452
|
+
setTimeout(() => this.playTone(660, 'square', 0.09), 110);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
playTool() {
|
|
456
|
+
if (!appState.audioEnabled) return;
|
|
457
|
+
this.playTone(520, 'triangle', 0.04, 0.03);
|
|
458
|
+
setTimeout(() => this.playTone(780, 'square', 0.04, 0.02), 35);
|
|
153
459
|
}
|
|
154
460
|
|
|
155
461
|
playSkill() {
|
|
156
462
|
if (!appState.audioEnabled) return;
|
|
157
|
-
this.playTone(659.25, 'square', 0.05, 0.
|
|
158
|
-
setTimeout(() => this.playTone(987.77, 'square', 0.
|
|
463
|
+
this.playTone(659.25, 'square', 0.05, 0.03);
|
|
464
|
+
setTimeout(() => this.playTone(987.77, 'square', 0.09, 0.03), 45);
|
|
159
465
|
}
|
|
160
466
|
|
|
161
467
|
playComplete() {
|
|
162
468
|
if (!appState.audioEnabled) return;
|
|
163
469
|
const notes = [523.25, 659.25, 783.99, 1046.50];
|
|
164
470
|
notes.forEach((note, i) => {
|
|
165
|
-
setTimeout(() => this.playTone(note, 'square', 0.
|
|
471
|
+
setTimeout(() => this.playTone(note, 'square', 0.1, 0.04), i * 75);
|
|
166
472
|
});
|
|
167
473
|
}
|
|
168
474
|
|
|
169
475
|
playError() {
|
|
170
476
|
if (!appState.audioEnabled) return;
|
|
171
|
-
this.playTone(180, 'sawtooth', 0.
|
|
172
|
-
setTimeout(() => this.playTone(140, 'sawtooth', 0.
|
|
477
|
+
this.playTone(180, 'sawtooth', 0.12, 0.06);
|
|
478
|
+
setTimeout(() => this.playTone(140, 'sawtooth', 0.2, 0.06), 90);
|
|
173
479
|
}
|
|
174
480
|
|
|
175
481
|
playClick() {
|
|
@@ -179,7 +485,9 @@ class RetroAudioSynth {
|
|
|
179
485
|
|
|
180
486
|
const synth = new RetroAudioSynth();
|
|
181
487
|
|
|
182
|
-
//
|
|
488
|
+
// ============================================================================
|
|
489
|
+
// PIXEL OFFICE CANVAS RENDERER (HIGH-DPI RETINA SUPPORT)
|
|
490
|
+
// ============================================================================
|
|
183
491
|
let officeCanvas, officeCtx;
|
|
184
492
|
let animFrame = 0;
|
|
185
493
|
|
|
@@ -188,11 +496,20 @@ function initOfficeCanvas() {
|
|
|
188
496
|
if (!officeCanvas) return;
|
|
189
497
|
officeCtx = officeCanvas.getContext('2d');
|
|
190
498
|
|
|
499
|
+
// Handle Retina / High-DPI Display Scaling
|
|
500
|
+
const dpr = window.devicePixelRatio || 1;
|
|
501
|
+
const baseW = 960;
|
|
502
|
+
const baseH = 420;
|
|
503
|
+
|
|
504
|
+
officeCanvas.width = baseW * dpr;
|
|
505
|
+
officeCanvas.height = baseH * dpr;
|
|
506
|
+
officeCtx.scale(dpr, dpr);
|
|
507
|
+
|
|
191
508
|
// Mouse interaction for tooltips and clicking workstations
|
|
192
509
|
officeCanvas.addEventListener('mousemove', (e) => {
|
|
193
510
|
const rect = officeCanvas.getBoundingClientRect();
|
|
194
|
-
const scaleX =
|
|
195
|
-
const scaleY =
|
|
511
|
+
const scaleX = baseW / rect.width;
|
|
512
|
+
const scaleY = baseH / rect.height;
|
|
196
513
|
const mouseX = (e.clientX - rect.left) * scaleX;
|
|
197
514
|
const mouseY = (e.clientY - rect.top) * scaleY;
|
|
198
515
|
|
|
@@ -433,6 +750,12 @@ function renderOfficeWorker(ctx, agentKey, state, x, y, frame) {
|
|
|
433
750
|
|
|
434
751
|
const suitColor = colors[agentKey] || '#00f0ff';
|
|
435
752
|
|
|
753
|
+
// Monitor Glow Reflection on Desk
|
|
754
|
+
if (state === 'WORKING' || state === 'ANALYZING') {
|
|
755
|
+
ctx.fillStyle = suitColor + '18';
|
|
756
|
+
ctx.fillRect(x - 14, y + 14, 40, 16);
|
|
757
|
+
}
|
|
758
|
+
|
|
436
759
|
// Hair / Head
|
|
437
760
|
ctx.fillStyle = '#ffd1a4'; // skin
|
|
438
761
|
ctx.fillRect(x, y + offsetY, 10, 10);
|
|
@@ -464,17 +787,56 @@ function renderOfficeWorker(ctx, agentKey, state, x, y, frame) {
|
|
|
464
787
|
ctx.fillStyle = '#ffffff';
|
|
465
788
|
ctx.fillRect(x + ((frame * 3) % 12) - 2, y + 8, 2, 2);
|
|
466
789
|
} else if (state === 'ANALYZING') {
|
|
467
|
-
//
|
|
790
|
+
// Small thought icon
|
|
468
791
|
ctx.fillStyle = '#ffffff';
|
|
469
|
-
ctx.fillRect(x + 12, y - 6, 8, 6);
|
|
792
|
+
ctx.fillRect(x + 12, y - 6 + offsetY, 8, 6);
|
|
470
793
|
ctx.fillStyle = '#000000';
|
|
471
|
-
ctx.fillRect(x + 14, y - 4, 4, 2);
|
|
794
|
+
ctx.fillRect(x + 14, y - 4 + offsetY, 4, 2);
|
|
472
795
|
} else if (state === 'COMPLETED') {
|
|
473
796
|
// Victory hands up
|
|
474
797
|
ctx.fillStyle = '#ffd1a4';
|
|
475
798
|
ctx.fillRect(x - 5, y + 6 + offsetY, 3, 5);
|
|
476
799
|
ctx.fillRect(x + 12, y + 6 + offsetY, 3, 5);
|
|
477
800
|
}
|
|
801
|
+
|
|
802
|
+
// Floating Live Task / Speech Bubble above Worker's Head
|
|
803
|
+
if (state === 'WORKING' || state === 'ANALYZING') {
|
|
804
|
+
const aData = (appState.state && appState.state.agents) ? appState.state.agents[agentKey] : null;
|
|
805
|
+
let label = state === 'ANALYZING' ? '🧠 Thinking...' : '⚡ Building...';
|
|
806
|
+
if (aData && aData.currentTask && aData.currentTask !== 'Idle') {
|
|
807
|
+
const t = aData.currentTask;
|
|
808
|
+
if (t.includes('view_file') || t.includes('reading') || t.includes('Read')) label = '📄 ' + (t.split('/').pop().slice(0, 12) || 'file');
|
|
809
|
+
else if (t.includes('replace_file') || t.includes('writing') || t.includes('Edit')) label = '📝 ' + (t.split('/').pop().slice(0, 12) || 'code');
|
|
810
|
+
else if (t.includes('run_command') || t.includes('exec') || t.includes('npm')) label = '⚡ ' + (t.split(' ')[0].slice(0, 10) || 'exec');
|
|
811
|
+
else if (t.includes('grep') || t.includes('search')) label = '🔍 Search';
|
|
812
|
+
else label = t.length > 14 ? t.slice(0, 12) + '..' : t;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
ctx.font = '7px "Press Start 2P", monospace';
|
|
816
|
+
const textWidth = ctx.measureText(label).width;
|
|
817
|
+
const bubbleW = textWidth + 12;
|
|
818
|
+
const bubbleH = 15;
|
|
819
|
+
const bubbleX = x - bubbleW / 2 + 5;
|
|
820
|
+
const bubbleY = y - 22 + offsetY;
|
|
821
|
+
|
|
822
|
+
ctx.fillStyle = '#000000';
|
|
823
|
+
ctx.fillRect(bubbleX + 2, bubbleY + 2, bubbleW, bubbleH);
|
|
824
|
+
ctx.fillStyle = '#0a0e1c';
|
|
825
|
+
ctx.fillRect(bubbleX, bubbleY, bubbleW, bubbleH);
|
|
826
|
+
ctx.strokeStyle = suitColor;
|
|
827
|
+
ctx.lineWidth = 1;
|
|
828
|
+
ctx.strokeRect(bubbleX, bubbleY, bubbleW, bubbleH);
|
|
829
|
+
|
|
830
|
+
ctx.fillStyle = '#0a0e1c';
|
|
831
|
+
ctx.beginPath();
|
|
832
|
+
ctx.moveTo(x + 3, bubbleY + bubbleH);
|
|
833
|
+
ctx.lineTo(x + 7, bubbleY + bubbleH);
|
|
834
|
+
ctx.lineTo(x + 5, bubbleY + bubbleH + 3);
|
|
835
|
+
ctx.fill();
|
|
836
|
+
|
|
837
|
+
ctx.fillStyle = '#ffffff';
|
|
838
|
+
ctx.fillText(label, bubbleX + 6, bubbleY + 10);
|
|
839
|
+
}
|
|
478
840
|
}
|
|
479
841
|
|
|
480
842
|
// Break Lounge Area (Espresso machine, water cooler, beanbag)
|
|
@@ -493,7 +855,6 @@ function renderBreakLounge(ctx, x, y, w, h) {
|
|
|
493
855
|
// Espresso Bar
|
|
494
856
|
ctx.fillStyle = '#2c223b';
|
|
495
857
|
ctx.fillRect(x + 14, y + 30, 65, 45);
|
|
496
|
-
ctx.fillStyle = '#silver';
|
|
497
858
|
ctx.fillStyle = '#a0aab8';
|
|
498
859
|
ctx.fillRect(x + 20, y + 34, 24, 20); // coffee maker
|
|
499
860
|
ctx.fillStyle = '#00f0ff';
|
|
@@ -552,7 +913,284 @@ function startOfficeAnimationLoop() {
|
|
|
552
913
|
requestAnimationFrame(loop);
|
|
553
914
|
}
|
|
554
915
|
|
|
555
|
-
//
|
|
916
|
+
// ============================================================================
|
|
917
|
+
// REALTIME ANTIGRAVITY SIDECHAT VIEW ENGINE
|
|
918
|
+
// ============================================================================
|
|
919
|
+
function updateActiveTaskHud(taskText, toolName = null) {
|
|
920
|
+
const hudTaskText = document.getElementById('hudActiveTaskText');
|
|
921
|
+
const hudToolBadge = document.getElementById('hudToolBadge');
|
|
922
|
+
const hudToolName = document.getElementById('hudToolName');
|
|
923
|
+
|
|
924
|
+
if (hudTaskText && taskText) {
|
|
925
|
+
hudTaskText.textContent = taskText;
|
|
926
|
+
hudTaskText.title = taskText;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (hudToolBadge && hudToolName) {
|
|
930
|
+
if (toolName) {
|
|
931
|
+
hudToolBadge.style.display = 'inline-flex';
|
|
932
|
+
hudToolName.textContent = toolName.toUpperCase();
|
|
933
|
+
} else {
|
|
934
|
+
hudToolBadge.style.display = 'none';
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function parseToolAction(msg, metadata = {}) {
|
|
940
|
+
if (!msg) return { action: 'EXEC', file: '', snippet: '' };
|
|
941
|
+
const lower = msg.toLowerCase();
|
|
942
|
+
|
|
943
|
+
let action = metadata.action || 'EXEC';
|
|
944
|
+
let file = metadata.file || '';
|
|
945
|
+
let snippet = msg;
|
|
946
|
+
|
|
947
|
+
if (!metadata.action) {
|
|
948
|
+
if (lower.includes('view_file') || lower.includes('reading') || lower.includes('read ')) {
|
|
949
|
+
action = 'READ';
|
|
950
|
+
} else if (lower.includes('replace_file') || lower.includes('writing') || lower.includes('modify') || lower.includes('edit')) {
|
|
951
|
+
action = 'EDIT';
|
|
952
|
+
} else if (lower.includes('run_command') || lower.includes('npm') || lower.includes('executing')) {
|
|
953
|
+
action = 'EXEC';
|
|
954
|
+
} else if (lower.includes('grep') || lower.includes('search') || lower.includes('finding')) {
|
|
955
|
+
action = 'SEARCH';
|
|
956
|
+
} else if (lower.includes('browser') || lower.includes('open_url')) {
|
|
957
|
+
action = 'BROWSER';
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Extract file path if not already provided
|
|
962
|
+
if (!file) {
|
|
963
|
+
const fileMatch = msg.match(/(?:file|path|to|in|edited|reading)\s*[:=]?\s*[`"']?([a-zA-Z0-9_\-./\\]+\.[a-zA-Z0-9]+)[`"']?/i);
|
|
964
|
+
if (fileMatch) {
|
|
965
|
+
file = fileMatch[1];
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return { action, file, snippet };
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function appendSidechatCard(event) {
|
|
973
|
+
const container = document.getElementById('sidechatStream');
|
|
974
|
+
if (!container) return;
|
|
975
|
+
|
|
976
|
+
const timeStr = new Date(event.timestamp || Date.now()).toTimeString().split(' ')[0];
|
|
977
|
+
const agentKey = event.agent || 'orchestrator';
|
|
978
|
+
const ws = WORKSTATIONS[agentKey] || { name: 'Tech Lead', color: '#ffd700', icon: '👔' };
|
|
979
|
+
const agentColor = ws.color || '#00f0ff';
|
|
980
|
+
const agentIcon = ws.icon || '🤖';
|
|
981
|
+
const agentUpper = agentKey.toUpperCase();
|
|
982
|
+
|
|
983
|
+
const card = document.createElement('div');
|
|
984
|
+
card.className = `sidechat-card card-${agentKey}`;
|
|
985
|
+
|
|
986
|
+
if (event.type === 'thinking' || (agentKey === 'orchestrator' && event.type === 'progress')) {
|
|
987
|
+
card.innerHTML = `
|
|
988
|
+
<div class="sidechat-card-header">
|
|
989
|
+
<span class="sidechat-agent-badge" style="color: ${agentColor}; border-color: ${agentColor};">
|
|
990
|
+
${agentIcon} #${agentUpper}
|
|
991
|
+
</span>
|
|
992
|
+
<span class="sidechat-type-badge">THINKING</span>
|
|
993
|
+
<span class="sidechat-timestamp">${timeStr}</span>
|
|
994
|
+
</div>
|
|
995
|
+
<div class="sidechat-thought-box">
|
|
996
|
+
<span class="thought-brain-icon">🧠</span>
|
|
997
|
+
<div class="thought-content">
|
|
998
|
+
${escapeHtml(event.message)}
|
|
999
|
+
<span class="thought-typing-dots"></span>
|
|
1000
|
+
</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
`;
|
|
1003
|
+
updateActiveTaskHud(event.message, 'REASONING');
|
|
1004
|
+
} else if (event.type === 'tool' || event.type === 'tool_call' || event.skill || event.message.includes('file') || event.message.includes('npm') || event.message.includes('run') || event.message.includes('Edit')) {
|
|
1005
|
+
const toolInfo = parseToolAction(event.message, event.metadata || {});
|
|
1006
|
+
const actionClass = toolInfo.action.toLowerCase();
|
|
1007
|
+
|
|
1008
|
+
card.innerHTML = `
|
|
1009
|
+
<div class="sidechat-card-header">
|
|
1010
|
+
<span class="sidechat-agent-badge" style="color: ${agentColor}; border-color: ${agentColor};">
|
|
1011
|
+
${agentIcon} #${agentUpper}
|
|
1012
|
+
</span>
|
|
1013
|
+
<span class="sidechat-type-badge">${toolInfo.action}</span>
|
|
1014
|
+
<span class="sidechat-timestamp">${timeStr}</span>
|
|
1015
|
+
</div>
|
|
1016
|
+
<div class="sidechat-tool-box">
|
|
1017
|
+
<div class="tool-box-header">
|
|
1018
|
+
<span class="tool-action-pill ${actionClass}">[${toolInfo.action}]</span>
|
|
1019
|
+
${toolInfo.file ? `<span class="tool-target-path">${escapeHtml(toolInfo.file)}</span>` : ''}
|
|
1020
|
+
</div>
|
|
1021
|
+
<div class="tool-snippet-block">${escapeHtml(event.message)}</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
`;
|
|
1024
|
+
updateActiveTaskHud(event.message, toolInfo.action);
|
|
1025
|
+
synth.playTool();
|
|
1026
|
+
} else if (event.type === 'complete') {
|
|
1027
|
+
card.innerHTML = `
|
|
1028
|
+
<div class="sidechat-card-header">
|
|
1029
|
+
<span class="sidechat-agent-badge" style="color: #39ff14; border-color: #39ff14;">
|
|
1030
|
+
${agentIcon} #${agentUpper}
|
|
1031
|
+
</span>
|
|
1032
|
+
<span class="sidechat-type-badge" style="background: #0e2016; color: #39ff14;">COMPLETED</span>
|
|
1033
|
+
<span class="sidechat-timestamp">${timeStr}</span>
|
|
1034
|
+
</div>
|
|
1035
|
+
<div style="background: #0c1a14; border: 1px solid #165b38; padding: 8px 10px; color: #39ff14; font-size: 12px;">
|
|
1036
|
+
✓ ${escapeHtml(event.message)}
|
|
1037
|
+
</div>
|
|
1038
|
+
`;
|
|
1039
|
+
updateActiveTaskHud(event.message, 'SUCCESS');
|
|
1040
|
+
} else if (event.type === 'error') {
|
|
1041
|
+
card.innerHTML = `
|
|
1042
|
+
<div class="sidechat-card-header">
|
|
1043
|
+
<span class="sidechat-agent-badge" style="color: #ff3344; border-color: #ff3344;">
|
|
1044
|
+
${agentIcon} #${agentUpper}
|
|
1045
|
+
</span>
|
|
1046
|
+
<span class="sidechat-type-badge" style="background: #2a0c0e; color: #ff3344;">ERROR</span>
|
|
1047
|
+
<span class="sidechat-timestamp">${timeStr}</span>
|
|
1048
|
+
</div>
|
|
1049
|
+
<div style="background: #1c0606; border: 1px solid #551111; padding: 8px 10px; color: #ff3344; font-size: 12px;">
|
|
1050
|
+
⚠ ${escapeHtml(event.message)}
|
|
1051
|
+
</div>
|
|
1052
|
+
`;
|
|
1053
|
+
} else {
|
|
1054
|
+
card.innerHTML = `
|
|
1055
|
+
<div class="sidechat-card-header">
|
|
1056
|
+
<span class="sidechat-agent-badge" style="color: ${agentColor}; border-color: ${agentColor};">
|
|
1057
|
+
${agentIcon} #${agentUpper}
|
|
1058
|
+
</span>
|
|
1059
|
+
<span class="sidechat-type-badge">LOG</span>
|
|
1060
|
+
<span class="sidechat-timestamp">${timeStr}</span>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div style="color: #cbd5e1; font-size: 12px; line-height: 1.4;">
|
|
1063
|
+
${escapeHtml(event.message)}
|
|
1064
|
+
</div>
|
|
1065
|
+
`;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
container.appendChild(card);
|
|
1069
|
+
if (appState.autoScrollEnabled) {
|
|
1070
|
+
container.scrollTop = container.scrollHeight;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function renderSidechat(events) {
|
|
1075
|
+
const container = document.getElementById('sidechatStream');
|
|
1076
|
+
if (!container) return;
|
|
1077
|
+
container.innerHTML = '';
|
|
1078
|
+
for (const evt of events) {
|
|
1079
|
+
appendSidechatCard(evt);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// ============================================================================
|
|
1084
|
+
// ACCURATE PROVIDER SWITCHER & DIAGNOSTICS MODAL
|
|
1085
|
+
// ============================================================================
|
|
1086
|
+
function updateProviderUI(provData) {
|
|
1087
|
+
if (!provData) return;
|
|
1088
|
+
appState.providersData = provData;
|
|
1089
|
+
|
|
1090
|
+
const activeId = provData.activeProvider || 'antigravity';
|
|
1091
|
+
const activeName = provData.activeProviderName || 'Google Antigravity';
|
|
1092
|
+
const activeIcon = provData.activeProviderIcon || '🪐';
|
|
1093
|
+
const activeDesc = provData.activeProviderDescription || 'Integrated Antigravity IDE Autonomous Pair Programming Engine';
|
|
1094
|
+
|
|
1095
|
+
appState.activeProvider = activeId;
|
|
1096
|
+
appState.activeProviderName = activeName;
|
|
1097
|
+
appState.activeProviderIcon = activeIcon;
|
|
1098
|
+
|
|
1099
|
+
// Header Badge
|
|
1100
|
+
const badgeNameEl = document.getElementById('providerName');
|
|
1101
|
+
const badgeIconEl = document.getElementById('providerIcon');
|
|
1102
|
+
if (badgeNameEl) badgeNameEl.textContent = activeId.toUpperCase();
|
|
1103
|
+
if (badgeIconEl) badgeIconEl.textContent = activeIcon;
|
|
1104
|
+
|
|
1105
|
+
// Modal Header
|
|
1106
|
+
const modalNameEl = document.getElementById('modalActiveProvName');
|
|
1107
|
+
const modalIconEl = document.getElementById('modalActiveProvIcon');
|
|
1108
|
+
const modalDescEl = document.getElementById('modalActiveProvDesc');
|
|
1109
|
+
if (modalNameEl) modalNameEl.textContent = `${activeIcon} ${activeName} (${activeId.toUpperCase()})`;
|
|
1110
|
+
if (modalIconEl) modalIconEl.textContent = activeIcon;
|
|
1111
|
+
if (modalDescEl) modalDescEl.textContent = activeDesc;
|
|
1112
|
+
|
|
1113
|
+
// Render Grid
|
|
1114
|
+
renderProvidersGrid(provData);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function renderProvidersGrid(provData) {
|
|
1118
|
+
const grid = document.getElementById('providerGrid');
|
|
1119
|
+
if (!grid || !provData) return;
|
|
1120
|
+
|
|
1121
|
+
grid.innerHTML = '';
|
|
1122
|
+
|
|
1123
|
+
const allProviders = [
|
|
1124
|
+
...(provData.available || []),
|
|
1125
|
+
...(provData.missing || [])
|
|
1126
|
+
];
|
|
1127
|
+
|
|
1128
|
+
allProviders.forEach(p => {
|
|
1129
|
+
const isActive = p.id === provData.activeProvider;
|
|
1130
|
+
const isAvailable = (provData.available || []).some(av => av.id === p.id);
|
|
1131
|
+
const card = document.createElement('div');
|
|
1132
|
+
card.className = `provider-card ${isActive ? 'active' : (isAvailable ? 'available' : 'missing')}`;
|
|
1133
|
+
|
|
1134
|
+
card.innerHTML = `
|
|
1135
|
+
<div class="provider-card-header">
|
|
1136
|
+
<div class="provider-card-title">
|
|
1137
|
+
<span>${p.icon || '💻'}</span>
|
|
1138
|
+
<span>${p.name}</span>
|
|
1139
|
+
</div>
|
|
1140
|
+
<span class="provider-card-status">
|
|
1141
|
+
${isActive ? 'ACTIVE' : (isAvailable ? 'DETECTED' : 'UNAVAILABLE')}
|
|
1142
|
+
</span>
|
|
1143
|
+
</div>
|
|
1144
|
+
<div class="provider-card-desc">${p.description || ''}</div>
|
|
1145
|
+
`;
|
|
1146
|
+
|
|
1147
|
+
card.addEventListener('click', async () => {
|
|
1148
|
+
synth.playClick();
|
|
1149
|
+
await selectProvider(p.id);
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
grid.appendChild(card);
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
async function selectProvider(providerId) {
|
|
1157
|
+
try {
|
|
1158
|
+
const res = await fetch('/api/providers/select', {
|
|
1159
|
+
method: 'POST',
|
|
1160
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1161
|
+
body: JSON.stringify({ provider: providerId })
|
|
1162
|
+
});
|
|
1163
|
+
if (res.ok) {
|
|
1164
|
+
const data = await res.json();
|
|
1165
|
+
if (data.providers) {
|
|
1166
|
+
updateProviderUI(data.providers);
|
|
1167
|
+
}
|
|
1168
|
+
synth.playSkill();
|
|
1169
|
+
}
|
|
1170
|
+
} catch (err) {
|
|
1171
|
+
console.error('Failed to select provider:', err);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function openProviderModal() {
|
|
1176
|
+
const modal = document.getElementById('providerModal');
|
|
1177
|
+
if (modal) {
|
|
1178
|
+
if (appState.providersData) {
|
|
1179
|
+
updateProviderUI(appState.providersData);
|
|
1180
|
+
}
|
|
1181
|
+
modal.classList.add('open');
|
|
1182
|
+
synth.playClick();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function closeProviderModal() {
|
|
1187
|
+
const modal = document.getElementById('providerModal');
|
|
1188
|
+
if (modal) modal.classList.remove('open');
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ============================================================================
|
|
1192
|
+
// INITIALIZATION & DOM BINDINGS
|
|
1193
|
+
// ============================================================================
|
|
556
1194
|
async function initApp() {
|
|
557
1195
|
setupEventListeners();
|
|
558
1196
|
initOfficeCanvas();
|
|
@@ -560,9 +1198,11 @@ async function initApp() {
|
|
|
560
1198
|
|
|
561
1199
|
// Instant initial fetch before SSE stream connects
|
|
562
1200
|
try {
|
|
563
|
-
const [cfgRes, stRes] = await Promise.all([
|
|
1201
|
+
const [cfgRes, stRes, provRes, tokRes] = await Promise.all([
|
|
564
1202
|
fetch('/api/config').then(r => r.ok ? r.json() : null),
|
|
565
|
-
fetch('/api/state').then(r => r.ok ? r.json() : null)
|
|
1203
|
+
fetch('/api/state').then(r => r.ok ? r.json() : null),
|
|
1204
|
+
fetch('/api/providers').then(r => r.ok ? r.json() : null),
|
|
1205
|
+
fetch('/api/token-telemetry').then(r => r.ok ? r.json() : null)
|
|
566
1206
|
]);
|
|
567
1207
|
if (cfgRes) {
|
|
568
1208
|
appState.config = cfgRes;
|
|
@@ -574,6 +1214,12 @@ async function initApp() {
|
|
|
574
1214
|
appState.state = stRes;
|
|
575
1215
|
renderState();
|
|
576
1216
|
}
|
|
1217
|
+
if (provRes) {
|
|
1218
|
+
updateProviderUI(provRes);
|
|
1219
|
+
}
|
|
1220
|
+
if (tokRes) {
|
|
1221
|
+
renderTokenTelemetry(tokRes);
|
|
1222
|
+
}
|
|
577
1223
|
await fetchReports();
|
|
578
1224
|
} catch (e) {
|
|
579
1225
|
// Handled by SSE
|
|
@@ -582,16 +1228,200 @@ async function initApp() {
|
|
|
582
1228
|
connectSSE();
|
|
583
1229
|
}
|
|
584
1230
|
|
|
1231
|
+
let selectedAutocompleteIndex = -1;
|
|
1232
|
+
let currentSuggestions = [];
|
|
1233
|
+
|
|
585
1234
|
// Setup DOM Event Listeners
|
|
586
1235
|
function setupEventListeners() {
|
|
1236
|
+
initTokenTelemetryModal();
|
|
1237
|
+
|
|
1238
|
+
// Provider Badge & Switcher Modal
|
|
1239
|
+
const providerBadge = document.getElementById('providerBadge');
|
|
1240
|
+
if (providerBadge) {
|
|
1241
|
+
providerBadge.addEventListener('click', () => {
|
|
1242
|
+
openProviderModal();
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const btnCloseProviderModal = document.getElementById('btnCloseProviderModal');
|
|
1247
|
+
if (btnCloseProviderModal) {
|
|
1248
|
+
btnCloseProviderModal.addEventListener('click', () => {
|
|
1249
|
+
closeProviderModal();
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
const providerModal = document.getElementById('providerModal');
|
|
1254
|
+
if (providerModal) {
|
|
1255
|
+
providerModal.addEventListener('click', (e) => {
|
|
1256
|
+
if (e.target === providerModal) closeProviderModal();
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Lo-Fi Music Synthesizer Controls
|
|
1261
|
+
const btnLofiToggle = document.getElementById('btnLofiToggle');
|
|
1262
|
+
if (btnLofiToggle) {
|
|
1263
|
+
btnLofiToggle.addEventListener('click', () => {
|
|
1264
|
+
synth.playClick();
|
|
1265
|
+
lofiEngine.toggle();
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const lofiTrackSelect = document.getElementById('lofiTrackSelect');
|
|
1270
|
+
if (lofiTrackSelect) {
|
|
1271
|
+
lofiTrackSelect.addEventListener('change', (e) => {
|
|
1272
|
+
lofiEngine.setTrack(e.target.value);
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const lofiVolSlider = document.getElementById('lofiVolSlider');
|
|
1277
|
+
if (lofiVolSlider) {
|
|
1278
|
+
lofiVolSlider.addEventListener('input', (e) => {
|
|
1279
|
+
lofiEngine.setVolume(parseFloat(e.target.value));
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Stream Tab Switcher (#AI-SIDECHAT vs #TEAM-SLACK)
|
|
1284
|
+
const tabSidechat = document.getElementById('tabSidechat');
|
|
1285
|
+
const tabSlack = document.getElementById('tabSlack');
|
|
1286
|
+
const sidechatStream = document.getElementById('sidechatStream');
|
|
1287
|
+
const terminalLogs = document.getElementById('terminalLogs');
|
|
1288
|
+
|
|
1289
|
+
if (tabSidechat && tabSlack) {
|
|
1290
|
+
tabSidechat.addEventListener('click', () => {
|
|
1291
|
+
synth.playClick();
|
|
1292
|
+
tabSidechat.classList.add('active');
|
|
1293
|
+
tabSlack.classList.remove('active');
|
|
1294
|
+
if (sidechatStream) sidechatStream.style.display = 'flex';
|
|
1295
|
+
if (terminalLogs) terminalLogs.style.display = 'none';
|
|
1296
|
+
appState.streamMode = 'sidechat';
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
tabSlack.addEventListener('click', () => {
|
|
1300
|
+
synth.playClick();
|
|
1301
|
+
tabSlack.classList.add('active');
|
|
1302
|
+
tabSidechat.classList.remove('active');
|
|
1303
|
+
if (sidechatStream) sidechatStream.style.display = 'none';
|
|
1304
|
+
if (terminalLogs) terminalLogs.style.display = 'flex';
|
|
1305
|
+
appState.streamMode = 'slack';
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Auto Scroll Toggle Button
|
|
1310
|
+
const btnAutoScroll = document.getElementById('btnAutoScroll');
|
|
1311
|
+
if (btnAutoScroll) {
|
|
1312
|
+
btnAutoScroll.addEventListener('click', () => {
|
|
1313
|
+
synth.playClick();
|
|
1314
|
+
appState.autoScrollEnabled = !appState.autoScrollEnabled;
|
|
1315
|
+
btnAutoScroll.classList.toggle('active', appState.autoScrollEnabled);
|
|
1316
|
+
btnAutoScroll.textContent = appState.autoScrollEnabled ? 'AUTO-SCROLL ON' : 'AUTO-SCROLL OFF';
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Clear Sidechat / Clear Logs
|
|
1321
|
+
const btnClearSidechat = document.getElementById('btnClearSidechat');
|
|
1322
|
+
if (btnClearSidechat) {
|
|
1323
|
+
btnClearSidechat.addEventListener('click', () => {
|
|
1324
|
+
synth.playClick();
|
|
1325
|
+
if (sidechatStream) sidechatStream.innerHTML = '';
|
|
1326
|
+
if (terminalLogs) terminalLogs.innerHTML = '';
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
|
|
587
1330
|
// Task form submission
|
|
588
1331
|
const taskForm = document.getElementById('taskForm');
|
|
589
1332
|
const taskInput = document.getElementById('taskInput');
|
|
1333
|
+
const autocompletePopup = document.getElementById('commandAutocomplete');
|
|
1334
|
+
const autocompleteList = document.getElementById('autocompleteList');
|
|
1335
|
+
|
|
1336
|
+
// Handle Autocomplete popup
|
|
1337
|
+
async function updateAutocomplete() {
|
|
1338
|
+
const val = taskInput.value.trim();
|
|
1339
|
+
if (!val.startsWith('/')) {
|
|
1340
|
+
autocompletePopup.style.display = 'none';
|
|
1341
|
+
selectedAutocompleteIndex = -1;
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
try {
|
|
1346
|
+
const res = await fetch(`/api/commands/autocomplete?q=${encodeURIComponent(val)}`);
|
|
1347
|
+
if (res.ok) {
|
|
1348
|
+
const data = await res.json();
|
|
1349
|
+
currentSuggestions = data.suggestions || [];
|
|
1350
|
+
if (currentSuggestions.length > 0) {
|
|
1351
|
+
renderAutocomplete(currentSuggestions);
|
|
1352
|
+
autocompletePopup.style.display = 'block';
|
|
1353
|
+
} else {
|
|
1354
|
+
autocompletePopup.style.display = 'none';
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
} catch {
|
|
1358
|
+
autocompletePopup.style.display = 'none';
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function renderAutocomplete(suggestions) {
|
|
1363
|
+
autocompleteList.innerHTML = suggestions.map((s, idx) => `
|
|
1364
|
+
<div class="autocomplete-item ${idx === selectedAutocompleteIndex ? 'selected' : ''}" data-index="${idx}">
|
|
1365
|
+
<span class="autocomplete-name">${escapeHtml(s.name)}</span>
|
|
1366
|
+
<span class="autocomplete-desc">${escapeHtml(s.description)}</span>
|
|
1367
|
+
<span class="autocomplete-usage">${escapeHtml(s.usage)}</span>
|
|
1368
|
+
</div>
|
|
1369
|
+
`).join('');
|
|
1370
|
+
|
|
1371
|
+
autocompleteList.querySelectorAll('.autocomplete-item').forEach(item => {
|
|
1372
|
+
item.addEventListener('click', () => {
|
|
1373
|
+
const idx = parseInt(item.getAttribute('data-index'), 10);
|
|
1374
|
+
selectSuggestion(idx);
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function selectSuggestion(idx) {
|
|
1380
|
+
if (idx >= 0 && idx < currentSuggestions.length) {
|
|
1381
|
+
const s = currentSuggestions[idx];
|
|
1382
|
+
taskInput.value = `${s.name} `;
|
|
1383
|
+
autocompletePopup.style.display = 'none';
|
|
1384
|
+
taskInput.focus();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
taskInput.addEventListener('input', () => {
|
|
1389
|
+
selectedAutocompleteIndex = 0;
|
|
1390
|
+
updateAutocomplete();
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
taskInput.addEventListener('keydown', (e) => {
|
|
1394
|
+
if (autocompletePopup.style.display === 'block' && currentSuggestions.length > 0) {
|
|
1395
|
+
if (e.key === 'ArrowDown') {
|
|
1396
|
+
e.preventDefault();
|
|
1397
|
+
selectedAutocompleteIndex = (selectedAutocompleteIndex + 1) % currentSuggestions.length;
|
|
1398
|
+
renderAutocomplete(currentSuggestions);
|
|
1399
|
+
} else if (e.key === 'ArrowUp') {
|
|
1400
|
+
e.preventDefault();
|
|
1401
|
+
selectedAutocompleteIndex = (selectedAutocompleteIndex - 1 + currentSuggestions.length) % currentSuggestions.length;
|
|
1402
|
+
renderAutocomplete(currentSuggestions);
|
|
1403
|
+
} else if (e.key === 'Tab' || (e.key === 'Enter' && selectedAutocompleteIndex >= 0 && taskInput.value.trim().split(' ').length === 1)) {
|
|
1404
|
+
e.preventDefault();
|
|
1405
|
+
selectSuggestion(selectedAutocompleteIndex);
|
|
1406
|
+
} else if (e.key === 'Escape') {
|
|
1407
|
+
autocompletePopup.style.display = 'none';
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
document.addEventListener('click', (e) => {
|
|
1413
|
+
if (!taskInput.contains(e.target) && !autocompletePopup.contains(e.target)) {
|
|
1414
|
+
autocompletePopup.style.display = 'none';
|
|
1415
|
+
}
|
|
1416
|
+
});
|
|
1417
|
+
|
|
590
1418
|
taskForm.addEventListener('submit', async (e) => {
|
|
591
1419
|
e.preventDefault();
|
|
1420
|
+
autocompletePopup.style.display = 'none';
|
|
592
1421
|
const prompt = taskInput.value.trim();
|
|
593
1422
|
if (!prompt) return;
|
|
594
1423
|
synth.playClick();
|
|
1424
|
+
updateActiveTaskHud(prompt, 'RUNNING');
|
|
595
1425
|
await submitTask(prompt);
|
|
596
1426
|
taskInput.value = '';
|
|
597
1427
|
});
|
|
@@ -601,6 +1431,7 @@ function setupEventListeners() {
|
|
|
601
1431
|
btn.addEventListener('click', async () => {
|
|
602
1432
|
synth.playClick();
|
|
603
1433
|
const prompt = btn.getAttribute('data-prompt');
|
|
1434
|
+
updateActiveTaskHud(prompt, 'RUNNING');
|
|
604
1435
|
await submitTask(prompt);
|
|
605
1436
|
});
|
|
606
1437
|
});
|
|
@@ -608,11 +1439,13 @@ function setupEventListeners() {
|
|
|
608
1439
|
// Header buttons
|
|
609
1440
|
document.getElementById('btnDemo').addEventListener('click', async () => {
|
|
610
1441
|
synth.playClick();
|
|
1442
|
+
updateActiveTaskHud('Running Full PixelCrew Multi-Agent Sprint Demo', 'SPRINT');
|
|
611
1443
|
await fetch('/api/demo', { method: 'POST' });
|
|
612
1444
|
});
|
|
613
1445
|
|
|
614
1446
|
document.getElementById('btnReset').addEventListener('click', async () => {
|
|
615
1447
|
synth.playClick();
|
|
1448
|
+
updateActiveTaskHud('Standing by for sprint assignment', null);
|
|
616
1449
|
await fetch('/api/reset', { method: 'POST' });
|
|
617
1450
|
});
|
|
618
1451
|
|
|
@@ -638,6 +1471,8 @@ function setupEventListeners() {
|
|
|
638
1471
|
document.getElementById('btnClearLogs').addEventListener('click', () => {
|
|
639
1472
|
synth.playClick();
|
|
640
1473
|
document.getElementById('terminalLogs').innerHTML = '';
|
|
1474
|
+
const sidechat = document.getElementById('sidechatStream');
|
|
1475
|
+
if (sidechat) sidechat.innerHTML = '';
|
|
641
1476
|
});
|
|
642
1477
|
|
|
643
1478
|
// Log filter chips
|
|
@@ -1113,16 +1948,24 @@ function escapeHtml(str) {
|
|
|
1113
1948
|
.replace(/'/g, ''');
|
|
1114
1949
|
}
|
|
1115
1950
|
|
|
1116
|
-
// Submit Task to Server
|
|
1951
|
+
// Submit Task or Slash Command to Server
|
|
1117
1952
|
async function submitTask(prompt) {
|
|
1118
1953
|
try {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1954
|
+
if (prompt.startsWith('/')) {
|
|
1955
|
+
await fetch('/api/command', {
|
|
1956
|
+
method: 'POST',
|
|
1957
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1958
|
+
body: JSON.stringify({ input: prompt })
|
|
1959
|
+
});
|
|
1960
|
+
} else {
|
|
1961
|
+
await fetch('/api/task', {
|
|
1962
|
+
method: 'POST',
|
|
1963
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1964
|
+
body: JSON.stringify({ prompt })
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1124
1967
|
} catch (err) {
|
|
1125
|
-
console.error('Failed to submit task:', err);
|
|
1968
|
+
console.error('Failed to submit task or command:', err);
|
|
1126
1969
|
}
|
|
1127
1970
|
}
|
|
1128
1971
|
|
|
@@ -1253,17 +2096,30 @@ function connectSSE() {
|
|
|
1253
2096
|
appState.state = data.state;
|
|
1254
2097
|
appState.events = data.history || [];
|
|
1255
2098
|
|
|
2099
|
+
if (data.providers) {
|
|
2100
|
+
updateProviderUI(data.providers);
|
|
2101
|
+
} else if (data.state?.activeProvider) {
|
|
2102
|
+
updateProviderUI({
|
|
2103
|
+
activeProvider: data.state.activeProvider,
|
|
2104
|
+
activeProviderName: data.state.activeProviderName,
|
|
2105
|
+
activeProviderIcon: data.state.activeProviderIcon,
|
|
2106
|
+
available: [{ id: data.state.activeProvider, name: data.state.activeProviderName, icon: data.state.activeProviderIcon }]
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1256
2110
|
renderProjectInfo();
|
|
1257
2111
|
renderAgentGrid();
|
|
1258
2112
|
renderSkillsList();
|
|
1259
2113
|
renderState();
|
|
1260
2114
|
renderLogs(appState.events);
|
|
2115
|
+
renderSidechat(appState.events);
|
|
1261
2116
|
});
|
|
1262
2117
|
|
|
1263
2118
|
eventSource.addEventListener('agent_event', (e) => {
|
|
1264
2119
|
const event = JSON.parse(e.data);
|
|
1265
2120
|
appState.events.push(event);
|
|
1266
2121
|
appendLog(event);
|
|
2122
|
+
appendSidechatCard(event);
|
|
1267
2123
|
|
|
1268
2124
|
// Real-time visual state & sprite animation update
|
|
1269
2125
|
if (appState.state && event.agent) {
|
|
@@ -1349,6 +2205,11 @@ function connectSSE() {
|
|
|
1349
2205
|
renderOneShotResults(event.metadata);
|
|
1350
2206
|
}
|
|
1351
2207
|
|
|
2208
|
+
// Real-time Token Telemetry Update
|
|
2209
|
+
if (event.type === 'token_telemetry' || event.tokenStats || event.metadata?.tokenStats) {
|
|
2210
|
+
renderTokenTelemetry(event.tokenStats || event.metadata?.tokenStats);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
1352
2213
|
// Audio cues & Report Refresh
|
|
1353
2214
|
if (event.type === 'spawn') {
|
|
1354
2215
|
synth.playSpawn();
|
|
@@ -1644,5 +2505,286 @@ function openAgentModal(agentKey) {
|
|
|
1644
2505
|
synth.playClick();
|
|
1645
2506
|
}
|
|
1646
2507
|
|
|
2508
|
+
// ============================================================================
|
|
2509
|
+
// REAL-TIME TOKEN TELEMETRY & VISUALIZER ENGINE
|
|
2510
|
+
// ============================================================================
|
|
2511
|
+
const tokenState = {
|
|
2512
|
+
rawTokensEstimated: 0,
|
|
2513
|
+
actualTokensUsed: 0,
|
|
2514
|
+
tokensSaved: 0,
|
|
2515
|
+
promptTokens: 0,
|
|
2516
|
+
completionTokens: 0,
|
|
2517
|
+
cachedTokens: 0,
|
|
2518
|
+
efficiencyRatio: 72,
|
|
2519
|
+
costUsd: 0,
|
|
2520
|
+
perAgent: {
|
|
2521
|
+
creativeDirector: 0,
|
|
2522
|
+
uxPlanner: 0,
|
|
2523
|
+
designSystem: 0,
|
|
2524
|
+
frontend: 0,
|
|
2525
|
+
backend: 0,
|
|
2526
|
+
database: 0,
|
|
2527
|
+
performance: 0,
|
|
2528
|
+
security: 0,
|
|
2529
|
+
qa: 0
|
|
2530
|
+
},
|
|
2531
|
+
timeline: []
|
|
2532
|
+
};
|
|
2533
|
+
|
|
2534
|
+
function initTokenTelemetryModal() {
|
|
2535
|
+
const btnOpen = document.getElementById('btnTokenAnalyticsModal');
|
|
2536
|
+
const btnClose = document.getElementById('btnCloseTokenModal');
|
|
2537
|
+
const modal = document.getElementById('tokenAnalyticsModal');
|
|
2538
|
+
|
|
2539
|
+
if (btnOpen && modal) {
|
|
2540
|
+
btnOpen.addEventListener('click', () => {
|
|
2541
|
+
modal.classList.add('open');
|
|
2542
|
+
renderTokenTelemetry(tokenState);
|
|
2543
|
+
synth.playClick();
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
if (btnClose && modal) {
|
|
2548
|
+
btnClose.addEventListener('click', () => {
|
|
2549
|
+
modal.classList.remove('open');
|
|
2550
|
+
synth.playClick();
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
if (modal) {
|
|
2555
|
+
modal.addEventListener('click', (e) => {
|
|
2556
|
+
if (e.target === modal) {
|
|
2557
|
+
modal.classList.remove('open');
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
async function fetchTokenTelemetry() {
|
|
2564
|
+
try {
|
|
2565
|
+
const res = await fetch('/api/token-telemetry');
|
|
2566
|
+
if (res.ok) {
|
|
2567
|
+
const data = await res.json();
|
|
2568
|
+
renderTokenTelemetry(data);
|
|
2569
|
+
}
|
|
2570
|
+
} catch (err) {
|
|
2571
|
+
console.warn('Failed to fetch initial token telemetry:', err);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
function renderTokenTelemetry(stats) {
|
|
2576
|
+
if (!stats) return;
|
|
2577
|
+
Object.assign(tokenState, stats);
|
|
2578
|
+
|
|
2579
|
+
const used = tokenState.actualTokensUsed || 0;
|
|
2580
|
+
const raw = tokenState.rawTokensEstimated || 0;
|
|
2581
|
+
const saved = tokenState.tokensSaved || 0;
|
|
2582
|
+
const ratio = tokenState.efficiencyRatio || 72;
|
|
2583
|
+
const cost = tokenState.costUsd || ((used / 1000000) * 2.4);
|
|
2584
|
+
const costSaved = ((saved / 1000000) * 2.4);
|
|
2585
|
+
|
|
2586
|
+
// 1. Header & HUD Badges
|
|
2587
|
+
const headerVal = document.getElementById('headerTokenVal');
|
|
2588
|
+
if (headerVal) {
|
|
2589
|
+
headerVal.textContent = used >= 1000 ? `${(used / 1000).toFixed(1)}k tok` : `${used} tok`;
|
|
2590
|
+
}
|
|
2591
|
+
const headerPill = document.getElementById('headerSavedPill');
|
|
2592
|
+
if (headerPill) {
|
|
2593
|
+
headerPill.textContent = `${ratio}% SAVED`;
|
|
2594
|
+
}
|
|
2595
|
+
const hudTokenVal = document.getElementById('hudTokenVal');
|
|
2596
|
+
if (hudTokenVal) {
|
|
2597
|
+
hudTokenVal.textContent = `${used.toLocaleString()} tokens`;
|
|
2598
|
+
}
|
|
2599
|
+
const hudCostVal = document.getElementById('hudCostVal');
|
|
2600
|
+
if (hudCostVal) {
|
|
2601
|
+
hudCostVal.textContent = `($${cost.toFixed(4)})`;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
// 2. OneShot Modal Token Card
|
|
2605
|
+
const rawEl = document.getElementById('rawTokenVal');
|
|
2606
|
+
const actualEl = document.getElementById('actualTokenVal');
|
|
2607
|
+
const savedEl = document.getElementById('savedTokenVal');
|
|
2608
|
+
if (rawEl && raw > 0) rawEl.textContent = raw.toLocaleString();
|
|
2609
|
+
if (actualEl && used > 0) actualEl.textContent = used.toLocaleString();
|
|
2610
|
+
if (savedEl && saved > 0) savedEl.textContent = `${saved.toLocaleString()} (${ratio}% Saved)`;
|
|
2611
|
+
|
|
2612
|
+
// 3. Analytics Modal KPI Cards
|
|
2613
|
+
const modalActual = document.getElementById('modalActualTokens');
|
|
2614
|
+
if (modalActual) modalActual.textContent = used.toLocaleString();
|
|
2615
|
+
|
|
2616
|
+
const modalSaved = document.getElementById('modalSavedTokens');
|
|
2617
|
+
if (modalSaved) modalSaved.textContent = `${saved.toLocaleString()} (${ratio}%)`;
|
|
2618
|
+
|
|
2619
|
+
const modalRaw = document.getElementById('modalRawTokens');
|
|
2620
|
+
if (modalRaw) modalRaw.textContent = `Raw Est: ${raw.toLocaleString()}`;
|
|
2621
|
+
|
|
2622
|
+
const modalCost = document.getElementById('modalEstCost');
|
|
2623
|
+
if (modalCost) modalCost.textContent = `$${cost.toFixed(4)}`;
|
|
2624
|
+
|
|
2625
|
+
const modalCostSaved = document.getElementById('modalCostSaved');
|
|
2626
|
+
if (modalCostSaved) modalCostSaved.textContent = `Saved: $${costSaved.toFixed(4)}`;
|
|
2627
|
+
|
|
2628
|
+
const modalRatio = document.getElementById('modalPromptOutputRatio');
|
|
2629
|
+
if (modalRatio) {
|
|
2630
|
+
const p = tokenState.promptTokens || 0;
|
|
2631
|
+
const c = tokenState.completionTokens || 0;
|
|
2632
|
+
modalRatio.textContent = `${p.toLocaleString()} / ${c.toLocaleString()}`;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
const modalCached = document.getElementById('modalCachedTokens');
|
|
2636
|
+
if (modalCached) {
|
|
2637
|
+
modalCached.textContent = `Cached: ${(tokenState.cachedTokens || 0).toLocaleString()}`;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// 4. Per-Agent Token Distribution Bars
|
|
2641
|
+
const agentBarsContainer = document.getElementById('agentTokenBars');
|
|
2642
|
+
if (agentBarsContainer && tokenState.perAgent) {
|
|
2643
|
+
const agents = [
|
|
2644
|
+
{ key: 'frontend', name: 'Frontend Eng', icon: '💻', class: 'fe' },
|
|
2645
|
+
{ key: 'backend', name: 'Backend Eng', icon: '⚙️', class: 'be' },
|
|
2646
|
+
{ key: 'qa', name: 'QA Critic', icon: '🔍', class: 'qa' },
|
|
2647
|
+
{ key: 'creativeDirector', name: 'Creative Dir', icon: '🎨', class: 'cd' },
|
|
2648
|
+
{ key: 'uxPlanner', name: 'UX Planner', icon: '📐', class: 'ux' },
|
|
2649
|
+
{ key: 'designSystem', name: 'Design System', icon: '🌿', class: 'ds' },
|
|
2650
|
+
{ key: 'database', name: 'DB Architect', icon: '🗄️', class: 'db' },
|
|
2651
|
+
{ key: 'performance', name: 'Perf SRE', icon: '⚡', class: 'perf' },
|
|
2652
|
+
{ key: 'security', name: 'Security Sentinel', icon: '🛡️', class: 'sec' }
|
|
2653
|
+
];
|
|
2654
|
+
|
|
2655
|
+
const maxAgentTokens = Math.max(...Object.values(tokenState.perAgent), 1);
|
|
2656
|
+
|
|
2657
|
+
agentBarsContainer.innerHTML = agents.map(ag => {
|
|
2658
|
+
const agTokens = tokenState.perAgent[ag.key] || 0;
|
|
2659
|
+
const pct = Math.round((agTokens / maxAgentTokens) * 100);
|
|
2660
|
+
const share = used > 0 ? Math.round((agTokens / used) * 100) : 0;
|
|
2661
|
+
return `
|
|
2662
|
+
<div class="agent-token-bar-row">
|
|
2663
|
+
<div class="agent-bar-meta">
|
|
2664
|
+
<span>${ag.icon}</span>
|
|
2665
|
+
<span>${ag.name}</span>
|
|
2666
|
+
</div>
|
|
2667
|
+
<div class="agent-bar-track">
|
|
2668
|
+
<div class="agent-bar-fill ${ag.class}" style="width: ${pct}%;"></div>
|
|
2669
|
+
</div>
|
|
2670
|
+
<div class="agent-bar-stats">
|
|
2671
|
+
<strong>${agTokens.toLocaleString()}</strong> <span style="color: var(--color-cyan);">(${share}%)</span>
|
|
2672
|
+
</div>
|
|
2673
|
+
</div>
|
|
2674
|
+
`;
|
|
2675
|
+
}).join('');
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// 5. Render Real-Time Sparkline Canvas
|
|
2679
|
+
drawTokenSparkline(tokenState.timeline || []);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
function drawTokenSparkline(timeline) {
|
|
2683
|
+
const canvas = document.getElementById('tokenSparklineCanvas');
|
|
2684
|
+
if (!canvas) return;
|
|
2685
|
+
const ctx = canvas.getContext('2d');
|
|
2686
|
+
if (!ctx) return;
|
|
2687
|
+
|
|
2688
|
+
const w = canvas.width;
|
|
2689
|
+
const h = canvas.height;
|
|
2690
|
+
|
|
2691
|
+
// Clear background
|
|
2692
|
+
ctx.fillStyle = '#04070f';
|
|
2693
|
+
ctx.fillRect(0, 0, w, h);
|
|
2694
|
+
|
|
2695
|
+
// Draw retro gridlines
|
|
2696
|
+
ctx.strokeStyle = '#0e1626';
|
|
2697
|
+
ctx.lineWidth = 1;
|
|
2698
|
+
for (let x = 0; x < w; x += 40) {
|
|
2699
|
+
ctx.beginPath();
|
|
2700
|
+
ctx.moveTo(x, 0);
|
|
2701
|
+
ctx.lineTo(x, h);
|
|
2702
|
+
ctx.stroke();
|
|
2703
|
+
}
|
|
2704
|
+
for (let y = 0; y < h; y += 25) {
|
|
2705
|
+
ctx.beginPath();
|
|
2706
|
+
ctx.moveTo(0, y);
|
|
2707
|
+
ctx.lineTo(w, y);
|
|
2708
|
+
ctx.stroke();
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
if (!timeline || timeline.length === 0) {
|
|
2712
|
+
// Render idle pulse wave
|
|
2713
|
+
ctx.strokeStyle = '#00f0ff33';
|
|
2714
|
+
ctx.setLineDash([4, 4]);
|
|
2715
|
+
ctx.beginPath();
|
|
2716
|
+
ctx.moveTo(0, h / 2);
|
|
2717
|
+
ctx.lineTo(w, h / 2);
|
|
2718
|
+
ctx.stroke();
|
|
2719
|
+
ctx.setLineDash([]);
|
|
2720
|
+
ctx.fillStyle = '#64748b';
|
|
2721
|
+
ctx.font = '9px "Press Start 2P", monospace';
|
|
2722
|
+
ctx.textAlign = 'center';
|
|
2723
|
+
ctx.fillText('AWAITING ACTIVE SPRINT TOKEN EMISSIONS...', w / 2, h / 2 - 10);
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
const padding = 20;
|
|
2728
|
+
const maxVal = Math.max(...timeline.map(t => t.totalTokens || 0), 1000);
|
|
2729
|
+
const minVal = 0;
|
|
2730
|
+
|
|
2731
|
+
const getX = (idx) => padding + (idx / Math.max(timeline.length - 1, 1)) * (w - padding * 2);
|
|
2732
|
+
const getY = (val) => (h - padding) - ((val - minVal) / (maxVal - minVal)) * (h - padding * 2);
|
|
2733
|
+
|
|
2734
|
+
// Gradient fill under curve
|
|
2735
|
+
const gradient = ctx.createLinearGradient(0, 0, 0, h);
|
|
2736
|
+
gradient.addColorStop(0, 'rgba(0, 240, 255, 0.3)');
|
|
2737
|
+
gradient.addColorStop(1, 'rgba(0, 255, 136, 0.02)');
|
|
2738
|
+
|
|
2739
|
+
ctx.beginPath();
|
|
2740
|
+
ctx.moveTo(getX(0), h - padding);
|
|
2741
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
2742
|
+
ctx.lineTo(getX(i), getY(timeline[i].totalTokens || 0));
|
|
2743
|
+
}
|
|
2744
|
+
ctx.lineTo(getX(timeline.length - 1), h - padding);
|
|
2745
|
+
ctx.closePath();
|
|
2746
|
+
ctx.fillStyle = gradient;
|
|
2747
|
+
ctx.fill();
|
|
2748
|
+
|
|
2749
|
+
// Glow line
|
|
2750
|
+
ctx.shadowColor = '#00f0ff';
|
|
2751
|
+
ctx.shadowBlur = 8;
|
|
2752
|
+
ctx.strokeStyle = '#00f0ff';
|
|
2753
|
+
ctx.lineWidth = 2;
|
|
2754
|
+
|
|
2755
|
+
ctx.beginPath();
|
|
2756
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
2757
|
+
const x = getX(i);
|
|
2758
|
+
const y = getY(timeline[i].totalTokens || 0);
|
|
2759
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
2760
|
+
else ctx.lineTo(x, y);
|
|
2761
|
+
}
|
|
2762
|
+
ctx.stroke();
|
|
2763
|
+
ctx.shadowBlur = 0;
|
|
2764
|
+
|
|
2765
|
+
// Draw node points & step labels
|
|
2766
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
2767
|
+
const pt = timeline[i];
|
|
2768
|
+
const x = getX(i);
|
|
2769
|
+
const y = getY(pt.totalTokens || 0);
|
|
2770
|
+
|
|
2771
|
+
ctx.fillStyle = '#00ff88';
|
|
2772
|
+
ctx.beginPath();
|
|
2773
|
+
ctx.arc(x, y, 4, 0, Math.PI * 2);
|
|
2774
|
+
ctx.fill();
|
|
2775
|
+
ctx.strokeStyle = '#000000';
|
|
2776
|
+
ctx.lineWidth = 1.5;
|
|
2777
|
+
ctx.stroke();
|
|
2778
|
+
|
|
2779
|
+
// Node label
|
|
2780
|
+
if (i === timeline.length - 1 || i % 2 === 0) {
|
|
2781
|
+
ctx.fillStyle = '#ffffff';
|
|
2782
|
+
ctx.font = '8px "Press Start 2P", monospace';
|
|
2783
|
+
ctx.textAlign = 'center';
|
|
2784
|
+
ctx.fillText(`${(pt.totalTokens || 0).toLocaleString()}`, x, y - 8);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
1647
2789
|
// Bootstrap
|
|
1648
2790
|
window.addEventListener('DOMContentLoaded', initApp);
|