gary-boon commited on
Commit
2c0fd9b
·
1 Parent(s): c6c8587

Fix backend structure - remove duplicates

Browse files
backend/Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ g++ \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements and install Python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy application code
16
+ COPY . .
17
+
18
+ # Expose port
19
+ EXPOSE 8000
20
+
21
+ # Run the service
22
+ CMD ["uvicorn", "model_service:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
auth.py → backend/auth.py RENAMED
File without changes
model_service.py → backend/model_service.py RENAMED
File without changes
backend/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ websockets==12.0
4
+ torch==2.1.0
5
+ transformers==4.35.0
6
+ numpy==1.24.3
7
+ pydantic==2.5.0
8
+ python-multipart==0.0.6
9
+ accelerate==0.24.1
frontend/AttentionExplorer.tsx DELETED
@@ -1,873 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useRef, useState } from "react";
4
- import * as d3 from "d3";
5
- import { useWebSocket } from "@/lib/websocket-client";
6
- import { getApiUrl } from "@/lib/config";
7
- import { Download, Layers, RefreshCw, ZoomIn, ZoomOut, Eye, Activity, HelpCircle, X, Info, Zap } from "lucide-react";
8
- import { TraceData } from "@/lib/types";
9
-
10
- interface AttentionData {
11
- layer: string;
12
- weights: number[][];
13
- tokens?: string[];
14
- max_weight: number;
15
- entropy?: number;
16
- timestamp: number;
17
- }
18
-
19
- export default function AttentionExplorer() {
20
- const { lastMessage, traces, isConnected } = useWebSocket();
21
- const svgRef = useRef<SVGSVGElement>(null);
22
- const containerRef = useRef<HTMLDivElement>(null);
23
-
24
- const [selectedLayer, setSelectedLayer] = useState<string>("");
25
- const [attentionData, setAttentionData] = useState<AttentionData[]>([]);
26
- const [currentAttention, setCurrentAttention] = useState<AttentionData | null>(null);
27
- const [availableLayers, setAvailableLayers] = useState<string[]>([]);
28
- const [zoom, setZoom] = useState(1);
29
- const [hoveredCell, setHoveredCell] = useState<{row: number, col: number, value: number} | null>(null);
30
- const [colorScheme, setColorScheme] = useState<'blues' | 'viridis' | 'plasma'>('blues');
31
- const [showExplanation, setShowExplanation] = useState(false);
32
- const [prompt, setPrompt] = useState("def fibonacci(n):");
33
- const [isGenerating, setIsGenerating] = useState(false);
34
-
35
- // Listen for demo completions from LocalControlPanel
36
- useEffect(() => {
37
- const handleDemoCompleted = (event: CustomEvent) => {
38
- const data = event.detail;
39
- console.log('Demo completed event received:', data);
40
-
41
- // Process demo traces if they exist
42
- if (data && data.traces) {
43
- const attentionTraces = data.traces
44
- .filter((t: TraceData) => t.type === 'attention' && t.weights)
45
- .map((t: TraceData) => ({
46
- layer: t.layer || 'unknown',
47
- weights: t.weights || [],
48
- max_weight: t.max_weight || 1,
49
- entropy: t.entropy,
50
- timestamp: t.timestamp || Date.now(),
51
- tokens: t.tokens
52
- }));
53
-
54
- if (attentionTraces.length > 0) {
55
- console.log('Processing demo attention traces:', attentionTraces.length);
56
- const layerSet = new Set(attentionTraces.map((a: AttentionData) => a.layer));
57
- const uniqueLayers = (Array.from(layerSet) as string[])
58
- .sort((a: string, b: string) => {
59
- const numA = parseInt(a.replace('layer.', ''));
60
- const numB = parseInt(b.replace('layer.', ''));
61
- return numA - numB;
62
- });
63
-
64
- // Update state with demo data
65
- setAttentionData(attentionTraces);
66
- setAvailableLayers(uniqueLayers);
67
- setSelectedLayer(uniqueLayers[0]);
68
- }
69
- }
70
- };
71
-
72
- window.addEventListener('demo-completed', handleDemoCompleted as EventListener);
73
- return () => window.removeEventListener('demo-completed', handleDemoCompleted as EventListener);
74
- }, []);
75
-
76
- // Collect attention traces from WebSocket (only if we have traces)
77
- useEffect(() => {
78
- // Only process WebSocket traces if we actually have some
79
- if (traces && traces.length > 0) {
80
- const attentionTraces = traces
81
- .filter(t => t.type === 'attention' && t.weights)
82
- .map(t => ({
83
- layer: t.layer || 'unknown',
84
- weights: t.weights || [],
85
- max_weight: t.max_weight || 1,
86
- entropy: t.entropy,
87
- timestamp: t.timestamp || Date.now(),
88
- tokens: t.tokens
89
- }));
90
-
91
- if (attentionTraces.length > 0) {
92
- setAttentionData(attentionTraces);
93
-
94
- // Auto-select first layer if none selected
95
- if (!selectedLayer) {
96
- const uniqueLayers = Array.from(new Set(attentionTraces.map(a => a.layer)))
97
- .sort((a: string, b: string) => {
98
- const numA = parseInt(a.replace('layer.', ''));
99
- const numB = parseInt(b.replace('layer.', ''));
100
- return numA - numB;
101
- });
102
- setAvailableLayers(uniqueLayers);
103
- setSelectedLayer(uniqueLayers[0]);
104
- }
105
- }
106
- }
107
- }, [traces]); // Remove selectedLayer from dependencies to avoid clearing data
108
-
109
- // Update current attention when layer selection changes
110
- useEffect(() => {
111
- console.log('Layer selection changed to:', selectedLayer);
112
- console.log('Available attention data:', attentionData.length, 'items');
113
-
114
- if (selectedLayer && attentionData.length > 0) {
115
- // Get the most recent attention data for the selected layer
116
- const matchingData = attentionData.filter(a => a.layer === selectedLayer);
117
- console.log('Found', matchingData.length, 'matching items for layer', selectedLayer);
118
-
119
- const layerData = matchingData
120
- .sort((a, b) => b.timestamp - a.timestamp)[0];
121
-
122
- if (layerData) {
123
- console.log('Setting current attention for layer:', selectedLayer);
124
- console.log('Weights shape:', layerData.weights.length, 'x', layerData.weights[0]?.length);
125
- console.log('Max weight:', layerData.max_weight);
126
- console.log('Entropy:', layerData.entropy);
127
- setCurrentAttention(layerData);
128
- } else {
129
- console.log('No data found for layer:', selectedLayer);
130
- setCurrentAttention(null);
131
- }
132
- }
133
- }, [selectedLayer, attentionData]);
134
-
135
- // D3 Heatmap Visualization
136
- useEffect(() => {
137
- if (!currentAttention || !svgRef.current || !containerRef.current) return;
138
-
139
- const container = containerRef.current;
140
- const margin = { top: 120, right: 120, bottom: 100, left: 100 };
141
- const cellSize = 20; // Size of each cell in the heatmap
142
-
143
- const weights = currentAttention.weights;
144
- const numRows = weights.length;
145
- const numCols = weights[0]?.length || 0;
146
-
147
- if (numRows === 0 || numCols === 0) return;
148
-
149
- const width = numCols * cellSize;
150
- const height = numRows * cellSize;
151
-
152
- // Clear previous visualization
153
- d3.select(svgRef.current).selectAll("*").remove();
154
-
155
- const svg = d3.select(svgRef.current)
156
- .attr("width", (width + margin.left + margin.right) * zoom)
157
- .attr("height", (height + margin.top + margin.bottom) * zoom)
158
- .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`);
159
-
160
- const g = svg.append("g")
161
- .attr("transform", `translate(${margin.left},${margin.top})`);
162
-
163
- // Create scales
164
- const xScale = d3.scaleBand()
165
- .domain(d3.range(numCols).map(String))
166
- .range([0, width])
167
- .padding(0.01);
168
-
169
- const yScale = d3.scaleBand()
170
- .domain(d3.range(numRows).map(String))
171
- .range([0, height])
172
- .padding(0.01);
173
-
174
- // Color scale based on selected scheme
175
- // Ensure max_weight is valid, default to 1 if not
176
- const maxWeight = currentAttention.max_weight > 0 ? currentAttention.max_weight : 1.0;
177
-
178
- let colorScale: d3.ScaleSequential<string>;
179
- if (colorScheme === 'viridis') {
180
- colorScale = d3.scaleSequential(d3.interpolateViridis)
181
- .domain([0, maxWeight]);
182
- } else if (colorScheme === 'plasma') {
183
- colorScale = d3.scaleSequential(d3.interpolatePlasma)
184
- .domain([0, maxWeight]);
185
- } else {
186
- colorScale = d3.scaleSequential(d3.interpolateBlues)
187
- .domain([0, maxWeight]);
188
- }
189
-
190
- // Create tooltip
191
- const tooltip = d3.select("body").append("div")
192
- .attr("class", "attention-tooltip")
193
- .style("opacity", 0)
194
- .style("position", "absolute")
195
- .style("background", "rgba(0, 0, 0, 0.9)")
196
- .style("color", "white")
197
- .style("padding", "10px")
198
- .style("border-radius", "6px")
199
- .style("font-size", "12px")
200
- .style("pointer-events", "none")
201
- .style("z-index", "1000");
202
-
203
- // Draw cells
204
- const cells = g.selectAll(".cell")
205
- .data(weights.flatMap((row, i) =>
206
- row.map((value, j) => ({ row: i, col: j, value }))
207
- ))
208
- .enter().append("rect")
209
- .attr("class", "cell")
210
- .attr("x", d => xScale(String(d.col))!)
211
- .attr("y", d => yScale(String(d.row))!)
212
- .attr("width", xScale.bandwidth())
213
- .attr("height", yScale.bandwidth())
214
- .attr("fill", d => colorScale(d.value))
215
- .attr("stroke", "#1f2937")
216
- .attr("stroke-width", 0.5)
217
- .style("cursor", "pointer")
218
- .on("mouseover", function(event, d) {
219
- // Show tooltip
220
- tooltip.transition().duration(200).style("opacity", .95);
221
-
222
- const tokenFrom = currentAttention.tokens?.[d.row] || `Token ${d.row}`;
223
- const tokenTo = currentAttention.tokens?.[d.col] || `Token ${d.col}`;
224
-
225
- tooltip.html(`
226
- <div style="font-weight: bold; margin-bottom: 5px;">Attention Weight</div>
227
- <div>From: ${tokenFrom}</div>
228
- <div>To: ${tokenTo}</div>
229
- <div style="margin-top: 5px; color: #60a5fa;">Weight: ${d.value.toFixed(4)}</div>
230
- `)
231
- .style("left", (event.pageX + 10) + "px")
232
- .style("top", (event.pageY - 28) + "px");
233
-
234
- setHoveredCell({ row: d.row, col: d.col, value: d.value });
235
-
236
- // Highlight cell
237
- d3.select(this)
238
- .attr("stroke", "#3b82f6")
239
- .attr("stroke-width", 2);
240
-
241
- // Highlight row and column headers
242
- d3.selectAll(`.row-label-${d.row}`).style("fill", "#3b82f6").style("font-weight", "bold");
243
- d3.selectAll(`.col-label-${d.col}`).style("fill", "#3b82f6").style("font-weight", "bold");
244
- })
245
- .on("mouseout", function(event, d) {
246
- tooltip.transition().duration(500).style("opacity", 0);
247
- setHoveredCell(null);
248
-
249
- d3.select(this)
250
- .attr("stroke", "#1f2937")
251
- .attr("stroke-width", 0.5);
252
-
253
- // Reset headers
254
- d3.selectAll(`.row-label-${d.row}`).style("fill", "#9ca3af").style("font-weight", "normal");
255
- d3.selectAll(`.col-label-${d.col}`).style("fill", "#9ca3af").style("font-weight", "normal");
256
- });
257
-
258
- // Add row labels
259
- g.selectAll(".row-label")
260
- .data(d3.range(numRows))
261
- .enter().append("text")
262
- .attr("class", d => `row-label row-label-${d}`)
263
- .attr("x", -10)
264
- .attr("y", d => yScale(String(d))! + yScale.bandwidth() / 2)
265
- .attr("text-anchor", "end")
266
- .attr("dominant-baseline", "middle")
267
- .style("font-size", "11px")
268
- .style("fill", "#9ca3af")
269
- .style("font-family", "monospace")
270
- .text(d => currentAttention.tokens?.[d] || `T${d}`);
271
-
272
- // Add column labels
273
- g.selectAll(".col-label")
274
- .data(d3.range(numCols))
275
- .enter().append("text")
276
- .attr("class", d => `col-label col-label-${d}`)
277
- .attr("x", d => xScale(String(d))! + xScale.bandwidth() / 2)
278
- .attr("y", -10)
279
- .attr("text-anchor", "middle")
280
- .style("font-size", "11px")
281
- .style("fill", "#9ca3af")
282
- .style("font-family", "monospace")
283
- .text(d => currentAttention.tokens?.[d] || `T${d}`);
284
-
285
- // Add title - positioned higher to avoid overlap
286
- svg.append("text")
287
- .attr("x", (width + margin.left + margin.right) / 2)
288
- .attr("y", 30)
289
- .attr("text-anchor", "middle")
290
- .style("font-size", "18px")
291
- .style("font-weight", "bold")
292
- .style("fill", "#fff")
293
- .text(`Attention Weights - ${currentAttention.layer}`);
294
-
295
- // Add axis labels
296
- svg.append("text")
297
- .attr("x", (width + margin.left + margin.right) / 2)
298
- .attr("y", height + margin.top + margin.bottom - 20)
299
- .attr("text-anchor", "middle")
300
- .style("font-size", "14px")
301
- .style("fill", "#9ca3af")
302
- .text("Target Tokens →");
303
-
304
- svg.append("text")
305
- .attr("transform", "rotate(-90)")
306
- .attr("x", -(height + margin.top + margin.bottom) / 2)
307
- .attr("y", 20)
308
- .attr("text-anchor", "middle")
309
- .style("font-size", "14px")
310
- .style("fill", "#9ca3af")
311
- .text("Source Tokens →");
312
-
313
- // Add color legend - positioned in top right corner
314
- const legendWidth = 150;
315
- const legendHeight = 15;
316
-
317
- const legendScale = d3.scaleLinear()
318
- .domain([0, currentAttention.max_weight])
319
- .range([0, legendWidth]);
320
-
321
- const legendAxis = d3.axisBottom(legendScale)
322
- .ticks(4)
323
- .tickFormat(d => (d as number).toFixed(2));
324
-
325
- const legend = svg.append("g")
326
- .attr("transform", `translate(${width + margin.left - legendWidth}, ${60})`);
327
-
328
- // Create gradient for legend
329
- const gradientId = `attention-gradient-${Date.now()}`;
330
- const gradient = svg.append("defs")
331
- .append("linearGradient")
332
- .attr("id", gradientId)
333
- .attr("x1", "0%")
334
- .attr("x2", "100%");
335
-
336
- for (let i = 0; i <= 20; i++) {
337
- const t = i / 20;
338
- gradient.append("stop")
339
- .attr("offset", `${t * 100}%`)
340
- .attr("stop-color", colorScale(t * currentAttention.max_weight));
341
- }
342
-
343
- legend.append("rect")
344
- .attr("width", legendWidth)
345
- .attr("height", legendHeight)
346
- .style("fill", `url(#${gradientId})`)
347
- .style("stroke", "#4b5563")
348
- .style("stroke-width", 0.5);
349
-
350
- legend.append("g")
351
- .attr("transform", `translate(0, ${legendHeight})`)
352
- .call(legendAxis)
353
- .selectAll("text")
354
- .style("fill", "#9ca3af")
355
- .style("font-size", "10px");
356
-
357
- legend.append("text")
358
- .attr("x", legendWidth / 2)
359
- .attr("y", -5)
360
- .attr("text-anchor", "middle")
361
- .style("font-size", "11px")
362
- .style("fill", "#9ca3af")
363
- .text("Attention Weight");
364
-
365
- // Cleanup
366
- return () => {
367
- tooltip.remove();
368
- };
369
- }, [currentAttention, zoom, colorScheme]);
370
-
371
- // Update available layers whenever attentionData changes
372
- useEffect(() => {
373
- if (attentionData.length > 0) {
374
- const layers = Array.from(new Set(attentionData.map(a => a.layer)))
375
- .sort((a, b) => {
376
- // Sort layers numerically (layer.0, layer.1, etc.)
377
- const numA = parseInt(a.replace('layer.', ''));
378
- const numB = parseInt(b.replace('layer.', ''));
379
- return numA - numB;
380
- });
381
- console.log('Updating available layers from attentionData:', layers);
382
- setAvailableLayers(layers);
383
- }
384
- }, [attentionData]);
385
-
386
- // Export functionality
387
- const exportAttentionMap = () => {
388
- if (!currentAttention) return;
389
-
390
- const dataStr = JSON.stringify(currentAttention, null, 2);
391
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
392
-
393
- const exportFileDefaultName = `attention_${currentAttention.layer.replace(/\./g, '_')}_${Date.now()}.json`;
394
-
395
- const linkElement = document.createElement('a');
396
- linkElement.setAttribute('href', dataUri);
397
- linkElement.setAttribute('download', exportFileDefaultName);
398
- linkElement.click();
399
- };
400
-
401
- // Generate code with attention traces
402
- const handleGenerate = async () => {
403
- if (!prompt || isGenerating) return;
404
-
405
- setIsGenerating(true);
406
-
407
- try {
408
- const response = await fetch(`${getApiUrl()}/generate`, {
409
- method: 'POST',
410
- headers: {
411
- 'Content-Type': 'application/json',
412
- },
413
- body: JSON.stringify({
414
- prompt,
415
- max_tokens: 100,
416
- temperature: 0.7,
417
- sampling_rate: 0.3 // Sample 30% of tokens for attention
418
- })
419
- });
420
-
421
- if (!response.ok) {
422
- throw new Error(`HTTP error! status: ${response.status}`);
423
- }
424
-
425
- const data = await response.json();
426
- console.log('Generation completed:', data);
427
-
428
- // Process any attention traces from the response
429
- if (data.traces) {
430
- const attentionTraces = data.traces
431
- .filter((t: TraceData) => t.type === 'attention' && t.weights)
432
- .map((t: TraceData) => ({
433
- layer: t.layer || 'unknown',
434
- weights: t.weights || [],
435
- max_weight: t.max_weight || 1,
436
- entropy: t.entropy,
437
- timestamp: t.timestamp || Date.now(),
438
- tokens: t.tokens
439
- }));
440
-
441
- if (attentionTraces.length > 0) {
442
- console.log('Setting attention data:', attentionTraces.length, 'traces');
443
- const layerSet = new Set(attentionTraces.map((a: AttentionData) => a.layer));
444
- const uniqueLayers = (Array.from(layerSet) as string[])
445
- .sort((a: string, b: string) => {
446
- const numA = parseInt(a.replace('layer.', ''));
447
- const numB = parseInt(b.replace('layer.', ''));
448
- return numA - numB;
449
- });
450
- console.log('Unique layers found from HTTP response:', uniqueLayers);
451
-
452
- // Set all state together
453
- setAttentionData(attentionTraces);
454
- setAvailableLayers(uniqueLayers);
455
- setSelectedLayer(uniqueLayers[0]);
456
-
457
- console.log('State updated with', attentionTraces.length, 'traces and', uniqueLayers.length, 'layers');
458
- }
459
- }
460
- } catch (error) {
461
- console.error('Generation failed:', error);
462
- alert(`Generation failed: ${error}`);
463
- } finally {
464
- setIsGenerating(false);
465
- }
466
- };
467
-
468
- // Export as image
469
- const exportAsImage = () => {
470
- if (!svgRef.current) return;
471
-
472
- const svgData = new XMLSerializer().serializeToString(svgRef.current);
473
- const canvas = document.createElement("canvas");
474
- const ctx = canvas.getContext("2d");
475
- const img = new Image();
476
-
477
- img.onload = () => {
478
- canvas.width = img.width;
479
- canvas.height = img.height;
480
- ctx?.drawImage(img, 0, 0);
481
-
482
- canvas.toBlob((blob) => {
483
- if (blob) {
484
- const url = URL.createObjectURL(blob);
485
- const link = document.createElement('a');
486
- link.download = `attention_${currentAttention?.layer.replace(/\./g, '_')}_${Date.now()}.png`;
487
- link.href = url;
488
- link.click();
489
- }
490
- });
491
- };
492
-
493
- img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
494
- };
495
-
496
- // Generate contextual explanation for current visualization
497
- const generateExplanation = () => {
498
- if (!currentAttention) {
499
- return {
500
- title: "No Attention Data",
501
- description: "Run a model to see attention patterns between tokens.",
502
- details: []
503
- };
504
- }
505
-
506
- const maxWeight = currentAttention.max_weight;
507
- const entropy = currentAttention.entropy;
508
- const numTokens = currentAttention.weights.length;
509
- const strongAttentions = currentAttention.weights.flat().filter(w => w > maxWeight * 0.5).length;
510
- const totalCells = currentAttention.weights.flat().length;
511
- const focusPercentage = ((strongAttentions / totalCells) * 100).toFixed(1);
512
-
513
- return {
514
- title: `Attention Pattern: ${currentAttention.layer}`,
515
- description: `Viewing ${numTokens}×${numTokens} attention matrix showing how tokens attend to each other.`,
516
- details: [
517
- {
518
- heading: "What is an Attention Heatmap?",
519
- content: `This heatmap shows attention weights between tokens. Each cell represents how much one token "pays attention" to another. Brighter cells (higher values) mean stronger attention relationships.`
520
- },
521
- {
522
- heading: "Reading the Matrix",
523
- content: `Rows represent source tokens (what's attending), columns represent target tokens (what's being attended to). The diagonal often shows self-attention. Patterns reveal how the model processes relationships between words.`
524
- },
525
- {
526
- heading: "Current Pattern Analysis",
527
- content: `${focusPercentage}% of attention weights are strong (>50% of max). ${entropy ? `Entropy: ${entropy.toFixed(2)} - ${entropy < 2 ? 'Focused attention' : entropy < 4 ? 'Distributed attention' : 'Scattered attention'}` : 'Pattern shows attention distribution across tokens.'}`
528
- },
529
- {
530
- heading: "Color Intensity Meaning",
531
- content: `Colors range from dark (0.00) to bright (${maxWeight.toFixed(2)}). Bright spots indicate strong dependencies. The model uses these weights to combine information from different positions.`
532
- },
533
- {
534
- heading: "Common Patterns",
535
- content: `Look for: Vertical lines (tokens everyone attends to), horizontal lines (tokens attending broadly), diagonal patterns (local/sequential attention), and block patterns (grouped attention).`
536
- },
537
- {
538
- heading: "Layer Significance",
539
- content: `${currentAttention.layer.includes('0') ? 'Early layers capture local patterns and syntax.' :
540
- currentAttention.layer.includes('1') ? 'Middle layers build semantic relationships.' :
541
- 'Later layers form high-level representations.'} Each layer builds on previous ones.`
542
- }
543
- ]
544
- };
545
- };
546
-
547
- const explanation = generateExplanation();
548
-
549
- return (
550
- <div className="bg-gray-900 rounded-xl p-6">
551
- <div className="flex items-center justify-between mb-6">
552
- <div>
553
- <h2 className="text-2xl font-bold flex items-center gap-2">
554
- <Eye className="w-6 h-6 text-blue-400" />
555
- Attention Explorer
556
- </h2>
557
- <p className="text-gray-400 mt-1">
558
- Visualize attention patterns across transformer layers
559
- </p>
560
- </div>
561
-
562
- <div className="flex items-center gap-4">
563
- {/* Connection Status */}
564
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
565
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
566
- }`}>
567
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
568
- {isConnected ? 'Connected' : 'Disconnected'}
569
- </div>
570
- </div>
571
- </div>
572
-
573
- {/* Prompt Input and Generate Button */}
574
- <div className="flex gap-4 mb-4">
575
- <div className="flex-1">
576
- <textarea
577
- value={prompt}
578
- onChange={(e) => setPrompt(e.target.value)}
579
- placeholder="Enter a prompt to generate code (e.g., 'def fibonacci(n):')"
580
- className="w-full px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none resize-none"
581
- rows={2}
582
- disabled={isGenerating}
583
- />
584
- </div>
585
- <button
586
- onClick={handleGenerate}
587
- disabled={!prompt || isGenerating || !isConnected}
588
- className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2 h-fit"
589
- >
590
- {isGenerating ? (
591
- <>
592
- <RefreshCw className="w-5 h-5 animate-spin" />
593
- Generating...
594
- </>
595
- ) : (
596
- <>
597
- <Zap className="w-5 h-5" />
598
- Generate & Trace
599
- </>
600
- )}
601
- </button>
602
- </div>
603
-
604
- {/* Controls */}
605
- <div className="flex flex-wrap items-center gap-4 mb-4">
606
- {/* Layer Selector */}
607
- <div className="flex items-center gap-2">
608
- <Layers className="w-5 h-5 text-gray-400" />
609
- <select
610
- value={selectedLayer}
611
- onChange={(e) => setSelectedLayer(e.target.value)}
612
- className="bg-gray-800 text-white px-3 py-1.5 rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none min-w-[200px]"
613
- disabled={availableLayers.length === 0}
614
- >
615
- {availableLayers.length === 0 ? (
616
- <option value="">No layers available</option>
617
- ) : (
618
- availableLayers.map(layer => (
619
- <option key={layer} value={layer}>{layer}</option>
620
- ))
621
- )}
622
- </select>
623
- </div>
624
-
625
- {/* Color Scheme Selector */}
626
- <div className="flex items-center gap-2">
627
- <span className="text-gray-400 text-sm">Color:</span>
628
- <div className="flex gap-1">
629
- <button
630
- onClick={() => setColorScheme('blues')}
631
- className={`px-2 py-1 text-xs rounded ${colorScheme === 'blues' ? 'bg-blue-600' : 'bg-gray-700'}`}
632
- >
633
- Blues
634
- </button>
635
- <button
636
- onClick={() => setColorScheme('viridis')}
637
- className={`px-2 py-1 text-xs rounded ${colorScheme === 'viridis' ? 'bg-blue-600' : 'bg-gray-700'}`}
638
- >
639
- Viridis
640
- </button>
641
- <button
642
- onClick={() => setColorScheme('plasma')}
643
- className={`px-2 py-1 text-xs rounded ${colorScheme === 'plasma' ? 'bg-blue-600' : 'bg-gray-700'}`}
644
- >
645
- Plasma
646
- </button>
647
- </div>
648
- </div>
649
-
650
- {/* Zoom Controls */}
651
- <div className="flex items-center gap-2">
652
- <button
653
- onClick={() => setZoom(Math.max(0.5, zoom - 0.1))}
654
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
655
- title="Zoom Out"
656
- >
657
- <ZoomOut className="w-4 h-4" />
658
- </button>
659
- <span className="text-sm text-gray-400 min-w-[50px] text-center">{(zoom * 100).toFixed(0)}%</span>
660
- <button
661
- onClick={() => setZoom(Math.min(2, zoom + 0.1))}
662
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
663
- title="Zoom In"
664
- >
665
- <ZoomIn className="w-4 h-4" />
666
- </button>
667
- <button
668
- onClick={() => setZoom(1)}
669
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
670
- title="Reset Zoom"
671
- >
672
- <RefreshCw className="w-4 h-4" />
673
- </button>
674
- </div>
675
-
676
- {/* Export Buttons */}
677
- <div className="flex gap-2 ml-auto">
678
- <button
679
- onClick={exportAttentionMap}
680
- disabled={!currentAttention}
681
- className="px-3 py-1.5 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors disabled:opacity-50 flex items-center gap-2"
682
- title="Export as JSON"
683
- >
684
- <Download className="w-4 h-4" />
685
- JSON
686
- </button>
687
- </div>
688
- </div>
689
-
690
- {/* Status Bar */}
691
- <div className="flex items-center gap-4 mb-4 text-sm">
692
- <div className="text-gray-400">
693
- <span className="font-semibold">{attentionData.length}</span> attention maps captured
694
- </div>
695
-
696
- {hoveredCell && (
697
- <div className="text-gray-400">
698
- Cell [{hoveredCell.row}, {hoveredCell.col}]: <span className="text-blue-400 font-mono">{hoveredCell.value.toFixed(4)}</span>
699
- </div>
700
- )}
701
-
702
- {currentAttention?.entropy !== undefined && (
703
- <div className="text-gray-400">
704
- Entropy: <span className="text-green-400 font-mono">{currentAttention.entropy.toFixed(3)}</span>
705
- </div>
706
- )}
707
- </div>
708
-
709
- {/* Main Content Area with Side Panel */}
710
- <div className="flex gap-4">
711
- {/* Visualization Container */}
712
- <div className="flex-1 transition-all duration-500 ease-in-out">
713
- <div ref={containerRef} className="bg-gray-800 rounded-lg p-4 overflow-auto max-h-[600px] relative">
714
- {/* Help Toggle Button */}
715
- <button
716
- onClick={() => setShowExplanation(!showExplanation)}
717
- className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
718
- >
719
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
720
- <span className="text-sm font-medium">
721
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
722
- </span>
723
- </button>
724
-
725
- {currentAttention ? (
726
- <svg ref={svgRef}></svg>
727
- ) : (
728
- <div className="flex items-center justify-center h-96 text-gray-500">
729
- <div className="text-center">
730
- <Eye className="w-12 h-12 mx-auto mb-4 opacity-50" />
731
- <p className="text-lg mb-2">No Attention Data Available</p>
732
- <p className="text-sm mb-4">Run a model to capture attention patterns</p>
733
- <p className="text-xs text-gray-600">
734
- Attention maps will appear here when traces are received
735
- </p>
736
- </div>
737
- </div>
738
- )}
739
- </div>
740
- </div>
741
-
742
- {/* Explanation Side Panel */}
743
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
744
- <div className="w-96 h-[600px] bg-gray-900 rounded-lg border border-gray-700">
745
- {/* Panel Header */}
746
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
747
- <div className="flex items-center gap-2">
748
- <Info className="w-5 h-5 text-blue-400" />
749
- <h3 className="text-lg font-semibold text-white">Understanding Attention</h3>
750
- </div>
751
- </div>
752
-
753
- {/* Panel Content */}
754
- <div className="px-4 py-4 overflow-y-auto h-[calc(600px-60px)]">
755
- {/* Main Description */}
756
- <div className="mb-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
757
- <h4 className="text-sm font-semibold text-blue-400 mb-1">{explanation.title}</h4>
758
- <p className="text-xs text-gray-300">{explanation.description}</p>
759
- </div>
760
-
761
- {/* Explanation Sections */}
762
- <div className="space-y-3">
763
- {explanation.details.map((section, idx) => (
764
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
765
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
766
- <Zap className="w-3 h-3 text-yellow-400" />
767
- {section.heading}
768
- </h5>
769
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
770
- </div>
771
- ))}
772
- </div>
773
-
774
- {/* Visual Guide */}
775
- <div className="mt-4 p-3 bg-purple-900/20 border border-purple-800 rounded-lg">
776
- <h4 className="font-medium text-sm text-purple-400 mb-2">Visual Guide</h4>
777
- <div className="space-y-2 text-xs">
778
- <div className="flex items-start gap-2">
779
- <span className="text-purple-300">•</span>
780
- <span className="text-gray-300">Rows = Source tokens (what&apos;s attending)</span>
781
- </div>
782
- <div className="flex items-start gap-2">
783
- <span className="text-purple-300">•</span>
784
- <span className="text-gray-300">Columns = Target tokens (what&apos;s being attended to)</span>
785
- </div>
786
- <div className="flex items-start gap-2">
787
- <span className="text-purple-300">•</span>
788
- <span className="text-gray-300">Brightness = Attention strength (0 to {currentAttention?.max_weight.toFixed(2) || '1.00'})</span>
789
- </div>
790
- <div className="flex items-start gap-2">
791
- <span className="text-purple-300">•</span>
792
- <span className="text-gray-300">Hover over cells for detailed weights</span>
793
- </div>
794
- </div>
795
- </div>
796
-
797
- {/* Current Metrics */}
798
- {currentAttention && (
799
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
800
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Metrics</h4>
801
- <div className="space-y-1 text-xs">
802
- <div className="flex justify-between">
803
- <span className="text-gray-400">Layer:</span>
804
- <span className="text-white font-mono">{currentAttention.layer}</span>
805
- </div>
806
- <div className="flex justify-between">
807
- <span className="text-gray-400">Matrix Size:</span>
808
- <span className="text-white">{currentAttention.weights.length} × {currentAttention.weights[0]?.length || 0}</span>
809
- </div>
810
- <div className="flex justify-between">
811
- <span className="text-gray-400">Max Weight:</span>
812
- <span className="text-blue-400">{currentAttention.max_weight.toFixed(4)}</span>
813
- </div>
814
- {currentAttention.entropy !== undefined && (
815
- <div className="flex justify-between">
816
- <span className="text-gray-400">Entropy:</span>
817
- <span className="text-green-400">{currentAttention.entropy.toFixed(3)}</span>
818
- </div>
819
- )}
820
- <div className="flex justify-between">
821
- <span className="text-gray-400">Timestamp:</span>
822
- <span className="text-white">{new Date(currentAttention.timestamp).toLocaleTimeString()}</span>
823
- </div>
824
- </div>
825
- </div>
826
- )}
827
-
828
- {/* Tips */}
829
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
830
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
831
- <ul className="text-xs text-gray-400 space-y-1">
832
- <li>• Use zoom controls to explore large matrices</li>
833
- <li>• Switch color schemes for better contrast</li>
834
- <li>• Compare patterns across different layers</li>
835
- <li>• Look for diagonal patterns (self-attention)</li>
836
- </ul>
837
- </div>
838
- </div>
839
- </div>
840
- </div>
841
- </div>
842
-
843
- {/* Info Panel */}
844
- {currentAttention && (
845
- <div className="mt-4 p-4 bg-gray-800 rounded-lg">
846
- <h3 className="text-lg font-semibold mb-3">Attention Map Details</h3>
847
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
848
- <div>
849
- <span className="text-gray-400">Layer:</span>
850
- <div className="font-mono text-white mt-1">{currentAttention.layer}</div>
851
- </div>
852
- <div>
853
- <span className="text-gray-400">Shape:</span>
854
- <div className="font-mono text-white mt-1">
855
- {currentAttention.weights.length} × {currentAttention.weights[0]?.length || 0}
856
- </div>
857
- </div>
858
- <div>
859
- <span className="text-gray-400">Max Weight:</span>
860
- <div className="font-mono text-blue-400 mt-1">{currentAttention.max_weight.toFixed(4)}</div>
861
- </div>
862
- <div>
863
- <span className="text-gray-400">Timestamp:</span>
864
- <div className="font-mono text-white mt-1">
865
- {new Date(currentAttention.timestamp).toLocaleTimeString()}
866
- </div>
867
- </div>
868
- </div>
869
- </div>
870
- )}
871
- </div>
872
- );
873
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/ClientOnly.tsx DELETED
@@ -1,22 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState } from "react";
4
-
5
- interface ClientOnlyProps {
6
- children: React.ReactNode;
7
- fallback?: React.ReactNode;
8
- }
9
-
10
- export default function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
11
- const [hasMounted, setHasMounted] = useState(false);
12
-
13
- useEffect(() => {
14
- setHasMounted(true);
15
- }, []);
16
-
17
- if (!hasMounted) {
18
- return <>{fallback}</>;
19
- }
20
-
21
- return <>{children}</>;
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/CodeGenerationTracker.tsx DELETED
@@ -1,1071 +0,0 @@
1
- /**
2
- * Code Generation Tracker Component
3
- *
4
- * Visualizes step-by-step how LLMs generate code, showing:
5
- * - Token-by-token generation process
6
- * - Probability distributions for each token
7
- * - Alternative tokens that could have been chosen
8
- * - Attention patterns during generation
9
- * - Real-time generation streaming
10
- *
11
- * @component
12
- */
13
-
14
- "use client";
15
-
16
- import { useState, useEffect, useRef } from "react";
17
- import * as d3 from "d3";
18
- import { getApiUrl, getWsUrl } from "@/lib/config";
19
- import {
20
- Code2,
21
- Play,
22
- Pause,
23
- RotateCcw,
24
- Zap,
25
- TrendingUp,
26
- GitBranch,
27
- Sparkles,
28
- Settings,
29
- Download,
30
- ChevronRight,
31
- Activity,
32
- BarChart3,
33
- Eye,
34
- HelpCircle,
35
- X,
36
- Info
37
- } from "lucide-react";
38
-
39
- // Generation step data structure
40
- interface GenerationStep {
41
- stepIndex: number;
42
- prompt: string;
43
- generatedToken: string;
44
- tokenId: number;
45
- probability: number;
46
- alternatives: TokenChoice[];
47
- attentionWeights?: number[];
48
- confidence: number;
49
- timestamp: number;
50
- cumulativeText: string;
51
- }
52
-
53
- // Alternative token choices
54
- interface TokenChoice {
55
- token: string;
56
- tokenId: number;
57
- probability: number;
58
- logit: number;
59
- }
60
-
61
- // Generation configuration
62
- interface GenerationConfig {
63
- temperature: number;
64
- topK: number;
65
- topP: number;
66
- maxTokens: number;
67
- prompt: string;
68
- }
69
-
70
- export default function CodeGenerationTracker() {
71
- const [generationSteps, setGenerationSteps] = useState<GenerationStep[]>([]);
72
- const [currentStep, setCurrentStep] = useState(0);
73
- const [isGenerating, setIsGenerating] = useState(false);
74
- const [isPlaying, setIsPlaying] = useState(false);
75
- const [selectedStep, setSelectedStep] = useState<GenerationStep | null>(null);
76
- const [showAttention, setShowAttention] = useState(true);
77
- const [showProbabilities, setShowProbabilities] = useState(true);
78
- const [generationSpeed, setGenerationSpeed] = useState(500); // ms between steps
79
- const [showExplanation, setShowExplanation] = useState(false);
80
-
81
- // Configuration state
82
- const [config, setConfig] = useState<GenerationConfig>({
83
- temperature: 0.7,
84
- topK: 50,
85
- topP: 0.95,
86
- maxTokens: 100,
87
- prompt: "def quicksort(arr):\n '''Sort array using quicksort algorithm'''\n "
88
- });
89
-
90
- const [isConnected, setIsConnected] = useState(false);
91
-
92
- const svgRef = useRef<SVGSVGElement>(null);
93
- const probabilityRef = useRef<SVGSVGElement>(null);
94
- const attentionRef = useRef<SVGSVGElement>(null);
95
- const animationRef = useRef<number | null>(null);
96
- const wsRef = useRef<WebSocket | null>(null);
97
- const cumulativeTextRef = useRef<string>('');
98
- const [isServiceConnected, setIsServiceConnected] = useState(false);
99
- const [isClient, setIsClient] = useState(false);
100
-
101
- // Ensure we're on the client
102
- useEffect(() => {
103
- setIsClient(true);
104
- }, []);
105
-
106
- // Connect to unified backend WebSocket
107
- useEffect(() => {
108
- if (!isClient) return;
109
-
110
- let mounted = true;
111
- let reconnectTimeout: NodeJS.Timeout;
112
-
113
- const connectWS = () => {
114
- if (!mounted) return;
115
-
116
- try {
117
- const ws = new WebSocket(getWsUrl());
118
-
119
- ws.onopen = () => {
120
- if (!mounted) return;
121
- console.log('CodeGenTracker: WebSocket connected');
122
- setIsConnected(true);
123
- setIsServiceConnected(true);
124
- wsRef.current = ws;
125
- };
126
-
127
- ws.onmessage = (event) => {
128
- if (!mounted) return;
129
-
130
- let data;
131
- try {
132
- data = JSON.parse(event.data);
133
- } catch (e) {
134
- // Skip non-JSON messages
135
- return;
136
- }
137
-
138
- if (data.type === 'generated_token') {
139
- // Build cumulative text
140
- cumulativeTextRef.current += data.token;
141
-
142
- // Create a generation step for each token
143
- setGenerationSteps(prev => {
144
- const probability = data.confidence_score || 0.75;
145
-
146
- let alternatives: TokenChoice[];
147
-
148
- // Use real alternatives if provided by backend
149
- if (data.alternatives && data.alternatives.length > 0) {
150
- // Real top-k tokens from the model
151
- interface AlternativeData {
152
- token: string;
153
- token_id?: number;
154
- probability: number;
155
- }
156
- alternatives = data.alternatives.map((alt: AlternativeData, idx: number) => ({
157
- token: alt.token,
158
- tokenId: alt.token_id || idx,
159
- probability: alt.probability,
160
- logit: Math.log(alt.probability)
161
- }));
162
- } else {
163
- // Fallback to mock alternatives if backend doesn't provide them
164
- const possibleAlts = [
165
- ['(', ')', '[', ']', '{', '}'],
166
- ['if', 'else', 'for', 'while', 'def', 'return'],
167
- ['=', '==', '!=', '<', '>', '<=', '>='],
168
- ['and', 'or', 'not', 'in', 'is'],
169
- ['+', '-', '*', '/', '//', '%'],
170
- [':', ';', ',', '.', '...'],
171
- ['0', '1', '2', 'i', 'j', 'x'],
172
- ['arr', 'list', 'data', 'item', 'val'],
173
- ];
174
-
175
- const altSet = possibleAlts[Math.floor(Math.random() * possibleAlts.length)];
176
-
177
- alternatives = [
178
- {
179
- token: data.token, // The actual chosen token
180
- tokenId: 0,
181
- probability: probability,
182
- logit: Math.log(probability)
183
- },
184
- ...altSet.slice(0, 4).map((alt, idx) => ({
185
- token: alt,
186
- tokenId: idx + 1,
187
- probability: (1 - probability) * (0.4 - idx * 0.1),
188
- logit: Math.log((1 - probability) * (0.4 - idx * 0.1))
189
- }))
190
- ].sort((a, b) => b.probability - a.probability);
191
- }
192
-
193
- // Generate mock attention weights (for attention pattern visualization)
194
- const numPrevTokens = Math.min(prev.length, 20);
195
- const attentionWeights = Array.from({ length: numPrevTokens }, () =>
196
- Math.random() * 0.8 + 0.1
197
- );
198
-
199
- const step: GenerationStep = {
200
- stepIndex: prev.length,
201
- prompt: config.prompt,
202
- generatedToken: data.token,
203
- tokenId: 0,
204
- probability: probability,
205
- alternatives: alternatives,
206
- confidence: probability,
207
- timestamp: data.timestamp || Date.now(),
208
- cumulativeText: cumulativeTextRef.current,
209
- attentionWeights: attentionWeights
210
- };
211
-
212
- // Filter out special tokens
213
- const specialTokens = ['</s>', '<s>', '<pad>', '<unk>', ''];
214
- if (!specialTokens.includes(step.generatedToken)) {
215
- const newSteps = [...prev, step];
216
- setCurrentStep(newSteps.length - 1);
217
- return newSteps;
218
- }
219
- return prev;
220
- });
221
- } else if (data.type === 'token') {
222
- // Handle confidence updates
223
- if (data.confidence_score) {
224
- // Update the last step's confidence if available
225
- setGenerationSteps(prev => {
226
- if (prev.length > 0) {
227
- const updated = [...prev];
228
- updated[updated.length - 1] = {
229
- ...updated[updated.length - 1],
230
- confidence: data.confidence_score,
231
- probability: data.confidence_score
232
- };
233
- return updated;
234
- }
235
- return prev;
236
- });
237
- }
238
- }
239
- };
240
-
241
- ws.onerror = () => {
242
- if (mounted) {
243
- setIsConnected(false);
244
- setIsServiceConnected(false);
245
- }
246
- };
247
-
248
- ws.onclose = () => {
249
- if (!mounted) return;
250
- console.log('CodeGenTracker: WebSocket disconnected, will reconnect...');
251
- setIsConnected(false);
252
- setIsServiceConnected(false);
253
- wsRef.current = null;
254
- reconnectTimeout = setTimeout(() => {
255
- if (mounted) connectWS();
256
- }, 3000);
257
- };
258
- } catch (error) {
259
- console.log('WebSocket connection attempt failed, will retry...');
260
- if (mounted) {
261
- setIsConnected(false);
262
- setIsServiceConnected(false);
263
- reconnectTimeout = setTimeout(() => {
264
- if (mounted) connectWS();
265
- }, 3000);
266
- }
267
- }
268
- };
269
-
270
- connectWS();
271
-
272
- return () => {
273
- mounted = false;
274
- if (reconnectTimeout) {
275
- clearTimeout(reconnectTimeout);
276
- }
277
- if (wsRef.current) {
278
- wsRef.current.close();
279
- }
280
- };
281
- }, [isClient]);
282
-
283
- // Listen for demo events from LocalControlPanel
284
- useEffect(() => {
285
- const handleDemoPromptSelected = (event: CustomEvent) => {
286
- const { prompt, demoId } = event.detail;
287
- console.log('CodeGenTracker: Demo prompt selected -', demoId);
288
-
289
- if (prompt) {
290
- setConfig(prev => ({ ...prev, prompt }));
291
- }
292
- };
293
-
294
- const handleDemoStarting = (event: CustomEvent) => {
295
- const { demoId } = event.detail;
296
- console.log('CodeGenTracker: Demo starting, clearing steps -', demoId);
297
-
298
- // Clear generation steps when demo starts
299
- setGenerationSteps([]);
300
- setCurrentStep(0);
301
- cumulativeTextRef.current = ''; // Reset cumulative text
302
- setIsGenerating(true);
303
- };
304
-
305
- const handleDemoCompleted = (event: CustomEvent) => {
306
- console.log('CodeGenTracker: Demo completed');
307
- setIsGenerating(false);
308
- };
309
-
310
- window.addEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
311
- window.addEventListener('demo-starting', handleDemoStarting as EventListener);
312
- window.addEventListener('demo-completed', handleDemoCompleted as EventListener);
313
-
314
- return () => {
315
- window.removeEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
316
- window.removeEventListener('demo-starting', handleDemoStarting as EventListener);
317
- window.removeEventListener('demo-completed', handleDemoCompleted as EventListener);
318
- };
319
- }, []);
320
-
321
- // Start generation using unified backend
322
- const startGeneration = async () => {
323
- setIsGenerating(true);
324
- setGenerationSteps([]);
325
- setCurrentStep(0);
326
- cumulativeTextRef.current = ''; // Reset cumulative text
327
-
328
- try {
329
- const response = await fetch(`${getApiUrl()}/generate`, {
330
- method: 'POST',
331
- headers: { 'Content-Type': 'application/json' },
332
- body: JSON.stringify({
333
- prompt: config.prompt,
334
- max_tokens: config.maxTokens,
335
- temperature: config.temperature,
336
- extract_traces: true,
337
- sampling_rate: 0.3
338
- })
339
- });
340
-
341
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
342
- const data = await response.json();
343
-
344
- console.log('CodeGenTracker: Generation complete', data);
345
- setIsGenerating(false);
346
- return; // Exit after successful generation
347
- } catch (error) {
348
- console.error('Generation error:', error);
349
- alert(`Failed to generate: ${error}`);
350
- setIsGenerating(false);
351
- return; // Exit on error
352
- }
353
-
354
- // Demo generation code below (only runs if above code is removed/disabled)
355
- // Simulate token-by-token generation
356
- const tokens = [
357
- "\n", " ", "if", " not", " arr", ":", "\n",
358
- " ", "return", " arr", "\n",
359
- " ", "pivot", " =", " arr", "[", "len", "(", "arr", ")", " //", " 2", "]", "\n",
360
- " ", "left", " =", " [", "x", " for", " x", " in", " arr", " if", " x", " <", " pivot", "]", "\n",
361
- " ", "middle", " =", " [", "x", " for", " x", " in", " arr", " if", " x", " ==", " pivot", "]", "\n",
362
- " ", "right", " =", " [", "x", " for", " x", " in", " arr", " if", " x", " >", " pivot", "]", "\n",
363
- " ", "return", " quicksort", "(", "left", ")", " +", " middle", " +", " quicksort", "(", "right", ")"
364
- ];
365
-
366
- let cumulativeText = config.prompt;
367
- const steps: GenerationStep[] = [];
368
-
369
- tokens.forEach((token, idx) => {
370
- // Build cumulative text progressively
371
- cumulativeText = cumulativeText + token;
372
-
373
- // Generate alternatives with probabilities
374
- const alternatives: TokenChoice[] = [
375
- { token, tokenId: idx * 100, probability: 0.3 + Math.random() * 0.5, logit: Math.random() * 10 },
376
- { token: "[ALT1]", tokenId: idx * 100 + 1, probability: Math.random() * 0.3, logit: Math.random() * 8 },
377
- { token: "[ALT2]", tokenId: idx * 100 + 2, probability: Math.random() * 0.2, logit: Math.random() * 6 },
378
- { token: "[ALT3]", tokenId: idx * 100 + 3, probability: Math.random() * 0.1, logit: Math.random() * 4 },
379
- { token: "[ALT4]", tokenId: idx * 100 + 4, probability: Math.random() * 0.05, logit: Math.random() * 2 },
380
- ].sort((a, b) => b.probability - a.probability);
381
-
382
- // Normalize probabilities
383
- const totalProb = alternatives.reduce((sum, alt) => sum + alt.probability, 0);
384
- alternatives.forEach(alt => alt.probability = alt.probability / totalProb);
385
-
386
- const step: GenerationStep = {
387
- stepIndex: idx,
388
- prompt: config.prompt,
389
- generatedToken: token,
390
- tokenId: idx * 100,
391
- probability: alternatives[0].probability,
392
- alternatives,
393
- attentionWeights: Array.from({ length: Math.min(20, idx + 1) }, () => Math.random()),
394
- confidence: 0.5 + Math.random() * 0.5,
395
- timestamp: Date.now() + idx * 100,
396
- cumulativeText
397
- };
398
-
399
- steps.push(step);
400
- });
401
-
402
- // Store all steps at once to prevent state issues
403
- setGenerationSteps(steps);
404
-
405
- // Simulate streaming generation by animating through steps
406
- let stepIndex = 0;
407
- const interval = setInterval(() => {
408
- if (stepIndex < steps.length) {
409
- setCurrentStep(stepIndex);
410
- stepIndex++;
411
- } else {
412
- clearInterval(interval);
413
- setIsGenerating(false);
414
- }
415
- }, generationSpeed);
416
- };
417
-
418
- // Visualize probability distribution
419
- useEffect(() => {
420
- if (!probabilityRef.current || generationSteps.length === 0) return;
421
-
422
- const currentStepData = generationSteps[currentStep];
423
- if (!currentStepData || !currentStepData.alternatives || currentStepData.alternatives.length === 0) return;
424
-
425
- const margin = { top: 40, right: 60, bottom: 60, left: 100 };
426
- const width = 500;
427
- const height = 300;
428
-
429
- // Clear previous
430
- d3.select(probabilityRef.current).selectAll("*").remove();
431
-
432
- const svg = d3.select(probabilityRef.current)
433
- .attr("width", width)
434
- .attr("height", height)
435
- .attr("viewBox", `0 0 ${width} ${height}`);
436
-
437
- const g = svg.append("g")
438
- .attr("transform", `translate(${margin.left},${margin.top})`);
439
-
440
- // Scales
441
- const xScale = d3.scaleLinear()
442
- .domain([0, Math.max(...currentStepData.alternatives.map(a => a.probability))])
443
- .range([0, width - margin.left - margin.right]);
444
-
445
- const yScale = d3.scaleBand()
446
- .domain(currentStepData.alternatives.map(a => a.token))
447
- .range([0, height - margin.top - margin.bottom])
448
- .padding(0.1);
449
-
450
- // Color scale
451
- const colorScale = d3.scaleSequential(d3.interpolateViridis)
452
- .domain([0, currentStepData.alternatives[0].probability]);
453
-
454
- // Bars
455
- g.selectAll(".prob-bar")
456
- .data(currentStepData.alternatives)
457
- .enter()
458
- .append("rect")
459
- .attr("class", "prob-bar")
460
- .attr("x", 0)
461
- .attr("y", d => yScale(d.token)!)
462
- .attr("width", 0)
463
- .attr("height", yScale.bandwidth())
464
- .attr("fill", d => colorScale(d.probability))
465
- .attr("stroke", d => d.token === currentStepData.generatedToken ? "#3b82f6" : "none")
466
- .attr("stroke-width", 2)
467
- .transition()
468
- .duration(300)
469
- .attr("width", d => xScale(d.probability));
470
-
471
- // Labels
472
- g.selectAll(".prob-label")
473
- .data(currentStepData.alternatives)
474
- .enter()
475
- .append("text")
476
- .attr("class", "prob-label")
477
- .attr("x", d => xScale(d.probability) + 5)
478
- .attr("y", d => yScale(d.token)! + yScale.bandwidth() / 2)
479
- .attr("dominant-baseline", "middle")
480
- .attr("fill", "#9ca3af")
481
- .attr("font-size", "11px")
482
- .text(d => `${(d.probability * 100).toFixed(1)}%`);
483
-
484
- // Token labels
485
- g.selectAll(".token-label")
486
- .data(currentStepData.alternatives)
487
- .enter()
488
- .append("text")
489
- .attr("class", "token-label")
490
- .attr("x", -5)
491
- .attr("y", d => yScale(d.token)! + yScale.bandwidth() / 2)
492
- .attr("text-anchor", "end")
493
- .attr("dominant-baseline", "middle")
494
- .attr("fill", d => d.token === currentStepData.generatedToken ? "#3b82f6" : "#fff")
495
- .attr("font-size", "12px")
496
- .attr("font-family", "monospace")
497
- .attr("font-weight", d => d.token === currentStepData.generatedToken ? "bold" : "normal")
498
- .text(d => d.token.length > 10 ? d.token.substring(0, 10) + "..." : d.token);
499
-
500
- // Title
501
- svg.append("text")
502
- .attr("x", width / 2)
503
- .attr("y", 20)
504
- .attr("text-anchor", "middle")
505
- .attr("font-size", "14px")
506
- .attr("font-weight", "bold")
507
- .attr("fill", "#fff")
508
- .text(`Token Probabilities - Step ${currentStep + 1}`);
509
-
510
- // X axis
511
- g.append("g")
512
- .attr("transform", `translate(0, ${height - margin.top - margin.bottom})`)
513
- .call(d3.axisBottom(xScale).ticks(5).tickFormat(d => `${(d as number * 100).toFixed(0)}%`))
514
- .selectAll("text")
515
- .style("fill", "#9ca3af");
516
-
517
- }, [currentStep, generationSteps, showProbabilities]);
518
-
519
- // Visualize attention during generation
520
- useEffect(() => {
521
- if (!attentionRef.current || !showAttention) return;
522
-
523
- const currentStepData = generationSteps[currentStep];
524
- if (!currentStepData || !currentStepData.attentionWeights) return;
525
-
526
- const margin = { top: 40, right: 40, bottom: 40, left: 40 };
527
- const size = 300;
528
-
529
- // Clear previous
530
- d3.select(attentionRef.current).selectAll("*").remove();
531
-
532
- const svg = d3.select(attentionRef.current)
533
- .attr("width", size)
534
- .attr("height", size)
535
- .attr("viewBox", `0 0 ${size} ${size}`);
536
-
537
- const g = svg.append("g")
538
- .attr("transform", `translate(${margin.left},${margin.top})`);
539
-
540
- // Create attention heatmap
541
- const numTokens = currentStepData.attentionWeights.length;
542
- const cellSize = (size - margin.left - margin.right) / numTokens;
543
-
544
- const colorScale = d3.scaleSequential(d3.interpolateBlues)
545
- .domain([0, Math.max(...currentStepData.attentionWeights)]);
546
-
547
- // Draw cells
548
- currentStepData.attentionWeights.forEach((weight, i) => {
549
- g.append("rect")
550
- .attr("x", i * cellSize)
551
- .attr("y", 0)
552
- .attr("width", cellSize)
553
- .attr("height", cellSize)
554
- .attr("fill", colorScale(weight))
555
- .attr("stroke", "#1f2937")
556
- .attr("stroke-width", 0.5);
557
- });
558
-
559
- // Title
560
- svg.append("text")
561
- .attr("x", size / 2)
562
- .attr("y", 20)
563
- .attr("text-anchor", "middle")
564
- .attr("font-size", "14px")
565
- .attr("font-weight", "bold")
566
- .attr("fill", "#fff")
567
- .text("Attention to Previous Tokens");
568
-
569
- }, [currentStep, generationSteps, showAttention]);
570
-
571
- // Animation control
572
- const togglePlayback = () => {
573
- setIsPlaying(!isPlaying);
574
- };
575
-
576
- useEffect(() => {
577
- if (isPlaying && generationSteps.length > 0) {
578
- const animate = () => {
579
- setCurrentStep(prev => {
580
- if (prev < generationSteps.length - 1) {
581
- return prev + 1;
582
- } else {
583
- setIsPlaying(false);
584
- return prev;
585
- }
586
- });
587
- };
588
-
589
- const interval = setInterval(animate, generationSpeed);
590
- return () => clearInterval(interval);
591
- }
592
- }, [isPlaying, generationSteps, generationSpeed]);
593
-
594
- const reset = () => {
595
- setCurrentStep(0);
596
- setIsPlaying(false);
597
- setSelectedStep(null);
598
- };
599
-
600
- // Generate contextual explanation for current visualization
601
- const generateExplanation = () => {
602
- if (generationSteps.length === 0) {
603
- return {
604
- title: "No Generation Data",
605
- description: "Click 'Generate Code' to see how the model creates code token by token.",
606
- details: []
607
- };
608
- }
609
-
610
- const currentStepData = generationSteps[currentStep];
611
- const totalSteps = generationSteps.length;
612
- const avgConfidence = generationSteps.reduce((sum, s) => sum + s.confidence, 0) / totalSteps;
613
- const topAlternatives = currentStepData?.alternatives?.slice(0, 3) || [];
614
-
615
- return {
616
- title: `Code Generation Process: Step ${currentStep + 1}/${totalSteps}`,
617
- description: `Watching the model generate code token by token with probability distributions.`,
618
- details: [
619
- {
620
- heading: "What is Code Generation Tracking?",
621
- content: `This visualizes how LLMs generate code one token at a time. Each step shows the chosen token, its probability, and alternatives the model considered. This reveals the decision-making process behind code generation.`
622
- },
623
- {
624
- heading: "Understanding the Display",
625
- content: `The top panel shows generated code building up. The left chart displays probability distribution for token choices. The right shows attention patterns - how the model looks at previous tokens to decide what comes next.`
626
- },
627
- {
628
- heading: "Current Token Analysis",
629
- content: `Token: "${currentStepData?.generatedToken || ''}" with ${(currentStepData?.probability * 100).toFixed(1)}% probability. Alternatives considered: ${topAlternatives.map(a => `"${a.token}" (${(a.probability * 100).toFixed(1)}%)`).join(', ') || 'none'}.`
630
- },
631
- {
632
- heading: "Probability Distribution",
633
- content: `The bar chart shows top token candidates. Higher bars mean higher probability. The model samples from this distribution based on temperature (${config.temperature}). Lower temperature = more deterministic, higher = more creative.`
634
- },
635
- {
636
- heading: "Attention Patterns",
637
- content: `The heatmap shows which previous tokens the model is "looking at" to generate the current token. Brighter cells indicate stronger attention. This reveals context dependencies in code generation.`
638
- },
639
- {
640
- heading: "Generation Statistics",
641
- content: `Average confidence: ${(avgConfidence * 100).toFixed(1)}%. Total tokens: ${totalSteps}. Speed: ${generationSpeed}ms per token. Model considers context, syntax, and learned patterns to generate coherent code.`
642
- }
643
- ]
644
- };
645
- };
646
-
647
- const explanation = generateExplanation();
648
-
649
- const exportGeneration = () => {
650
- const data = {
651
- config,
652
- steps: generationSteps,
653
- timestamp: Date.now()
654
- };
655
-
656
- const dataStr = JSON.stringify(data, null, 2);
657
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
658
-
659
- const link = document.createElement('a');
660
- link.href = dataUri;
661
- link.download = `code_generation_${Date.now()}.json`;
662
- link.click();
663
- };
664
-
665
- return (
666
- <div className="bg-gray-900 rounded-xl p-6">
667
- {/* Header */}
668
- <div className="flex items-center justify-between mb-6">
669
- <div>
670
- <h2 className="text-2xl font-bold flex items-center gap-2">
671
- <Code2 className="w-6 h-6 text-blue-400" />
672
- Code Generation Tracker
673
- </h2>
674
- <p className="text-gray-400 mt-1">
675
- Visualize step-by-step how the model generates code
676
- </p>
677
- </div>
678
-
679
- <div className="flex items-center gap-4">
680
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
681
- isServiceConnected ? 'bg-green-900/30 text-green-400' : 'bg-yellow-900/30 text-yellow-400'
682
- }`}>
683
- <Activity className={`w-4 h-4 ${isServiceConnected ? 'animate-pulse' : ''}`} />
684
- {isServiceConnected ? 'Real Model' : 'Demo Mode'}
685
- </div>
686
- <button
687
- onClick={exportGeneration}
688
- className="px-3 py-1.5 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2"
689
- >
690
- <Download className="w-4 h-4" />
691
- Export
692
- </button>
693
- </div>
694
- </div>
695
-
696
- {/* Configuration Panel */}
697
- <div className="bg-gray-800 rounded-lg p-4 mb-6">
698
- <div className="flex items-center gap-2 mb-4">
699
- <Settings className="w-5 h-5 text-gray-400" />
700
- <span className="font-semibold">Generation Settings</span>
701
- </div>
702
-
703
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
704
- <div>
705
- <label className="block text-sm text-gray-400 mb-2">
706
- Starting Prompt (model will continue from here)
707
- </label>
708
- <textarea
709
- value={config.prompt}
710
- onChange={(e) => setConfig(prev => ({ ...prev, prompt: e.target.value }))}
711
- className="w-full h-24 px-3 py-2 bg-gray-900 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
712
- placeholder="Enter your starting code..."
713
- />
714
- </div>
715
-
716
- <div className="space-y-3">
717
- <div className="flex items-center justify-between">
718
- <label className="text-sm text-gray-400">Temperature: {config.temperature}</label>
719
- <input
720
- type="range"
721
- min="0.1"
722
- max="2.0"
723
- step="0.1"
724
- value={config.temperature}
725
- onChange={(e) => setConfig(prev => ({ ...prev, temperature: parseFloat(e.target.value) }))}
726
- className="w-32"
727
- />
728
- </div>
729
-
730
- <div className="flex items-center justify-between">
731
- <label className="text-sm text-gray-400">Top-K: {config.topK}</label>
732
- <input
733
- type="range"
734
- min="1"
735
- max="100"
736
- step="1"
737
- value={config.topK}
738
- onChange={(e) => setConfig(prev => ({ ...prev, topK: parseInt(e.target.value) }))}
739
- className="w-32"
740
- />
741
- </div>
742
-
743
- <div className="flex items-center justify-between">
744
- <label className="text-sm text-gray-400">Top-P: {config.topP}</label>
745
- <input
746
- type="range"
747
- min="0.1"
748
- max="1.0"
749
- step="0.05"
750
- value={config.topP}
751
- onChange={(e) => setConfig(prev => ({ ...prev, topP: parseFloat(e.target.value) }))}
752
- className="w-32"
753
- />
754
- </div>
755
- </div>
756
- </div>
757
-
758
- <button
759
- onClick={startGeneration}
760
- disabled={isGenerating}
761
- className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
762
- >
763
- {isGenerating ? (
764
- <>
765
- <Activity className="w-4 h-4 animate-spin" />
766
- Generating...
767
- </>
768
- ) : (
769
- <>
770
- <Sparkles className="w-4 h-4" />
771
- Start Generation
772
- </>
773
- )}
774
- </button>
775
- </div>
776
-
777
- {/* Main Content Area with Side Panel */}
778
- <div className="flex gap-4">
779
- {/* Main Visualization Container */}
780
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
781
- {/* Generated Code Display */}
782
- {generationSteps.length > 0 && (
783
- <div className="bg-gray-800 rounded-lg p-4 mb-6 relative">
784
- {/* Help Toggle Button */}
785
- <button
786
- onClick={() => setShowExplanation(!showExplanation)}
787
- className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
788
- >
789
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
790
- <span className="text-sm font-medium">
791
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
792
- </span>
793
- </button>
794
- <h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
795
- <Code2 className="w-5 h-5 text-green-400" />
796
- Generated Code
797
- </h3>
798
- <pre className="text-sm font-mono bg-gray-900 p-4 rounded-lg overflow-x-auto whitespace-pre-wrap">
799
- <code className="text-gray-300">
800
- {(currentStep >= 0 && currentStep < generationSteps.length && generationSteps[currentStep]?.cumulativeText)
801
- ? generationSteps[currentStep].cumulativeText
802
- : config.prompt}
803
- {isGenerating && <span className="animate-pulse text-blue-400">█</span>}
804
- </code>
805
- </pre>
806
-
807
- {/* Token-by-token display */}
808
- <div className="mt-4 flex flex-wrap gap-2">
809
- {generationSteps.slice(0, currentStep + 1).map((step, idx) => (
810
- step ? (
811
- <span
812
- key={idx}
813
- className={`px-2 py-1 rounded text-xs font-mono cursor-pointer transition-all ${
814
- idx === currentStep
815
- ? 'bg-blue-600 text-white ring-2 ring-blue-400'
816
- : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
817
- }`}
818
- onClick={() => setCurrentStep(idx)}
819
- title={`Step ${idx + 1}: ${((step.probability || 0) * 100).toFixed(1)}% confidence`}
820
- >
821
- {step.generatedToken || ''}
822
- </span>
823
- ) : null
824
- ))}
825
- </div>
826
- </div>
827
- )}
828
-
829
- {/* Playback Controls */}
830
- {generationSteps.length > 0 && (
831
- <div className="flex items-center gap-4 mb-6">
832
- <button
833
- onClick={togglePlayback}
834
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
835
- >
836
- {isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
837
- </button>
838
-
839
- <button
840
- onClick={reset}
841
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
842
- >
843
- <RotateCcw className="w-4 h-4" />
844
- </button>
845
-
846
- <input
847
- type="range"
848
- min="0"
849
- max={Math.max(0, generationSteps.length - 1)}
850
- value={Math.min(currentStep, generationSteps.length - 1)}
851
- onChange={(e) => setCurrentStep(parseInt(e.target.value))}
852
- className="flex-1"
853
- />
854
-
855
- <span className="text-sm text-gray-400">
856
- Step {Math.min(currentStep + 1, generationSteps.length)} / {generationSteps.length}
857
- </span>
858
-
859
- <div className="flex items-center gap-2">
860
- <label className="text-sm text-gray-400">Speed:</label>
861
- <select
862
- value={generationSpeed}
863
- onChange={(e) => setGenerationSpeed(parseInt(e.target.value))}
864
- className="px-2 py-1 bg-gray-800 text-white rounded border border-gray-700"
865
- >
866
- <option value="100">Fast</option>
867
- <option value="500">Normal</option>
868
- <option value="1000">Slow</option>
869
- </select>
870
- </div>
871
- </div>
872
- )}
873
-
874
- {/* Visualization Panels */}
875
- {generationSteps.length > 0 && (
876
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
877
- {/* Probability Distribution */}
878
- <div className="bg-gray-800 rounded-lg p-4">
879
- <div className="flex items-center justify-between mb-3">
880
- <h3 className="text-lg font-semibold flex items-center gap-2">
881
- <BarChart3 className="w-5 h-5 text-purple-400" />
882
- Token Probabilities
883
- </h3>
884
- <label className="flex items-center gap-2 text-sm">
885
- <input
886
- type="checkbox"
887
- checked={showProbabilities}
888
- onChange={(e) => setShowProbabilities(e.target.checked)}
889
- className="rounded"
890
- />
891
- Show
892
- </label>
893
- </div>
894
- {showProbabilities && (
895
- <svg ref={probabilityRef}></svg>
896
- )}
897
- </div>
898
-
899
- {/* Attention Visualization */}
900
- <div className="bg-gray-800 rounded-lg p-4">
901
- <div className="flex items-center justify-between mb-3">
902
- <h3 className="text-lg font-semibold flex items-center gap-2">
903
- <Eye className="w-5 h-5 text-yellow-400" />
904
- Attention Pattern
905
- </h3>
906
- <label className="flex items-center gap-2 text-sm">
907
- <input
908
- type="checkbox"
909
- checked={showAttention}
910
- onChange={(e) => setShowAttention(e.target.checked)}
911
- className="rounded"
912
- />
913
- Show
914
- </label>
915
- </div>
916
- {showAttention && (
917
- <svg ref={attentionRef}></svg>
918
- )}
919
- </div>
920
- </div>
921
- )}
922
-
923
- {/* Statistics Panel */}
924
- {generationSteps.length > 0 && (
925
- <div className="mt-6 bg-gray-800 rounded-lg p-4">
926
- <h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
927
- <TrendingUp className="w-5 h-5 text-green-400" />
928
- Generation Statistics
929
- </h3>
930
-
931
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
932
- <div className="bg-gray-900 rounded-lg p-3">
933
- <div className="text-xs text-gray-400">Tokens Generated</div>
934
- <div className="text-xl font-bold text-white">{generationSteps.length}</div>
935
- </div>
936
-
937
- <div className="bg-gray-900 rounded-lg p-3">
938
- <div className="text-xs text-gray-400">Avg Confidence</div>
939
- <div className="text-xl font-bold text-blue-400">
940
- {generationSteps.length > 0
941
- ? (generationSteps.reduce((sum, s) => sum + (s?.confidence || 0), 0) / generationSteps.length * 100).toFixed(1) + '%'
942
- : '0%'}
943
- </div>
944
- </div>
945
-
946
- <div className="bg-gray-900 rounded-lg p-3">
947
- <div className="text-xs text-gray-400">Avg Probability</div>
948
- <div className="text-xl font-bold text-purple-400">
949
- {generationSteps.length > 0
950
- ? (generationSteps.reduce((sum, s) => sum + (s?.probability || 0), 0) / generationSteps.length * 100).toFixed(1) + '%'
951
- : '0%'}
952
- </div>
953
- </div>
954
-
955
- <div className="bg-gray-900 rounded-lg p-3">
956
- <div className="text-xs text-gray-400">Temperature</div>
957
- <div className="text-xl font-bold text-yellow-400">{config.temperature}</div>
958
- </div>
959
- </div>
960
- </div>
961
- )}
962
-
963
- {/* Empty State */}
964
- {generationSteps.length === 0 && !isGenerating && (
965
- <div className="bg-gray-800 rounded-lg p-8 text-center">
966
- <Code2 className="w-12 h-12 mx-auto mb-4 text-gray-600" />
967
- <p className="text-gray-400 mb-2">No generation yet</p>
968
- <p className="text-sm text-gray-500">
969
- Configure your prompt and settings, then click &quot;Start Generation&quot; to visualize the generation process
970
- </p>
971
- </div>
972
- )}
973
- </div>
974
-
975
- {/* Explanation Side Panel */}
976
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
977
- <div className="w-96 h-[800px] bg-gray-900 rounded-lg border border-gray-700">
978
- {/* Panel Header */}
979
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
980
- <div className="flex items-center gap-2">
981
- <Info className="w-5 h-5 text-blue-400" />
982
- <h3 className="text-lg font-semibold text-white">Understanding Code Generation</h3>
983
- </div>
984
- </div>
985
-
986
- {/* Panel Content */}
987
- <div className="px-4 py-4 overflow-y-auto h-[calc(800px-60px)]">
988
- {/* Main Description */}
989
- <div className="mb-4 p-3 bg-green-900/20 border border-green-800 rounded-lg">
990
- <h4 className="text-sm font-semibold text-green-400 mb-1">{explanation.title}</h4>
991
- <p className="text-xs text-gray-300">{explanation.description}</p>
992
- </div>
993
-
994
- {/* Explanation Sections */}
995
- <div className="space-y-3">
996
- {explanation.details.map((section, idx) => (
997
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
998
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
999
- <Zap className="w-3 h-3 text-yellow-400" />
1000
- {section.heading}
1001
- </h5>
1002
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
1003
- </div>
1004
- ))}
1005
- </div>
1006
-
1007
- {/* Visual Guide */}
1008
- <div className="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
1009
- <h4 className="font-medium text-sm text-blue-400 mb-2">Interface Guide</h4>
1010
- <div className="space-y-2 text-xs">
1011
- <div className="flex items-start gap-2">
1012
- <span className="text-blue-300">•</span>
1013
- <span className="text-gray-300">Top panel: Generated code building up</span>
1014
- </div>
1015
- <div className="flex items-start gap-2">
1016
- <span className="text-blue-300">•</span>
1017
- <span className="text-gray-300">Left chart: Token probability distribution</span>
1018
- </div>
1019
- <div className="flex items-start gap-2">
1020
- <span className="text-blue-300">•</span>
1021
- <span className="text-gray-300">Right heatmap: Attention patterns</span>
1022
- </div>
1023
- <div className="flex items-start gap-2">
1024
- <span className="text-blue-300">•</span>
1025
- <span className="text-gray-300">Bottom: Generation statistics</span>
1026
- </div>
1027
- </div>
1028
- </div>
1029
-
1030
- {/* Current Metrics */}
1031
- {generationSteps.length > 0 && (
1032
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
1033
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Metrics</h4>
1034
- <div className="space-y-1 text-xs">
1035
- <div className="flex justify-between">
1036
- <span className="text-gray-400">Step:</span>
1037
- <span className="text-white">{currentStep + 1} / {generationSteps.length}</span>
1038
- </div>
1039
- <div className="flex justify-between">
1040
- <span className="text-gray-400">Current Token:</span>
1041
- <span className="text-yellow-400 font-mono">&quot;{generationSteps[currentStep]?.generatedToken || ''}&quot;</span>
1042
- </div>
1043
- <div className="flex justify-between">
1044
- <span className="text-gray-400">Probability:</span>
1045
- <span className="text-green-400">{((generationSteps[currentStep]?.probability || 0) * 100).toFixed(1)}%</span>
1046
- </div>
1047
- <div className="flex justify-between">
1048
- <span className="text-gray-400">Temperature:</span>
1049
- <span className="text-orange-400">{config.temperature}</span>
1050
- </div>
1051
- </div>
1052
- </div>
1053
- )}
1054
-
1055
- {/* Tips */}
1056
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
1057
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
1058
- <ul className="text-xs text-gray-400 space-y-1">
1059
- <li>• Use playback controls to step through generation</li>
1060
- <li>• Lower temperature for more predictable code</li>
1061
- <li>• Watch attention to see context dependencies</li>
1062
- <li>• Export data for detailed analysis</li>
1063
- </ul>
1064
- </div>
1065
- </div>
1066
- </div>
1067
- </div>
1068
- </div>
1069
- </div>
1070
- );
1071
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/ConfidenceMeter.tsx DELETED
@@ -1,834 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect, useRef } from "react";
4
- import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, AreaChart } from "recharts";
5
- import { AlertCircle, TrendingUp, Shield, Activity, HelpCircle, X, Info, Zap, RefreshCw, CheckCircle } from "lucide-react";
6
- import { getApiUrl, getWsUrl } from "@/lib/config";
7
-
8
- interface ConfidenceTrace {
9
- token: string;
10
- index: number;
11
- confidence: number;
12
- activation: number;
13
- entropy: number;
14
- hallucination_risk?: number;
15
- }
16
-
17
- interface LayerActivation {
18
- layer: string;
19
- value: number;
20
- }
21
-
22
- export default function ConfidenceMeter() {
23
- const [selectedToken, setSelectedToken] = useState(0);
24
- const [showExplanation, setShowExplanation] = useState(false);
25
- const [isGenerating, setIsGenerating] = useState(false);
26
- const [prompt, setPrompt] = useState("def fibonacci(n):\n '''Calculate fibonacci number'''");
27
- const [generatedText, setGeneratedText] = useState("");
28
- const [isConnected, setIsConnected] = useState(false);
29
-
30
- // Real data from model
31
- const [tokens, setTokens] = useState<string[]>([]);
32
- const [confidenceData, setConfidenceData] = useState<ConfidenceTrace[]>([]);
33
- const [layerActivations, setLayerActivations] = useState<LayerActivation[]>([]);
34
- const [overallConfidence, setOverallConfidence] = useState(0);
35
- const [hallucinationRisk, setHallucinationRisk] = useState(0);
36
-
37
- const wsRef = useRef<WebSocket | null>(null);
38
- const tokenBufferRef = useRef<string[]>([]);
39
- const confidenceBufferRef = useRef<ConfidenceTrace[]>([]);
40
-
41
- // Connect to WebSocket for real-time updates
42
- useEffect(() => {
43
- let mounted = true;
44
- let reconnectTimeout: NodeJS.Timeout;
45
-
46
- const connectWS = () => {
47
- if (!mounted) return;
48
-
49
- try {
50
- const ws = new WebSocket(getWsUrl());
51
-
52
- ws.onopen = () => {
53
- if (!mounted) return;
54
- console.log('ConfidenceMeter: WebSocket connected');
55
- setIsConnected(true);
56
- };
57
-
58
- ws.onmessage = (event) => {
59
- if (!mounted) return;
60
-
61
- let data;
62
- try {
63
- data = JSON.parse(event.data);
64
- } catch (e) {
65
- // Skip non-JSON messages
66
- return;
67
- }
68
-
69
- try {
70
- if (data.type === 'generated_token') {
71
- // Add token to the display as it's generated
72
- const newToken = data.token;
73
- setTokens(prev => [...prev, newToken]);
74
-
75
- // Add confidence data for this token
76
- const tokenConfidence: ConfidenceTrace = {
77
- token: newToken === "\n" ? "⏎" : newToken,
78
- index: tokens.length,
79
- confidence: Math.min(1, Math.max(0, data.confidence_score || 0.75)),
80
- activation: Math.min(1, Math.max(0, 0.5 + Math.random() * 0.3)),
81
- entropy: Math.min(1, Math.max(0, Math.random() * 0.3)),
82
- hallucination_risk: Math.min(1, Math.max(0, data.hallucination_risk || 0.1))
83
- };
84
- setConfidenceData(prev => [...prev, tokenConfidence]);
85
-
86
- } else if (data.type === 'token') {
87
- // Token with confidence score - also update overall metrics
88
- const confidence = data.confidence_score || 0.75;
89
-
90
- // Update the last token's confidence
91
- setConfidenceData(prev => {
92
- if (prev.length > 0) {
93
- const updated = [...prev];
94
- // Create a new object instead of modifying the existing one
95
- updated[updated.length - 1] = {
96
- ...updated[updated.length - 1],
97
- confidence: confidence
98
- };
99
-
100
- // Calculate running average confidence
101
- const avgConfidence = updated.reduce((sum, d) => sum + d.confidence, 0) / updated.length;
102
- setOverallConfidence(avgConfidence);
103
-
104
- // Update hallucination risk based on low confidence tokens
105
- const lowConfTokens = updated.filter(d => d.confidence < 0.6).length;
106
- const riskLevel = lowConfTokens / updated.length;
107
- setHallucinationRisk(Math.min(1, riskLevel * 2)); // Scale up for visibility
108
-
109
- return updated;
110
- }
111
- return prev;
112
- });
113
-
114
- } else if (data.type === 'confidence') {
115
- // Update overall confidence metrics
116
- setOverallConfidence(data.confidence_score || 0);
117
- setHallucinationRisk(data.hallucination_risk || 0);
118
-
119
- } else if (data.type === 'activation') {
120
- // Update layer activations with debugging
121
- const layer = data.layer?.replace('layer.', 'L') || 'L0';
122
- const value = Math.min(1, Math.max(0, data.mean || 0.5)); // Clamp between 0 and 1
123
-
124
- console.log(`Received activation for ${layer}: ${value.toFixed(3)}`);
125
-
126
- setLayerActivations(prev => {
127
- // Create a completely new array with new objects
128
- const newActivations = prev.map(a => ({...a}));
129
- const existing = newActivations.findIndex(l => l.layer === layer);
130
-
131
- if (existing >= 0) {
132
- // Update existing layer with a new object
133
- newActivations[existing] = { layer, value };
134
- } else {
135
- // Add new layer
136
- newActivations.push({ layer, value });
137
- }
138
-
139
- // Sort and keep only first 8
140
- const sorted = newActivations.sort((a, b) => {
141
- const aNum = parseInt(a.layer.replace('L', '')) || 0;
142
- const bNum = parseInt(b.layer.replace('L', '')) || 0;
143
- return aNum - bNum;
144
- }).slice(0, 8);
145
-
146
- console.log('Updated layer activations:', sorted.map(l => `${l.layer}:${(l.value*100).toFixed(0)}%`).join(', '));
147
- return sorted;
148
- });
149
- }
150
- } catch (e) {
151
- console.log('Failed to parse WebSocket message:', e);
152
- }
153
- };
154
-
155
- ws.onerror = () => {
156
- // WebSocket errors don't provide useful information in the browser
157
- // Just mark as disconnected, onclose will handle reconnection
158
- if (mounted) {
159
- setIsConnected(false);
160
- }
161
- };
162
-
163
- ws.onclose = () => {
164
- if (!mounted) return;
165
- console.log('ConfidenceMeter: WebSocket disconnected, will reconnect...');
166
- setIsConnected(false);
167
- // Reconnect after 3 seconds if component is still mounted
168
- reconnectTimeout = setTimeout(() => {
169
- if (mounted) connectWS();
170
- }, 3000);
171
- };
172
-
173
- wsRef.current = ws;
174
- } catch (error) {
175
- console.log('WebSocket connection attempt failed, will retry...');
176
- if (mounted) {
177
- setIsConnected(false);
178
- reconnectTimeout = setTimeout(() => {
179
- if (mounted) connectWS();
180
- }, 3000);
181
- }
182
- }
183
- };
184
-
185
- connectWS();
186
-
187
- return () => {
188
- mounted = false;
189
- if (reconnectTimeout) {
190
- clearTimeout(reconnectTimeout);
191
- }
192
- if (wsRef.current) {
193
- wsRef.current.close();
194
- }
195
- };
196
- }, []);
197
-
198
- // Listen for immediate demo selection from LocalControlPanel
199
- useEffect(() => {
200
- const handleDemoPromptSelected = (event: CustomEvent) => {
201
- const { prompt, demoId } = event.detail;
202
- console.log('ConfidenceMeter: Demo prompt selected -', demoId);
203
-
204
- if (prompt) {
205
- setPrompt(prompt);
206
- // Don't clear tokens here - wait for demo-starting event
207
- }
208
- };
209
-
210
- window.addEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
211
- return () => window.removeEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
212
- }, []);
213
-
214
- // Listen for demo starting event to clear tokens
215
- useEffect(() => {
216
- const handleDemoStarting = (event: CustomEvent) => {
217
- const { demoId } = event.detail;
218
- console.log('ConfidenceMeter: Demo starting, clearing tokens -', demoId);
219
-
220
- // Clear tokens when demo actually starts generating
221
- setTokens([]);
222
- setConfidenceData([]);
223
- setGeneratedText("");
224
- setSelectedToken(0);
225
- };
226
-
227
- window.addEventListener('demo-starting', handleDemoStarting as EventListener);
228
- return () => window.removeEventListener('demo-starting', handleDemoStarting as EventListener);
229
- }, []);
230
-
231
- // Listen for demo completion events from LocalControlPanel
232
- useEffect(() => {
233
- const handleDemoCompleted = (event: CustomEvent) => {
234
- const data = event.detail;
235
- console.log('ConfidenceMeter: Demo completed', data);
236
-
237
- // Don't overwrite the streaming data!
238
- // We already have the tokens and confidence from streaming
239
- // Just update the final generated text and overall metrics
240
-
241
- if (data && data.generated_text) {
242
- setGeneratedText(data.generated_text);
243
-
244
- // Only update overall metrics from the completion data
245
- if (data.confidence) {
246
- setOverallConfidence(data.confidence);
247
- }
248
- if (data.hallucination_risk) {
249
- setHallucinationRisk(data.hallucination_risk);
250
- }
251
-
252
- // Update layer activations if provided
253
- if (data.traces) {
254
- interface TraceData {
255
- type: string;
256
- mean?: number;
257
- }
258
- const activationTraces = data.traces.filter((t: TraceData) => t.type === 'activation');
259
- if (activationTraces.length > 0) {
260
- const layers = activationTraces.slice(0, 8).map((t: TraceData, idx: number) => ({
261
- layer: `L${idx}`,
262
- value: Math.min(1, Math.max(0, t.mean || 0.5))
263
- }));
264
- setLayerActivations(layers);
265
- }
266
- }
267
- }
268
- };
269
-
270
- window.addEventListener('demo-completed', handleDemoCompleted as EventListener);
271
- return () => window.removeEventListener('demo-completed', handleDemoCompleted as EventListener);
272
- }, []);
273
-
274
- // Generate with the model
275
- const generateWithConfidence = async () => {
276
- setIsGenerating(true);
277
- tokenBufferRef.current = [];
278
- confidenceBufferRef.current = [];
279
-
280
- // Clear existing tokens to show streaming
281
- setTokens([]);
282
- setConfidenceData([]);
283
-
284
- try {
285
- const response = await fetch(`${getApiUrl()}/generate`, {
286
- method: 'POST',
287
- headers: { 'Content-Type': 'application/json' },
288
- body: JSON.stringify({
289
- prompt,
290
- max_tokens: 50,
291
- temperature: 0.7,
292
- extract_traces: true,
293
- sampling_rate: 0.3
294
- })
295
- });
296
-
297
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
298
- const data = await response.json();
299
-
300
- // Process the response
301
- const text = data.generated_text;
302
- const newTokens = text.split(/(\s+|[(){}[\].,;:!?])/g).filter((t: string) => t.trim());
303
- setTokens(newTokens);
304
- setGeneratedText(text);
305
-
306
- // Extract confidence metrics
307
- interface TraceData {
308
- type: string;
309
- entropy?: number;
310
- mean?: number;
311
- }
312
- const confidenceTraces = data.traces?.filter((t: TraceData) => t.type === 'confidence') || [];
313
- const activationTraces = data.traces?.filter((t: TraceData) => t.type === 'activation') || [];
314
-
315
- // Create confidence data
316
- const newConfidenceData: ConfidenceTrace[] = newTokens.map((token: string, idx: number) => ({
317
- token: token === "\n" ? "⏎" : token,
318
- index: idx,
319
- confidence: data.confidence || 0.75,
320
- activation: 0.5 + Math.random() * 0.3,
321
- entropy: confidenceTraces[0]?.entropy || Math.random() * 0.5,
322
- hallucination_risk: data.hallucination_risk || 0.1
323
- }));
324
-
325
- setConfidenceData(newConfidenceData);
326
- setOverallConfidence(data.confidence || 0.75);
327
- setHallucinationRisk(data.hallucination_risk || 0.1);
328
-
329
- // Update layer activations
330
- if (activationTraces.length > 0) {
331
- const layers = activationTraces.slice(0, 8).map((t: TraceData, idx: number) => ({
332
- layer: `L${idx}`,
333
- value: Math.min(1, Math.max(0, t.mean || 0.5))
334
- }));
335
- setLayerActivations(layers);
336
- }
337
-
338
- } catch (error) {
339
- console.error('Generation error:', error);
340
- alert(`Failed to generate: ${error}`);
341
- } finally {
342
- setIsGenerating(false);
343
- }
344
- };
345
-
346
- // Initialize with empty data - no mock data
347
- useEffect(() => {
348
- // Only set mock layer activations if we have none
349
- if (layerActivations.length === 0) {
350
- const mockLayers = Array.from({ length: 8 }, (_, i) => ({
351
- layer: `L${i}`,
352
- value: 0.5 + Math.random() * 0.5
353
- }));
354
- setLayerActivations(mockLayers);
355
- }
356
- }, [layerActivations.length]);
357
-
358
- const getConfidenceColor = (confidence: number) => {
359
- if (confidence > 0.8) return "text-green-400";
360
- if (confidence > 0.6) return "text-yellow-400";
361
- return "text-red-400";
362
- };
363
-
364
- const getConfidenceLabel = (confidence: number) => {
365
- if (confidence > 0.8) return "High Confidence";
366
- if (confidence > 0.6) return "Medium Confidence";
367
- return "Low Confidence - Potential Hallucination";
368
- };
369
-
370
- // Generate contextual explanation for current visualization
371
- const generateExplanation = () => {
372
- const currentConfidence = confidenceData[selectedToken]?.confidence || 0;
373
- const avgConfidence = confidenceData.length > 0
374
- ? confidenceData.reduce((sum, d) => sum + d.confidence, 0) / confidenceData.length
375
- : 0;
376
- const lowConfidenceTokens = confidenceData.filter(d => d.confidence < 0.6).length;
377
-
378
- return {
379
- title: "Real-time Confidence Tracking",
380
- description: "Live monitoring of model confidence as tokens are generated, with dynamic risk assessment and hallucination detection.",
381
- details: [
382
- {
383
- heading: "What is Confidence Tracking?",
384
- content: `This visualization shows real-time confidence scores for each token as it's generated. The blue line represents confidence (0-100%), while the purple line shows activation strength. Tokens appear progressively as the model generates them.`
385
- },
386
- {
387
- heading: "Live Metrics",
388
- content: `Overall Confidence: Running average updated with each token. Hallucination Risk: Percentage of low-confidence tokens (<60%). Both metrics update in real-time during generation. The chart uses a fixed 0-100% scale for consistency.`
389
- },
390
- {
391
- heading: "Current Token Analysis",
392
- content: tokens[selectedToken]
393
- ? `Token "${tokens[selectedToken]}" has ${(currentConfidence * 100).toFixed(0)}% confidence (${getConfidenceLabel(currentConfidence)}). Average confidence: ${(avgConfidence * 100).toFixed(0)}%. ${lowConfidenceTokens} tokens below safety threshold.`
394
- : `Click on a token above to see its confidence score. Overall average: ${(avgConfidence * 100).toFixed(0)}%.`
395
- },
396
- {
397
- heading: "Dynamic Alerts",
398
- content: `The alert box changes color and message based on real-time analysis: Red (high risk), Yellow (dropping confidence), Green (high confidence), Blue (normal). It shows specific problematic tokens when detected.`
399
- },
400
- {
401
- heading: "Token Streaming",
402
- content: `Tokens appear one-by-one as generated, not all at once. Each token's confidence is calculated from the model's output probability. The visualization updates immediately as new tokens arrive via WebSocket.`
403
- },
404
- {
405
- heading: "Hallucination Detection",
406
- content: `Real-time detection based on: Running confidence average, percentage of low-confidence tokens, sudden drops in recent tokens. Risk level updates continuously and triggers appropriate alerts.`
407
- }
408
- ]
409
- };
410
- };
411
-
412
- const explanation = generateExplanation();
413
-
414
- return (
415
- <div className="bg-gray-900 rounded-xl p-6">
416
- <div className="flex items-center justify-between mb-6">
417
- <div>
418
- <h2 className="text-2xl font-bold mb-2">Confidence Meter</h2>
419
- <p className="text-gray-400">
420
- Track model confidence and activation patterns to detect potential hallucinations
421
- </p>
422
- </div>
423
-
424
- <div className="flex items-center gap-4">
425
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
426
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
427
- }`}>
428
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
429
- {isConnected ? 'Connected' : 'Disconnected'}
430
- </div>
431
- </div>
432
- </div>
433
-
434
- {/* Generation Controls */}
435
- <div className="mb-6">
436
- <div className="flex gap-4">
437
- <input
438
- type="text"
439
- value={prompt}
440
- onChange={(e) => setPrompt(e.target.value)}
441
- className="flex-1 px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
442
- placeholder="Enter prompt to analyze confidence..."
443
- />
444
- <button
445
- onClick={generateWithConfidence}
446
- disabled={isGenerating || !isConnected}
447
- className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
448
- >
449
- {isGenerating ? (
450
- <>
451
- <RefreshCw className="w-4 h-4 animate-spin" />
452
- Analyzing...
453
- </>
454
- ) : (
455
- <>
456
- Generate & Track
457
- <Zap className="w-4 h-4" />
458
- </>
459
- )}
460
- </button>
461
- </div>
462
-
463
- {/* Overall Metrics */}
464
- {overallConfidence > 0 && (
465
- <div className="mt-4 grid grid-cols-2 gap-4">
466
- <div className="bg-gray-800 rounded-lg p-3">
467
- <div className="text-xs text-gray-400 mb-1">Overall Confidence</div>
468
- <div className={`text-2xl font-bold ${getConfidenceColor(overallConfidence)}`}>
469
- {(overallConfidence * 100).toFixed(1)}%
470
- </div>
471
- </div>
472
- <div className="bg-gray-800 rounded-lg p-3">
473
- <div className="text-xs text-gray-400 mb-1">Hallucination Risk</div>
474
- <div className={`text-2xl font-bold ${
475
- hallucinationRisk > 0.5 ? 'text-red-400' : hallucinationRisk > 0.3 ? 'text-yellow-400' : 'text-green-400'
476
- }`}>
477
- {(hallucinationRisk * 100).toFixed(1)}%
478
- </div>
479
- </div>
480
- </div>
481
- )}
482
- </div>
483
-
484
- {/* Main Content Area with Side Panel */}
485
- <div className="flex gap-4">
486
- {/* Main Visualization Container */}
487
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
488
- <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
489
- <div className="lg:col-span-2">
490
- <div className="bg-gray-800 rounded-lg p-4 relative">
491
- {/* Help Toggle Button */}
492
- <button
493
- onClick={() => setShowExplanation(!showExplanation)}
494
- className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
495
- >
496
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
497
- <span className="text-sm font-medium">
498
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
499
- </span>
500
- </button>
501
-
502
- <h3 className="text-lg font-semibold mb-4">Token-Level Confidence</h3>
503
-
504
- <div className="mb-4 p-3 bg-black rounded-lg font-mono text-sm">
505
- <div className="flex flex-wrap gap-1">
506
- {tokens.map((token, idx) => (
507
- <button
508
- key={idx}
509
- onClick={() => setSelectedToken(idx)}
510
- className={`px-2 py-1 rounded transition-all ${
511
- idx === selectedToken
512
- ? "bg-blue-600 text-white"
513
- : "bg-gray-700 text-gray-300 hover:bg-gray-600"
514
- }`}
515
- >
516
- {token === "\\n" ? "⏎" : token}
517
- </button>
518
- ))}
519
- </div>
520
- </div>
521
-
522
- <div className="h-64">
523
- <ResponsiveContainer width="100%" height="100%">
524
- <AreaChart data={confidenceData} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
525
- <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
526
- <XAxis
527
- dataKey="index"
528
- stroke="#9ca3af"
529
- tick={{ fontSize: 12 }}
530
- tickFormatter={(value) => {
531
- // Show token text for specific indices, or just the index
532
- const token = confidenceData[value]?.token;
533
- return token && value % Math.ceil(confidenceData.length / 10) === 0 ? token : '';
534
- }}
535
- />
536
- <YAxis
537
- domain={[0, 1]}
538
- ticks={[0, 0.25, 0.5, 0.75, 1]}
539
- stroke="#9ca3af"
540
- tick={{ fontSize: 12 }}
541
- tickFormatter={(value) => (value * 100).toFixed(0) + '%'}
542
- />
543
- <Tooltip
544
- contentStyle={{
545
- backgroundColor: "#1f2937",
546
- border: "1px solid #374151",
547
- borderRadius: "8px"
548
- }}
549
- labelFormatter={(value) => {
550
- const token = confidenceData[value]?.token;
551
- return token ? `Token ${value}: "${token}"` : `Token ${value}`;
552
- }}
553
- formatter={(value: number) => [(value * 100).toFixed(1) + '%']}
554
- />
555
- <Area
556
- type="monotone"
557
- dataKey="confidence"
558
- stroke="#3b82f6"
559
- fill="#3b82f6"
560
- fillOpacity={0.3}
561
- strokeWidth={2}
562
- isAnimationActive={false}
563
- dot={false}
564
- />
565
- <Area
566
- type="monotone"
567
- dataKey="activation"
568
- stroke="#a855f7"
569
- fill="#a855f7"
570
- fillOpacity={0.2}
571
- strokeWidth={2}
572
- isAnimationActive={false}
573
- dot={false}
574
- />
575
- </AreaChart>
576
- </ResponsiveContainer>
577
- </div>
578
-
579
- {selectedToken !== null && confidenceData[selectedToken] && (
580
- <div className="mt-4 p-3 bg-gray-900 rounded-lg">
581
- <div className="flex items-center justify-between mb-2">
582
- <span className="text-sm text-gray-400">Selected Token:</span>
583
- <span className="font-mono font-bold">{tokens[selectedToken]}</span>
584
- </div>
585
- <div className="flex items-center justify-between mb-2">
586
- <span className="text-sm text-gray-400">Confidence:</span>
587
- <span className={`font-bold ${getConfidenceColor(confidenceData[selectedToken].confidence)}`}>
588
- {(confidenceData[selectedToken].confidence * 100).toFixed(1)}%
589
- </span>
590
- </div>
591
- <div className="flex items-center gap-2 mt-3">
592
- <AlertCircle className="w-4 h-4 text-yellow-500" />
593
- <span className="text-sm text-gray-300">
594
- {getConfidenceLabel(confidenceData[selectedToken].confidence)}
595
- </span>
596
- </div>
597
- </div>
598
- )}
599
- </div>
600
- </div>
601
-
602
- <div>
603
- <div className="bg-gray-800 rounded-lg p-4 mb-4">
604
- <h3 className="text-lg font-semibold mb-4">Layer Activations</h3>
605
- <div className="space-y-2">
606
- {layerActivations.map((layer, idx) => (
607
- <div key={`${layer.layer}-${idx}-${layer.value}`} className="flex items-center gap-2">
608
- <span className="text-sm text-gray-400 w-8">{layer.layer}</span>
609
- <div className="flex-1 h-4 bg-gray-700 rounded-full overflow-hidden">
610
- <div
611
- className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-300"
612
- style={{ width: `${layer.value * 100}%` }}
613
- />
614
- </div>
615
- <span className="text-xs text-gray-400 w-12 text-right">
616
- {(layer.value * 100).toFixed(0)}%
617
- </span>
618
- </div>
619
- ))}
620
- </div>
621
- </div>
622
-
623
- <div className="bg-gray-800 rounded-lg p-4">
624
- <h3 className="text-lg font-semibold mb-4">Risk Indicators</h3>
625
- <div className="space-y-3">
626
- <div className="flex items-center justify-between p-2 bg-gray-900 rounded">
627
- <div className="flex items-center gap-2">
628
- <Shield className="w-4 h-4 text-green-500" />
629
- <span className="text-sm">API Usage</span>
630
- </div>
631
- <span className="text-sm text-green-400">Valid</span>
632
- </div>
633
- <div className="flex items-center justify-between p-2 bg-gray-900 rounded">
634
- <div className="flex items-center gap-2">
635
- <Activity className="w-4 h-4 text-yellow-500" />
636
- <span className="text-sm">Pattern Match</span>
637
- </div>
638
- <span className="text-sm text-yellow-400">78%</span>
639
- </div>
640
- <div className="flex items-center justify-between p-2 bg-gray-900 rounded">
641
- <div className="flex items-center gap-2">
642
- <TrendingUp className="w-4 h-4 text-blue-500" />
643
- <span className="text-sm">Entropy</span>
644
- </div>
645
- <span className="text-sm text-blue-400">0.34</span>
646
- </div>
647
- </div>
648
- </div>
649
- </div>
650
- </div>
651
-
652
- {/* Dynamic Alert based on real-time data */}
653
- {confidenceData.length > 0 && (() => {
654
- const recentTokens = confidenceData.slice(-10);
655
- const avgRecentConfidence = recentTokens.reduce((sum, d) => sum + d.confidence, 0) / recentTokens.length;
656
- const lowConfTokens = confidenceData.filter(d => d.confidence < 0.6);
657
- const hasLowConfidence = lowConfTokens.length > 0;
658
-
659
- if (hallucinationRisk > 0.5) {
660
- return (
661
- <div className="bg-red-900/30 border border-red-700 rounded-lg p-4">
662
- <div className="flex items-start gap-3">
663
- <AlertCircle className="w-5 h-5 text-red-500 mt-0.5" />
664
- <div>
665
- <div className="font-semibold text-red-300 mb-1">High Hallucination Risk</div>
666
- <div className="text-sm text-gray-300">
667
- {(hallucinationRisk * 100).toFixed(0)}% risk detected. Multiple low-confidence tokens found.
668
- {lowConfTokens.length > 0 && ` Tokens with confidence below 60%: ${lowConfTokens.map(t => `"${t.token}"`).slice(0, 3).join(', ')}${lowConfTokens.length > 3 ? '...' : ''}`}
669
- </div>
670
- </div>
671
- </div>
672
- </div>
673
- );
674
- } else if (avgRecentConfidence < 0.7) {
675
- return (
676
- <div className="bg-yellow-900/30 border border-yellow-700 rounded-lg p-4">
677
- <div className="flex items-start gap-3">
678
- <AlertCircle className="w-5 h-5 text-yellow-500 mt-0.5" />
679
- <div>
680
- <div className="font-semibold text-yellow-300 mb-1">Confidence Dropping</div>
681
- <div className="text-sm text-gray-300">
682
- Recent average confidence: {(avgRecentConfidence * 100).toFixed(0)}%.
683
- Model may be uncertain about current generation. Consider reviewing output.
684
- </div>
685
- </div>
686
- </div>
687
- </div>
688
- );
689
- } else if (overallConfidence > 0.85) {
690
- return (
691
- <div className="bg-green-900/30 border border-green-700 rounded-lg p-4">
692
- <div className="flex items-start gap-3">
693
- <CheckCircle className="w-5 h-5 text-green-500 mt-0.5" />
694
- <div>
695
- <div className="font-semibold text-green-300 mb-1">High Confidence Generation</div>
696
- <div className="text-sm text-gray-300">
697
- Model confidence: {(overallConfidence * 100).toFixed(0)}%.
698
- Generated code patterns match training data well.
699
- </div>
700
- </div>
701
- </div>
702
- </div>
703
- );
704
- } else {
705
- return (
706
- <div className="bg-blue-900/30 border border-blue-700 rounded-lg p-4">
707
- <div className="flex items-start gap-3">
708
- <Activity className="w-5 h-5 text-blue-500 mt-0.5" />
709
- <div>
710
- <div className="font-semibold text-blue-300 mb-1">Normal Generation</div>
711
- <div className="text-sm text-gray-300">
712
- Model confidence: {(overallConfidence * 100).toFixed(0)}%.
713
- Generation proceeding normally with typical confidence levels.
714
- </div>
715
- </div>
716
- </div>
717
- </div>
718
- );
719
- }
720
- })()}
721
- {confidenceData.length === 0 && (
722
- <div className="bg-gray-800 border border-gray-700 rounded-lg p-4">
723
- <div className="flex items-start gap-3">
724
- <Activity className="w-5 h-5 text-gray-500 mt-0.5" />
725
- <div>
726
- <div className="font-semibold text-gray-300 mb-1">Ready to Generate</div>
727
- <div className="text-sm text-gray-400">
728
- Enter a prompt or select a demo to begin tracking confidence and detecting potential issues.
729
- </div>
730
- </div>
731
- </div>
732
- </div>
733
- )}
734
- </div>
735
-
736
- {/* Explanation Side Panel */}
737
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
738
- <div className="w-96 h-[600px] bg-gray-900 rounded-lg border border-gray-700">
739
- {/* Panel Header */}
740
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
741
- <div className="flex items-center gap-2">
742
- <Info className="w-5 h-5 text-blue-400" />
743
- <h3 className="text-lg font-semibold text-white">Understanding Confidence Tracking</h3>
744
- </div>
745
- </div>
746
-
747
- {/* Panel Content */}
748
- <div className="px-4 py-4 overflow-y-auto h-[calc(600px-60px)]">
749
- {/* Main Description */}
750
- <div className="mb-4 p-3 bg-red-900/20 border border-red-800 rounded-lg">
751
- <h4 className="text-sm font-semibold text-red-400 mb-1">{explanation.title}</h4>
752
- <p className="text-xs text-gray-300">{explanation.description}</p>
753
- </div>
754
-
755
- {/* Explanation Sections */}
756
- <div className="space-y-3">
757
- {explanation.details.map((section, idx) => (
758
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
759
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
760
- <Zap className="w-3 h-3 text-yellow-400" />
761
- {section.heading}
762
- </h5>
763
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
764
- </div>
765
- ))}
766
- </div>
767
-
768
- {/* Visual Guide */}
769
- <div className="mt-4 p-3 bg-yellow-900/20 border border-yellow-800 rounded-lg">
770
- <h4 className="font-medium text-sm text-yellow-400 mb-2">Confidence Levels</h4>
771
- <div className="space-y-2 text-xs">
772
- <div className="flex items-start gap-2">
773
- <div className="w-3 h-3 bg-green-500 rounded mt-0.5"></div>
774
- <span className="text-gray-300">High (&gt;80%) - Reliable output</span>
775
- </div>
776
- <div className="flex items-start gap-2">
777
- <div className="w-3 h-3 bg-yellow-500 rounded mt-0.5"></div>
778
- <span className="text-gray-300">Medium (60-80%) - Caution advised</span>
779
- </div>
780
- <div className="flex items-start gap-2">
781
- <div className="w-3 h-3 bg-red-500 rounded mt-0.5"></div>
782
- <span className="text-gray-300">Low (&lt;60%) - Potential hallucination</span>
783
- </div>
784
- </div>
785
- </div>
786
-
787
- {/* Current Metrics */}
788
- {selectedToken !== null && confidenceData[selectedToken] && (
789
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
790
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Token Metrics</h4>
791
- <div className="space-y-1 text-xs">
792
- <div className="flex justify-between">
793
- <span className="text-gray-400">Token:</span>
794
- <span className="text-white font-mono">&quot;{tokens[selectedToken]}&quot;</span>
795
- </div>
796
- <div className="flex justify-between">
797
- <span className="text-gray-400">Confidence:</span>
798
- <span className={getConfidenceColor(confidenceData[selectedToken].confidence)}>
799
- {(confidenceData[selectedToken].confidence * 100).toFixed(1)}%
800
- </span>
801
- </div>
802
- <div className="flex justify-between">
803
- <span className="text-gray-400">Activation:</span>
804
- <span className="text-purple-400">
805
- {(confidenceData[selectedToken].activation * 100).toFixed(1)}%
806
- </span>
807
- </div>
808
- <div className="flex justify-between">
809
- <span className="text-gray-400">Entropy:</span>
810
- <span className="text-blue-400">
811
- {confidenceData[selectedToken].entropy.toFixed(3)}
812
- </span>
813
- </div>
814
- </div>
815
- </div>
816
- )}
817
-
818
- {/* Tips */}
819
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
820
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
821
- <ul className="text-xs text-gray-400 space-y-1">
822
- <li>• Click tokens to see detailed metrics</li>
823
- <li>• Watch for sudden confidence drops</li>
824
- <li>• High entropy + low confidence = uncertainty</li>
825
- <li>• Monitor layer activation patterns</li>
826
- </ul>
827
- </div>
828
- </div>
829
- </div>
830
- </div>
831
- </div>
832
- </div>
833
- );
834
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/DecisionPath3D.tsx DELETED
@@ -1,621 +0,0 @@
1
- /**
2
- * Decision Path 3D Visualization
3
- *
4
- * Shows the exact path through the neural network when generating tokens,
5
- * highlighting critical layers, attention patterns, and decision factors.
6
- * This is the core "Glass Box" visualization for the PhD thesis.
7
- *
8
- * @component
9
- */
10
-
11
- "use client";
12
-
13
- import { useRef, useState, useEffect, useMemo } from "react";
14
- import { Canvas, useFrame, useThree } from "@react-three/fiber";
15
- import {
16
- OrbitControls,
17
- Text,
18
- Box,
19
- Sphere,
20
- Line,
21
- Billboard
22
- } from "@react-three/drei";
23
- import * as THREE from "three";
24
- import {
25
- Brain,
26
- Zap,
27
- Eye,
28
- GitBranch,
29
- Activity,
30
- Sparkles,
31
- AlertCircle,
32
- TrendingUp,
33
- Layers
34
- } from "lucide-react";
35
-
36
- interface LayerActivation {
37
- layer_index: number;
38
- attention_weights: number[][];
39
- hidden_state_norm: number;
40
- ffn_activation: number;
41
- top_attention_heads: number[];
42
- confidence: number;
43
- }
44
-
45
- interface DecisionPath {
46
- token: string;
47
- token_id: number;
48
- probability: number;
49
- layer_activations: LayerActivation[];
50
- attention_flow: Array<{
51
- from_layer: number;
52
- to_layer: number | string;
53
- strength: number;
54
- top_heads: number[];
55
- }>;
56
- alternatives: Array<{
57
- token: string;
58
- token_id: number;
59
- probability: number;
60
- }>;
61
- decision_factors: {
62
- attention_focus: number;
63
- semantic_alignment: number;
64
- syntactic_correctness: number;
65
- context_relevance: number;
66
- confidence: number;
67
- };
68
- critical_layers: number[];
69
- confidence_score: number;
70
- timestamp: number;
71
- }
72
-
73
- // Enhanced Transformer Layer with activation visualization
74
- function EnhancedTransformerLayer({
75
- position,
76
- layerIndex,
77
- activation,
78
- isCritical,
79
- isActive
80
- }: {
81
- position: [number, number, number];
82
- layerIndex: number;
83
- activation?: LayerActivation;
84
- isCritical?: boolean;
85
- isActive?: boolean;
86
- }) {
87
- const meshRef = useRef<THREE.Mesh>(null);
88
- const glowRef = useRef<THREE.Mesh>(null);
89
-
90
- useFrame((state) => {
91
- if (meshRef.current && isCritical) {
92
- // Pulse critical layers
93
- const scale = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.1;
94
- meshRef.current.scale.set(scale, scale, scale);
95
- }
96
-
97
- if (glowRef.current && activation) {
98
- // Glow based on activation strength
99
- glowRef.current.scale.set(
100
- 1 + activation.confidence * 0.3,
101
- 1 + activation.confidence * 0.3,
102
- 1 + activation.confidence * 0.3
103
- );
104
- }
105
- });
106
-
107
- const baseColor = isCritical ? "#ff6b6b" : isActive ? "#4ecdc4" : "#2d3748";
108
- const emissiveIntensity = activation ? activation.confidence : 0;
109
-
110
- return (
111
- <group position={position}>
112
- {/* Glow effect for active layers */}
113
- {activation && (
114
- <Sphere ref={glowRef} args={[2.5, 16, 16]}>
115
- <meshBasicMaterial
116
- color={isCritical ? "#ff6b6b" : "#4ecdc4"}
117
- transparent
118
- opacity={activation.confidence * 0.3}
119
- />
120
- </Sphere>
121
- )}
122
-
123
- {/* Main layer box */}
124
- <Box ref={meshRef} args={[4, 0.3, 3]}>
125
- <meshStandardMaterial
126
- color={baseColor}
127
- emissive={baseColor}
128
- emissiveIntensity={emissiveIntensity}
129
- metalness={0.8}
130
- roughness={0.2}
131
- />
132
- </Box>
133
-
134
- {/* Layer label */}
135
- <Text
136
- position={[0, 0.3, 0]}
137
- fontSize={0.15}
138
- color="white"
139
- anchorX="center"
140
- >
141
- Layer {layerIndex}
142
- </Text>
143
-
144
- {/* Confidence indicator */}
145
- {activation && (
146
- <Text
147
- position={[0, -0.3, 0]}
148
- fontSize={0.08}
149
- color={isCritical ? "#ff6b6b" : "#4ecdc4"}
150
- anchorX="center"
151
- >
152
- Confidence: {(activation.confidence * 100).toFixed(0)}%
153
- </Text>
154
- )}
155
-
156
- {/* Attention head activation visualization */}
157
- {activation && activation.top_attention_heads.map((headIdx, i) => (
158
- <Box
159
- key={headIdx}
160
- position={[(i - 1) * 0.4, 0, 1.8]}
161
- args={[0.3, 0.15, 0.3]}
162
- >
163
- <meshStandardMaterial
164
- color="#ffd93d"
165
- emissive="#ffd93d"
166
- emissiveIntensity={0.5}
167
- />
168
- </Box>
169
- ))}
170
- </group>
171
- );
172
- }
173
-
174
- // Animated decision flow particle
175
- function DecisionParticle({
176
- path,
177
- onComplete
178
- }: {
179
- path: THREE.Vector3[];
180
- onComplete?: () => void;
181
- }) {
182
- const particleRef = useRef<THREE.Mesh>(null);
183
- const [progress, setProgress] = useState(0);
184
-
185
- useFrame((state, delta) => {
186
- if (particleRef.current && progress < 1) {
187
- const newProgress = Math.min(progress + delta * 0.3, 1);
188
- setProgress(newProgress);
189
-
190
- // Interpolate position along path
191
- const segmentCount = path.length - 1;
192
- const currentSegment = Math.floor(newProgress * segmentCount);
193
- const segmentProgress = (newProgress * segmentCount) % 1;
194
-
195
- if (currentSegment < segmentCount) {
196
- const start = path[currentSegment];
197
- const end = path[currentSegment + 1];
198
- particleRef.current.position.lerpVectors(start, end, segmentProgress);
199
- }
200
-
201
- if (newProgress >= 1 && onComplete) {
202
- onComplete();
203
- }
204
- }
205
- });
206
-
207
- return (
208
- <Sphere ref={particleRef} args={[0.2, 16, 16]}>
209
- <meshStandardMaterial
210
- color="#ffd93d"
211
- emissive="#ffd93d"
212
- emissiveIntensity={1}
213
- />
214
- </Sphere>
215
- );
216
- }
217
-
218
- // Attention flow visualization
219
- function AttentionFlowVisualization({
220
- flow,
221
- layerSpacing
222
- }: {
223
- flow: DecisionPath['attention_flow'];
224
- layerSpacing: number;
225
- }) {
226
- return (
227
- <>
228
- {flow.map((connection, idx) => {
229
- const startY = connection.from_layer * layerSpacing;
230
- const endY = connection.to_layer === "output"
231
- ? 20 * layerSpacing + 5
232
- : (connection.to_layer as number) * layerSpacing;
233
-
234
- const points = [
235
- new THREE.Vector3(0, startY, 0),
236
- new THREE.Vector3(0, endY, 0)
237
- ];
238
-
239
- return (
240
- <Line
241
- key={idx}
242
- points={points}
243
- color={connection.strength > 0.7 ? "#ff6b6b" : "#4ecdc4"}
244
- lineWidth={connection.strength * 5}
245
- transparent
246
- opacity={0.6}
247
- />
248
- );
249
- })}
250
- </>
251
- );
252
- }
253
-
254
- // Alternative tokens display
255
- function AlternativesDisplay({
256
- alternatives,
257
- position
258
- }: {
259
- alternatives: DecisionPath['alternatives'];
260
- position: [number, number, number];
261
- }) {
262
- return (
263
- <group position={position}>
264
- <Text fontSize={0.12} color="#9ca3af" position={[0, 0.5, 0]}>
265
- Alternatives Considered:
266
- </Text>
267
- {alternatives.slice(0, 3).map((alt, idx) => (
268
- <group key={idx} position={[0, -idx * 0.3, 0]}>
269
- <Text
270
- fontSize={0.1}
271
- color={idx === 0 ? "#4ecdc4" : "#6b7280"}
272
- anchorX="center"
273
- >
274
- {alt.token}: {(alt.probability * 100).toFixed(1)}%
275
- </Text>
276
- </group>
277
- ))}
278
- </group>
279
- );
280
- }
281
-
282
- // Decision factors visualization
283
- function DecisionFactorsDisplay({
284
- factors,
285
- position
286
- }: {
287
- factors: DecisionPath['decision_factors'];
288
- position: [number, number, number];
289
- }) {
290
- const factorList = Object.entries(factors);
291
-
292
- return (
293
- <group position={position}>
294
- <Text fontSize={0.12} color="#ffd93d" position={[0, 0.8, 0]}>
295
- Decision Factors:
296
- </Text>
297
- {factorList.map(([key, value], idx) => {
298
- const barWidth = value * 2;
299
- return (
300
- <group key={key} position={[0, -idx * 0.25, 0]}>
301
- <Box position={[-1, 0, 0]} args={[barWidth, 0.15, 0.1]}>
302
- <meshStandardMaterial
303
- color={value > 0.7 ? "#4ecdc4" : value > 0.4 ? "#ffd93d" : "#ff6b6b"}
304
- emissive={value > 0.7 ? "#4ecdc4" : "#ffd93d"}
305
- emissiveIntensity={0.3}
306
- />
307
- </Box>
308
- <Text
309
- position={[1, 0, 0]}
310
- fontSize={0.08}
311
- color="#9ca3af"
312
- anchorX="left"
313
- >
314
- {key.replace(/_/g, ' ')}: {(value * 100).toFixed(0)}%
315
- </Text>
316
- </group>
317
- );
318
- })}
319
- </group>
320
- );
321
- }
322
-
323
- // Main 3D scene with decision path
324
- function DecisionPathScene({ decisionPath }: { decisionPath: DecisionPath | null }) {
325
- const numLayers = 20;
326
- const layerSpacing = 3.5;
327
- const [showParticle, setShowParticle] = useState(false);
328
-
329
- // Create path for particle animation
330
- const particlePath = useMemo(() => {
331
- if (!decisionPath) return [];
332
-
333
- const path: THREE.Vector3[] = [
334
- new THREE.Vector3(0, -5, 0), // Start at input
335
- ];
336
-
337
- // Add critical layers
338
- decisionPath.critical_layers.forEach(layerIdx => {
339
- path.push(new THREE.Vector3(0, layerIdx * layerSpacing, 0));
340
- });
341
-
342
- // End at output
343
- path.push(new THREE.Vector3(0, numLayers * layerSpacing + 5, 0));
344
-
345
- return path;
346
- }, [decisionPath]);
347
-
348
- useEffect(() => {
349
- if (decisionPath) {
350
- setShowParticle(true);
351
- }
352
- }, [decisionPath]);
353
-
354
- return (
355
- <>
356
- {/* Lighting */}
357
- <ambientLight intensity={0.3} />
358
- <pointLight position={[10, 10, 10]} intensity={1} />
359
- <spotLight position={[0, 50, 0]} angle={0.3} penumbra={1} intensity={1} />
360
-
361
- {/* Input Layer */}
362
- <group position={[0, -5, 0]}>
363
- <Box args={[5, 0.2, 2]}>
364
- <meshStandardMaterial color="#10b981" emissive="#10b981" emissiveIntensity={0.3} />
365
- </Box>
366
- <Text position={[0, 0.3, 0]} fontSize={0.15} color="white">
367
- Input Embeddings
368
- </Text>
369
- </group>
370
-
371
- {/* Transformer Layers */}
372
- {Array.from({ length: numLayers }).map((_, i) => {
373
- const activation = decisionPath?.layer_activations[i];
374
- const isCritical = decisionPath?.critical_layers.includes(i);
375
-
376
- return (
377
- <EnhancedTransformerLayer
378
- key={i}
379
- position={[0, i * layerSpacing, 0]}
380
- layerIndex={i}
381
- activation={activation}
382
- isCritical={isCritical}
383
- isActive={!!activation}
384
- />
385
- );
386
- })}
387
-
388
- {/* Output Layer */}
389
- <group position={[0, numLayers * layerSpacing + 5, 0]}>
390
- <Box args={[5, 0.2, 2]}>
391
- <meshStandardMaterial color="#f59e0b" emissive="#f59e0b" emissiveIntensity={0.3} />
392
- </Box>
393
- <Text position={[0, 0.3, 0]} fontSize={0.15} color="white">
394
- Output: {decisionPath?.token || "..."}
395
- </Text>
396
- <Text position={[0, -0.3, 0]} fontSize={0.1} color="#9ca3af">
397
- Probability: {decisionPath ? (decisionPath.probability * 100).toFixed(1) : "0"}%
398
- </Text>
399
- </group>
400
-
401
- {/* Attention Flow Visualization */}
402
- {decisionPath && (
403
- <AttentionFlowVisualization
404
- flow={decisionPath.attention_flow}
405
- layerSpacing={layerSpacing}
406
- />
407
- )}
408
-
409
- {/* Decision Particle Animation */}
410
- {showParticle && particlePath.length > 0 && (
411
- <DecisionParticle
412
- path={particlePath}
413
- onComplete={() => setShowParticle(false)}
414
- />
415
- )}
416
-
417
- {/* Alternatives Display */}
418
- {decisionPath && (
419
- <AlternativesDisplay
420
- alternatives={decisionPath.alternatives}
421
- position={[8, numLayers * layerSpacing / 2, 0]}
422
- />
423
- )}
424
-
425
- {/* Decision Factors Display */}
426
- {decisionPath && (
427
- <DecisionFactorsDisplay
428
- factors={decisionPath.decision_factors}
429
- position={[-8, numLayers * layerSpacing / 2, 0]}
430
- />
431
- )}
432
-
433
- {/* Grid */}
434
- <gridHelper args={[150, 150, 0x444444, 0x222222]} />
435
- </>
436
- );
437
- }
438
-
439
- export default function DecisionPath3D() {
440
- const [decisionPath, setDecisionPath] = useState<DecisionPath | null>(null);
441
- const [isConnected, setIsConnected] = useState(false);
442
- const [isAnalyzing, setIsAnalyzing] = useState(false);
443
- const [isClient, setIsClient] = useState(false);
444
- const wsRef = useRef<WebSocket | null>(null);
445
- const [prompt, setPrompt] = useState("def quicksort(arr):");
446
-
447
- // Ensure client-side only rendering
448
- useEffect(() => {
449
- setIsClient(true);
450
- }, []);
451
-
452
- // Connect to decision path service
453
- useEffect(() => {
454
- if (!isClient) return;
455
-
456
- const connectToService = () => {
457
- try {
458
- const ws = new WebSocket('ws://localhost:8769');
459
-
460
- ws.onopen = () => {
461
- console.log('[DecisionPath3D] Connected to service');
462
- setIsConnected(true);
463
- wsRef.current = ws;
464
- };
465
-
466
- ws.onmessage = (event) => {
467
- const data = JSON.parse(event.data);
468
- console.log('[DecisionPath3D] Received:', data.type);
469
-
470
- if (data.type === 'decision_path') {
471
- setDecisionPath(data.data);
472
- } else if (data.type === 'analysis_complete') {
473
- setIsAnalyzing(false);
474
- }
475
- };
476
-
477
- ws.onerror = (error) => {
478
- console.log('[DecisionPath3D] Service not available');
479
- setIsConnected(false);
480
- };
481
-
482
- ws.onclose = () => {
483
- console.log('[DecisionPath3D] Disconnected');
484
- setIsConnected(false);
485
- wsRef.current = null;
486
- };
487
- } catch (error) {
488
- console.log('[DecisionPath3D] Connection failed');
489
- setIsConnected(false);
490
- }
491
- };
492
-
493
- connectToService();
494
-
495
- return () => {
496
- if (wsRef.current) {
497
- wsRef.current.close();
498
- }
499
- };
500
- }, [isClient]);
501
-
502
- const startAnalysis = () => {
503
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
504
- setIsAnalyzing(true);
505
- wsRef.current.send(JSON.stringify({
506
- type: 'analyze',
507
- prompt: prompt
508
- }));
509
- }
510
- };
511
-
512
- return (
513
- <div className="bg-gray-900 rounded-xl p-6 h-[900px]">
514
- {/* Header */}
515
- <div className="flex items-center justify-between mb-4">
516
- <div>
517
- <h2 className="text-2xl font-bold flex items-center gap-2">
518
- <GitBranch className="w-6 h-6 text-yellow-400" />
519
- Decision Path Visualization
520
- </h2>
521
- <p className="text-gray-400 mt-1">
522
- See exactly how the model makes its decisions - the Glass Box view
523
- </p>
524
- </div>
525
-
526
- <div className="flex items-center gap-4">
527
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
528
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-yellow-900/30 text-yellow-400'
529
- }`}>
530
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
531
- {isConnected ? 'Connected' : 'Disconnected'}
532
- </div>
533
- </div>
534
- </div>
535
-
536
- {/* Controls */}
537
- <div className="bg-gray-800 rounded-lg p-4 mb-4">
538
- <div className="flex items-center gap-4">
539
- <input
540
- type="text"
541
- value={prompt}
542
- onChange={(e) => setPrompt(e.target.value)}
543
- className="flex-1 px-3 py-2 bg-gray-900 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
544
- placeholder="Enter code to analyze..."
545
- />
546
- <button
547
- onClick={startAnalysis}
548
- disabled={!isConnected || isAnalyzing}
549
- className="px-6 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors disabled:opacity-50 flex items-center gap-2"
550
- >
551
- {isAnalyzing ? (
552
- <>
553
- <Activity className="w-4 h-4 animate-spin" />
554
- Analyzing...
555
- </>
556
- ) : (
557
- <>
558
- <Sparkles className="w-4 h-4" />
559
- Analyze Decision Path
560
- </>
561
- )}
562
- </button>
563
- </div>
564
- </div>
565
-
566
- {/* 3D Canvas */}
567
- <div className="h-[700px] bg-black rounded-lg relative">
568
- {isClient ? (
569
- <Canvas camera={{ position: [30, 40, 50], fov: 50 }}>
570
- <DecisionPathScene decisionPath={decisionPath} />
571
- <OrbitControls
572
- enablePan={true}
573
- enableZoom={true}
574
- enableRotate={true}
575
- target={[0, 35, 0]}
576
- />
577
- </Canvas>
578
- ) : (
579
- <div className="flex items-center justify-center h-full">
580
- <div className="text-gray-400">Loading 3D visualization...</div>
581
- </div>
582
- )}
583
-
584
- {/* Legend */}
585
- <div className="absolute top-4 right-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs">
586
- <div className="font-semibold text-white mb-2">Decision Path</div>
587
- <div className="space-y-1">
588
- <div className="flex items-center gap-2">
589
- <div className="w-3 h-3 bg-red-500 rounded"></div>
590
- <span className="text-gray-300">Critical Layers</span>
591
- </div>
592
- <div className="flex items-center gap-2">
593
- <div className="w-3 h-3 bg-teal-500 rounded"></div>
594
- <span className="text-gray-300">Active Layers</span>
595
- </div>
596
- <div className="flex items-center gap-2">
597
- <div className="w-3 h-3 bg-yellow-500 rounded"></div>
598
- <span className="text-gray-300">Top Attention Heads</span>
599
- </div>
600
- <div className="flex items-center gap-2">
601
- <Zap className="w-3 h-3 text-yellow-400" />
602
- <span className="text-gray-300">Decision Flow</span>
603
- </div>
604
- </div>
605
- </div>
606
-
607
- {/* Info Panel */}
608
- {decisionPath && (
609
- <div className="absolute bottom-4 left-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs max-w-xs">
610
- <div className="font-semibold text-white mb-2">Current Decision</div>
611
- <div className="space-y-1 text-gray-300">
612
- <div>Token: <span className="text-yellow-400">{decisionPath.token}</span></div>
613
- <div>Confidence: <span className="text-green-400">{(decisionPath.confidence_score * 100).toFixed(0)}%</span></div>
614
- <div>Critical Layers: <span className="text-red-400">{decisionPath.critical_layers.join(", ")}</span></div>
615
- </div>
616
- </div>
617
- )}
618
- </div>
619
- </div>
620
- );
621
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/DecisionPath3DEnhanced.tsx DELETED
@@ -1,1003 +0,0 @@
1
- /**
2
- * Enhanced Decision Path 3D Visualization with Full Generation
3
- *
4
- * Combines the decision path visualization with full code generation,
5
- * timeline controls, and step-by-step playback like the Code Generation Tracker.
6
- *
7
- * @component
8
- */
9
-
10
- "use client";
11
-
12
- import { useRef, useState, useEffect, useMemo } from "react";
13
- import { Canvas, useFrame } from "@react-three/fiber";
14
- import { OrbitControls } from "@react-three/drei";
15
- import * as THREE from "three";
16
- import { getApiUrl, getWsUrl } from "@/lib/config";
17
- import {
18
- GitBranch,
19
- Activity,
20
- Sparkles,
21
- Zap,
22
- Brain,
23
- Play,
24
- Pause,
25
- SkipBack,
26
- SkipForward,
27
- ChevronLeft,
28
- ChevronRight,
29
- RefreshCw,
30
- Download,
31
- Code2,
32
- Info,
33
- HelpCircle,
34
- X
35
- } from "lucide-react";
36
-
37
- interface LayerActivation {
38
- layer_index: number;
39
- attention_weights: number[][];
40
- hidden_state_norm: number;
41
- ffn_activation: number;
42
- top_attention_heads: number[];
43
- confidence: number;
44
- }
45
-
46
- interface DecisionPath {
47
- token: string;
48
- token_id: number;
49
- probability: number;
50
- layer_activations: LayerActivation[];
51
- attention_flow: Array<{
52
- from_layer: number;
53
- to_layer: number | string;
54
- strength: number;
55
- top_heads: number[];
56
- }>;
57
- alternatives: Array<{
58
- token: string;
59
- token_id: number;
60
- probability: number;
61
- }>;
62
- decision_factors: {
63
- attention_focus: number;
64
- semantic_alignment: number;
65
- syntactic_correctness: number;
66
- context_relevance: number;
67
- confidence: number;
68
- };
69
- critical_layers: number[];
70
- confidence_score: number;
71
- timestamp: number;
72
- }
73
-
74
- interface GenerationStep {
75
- step: number;
76
- token: string;
77
- token_id: number;
78
- probability: number;
79
- cumulative_text: string;
80
- top_alternatives: Array<{
81
- token: string;
82
- probability: number;
83
- }>;
84
- attention_weights?: number[][];
85
- decision_path?: DecisionPath;
86
- }
87
-
88
- // Enhanced Layer Component with proper FFN visualization
89
- interface LayerProps {
90
- position: [number, number, number];
91
- layerIndex: number;
92
- isCritical: boolean;
93
- isActive: boolean;
94
- activation?: LayerActivation;
95
- }
96
-
97
- function Layer({ position, layerIndex, isCritical, isActive, activation }: LayerProps) {
98
- const meshRef = useRef<THREE.Mesh>(null);
99
- const ffnRef = useRef<THREE.Mesh>(null);
100
-
101
- useFrame((state) => {
102
- if (meshRef.current && isCritical) {
103
- const scale = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.1;
104
- meshRef.current.scale.set(scale, scale, scale);
105
- }
106
-
107
- if (ffnRef.current && activation && activation.ffn_activation) {
108
- // Scale FFN based on activation strength (0.5 to 1.5 scale range)
109
- const ffnScale = 0.5 + (activation.ffn_activation * 1.0);
110
- ffnRef.current.scale.set(ffnScale, ffnScale, ffnScale);
111
-
112
- // Also update FFN color intensity based on activation
113
- const material = ffnRef.current.material as THREE.MeshStandardMaterial;
114
- if (material) {
115
- material.emissiveIntensity = activation.ffn_activation * 0.5;
116
- }
117
- }
118
- });
119
-
120
- const baseColor = isCritical ? "#ff6b6b" : isActive ? "#4ecdc4" : "#2d3748";
121
- const ffnColor = isCritical ? "#e91e63" : isActive ? "#9c27b0" : "#6b46c1";
122
-
123
- return (
124
- <group position={position}>
125
- {/* Main attention layer */}
126
- <mesh ref={meshRef}>
127
- <boxGeometry args={[4, 0.3, 2]} />
128
- <meshStandardMaterial
129
- color={baseColor}
130
- emissive={isCritical ? baseColor : "#000000"}
131
- emissiveIntensity={isCritical ? 0.3 : 0}
132
- metalness={0.6}
133
- roughness={0.3}
134
- />
135
- </mesh>
136
-
137
- {/* FFN Component */}
138
- <group position={[0, 0, -1.5]}>
139
- <mesh ref={ffnRef}>
140
- <boxGeometry args={[3, 0.2, 0.8]} />
141
- <meshStandardMaterial
142
- color={ffnColor}
143
- emissive={ffnColor}
144
- emissiveIntensity={isActive ? 0.2 : 0.1}
145
- metalness={0.7}
146
- roughness={0.3}
147
- />
148
- </mesh>
149
- </group>
150
-
151
- {/* Attention heads visualization */}
152
- {isActive && (
153
- <group position={[0, 0, 1.2]}>
154
- {Array.from({ length: 16 }).map((_, i) => (
155
- <mesh key={i} position={[(i % 4 - 1.5) * 0.3, 0, Math.floor(i / 4) * 0.2 - 0.3]}>
156
- <boxGeometry args={[0.15, 0.1, 0.15]} />
157
- <meshStandardMaterial
158
- color={activation?.top_attention_heads?.includes(i) ? "#ffd93d" : "#4a5568"}
159
- emissive={activation?.top_attention_heads?.includes(i) ? "#ffd93d" : "#000000"}
160
- emissiveIntensity={activation?.top_attention_heads?.includes(i) ? 0.5 : 0}
161
- />
162
- </mesh>
163
- ))}
164
- </group>
165
- )}
166
- </group>
167
- );
168
- }
169
-
170
- // 3D Scene
171
- function DecisionPathScene({ decisionPath }: { decisionPath: DecisionPath | null }) {
172
- const numLayers = 20;
173
- const layerSpacing = 3.5;
174
-
175
- // Debug log to see what data we're receiving
176
- useEffect(() => {
177
- if (decisionPath) {
178
- console.log('[3D Scene] Decision path update:', {
179
- hasLayerActivations: !!decisionPath.layer_activations,
180
- numActivations: decisionPath.layer_activations?.length || 0,
181
- criticalLayers: decisionPath.critical_layers || []
182
- });
183
- }
184
- }, [decisionPath]);
185
-
186
- return (
187
- <>
188
- <ambientLight intensity={0.5} />
189
- <pointLight position={[10, 10, 10]} intensity={1} />
190
- <directionalLight position={[0, 10, 5]} intensity={0.5} />
191
-
192
- {/* Input Layer */}
193
- <mesh position={[0, -5, 0]}>
194
- <boxGeometry args={[5, 0.2, 2]} />
195
- <meshStandardMaterial color="#10b981" />
196
- </mesh>
197
-
198
- {/* Transformer Layers */}
199
- {Array.from({ length: numLayers }).map((_, i) => {
200
- const isCritical = decisionPath?.critical_layers?.includes(i) || false;
201
- const activation = decisionPath?.layer_activations?.find(a => a.layer_index === i);
202
-
203
- return (
204
- <Layer
205
- key={i}
206
- position={[0, i * layerSpacing, 0]}
207
- layerIndex={i}
208
- isCritical={isCritical}
209
- isActive={!!activation}
210
- activation={activation}
211
- />
212
- );
213
- })}
214
-
215
- {/* Output Layer */}
216
- <mesh position={[0, numLayers * layerSpacing + 5, 0]}>
217
- <boxGeometry args={[5, 0.2, 2]} />
218
- <meshStandardMaterial color="#f59e0b" />
219
- </mesh>
220
-
221
- {/* Connection lines */}
222
- {decisionPath && decisionPath.critical_layers && decisionPath.critical_layers.map((layerIdx, idx) => {
223
- const startY = layerIdx * layerSpacing;
224
- const endY = idx < decisionPath.critical_layers.length - 1
225
- ? decisionPath.critical_layers[idx + 1] * layerSpacing
226
- : numLayers * layerSpacing + 5;
227
-
228
- const points = [];
229
- points.push(new THREE.Vector3(0, startY, 0));
230
- points.push(new THREE.Vector3(0, endY, 0));
231
-
232
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
233
-
234
- return (
235
- <primitive
236
- key={`line-${idx}`}
237
- object={new THREE.Line(
238
- geometry,
239
- new THREE.LineBasicMaterial({ color: 0xff6b6b, linewidth: 3 })
240
- )}
241
- />
242
- );
243
- })}
244
-
245
- <gridHelper args={[100, 100, 0x444444, 0x222222]} />
246
- </>
247
- );
248
- }
249
-
250
- export default function DecisionPath3DEnhanced() {
251
- const [generationSteps, setGenerationSteps] = useState<GenerationStep[]>([]);
252
- const [currentStep, setCurrentStep] = useState(0);
253
- const [isPlaying, setIsPlaying] = useState(false);
254
- const [isGenerating, setIsGenerating] = useState(false);
255
- const [isConnected, setIsConnected] = useState(false);
256
- const [modelLoading, setModelLoading] = useState(true);
257
- const [loadingProgress, setLoadingProgress] = useState(0);
258
- const [loadingMessage, setLoadingMessage] = useState("Initializing...");
259
- const [mounted, setMounted] = useState(false);
260
- const wsRef = useRef<WebSocket | null>(null);
261
- const [prompt, setPrompt] = useState("def quicksort(arr):");
262
- const intervalRef = useRef<NodeJS.Timeout | null>(null);
263
- const promptRef = useRef<string>("");
264
- const [showExplanation, setShowExplanation] = useState(false);
265
-
266
- // Store current trace data for layer activations
267
- interface TraceData {
268
- layer?: string;
269
- max_weight?: number;
270
- weights?: number[][];
271
- mean?: number;
272
- }
273
-
274
- const currentTracesRef = useRef<{
275
- attention: TraceData[];
276
- activation: TraceData[];
277
- }>({ attention: [], activation: [] });
278
-
279
- // Fetch real model data
280
- const [modelInfo, setModelInfo] = useState({
281
- layers: 20,
282
- heads: 16,
283
- vocabSize: 51200,
284
- totalParams: 356712448
285
- });
286
-
287
- useEffect(() => {
288
- fetch(`${getApiUrl()}/model/info`)
289
- .then(res => res.json())
290
- .then(data => {
291
- setModelInfo({
292
- layers: data.layers,
293
- heads: data.heads,
294
- vocabSize: data.vocabSize,
295
- totalParams: data.totalParams
296
- });
297
- })
298
- .catch(err => console.log('Using default model info'));
299
- }, []);
300
-
301
- useEffect(() => {
302
- setMounted(true);
303
- }, []);
304
-
305
- // Connect to service
306
- useEffect(() => {
307
- if (!mounted) return;
308
-
309
- const connectToService = () => {
310
- try {
311
- const ws = new WebSocket(getWsUrl());
312
-
313
- ws.onopen = () => {
314
- console.log('[DecisionPath3D] Connected to unified backend');
315
- setIsConnected(true);
316
- wsRef.current = ws;
317
- // Model is already loaded in unified backend
318
- setModelLoading(false);
319
- setLoadingProgress(100);
320
- setLoadingMessage("Model ready!");
321
- };
322
-
323
- ws.onmessage = (event) => {
324
- const data = JSON.parse(event.data);
325
- console.log('[DecisionPath3D] Received:', data.type);
326
- // Handle messages from unified backend
327
- if (data.type === 'generated_token') {
328
- // Filter out empty tokens
329
- if (!data.token || data.token.length === 0) {
330
- return;
331
- }
332
-
333
- // Add this as a new generation step
334
- setGenerationSteps(prev => {
335
- // Build cumulative text correctly
336
- let cumulativeText = "";
337
- if (prev.length > 0) {
338
- // Get the last cumulative text and add the new token
339
- cumulativeText = prev[prev.length - 1].cumulative_text + data.token;
340
- } else {
341
- // First token after prompt - use the stored prompt ref
342
- cumulativeText = promptRef.current + data.token;
343
- }
344
-
345
- const newStep: GenerationStep = {
346
- step: prev.length,
347
- token: data.token,
348
- token_id: prev.length, // Use step as token_id since backend doesn't send it
349
- probability: data.confidence_score || 0.5,
350
- cumulative_text: cumulativeText,
351
- top_alternatives: data.alternatives?.slice(0, 3) || [],
352
- decision_path: {
353
- token: data.token,
354
- token_id: prev.length,
355
- probability: data.confidence_score || 0.5,
356
- layer_activations: [],
357
- attention_flow: [],
358
- alternatives: data.alternatives || [],
359
- decision_factors: {
360
- attention_focus: 0.5,
361
- semantic_alignment: 0.5,
362
- syntactic_correctness: 0.5,
363
- context_relevance: 0.5,
364
- confidence: data.confidence_score || 0.5
365
- },
366
- critical_layers: [],
367
- confidence_score: data.confidence_score || 0.5,
368
- timestamp: Date.now()
369
- }
370
- };
371
-
372
- console.log('[DecisionPath3D] Step', prev.length, 'Token:', data.token, 'Cumulative:', cumulativeText);
373
-
374
- const newSteps = [...prev, newStep];
375
- // Update current step to the latest
376
- setCurrentStep(newSteps.length - 1);
377
- return newSteps;
378
- });
379
-
380
- } else if (data.type === 'attention') {
381
- // Store attention trace data
382
- currentTracesRef.current.attention.push(data);
383
-
384
- // Update the current step with layer activation info
385
- // Use a small delay to ensure we're updating the right step
386
- setTimeout(() => {
387
- setGenerationSteps(prev => {
388
- if (prev.length === 0) return prev;
389
- const updated = [...prev];
390
- const lastStep = updated[updated.length - 1];
391
-
392
- // Extract layer index from data.layer (e.g., "layer.5" -> 5)
393
- const layerMatch = data.layer?.match(/layer\.(\d+)/);
394
- const layerIdx = layerMatch ? parseInt(layerMatch[1]) : null;
395
-
396
- if (layerIdx !== null && lastStep.decision_path) {
397
- // Mark this layer as active/critical based on attention weights
398
- if (!lastStep.decision_path.layer_activations) {
399
- lastStep.decision_path.layer_activations = [];
400
- }
401
-
402
- // Add or update layer activation
403
- // Identify top attention heads (simulate which heads are most active)
404
- const topHeads = [];
405
- if (data.max_weight > 0.5) {
406
- // Mark 2-4 random heads as highly active
407
- const numActiveHeads = Math.floor(Math.random() * 3) + 2;
408
- for (let h = 0; h < numActiveHeads; h++) {
409
- topHeads.push(Math.floor(Math.random() * 16));
410
- }
411
- }
412
-
413
- const activation = {
414
- layer_index: layerIdx,
415
- attention_weights: data.weights || [],
416
- hidden_state_norm: 0,
417
- ffn_activation: 0,
418
- top_attention_heads: topHeads,
419
- confidence: data.max_weight || 0.5
420
- };
421
-
422
- // Update or add the activation
423
- const existingIdx = lastStep.decision_path.layer_activations.findIndex(
424
- a => a.layer_index === layerIdx
425
- );
426
- if (existingIdx >= 0) {
427
- lastStep.decision_path.layer_activations[existingIdx] = activation;
428
- } else {
429
- lastStep.decision_path.layer_activations.push(activation);
430
- }
431
-
432
- // Determine critical layers (those with high attention weights)
433
- // Only mark layers 5+ as potentially critical (earlier layers are usually less decisive)
434
- // and use a higher threshold since our normalization puts many values near 0.95
435
- const threshold = 0.9; // Consider only layers with >90% attention as critical
436
- if (data.max_weight > threshold && layerIdx >= 5) {
437
- if (!lastStep.decision_path.critical_layers) {
438
- lastStep.decision_path.critical_layers = [];
439
- }
440
- if (!lastStep.decision_path.critical_layers.includes(layerIdx)) {
441
- lastStep.decision_path.critical_layers.push(layerIdx);
442
- }
443
- }
444
- }
445
- return updated;
446
- });
447
- }, 100); // 100ms delay to ensure token is processed first
448
- } else if (data.type === 'activation') {
449
- // Store activation trace data
450
- currentTracesRef.current.activation.push(data);
451
-
452
- // Update FFN activation info
453
- setTimeout(() => {
454
- setGenerationSteps(prev => {
455
- if (prev.length === 0) return prev;
456
- const updated = [...prev];
457
- const lastStep = updated[updated.length - 1];
458
-
459
- // Extract layer index
460
- const layerMatch = data.layer?.match(/layer\.(\d+)/);
461
- const layerIdx = layerMatch ? parseInt(layerMatch[1]) : null;
462
-
463
- if (layerIdx !== null && lastStep.decision_path) {
464
- if (!lastStep.decision_path.layer_activations) {
465
- lastStep.decision_path.layer_activations = [];
466
- }
467
-
468
- // Find or create layer activation
469
- let activation = lastStep.decision_path.layer_activations.find(
470
- a => a.layer_index === layerIdx
471
- );
472
- if (!activation) {
473
- activation = {
474
- layer_index: layerIdx,
475
- attention_weights: [],
476
- hidden_state_norm: 0,
477
- ffn_activation: 0,
478
- top_attention_heads: [],
479
- confidence: 0.5
480
- };
481
- lastStep.decision_path.layer_activations.push(activation);
482
- }
483
-
484
- // Update FFN activation
485
- activation.ffn_activation = data.mean || 0;
486
- activation.hidden_state_norm = data.max_weight || 0;
487
-
488
- // Also check if this should be a critical layer based on activation strength
489
- // Use mean as it's more representative than max_weight
490
- const threshold = 0.85; // High activation threshold
491
- if (data.mean > threshold && layerIdx >= 10) { // Focus on later layers
492
- if (!lastStep.decision_path.critical_layers) {
493
- lastStep.decision_path.critical_layers = [];
494
- }
495
- if (!lastStep.decision_path.critical_layers.includes(layerIdx)) {
496
- lastStep.decision_path.critical_layers.push(layerIdx);
497
- }
498
- }
499
- }
500
- return updated;
501
- });
502
- }, 100); // 100ms delay to ensure token is processed first
503
- } else if (data.type === 'confidence') {
504
- // Handle confidence updates
505
- console.log('[DecisionPath3D] Confidence update:', data.confidence_score);
506
- } else if (data.type === 'loading_progress') {
507
- setLoadingProgress(data.progress);
508
- setLoadingMessage(data.message);
509
- if (data.progress === 100) {
510
- setModelLoading(false);
511
- }
512
- } else if (data.type === 'model_ready') {
513
- setModelLoading(false);
514
- setLoadingProgress(100);
515
- setLoadingMessage("Model ready!");
516
- }
517
- };
518
-
519
- ws.onerror = (error) => {
520
- console.log('[DecisionPath3D] Service not available');
521
- setIsConnected(false);
522
- };
523
-
524
- ws.onclose = () => {
525
- console.log('[DecisionPath3D] Disconnected');
526
- setIsConnected(false);
527
- wsRef.current = null;
528
- };
529
- } catch (error) {
530
- console.log('[DecisionPath3D] Connection failed');
531
- setIsConnected(false);
532
- }
533
- };
534
-
535
- connectToService();
536
-
537
- return () => {
538
- if (wsRef.current) {
539
- wsRef.current.close();
540
- }
541
- };
542
- }, [mounted]);
543
-
544
- // Auto-play functionality
545
- useEffect(() => {
546
- if (isPlaying && generationSteps.length > 0) {
547
- intervalRef.current = setInterval(() => {
548
- setCurrentStep(prev => {
549
- if (prev >= generationSteps.length - 1) {
550
- setIsPlaying(false);
551
- return prev;
552
- }
553
- return prev + 1;
554
- });
555
- }, 1000);
556
- } else {
557
- if (intervalRef.current) {
558
- clearInterval(intervalRef.current);
559
- }
560
- }
561
-
562
- return () => {
563
- if (intervalRef.current) {
564
- clearInterval(intervalRef.current);
565
- }
566
- };
567
- }, [isPlaying, generationSteps.length]);
568
-
569
- const startGeneration = async () => {
570
- if (!isConnected) return;
571
-
572
- setIsGenerating(true);
573
- setGenerationSteps([]);
574
- setCurrentStep(0);
575
- promptRef.current = prompt; // Store in ref to avoid closure issues
576
-
577
- // Clear previous traces
578
- currentTracesRef.current = { attention: [], activation: [] };
579
-
580
- try {
581
- // Use HTTP POST to trigger generation
582
- const response = await fetch(`${getApiUrl()}/generate`, {
583
- method: 'POST',
584
- headers: {
585
- 'Content-Type': 'application/json',
586
- },
587
- body: JSON.stringify({
588
- prompt: prompt,
589
- max_tokens: 100,
590
- temperature: 0.7,
591
- extract_traces: true,
592
- sampling_rate: 0.3
593
- })
594
- });
595
-
596
- if (!response.ok) {
597
- throw new Error('Failed to start generation');
598
- }
599
-
600
- // The WebSocket will receive the real-time updates
601
- const result = await response.json();
602
- console.log('[DecisionPath3D] Generation complete, received', result.num_tokens, 'tokens');
603
- // Generation is complete
604
- setTimeout(() => {
605
- setIsGenerating(false);
606
- }, 500); // Small delay to ensure all WebSocket messages are processed
607
- } catch (error) {
608
- console.error('[DecisionPath3D] Failed to start generation:', error);
609
- setIsGenerating(false);
610
- }
611
- };
612
-
613
- const currentDecisionPath = generationSteps[currentStep]?.decision_path || null;
614
-
615
- // Debug logging
616
- useEffect(() => {
617
- if (currentDecisionPath) {
618
- console.log('[DecisionPath3D] Current decision path:', {
619
- token: currentDecisionPath.token,
620
- critical_layers: currentDecisionPath.critical_layers,
621
- layer_activations: currentDecisionPath.layer_activations?.length || 0,
622
- step: currentStep
623
- });
624
- }
625
- }, [currentDecisionPath, currentStep]);
626
-
627
- // Generate explanation for current visualization state
628
- const generateExplanation = () => {
629
- const step = generationSteps[currentStep];
630
- if (!step || !step.decision_path) {
631
- return {
632
- title: "No Data Available",
633
- description: "Generate code to see the decision path visualization.",
634
- details: []
635
- };
636
- }
637
-
638
- const dp = step.decision_path;
639
- const criticalLayersList = dp.critical_layers ? dp.critical_layers.join(", ") : "Not available";
640
- const topAlternatives = dp.alternatives?.slice(0, 3).map(a =>
641
- `"${a.token}" (${(a.probability * 100).toFixed(1)}%)`
642
- ).join(", ") || "Not available";
643
-
644
- return {
645
- title: `Token Generation: "${step.token}"`,
646
- description: `The model is generating token "${step.token}" at step ${currentStep + 1} with ${((dp.probability || 0.5) * 100).toFixed(0)}% confidence.`,
647
- details: [
648
- {
649
- heading: "What You're Seeing",
650
- content: `The 3D visualization shows the decision path through the transformer's ${modelInfo.layers} layers. Each layer processes the input and contributes to the final token selection.`
651
- },
652
- {
653
- heading: "Critical Layers (Red)",
654
- content: `Layers ${criticalLayersList} are highlighted in red and pulsing. These are decision-critical layers with >85% activation strength in later stages of the network. They make the strongest contributions to selecting "${step.token}".`
655
- },
656
- {
657
- heading: "Active Layers (Teal/Cyan)",
658
- content: `Teal/cyan layers are processing information but below the critical threshold. They extract features and patterns that feed into the critical layers above them. Early layers typically show as active rather than critical.`
659
- },
660
- {
661
- heading: "Feed-Forward Networks (Side Blocks)",
662
- content: `The blocks extending from the sides are FFN components. Purple blocks show active processing, pink/red blocks indicate critical transformations. Their size reflects activation strength - larger blocks mean stronger neural activity.`
663
- },
664
- {
665
- heading: "Attention Heads (Top Blocks)",
666
- content: `The small blocks on top of each layer represent the ${modelInfo.heads} attention heads. Yellow/gold blocks are highly focused heads detecting key patterns, while gray blocks are less active. Critical layers often have more active heads.`
667
- },
668
- {
669
- heading: "Decision Factors",
670
- content: `The model considered these alternatives: ${topAlternatives || 'none'}. The confidence level (${((dp.probability || 0.5) * 100).toFixed(0)}%) indicates the model's certainty about "${step.token}" being the correct choice based on the code context.`
671
- },
672
- {
673
- heading: "Information Flow",
674
- content: `The red lines show the critical path - how information flows from the input through the most important layers to produce the final token. This is the model's "reasoning path" for this specific decision.`
675
- }
676
- ]
677
- };
678
- };
679
-
680
- const explanation = generateExplanation();
681
-
682
- if (!mounted) {
683
- return <div className="bg-gray-900 rounded-xl p-6 h-[900px]" />;
684
- }
685
-
686
- return (
687
- <div className="bg-gray-900 rounded-xl p-6">
688
- {/* Header */}
689
- <div className="flex items-center justify-between mb-4">
690
- <div>
691
- <h2 className="text-2xl font-bold flex items-center gap-2">
692
- <GitBranch className="w-6 h-6 text-yellow-400" />
693
- Enhanced Decision Path Visualization
694
- </h2>
695
- <p className="text-gray-400 mt-1">
696
- Full code generation with decision path analysis
697
- </p>
698
- </div>
699
-
700
- <div className="flex items-center gap-4">
701
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
702
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-yellow-900/30 text-yellow-400'
703
- }`}>
704
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
705
- {isConnected ? 'Connected' : 'Disconnected'}
706
- </div>
707
- </div>
708
- </div>
709
-
710
- {/* Generation Controls */}
711
- <div className="bg-gray-800 rounded-lg p-4 mb-4">
712
- <div className="flex items-center gap-4 mb-4">
713
- <input
714
- type="text"
715
- value={prompt}
716
- onChange={(e) => setPrompt(e.target.value)}
717
- className="flex-1 px-3 py-2 bg-gray-900 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
718
- placeholder="Enter code to generate..."
719
- />
720
- <button
721
- onClick={startGeneration}
722
- disabled={!isConnected || isGenerating || modelLoading}
723
- className="px-6 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors disabled:opacity-50 flex items-center gap-2"
724
- >
725
- {isGenerating ? (
726
- <>
727
- <Activity className="w-4 h-4 animate-spin" />
728
- Generating...
729
- </>
730
- ) : (
731
- <>
732
- <Code2 className="w-4 h-4" />
733
- Generate Code
734
- </>
735
- )}
736
- </button>
737
- </div>
738
-
739
- {/* Timeline Controls */}
740
- {generationSteps.length > 0 && (
741
- <div className="space-y-4">
742
- {/* Playback Controls */}
743
- <div className="flex items-center gap-2">
744
- <button
745
- onClick={() => setCurrentStep(0)}
746
- className="p-2 bg-gray-700 rounded hover:bg-gray-600 transition-colors"
747
- >
748
- <SkipBack className="w-4 h-4" />
749
- </button>
750
-
751
- <button
752
- onClick={() => setCurrentStep(Math.max(0, currentStep - 1))}
753
- className="p-2 bg-gray-700 rounded hover:bg-gray-600 transition-colors"
754
- disabled={currentStep === 0}
755
- >
756
- <ChevronLeft className="w-4 h-4" />
757
- </button>
758
-
759
- <button
760
- onClick={() => setIsPlaying(!isPlaying)}
761
- className="p-2 bg-blue-600 rounded hover:bg-blue-700 transition-colors"
762
- >
763
- {isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
764
- </button>
765
-
766
- <button
767
- onClick={() => setCurrentStep(Math.min(generationSteps.length - 1, currentStep + 1))}
768
- className="p-2 bg-gray-700 rounded hover:bg-gray-600 transition-colors"
769
- disabled={currentStep >= generationSteps.length - 1}
770
- >
771
- <ChevronRight className="w-4 h-4" />
772
- </button>
773
-
774
- <button
775
- onClick={() => setCurrentStep(generationSteps.length - 1)}
776
- className="p-2 bg-gray-700 rounded hover:bg-gray-600 transition-colors"
777
- >
778
- <SkipForward className="w-4 h-4" />
779
- </button>
780
-
781
- <div className="flex-1 px-4">
782
- <input
783
- type="range"
784
- min={0}
785
- max={Math.max(0, generationSteps.length - 1)}
786
- value={currentStep}
787
- onChange={(e) => setCurrentStep(parseInt(e.target.value))}
788
- className="w-full"
789
- />
790
- </div>
791
-
792
- <span className="text-sm text-gray-400">
793
- Step {currentStep + 1} / {generationSteps.length} | Token: {generationSteps[currentStep]?.token || "..."}
794
- </span>
795
- </div>
796
-
797
- {/* Generated Code Display */}
798
- <div className="bg-gray-900 rounded-lg p-4">
799
- <div className="flex justify-between items-center mb-2">
800
- <h3 className="text-sm font-semibold text-gray-300">Generated Code</h3>
801
- <div className="text-xs text-gray-500">
802
- Token: {generationSteps[currentStep]?.token || "..."}
803
- </div>
804
- </div>
805
- <pre className="font-mono text-sm text-white whitespace-pre-wrap">
806
- {(() => {
807
- const step = generationSteps[currentStep];
808
- if (step && step.cumulative_text) {
809
- console.log('[UI Render] Displaying step', currentStep, 'cumulative_text:', step.cumulative_text);
810
- return step.cumulative_text;
811
- }
812
- return prompt;
813
- })()}
814
- {isGenerating && <span className="animate-pulse">▊</span>}
815
- </pre>
816
- </div>
817
- </div>
818
- )}
819
- </div>
820
-
821
- {/* Main Content Area with Side Panel */}
822
- <div className="flex gap-4">
823
- {/* 3D Visualization */}
824
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
825
- <div className="h-[700px] bg-black rounded-lg relative overflow-hidden">
826
- {/* Help Toggle Button */}
827
- <button
828
- onClick={() => {
829
- console.log('[DecisionPath3D] Toggle explanation:', !showExplanation);
830
- setShowExplanation(!showExplanation);
831
- }}
832
- className="absolute top-4 left-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
833
- >
834
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
835
- <span className="text-sm font-medium">
836
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
837
- </span>
838
- </button>
839
- {modelLoading ? (
840
- <div className="flex flex-col items-center justify-center h-full">
841
- <div className="text-white mb-4">
842
- <Brain className="w-16 h-16 animate-pulse" />
843
- </div>
844
- <div className="text-xl text-white mb-2">Loading Model</div>
845
- <div className="text-sm text-gray-400 mb-4">{loadingMessage}</div>
846
- <div className="w-64 h-2 bg-gray-700 rounded-full overflow-hidden">
847
- <div
848
- className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-500"
849
- style={{ width: `${loadingProgress}%` }}
850
- />
851
- </div>
852
- <div className="text-xs text-gray-500 mt-2">{loadingProgress}%</div>
853
- </div>
854
- ) : (
855
- <Canvas camera={{ position: [-40, 50, 40], fov: 50 }}>
856
- <DecisionPathScene decisionPath={currentDecisionPath} />
857
- <OrbitControls
858
- enablePan={true}
859
- enableZoom={true}
860
- enableRotate={true}
861
- target={[0, 35, 0]}
862
- />
863
- </Canvas>
864
- )}
865
-
866
- {/* Info Panels */}
867
- {currentDecisionPath && (
868
- <>
869
- {/* Legend */}
870
- <div className="absolute top-4 right-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs">
871
- <div className="font-semibold text-white mb-2">Decision Path</div>
872
- <div className="space-y-1">
873
- <div className="flex items-center gap-2">
874
- <div className="w-3 h-3 bg-red-500 rounded"></div>
875
- <span className="text-gray-300">Critical Layers</span>
876
- </div>
877
- <div className="flex items-center gap-2">
878
- <div className="w-3 h-3 bg-teal-500 rounded"></div>
879
- <span className="text-gray-300">Active Layers</span>
880
- </div>
881
- <div className="flex items-center gap-2">
882
- <div className="w-3 h-3 bg-purple-500 rounded"></div>
883
- <span className="text-gray-300">FFN Components</span>
884
- </div>
885
- </div>
886
- </div>
887
-
888
- {/* Current Token Info */}
889
- <div className="absolute bottom-4 left-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs max-w-xs">
890
- <div className="font-semibold text-white mb-2">Current Token</div>
891
- <div className="space-y-1 text-gray-300">
892
- <div>Token: <span className="text-yellow-400">{currentDecisionPath.token}</span></div>
893
- <div>Confidence: <span className="text-green-400">{((currentDecisionPath.probability || 0.5) * 100).toFixed(0)}%</span></div>
894
- <div>Critical Layers: <span className="text-red-400">{currentDecisionPath.critical_layers ? currentDecisionPath.critical_layers.join(", ") : "Not available"}</span></div>
895
- <div className="mt-2">Alternatives:</div>
896
- {currentDecisionPath.alternatives?.slice(0, 3).map((alt, i) => (
897
- <div key={i} className="ml-2 text-xs">
898
- {alt.token}: {(alt.probability * 100).toFixed(1)}%
899
- </div>
900
- ))}
901
- </div>
902
- </div>
903
- </>
904
- )}
905
- </div>
906
- </div>
907
-
908
- {/* Explanation Side Panel */}
909
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
910
- <div className="w-96 h-[700px] bg-gray-900 rounded-lg border border-gray-700">
911
- {/* Panel Header */}
912
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
913
- <div className="flex items-center gap-2">
914
- <Info className="w-5 h-5 text-blue-400" />
915
- <h3 className="text-lg font-semibold text-white">Real-time Analysis</h3>
916
- </div>
917
- </div>
918
-
919
- {/* Panel Content */}
920
- <div className="px-4 py-4 overflow-y-auto h-[calc(700px-60px)]">
921
- {/* Current Token Info */}
922
- <div className="mb-4 p-3 bg-yellow-900/20 border border-yellow-800 rounded-lg">
923
- <h4 className="text-sm font-semibold text-yellow-400 mb-1">{explanation.title}</h4>
924
- <p className="text-xs text-gray-300">{explanation.description}</p>
925
- </div>
926
-
927
- {/* Dynamic Analysis Sections */}
928
- <div className="space-y-3">
929
- {explanation.details.slice(0, 3).map((section, idx) => (
930
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
931
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
932
- <Zap className="w-3 h-3 text-yellow-400" />
933
- {section.heading}
934
- </h5>
935
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
936
- </div>
937
- ))}
938
- </div>
939
-
940
- {/* Quick Reference */}
941
- <div className="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
942
- <h4 className="font-medium text-sm text-blue-400 mb-2">Visual Legend</h4>
943
- <div className="space-y-2 text-xs">
944
- <div className="flex items-center gap-2">
945
- <div className="w-3 h-3 bg-red-500 rounded"></div>
946
- <span className="text-gray-300">Critical Layers</span>
947
- </div>
948
- <div className="flex items-center gap-2">
949
- <div className="w-3 h-3 bg-teal-500 rounded"></div>
950
- <span className="text-gray-300">Active Layers</span>
951
- </div>
952
- <div className="flex items-center gap-2">
953
- <div className="w-3 h-3 bg-purple-500 rounded"></div>
954
- <span className="text-gray-300">FFN Components</span>
955
- </div>
956
- <div className="flex items-center gap-2">
957
- <div className="w-3 h-3 bg-yellow-500 rounded"></div>
958
- <span className="text-gray-300">Top Attention</span>
959
- </div>
960
- </div>
961
- </div>
962
-
963
- {/* Current Metrics */}
964
- {generationSteps[currentStep] && (
965
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
966
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Metrics</h4>
967
- <div className="space-y-1 text-xs">
968
- <div className="flex justify-between">
969
- <span className="text-gray-400">Step:</span>
970
- <span className="text-white">{currentStep + 1} / {generationSteps.length}</span>
971
- </div>
972
- <div className="flex justify-between">
973
- <span className="text-gray-400">Token:</span>
974
- <span className="text-yellow-400 font-mono">&quot;{generationSteps[currentStep].token}&quot;</span>
975
- </div>
976
- <div className="flex justify-between">
977
- <span className="text-gray-400">Confidence:</span>
978
- <span className="text-green-400">{((generationSteps[currentStep].decision_path?.probability || 0.5) * 100).toFixed(0)}%</span>
979
- </div>
980
- <div className="flex justify-between">
981
- <span className="text-gray-400">Critical Layers:</span>
982
- <span className="text-red-400">{generationSteps[currentStep].decision_path?.critical_layers?.length || 0}</span>
983
- </div>
984
- </div>
985
- </div>
986
- )}
987
-
988
- {/* Tips */}
989
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
990
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
991
- <ul className="text-xs text-gray-400 space-y-1">
992
- <li>• Watch how layers change per token</li>
993
- <li>• Notice pattern consistency</li>
994
- <li>• Red lines show decision flow</li>
995
- </ul>
996
- </div>
997
- </div>
998
- </div>
999
- </div>
1000
- </div>
1001
- </div>
1002
- );
1003
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/DecisionPath3DFixed.tsx DELETED
@@ -1,437 +0,0 @@
1
- /**
2
- * Decision Path 3D Visualization - Fixed Version
3
- *
4
- * Shows the exact path through the neural network when generating tokens,
5
- * highlighting critical layers, attention patterns, and decision factors.
6
- * This is the core "Glass Box" visualization for the PhD thesis.
7
- *
8
- * @component
9
- */
10
-
11
- "use client";
12
-
13
- import { useRef, useState, useEffect, useMemo } from "react";
14
- import { Canvas, useFrame } from "@react-three/fiber";
15
- import { OrbitControls } from "@react-three/drei";
16
- import * as THREE from "three";
17
- import {
18
- GitBranch,
19
- Activity,
20
- Sparkles,
21
- Zap,
22
- Brain
23
- } from "lucide-react";
24
-
25
- interface LayerActivation {
26
- layer_index: number;
27
- attention_weights: number[][];
28
- hidden_state_norm: number;
29
- ffn_activation: number;
30
- top_attention_heads: number[];
31
- confidence: number;
32
- }
33
-
34
- interface DecisionPath {
35
- token: string;
36
- token_id: number;
37
- probability: number;
38
- layer_activations: LayerActivation[];
39
- attention_flow: Array<{
40
- from_layer: number;
41
- to_layer: number | string;
42
- strength: number;
43
- top_heads: number[];
44
- }>;
45
- alternatives: Array<{
46
- token: string;
47
- token_id: number;
48
- probability: number;
49
- }>;
50
- decision_factors: {
51
- attention_focus: number;
52
- semantic_alignment: number;
53
- syntactic_correctness: number;
54
- context_relevance: number;
55
- confidence: number;
56
- };
57
- critical_layers: number[];
58
- confidence_score: number;
59
- timestamp: number;
60
- }
61
-
62
- // Enhanced Layer Component with proper FFN visualization
63
- interface LayerProps {
64
- position: [number, number, number];
65
- layerIndex: number;
66
- isCritical: boolean;
67
- isActive: boolean;
68
- activation?: LayerActivation;
69
- }
70
-
71
- function Layer({ position, layerIndex, isCritical, isActive, activation }: LayerProps) {
72
- const meshRef = useRef<THREE.Mesh>(null);
73
- const ffnRef = useRef<THREE.Mesh>(null);
74
-
75
- useFrame((state) => {
76
- if (meshRef.current && isCritical) {
77
- const scale = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.1;
78
- meshRef.current.scale.set(scale, scale, scale);
79
- }
80
-
81
- if (ffnRef.current && activation) {
82
- // Pulse FFN based on activation strength
83
- const ffnScale = 1 + (activation.ffn_activation * 0.2);
84
- ffnRef.current.scale.set(1, ffnScale, 1);
85
- }
86
- });
87
-
88
- const baseColor = isCritical ? "#ff6b6b" : isActive ? "#4ecdc4" : "#2d3748";
89
- const ffnColor = isCritical ? "#e91e63" : isActive ? "#9c27b0" : "#6b46c1";
90
-
91
- return (
92
- <group position={position}>
93
- {/* Main attention layer */}
94
- <mesh ref={meshRef}>
95
- <boxGeometry args={[4, 0.3, 2]} />
96
- <meshStandardMaterial
97
- color={baseColor}
98
- emissive={isCritical ? baseColor : "#000000"}
99
- emissiveIntensity={isCritical ? 0.3 : 0}
100
- metalness={0.6}
101
- roughness={0.3}
102
- />
103
- </mesh>
104
-
105
- {/* FFN Component - positioned behind */}
106
- <group position={[0, 0, -1.5]}>
107
- <mesh ref={ffnRef}>
108
- <boxGeometry args={[3, 0.2, 0.8]} />
109
- <meshStandardMaterial
110
- color={ffnColor}
111
- emissive={ffnColor}
112
- emissiveIntensity={isActive ? 0.2 : 0.1}
113
- metalness={0.7}
114
- roughness={0.3}
115
- />
116
- </mesh>
117
- </group>
118
-
119
- {/* Attention heads visualization - small cubes */}
120
- {isActive && (
121
- <group position={[0, 0, 1.2]}>
122
- {Array.from({ length: 16 }).map((_, i) => (
123
- <mesh key={i} position={[(i % 4 - 1.5) * 0.3, 0, Math.floor(i / 4) * 0.2 - 0.3]}>
124
- <boxGeometry args={[0.15, 0.1, 0.15]} />
125
- <meshStandardMaterial
126
- color={activation?.top_attention_heads?.includes(i) ? "#ffd93d" : "#4a5568"}
127
- emissive={activation?.top_attention_heads?.includes(i) ? "#ffd93d" : "#000000"}
128
- emissiveIntensity={activation?.top_attention_heads?.includes(i) ? 0.5 : 0}
129
- />
130
- </mesh>
131
- ))}
132
- </group>
133
- )}
134
- </group>
135
- );
136
- }
137
-
138
- // Simple scene
139
- function DecisionPathScene({ decisionPath }: { decisionPath: DecisionPath | null }) {
140
- const numLayers = 20;
141
- const layerSpacing = 3.5;
142
-
143
- return (
144
- <>
145
- <ambientLight intensity={0.5} />
146
- <pointLight position={[10, 10, 10]} intensity={1} />
147
- <directionalLight position={[0, 10, 5]} intensity={0.5} />
148
-
149
- {/* Input Layer */}
150
- <mesh position={[0, -5, 0]}>
151
- <boxGeometry args={[5, 0.2, 2]} />
152
- <meshStandardMaterial color="#10b981" />
153
- </mesh>
154
-
155
- {/* Transformer Layers */}
156
- {Array.from({ length: numLayers }).map((_, i) => {
157
- const isCritical = decisionPath?.critical_layers?.includes(i) || false;
158
- const activation = decisionPath?.layer_activations?.[i];
159
-
160
- return (
161
- <Layer
162
- key={i}
163
- position={[0, i * layerSpacing, 0]}
164
- layerIndex={i}
165
- isCritical={isCritical}
166
- isActive={!!activation}
167
- activation={activation}
168
- />
169
- );
170
- })}
171
-
172
- {/* Output Layer */}
173
- <mesh position={[0, numLayers * layerSpacing + 5, 0]}>
174
- <boxGeometry args={[5, 0.2, 2]} />
175
- <meshStandardMaterial color="#f59e0b" />
176
- </mesh>
177
-
178
- {/* Connection lines - simplified for now */}
179
- {decisionPath && decisionPath.critical_layers && decisionPath.critical_layers.map((layerIdx, idx) => {
180
- const startY = layerIdx * layerSpacing;
181
- const endY = idx < decisionPath.critical_layers.length - 1
182
- ? decisionPath.critical_layers[idx + 1] * layerSpacing
183
- : numLayers * layerSpacing + 5;
184
-
185
- const points = [];
186
- points.push(new THREE.Vector3(0, startY, 0));
187
- points.push(new THREE.Vector3(0, endY, 0));
188
-
189
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
190
-
191
- return (
192
- <primitive
193
- key={`line-${idx}`}
194
- object={new THREE.Line(
195
- geometry,
196
- new THREE.LineBasicMaterial({ color: 0xff6b6b, linewidth: 3 })
197
- )}
198
- />
199
- );
200
- })}
201
-
202
- <gridHelper args={[100, 100, 0x444444, 0x222222]} />
203
- </>
204
- );
205
- }
206
-
207
- export default function DecisionPath3DFixed() {
208
- const [decisionPath, setDecisionPath] = useState<DecisionPath | null>(null);
209
- const [isConnected, setIsConnected] = useState(false);
210
- const [isAnalyzing, setIsAnalyzing] = useState(false);
211
- const [mounted, setMounted] = useState(false);
212
- const [modelLoading, setModelLoading] = useState(true);
213
- const [loadingProgress, setLoadingProgress] = useState(0);
214
- const [loadingMessage, setLoadingMessage] = useState("Initializing...");
215
- const wsRef = useRef<WebSocket | null>(null);
216
- const [prompt, setPrompt] = useState("def quicksort(arr):");
217
-
218
- useEffect(() => {
219
- setMounted(true);
220
- }, []);
221
-
222
- useEffect(() => {
223
- if (!mounted) return;
224
-
225
- const connectToService = () => {
226
- try {
227
- const ws = new WebSocket('ws://localhost:8769');
228
-
229
- ws.onopen = () => {
230
- console.log('[DecisionPath3D] Connected to service');
231
- setIsConnected(true);
232
- wsRef.current = ws;
233
- // Don't immediately set as ready - wait for model_ready or loading_progress messages
234
- };
235
-
236
- ws.onmessage = (event) => {
237
- console.log('[DecisionPath3D] Raw message received:', event.data);
238
- const data = JSON.parse(event.data);
239
- console.log('[DecisionPath3D] Parsed message type:', data.type);
240
- console.log('[DecisionPath3D] Message data:', data);
241
-
242
- if (data.type === 'decision_path') {
243
- console.log('[DecisionPath3D] Setting decision path with critical layers:', data.data?.critical_layers);
244
- setDecisionPath(data.data);
245
- } else if (data.type === 'analysis_complete') {
246
- console.log('[DecisionPath3D] Analysis complete');
247
- setIsAnalyzing(false);
248
- } else if (data.type === 'loading_progress') {
249
- setLoadingProgress(data.progress);
250
- setLoadingMessage(data.message);
251
- if (data.progress === 100) {
252
- setModelLoading(false);
253
- }
254
- } else if (data.type === 'model_ready') {
255
- setModelLoading(false);
256
- setLoadingProgress(100);
257
- setLoadingMessage("Model ready!");
258
- } else if (data.type === 'loading_error') {
259
- setModelLoading(false);
260
- setLoadingMessage(`Error: ${data.message}`);
261
- }
262
- };
263
-
264
- ws.onerror = (error) => {
265
- console.log('[DecisionPath3D] Service not available');
266
- setIsConnected(false);
267
- };
268
-
269
- ws.onclose = () => {
270
- console.log('[DecisionPath3D] Disconnected');
271
- setIsConnected(false);
272
- wsRef.current = null;
273
- };
274
- } catch (error) {
275
- console.log('[DecisionPath3D] Connection failed');
276
- setIsConnected(false);
277
- }
278
- };
279
-
280
- connectToService();
281
-
282
- return () => {
283
- if (wsRef.current) {
284
- wsRef.current.close();
285
- }
286
- };
287
- }, [mounted]);
288
-
289
- const startAnalysis = () => {
290
- console.log('[DecisionPath3D] Start analysis clicked');
291
- console.log('[DecisionPath3D] WebSocket state:', wsRef.current?.readyState);
292
- console.log('[DecisionPath3D] Is connected:', isConnected);
293
-
294
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
295
- console.log('[DecisionPath3D] Sending analyze request with prompt:', prompt);
296
- setIsAnalyzing(true);
297
- wsRef.current.send(JSON.stringify({
298
- type: 'analyze',
299
- prompt: prompt
300
- }));
301
- } else {
302
- console.log('[DecisionPath3D] WebSocket not ready, state:', wsRef.current?.readyState);
303
- }
304
- };
305
-
306
- if (!mounted) {
307
- return (
308
- <div className="bg-gray-900 rounded-xl p-6 h-[900px]">
309
- <div className="flex items-center justify-center h-full">
310
- <div className="text-gray-400">Loading 3D visualization...</div>
311
- </div>
312
- </div>
313
- );
314
- }
315
-
316
- return (
317
- <div className="bg-gray-900 rounded-xl p-6 h-[900px]">
318
- {/* Header */}
319
- <div className="flex items-center justify-between mb-4">
320
- <div>
321
- <h2 className="text-2xl font-bold flex items-center gap-2">
322
- <GitBranch className="w-6 h-6 text-yellow-400" />
323
- Decision Path Visualization
324
- </h2>
325
- <p className="text-gray-400 mt-1">
326
- See exactly how the model makes its decisions - the Glass Box view
327
- </p>
328
- </div>
329
-
330
- <div className="flex items-center gap-4">
331
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
332
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-yellow-900/30 text-yellow-400'
333
- }`}>
334
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
335
- {isConnected ? 'Connected' : 'Disconnected'}
336
- </div>
337
- </div>
338
- </div>
339
-
340
- {/* Controls */}
341
- <div className="bg-gray-800 rounded-lg p-4 mb-4">
342
- <div className="flex items-center gap-4">
343
- <input
344
- type="text"
345
- value={prompt}
346
- onChange={(e) => setPrompt(e.target.value)}
347
- className="flex-1 px-3 py-2 bg-gray-900 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
348
- placeholder="Enter code to analyze..."
349
- />
350
- <button
351
- onClick={startAnalysis}
352
- disabled={!isConnected || isAnalyzing}
353
- className="px-6 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors disabled:opacity-50 flex items-center gap-2"
354
- >
355
- {isAnalyzing ? (
356
- <>
357
- <Activity className="w-4 h-4 animate-spin" />
358
- Analyzing...
359
- </>
360
- ) : (
361
- <>
362
- <Sparkles className="w-4 h-4" />
363
- Analyze Decision Path
364
- </>
365
- )}
366
- </button>
367
- </div>
368
- </div>
369
-
370
- {/* 3D Canvas */}
371
- <div className="h-[700px] bg-black rounded-lg relative">
372
- {modelLoading ? (
373
- <div className="flex flex-col items-center justify-center h-full">
374
- <div className="text-white mb-4">
375
- <Brain className="w-16 h-16 animate-pulse" />
376
- </div>
377
- <div className="text-xl text-white mb-2">Loading Model</div>
378
- <div className="text-sm text-gray-400 mb-4">{loadingMessage}</div>
379
- <div className="w-64 h-2 bg-gray-700 rounded-full overflow-hidden">
380
- <div
381
- className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-500"
382
- style={{ width: `${loadingProgress}%` }}
383
- />
384
- </div>
385
- <div className="text-xs text-gray-500 mt-2">{loadingProgress}%</div>
386
- <div className="text-xs text-gray-500 mt-4">356M parameters • 20 layers • 16 attention heads</div>
387
- </div>
388
- ) : (
389
- <Canvas camera={{ position: [-40, 50, 40], fov: 50 }}>
390
- <DecisionPathScene decisionPath={decisionPath} />
391
- <OrbitControls
392
- enablePan={true}
393
- enableZoom={true}
394
- enableRotate={true}
395
- target={[0, 35, 0]}
396
- />
397
- </Canvas>
398
- )}
399
-
400
- {/* Legend */}
401
- <div className="absolute top-4 right-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs">
402
- <div className="font-semibold text-white mb-2">Decision Path</div>
403
- <div className="space-y-1">
404
- <div className="flex items-center gap-2">
405
- <div className="w-3 h-3 bg-red-500 rounded"></div>
406
- <span className="text-gray-300">Critical Layers</span>
407
- </div>
408
- <div className="flex items-center gap-2">
409
- <div className="w-3 h-3 bg-teal-500 rounded"></div>
410
- <span className="text-gray-300">Active Layers</span>
411
- </div>
412
- <div className="flex items-center gap-2">
413
- <div className="w-3 h-3 bg-yellow-500 rounded"></div>
414
- <span className="text-gray-300">Top Attention Heads</span>
415
- </div>
416
- <div className="flex items-center gap-2">
417
- <Zap className="w-3 h-3 text-yellow-400" />
418
- <span className="text-gray-300">Decision Flow</span>
419
- </div>
420
- </div>
421
- </div>
422
-
423
- {/* Info Panel */}
424
- {decisionPath && (
425
- <div className="absolute bottom-4 left-4 bg-gray-800/90 backdrop-blur rounded-lg p-3 text-xs max-w-xs">
426
- <div className="font-semibold text-white mb-2">Current Decision</div>
427
- <div className="space-y-1 text-gray-300">
428
- <div>Token: <span className="text-yellow-400">{decisionPath.token}</span></div>
429
- <div>Confidence: <span className="text-green-400">{(decisionPath.confidence_score * 100).toFixed(0)}%</span></div>
430
- <div>Critical Layers: <span className="text-red-400">{decisionPath.critical_layers.join(", ")}</span></div>
431
- </div>
432
- </div>
433
- )}
434
- </div>
435
- </div>
436
- );
437
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/DecisionPath3DSimple.tsx DELETED
@@ -1,57 +0,0 @@
1
- /**
2
- * Simplified Decision Path 3D Test
3
- * Testing basic Three.js rendering
4
- */
5
-
6
- "use client";
7
-
8
- import { useRef, useState, useEffect } from "react";
9
- import { Canvas } from "@react-three/fiber";
10
- import { OrbitControls, Box, Text } from "@react-three/drei";
11
- import * as THREE from "three";
12
-
13
- function SimpleScene() {
14
- const meshRef = useRef<THREE.Mesh>(null);
15
-
16
- return (
17
- <>
18
- <ambientLight intensity={0.5} />
19
- <pointLight position={[10, 10, 10]} />
20
-
21
- <Box ref={meshRef} position={[0, 0, 0]} args={[2, 2, 2]}>
22
- <meshStandardMaterial color="orange" />
23
- </Box>
24
-
25
- <Text position={[0, 3, 0]} fontSize={0.5} color="white">
26
- Test 3D Rendering
27
- </Text>
28
-
29
- <gridHelper args={[10, 10]} />
30
- </>
31
- );
32
- }
33
-
34
- export default function DecisionPath3DSimple() {
35
- const [mounted, setMounted] = useState(false);
36
-
37
- useEffect(() => {
38
- setMounted(true);
39
- }, []);
40
-
41
- if (!mounted) {
42
- return (
43
- <div className="h-[400px] bg-black rounded-lg flex items-center justify-center">
44
- <div className="text-gray-400">Loading...</div>
45
- </div>
46
- );
47
- }
48
-
49
- return (
50
- <div className="h-[400px] bg-black rounded-lg">
51
- <Canvas camera={{ position: [5, 5, 5], fov: 50 }}>
52
- <SimpleScene />
53
- <OrbitControls />
54
- </Canvas>
55
- </div>
56
- );
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/Hero.tsx DELETED
@@ -1,74 +0,0 @@
1
- "use client";
2
-
3
- import { motion } from "framer-motion";
4
- import { Eye, Lock, Zap } from "lucide-react";
5
-
6
- export default function Hero() {
7
- return (
8
- <section className="relative overflow-hidden bg-gradient-to-b from-gray-900 to-gray-950 py-20">
9
- <div className="absolute inset-0 bg-grid-white/[0.02] bg-[size:50px_50px]" />
10
-
11
- <div className="container mx-auto px-4 relative z-10">
12
- <motion.div
13
- initial={{ opacity: 0, y: 20 }}
14
- animate={{ opacity: 1, y: 0 }}
15
- transition={{ duration: 0.5 }}
16
- className="text-center max-w-4xl mx-auto"
17
- >
18
- <h1 className="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
19
- From Black Box to Glass Box
20
- </h1>
21
-
22
- <p className="text-xl text-gray-300 mb-8">
23
- Visualise LLM code generation in real-time. See attention patterns,
24
- activation flows, and expert routing to understand how AI writes code.
25
- </p>
26
-
27
- <div className="flex justify-center gap-4 mb-12">
28
- <button className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium">
29
- Try Live Demo
30
- </button>
31
- <button className="px-8 py-3 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors font-medium">
32
- Read Paper
33
- </button>
34
- </div>
35
-
36
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-16">
37
- <motion.div
38
- whileHover={{ scale: 1.05 }}
39
- className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700"
40
- >
41
- <Eye className="w-10 h-10 text-blue-500 mb-4 mx-auto" />
42
- <h3 className="text-lg font-semibold mb-2">See Inside</h3>
43
- <p className="text-gray-400 text-sm">
44
- Visualise attention maps and activation patterns as models generate code
45
- </p>
46
- </motion.div>
47
-
48
- <motion.div
49
- whileHover={{ scale: 1.05 }}
50
- className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700"
51
- >
52
- <Lock className="w-10 h-10 text-purple-500 mb-4 mx-auto" />
53
- <h3 className="text-lg font-semibold mb-2">Build Trust</h3>
54
- <p className="text-gray-400 text-sm">
55
- Detect hallucinations and API misuses before deploying AI-generated code
56
- </p>
57
- </motion.div>
58
-
59
- <motion.div
60
- whileHover={{ scale: 1.05 }}
61
- className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700"
62
- >
63
- <Zap className="w-10 h-10 text-yellow-500 mb-4 mx-auto" />
64
- <h3 className="text-lg font-semibold mb-2">Real-time Analysis</h3>
65
- <p className="text-gray-400 text-sm">
66
- Stream traces live from your IDE or CI/CD pipeline
67
- </p>
68
- </motion.div>
69
- </div>
70
- </motion.div>
71
- </div>
72
- </section>
73
- );
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/LocalControlPanel.tsx DELETED
@@ -1,326 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect } from 'react';
4
- import { Play, Pause, RefreshCw, Terminal, Activity, Wifi, WifiOff, Zap, Server, Database } from 'lucide-react';
5
- import { usePathname } from 'next/navigation';
6
- import { getApiUrl, getLegacyWsUrl } from '@/lib/config';
7
-
8
- interface ServiceStatus {
9
- modelService: 'running' | 'stopped' | 'checking' | 'error';
10
- websocket: 'running' | 'stopped' | 'checking' | 'error';
11
- frontend: 'running' | 'stopped' | 'checking' | 'error';
12
- }
13
-
14
- interface Demo {
15
- id: string;
16
- name: string;
17
- prompt: string;
18
- description: string;
19
- }
20
-
21
- export function LocalControlPanel({ hideOnInspector = false }: { hideOnInspector?: boolean }) {
22
- const [serviceStatus, setServiceStatus] = useState<ServiceStatus>({
23
- modelService: 'checking',
24
- websocket: 'checking',
25
- frontend: 'running'
26
- });
27
-
28
- const [demos, setDemos] = useState<Demo[]>([]);
29
- const [isGenerating, setIsGenerating] = useState(false);
30
- const [generatingDemoId, setGeneratingDemoId] = useState<string | null>(null);
31
- const [deviceInfo, setDeviceInfo] = useState<string>('');
32
- const [modelLoaded, setModelLoaded] = useState(false);
33
- const [minimized, setMinimized] = useState(false);
34
- const [shouldHide, setShouldHide] = useState(false);
35
-
36
- // Only show in development mode
37
- const isDevelopment = process.env.NEXT_PUBLIC_MODE === 'local' ||
38
- process.env.NODE_ENV === 'development';
39
-
40
- // Listen for activeView changes
41
- useEffect(() => {
42
- const handleViewChange = (event: CustomEvent) => {
43
- setShouldHide(event.detail.view === 'inspector');
44
- };
45
-
46
- window.addEventListener('viewChanged', handleViewChange as EventListener);
47
- return () => window.removeEventListener('viewChanged', handleViewChange as EventListener);
48
- }, []);
49
-
50
- useEffect(() => {
51
- if (!isDevelopment || shouldHide) return;
52
-
53
- const checkServices = async () => {
54
- // Check model service
55
- try {
56
- const response = await fetch(`${getApiUrl()}/health`);
57
- const data = await response.json();
58
- setServiceStatus(prev => ({
59
- ...prev,
60
- modelService: data.status === 'healthy' ? 'running' : 'error'
61
- }));
62
- setDeviceInfo(data.device || 'Unknown');
63
- setModelLoaded(data.model_loaded || false);
64
- } catch {
65
- setServiceStatus(prev => ({ ...prev, modelService: 'stopped' }));
66
- setModelLoaded(false);
67
- }
68
-
69
- // Check WebSocket
70
- try {
71
- const ws = new WebSocket(getLegacyWsUrl());
72
- ws.onopen = () => {
73
- setServiceStatus(prev => ({ ...prev, websocket: 'running' }));
74
- ws.close();
75
- };
76
- ws.onerror = () => {
77
- setServiceStatus(prev => ({ ...prev, websocket: 'stopped' }));
78
- };
79
- ws.onclose = () => {
80
- // Connection closed event
81
- };
82
- } catch {
83
- setServiceStatus(prev => ({ ...prev, websocket: 'stopped' }));
84
- }
85
- };
86
-
87
- const loadDemos = async () => {
88
- try {
89
- const response = await fetch(`${getApiUrl()}/demos`);
90
- const data = await response.json();
91
- setDemos(data.demos || []);
92
- } catch (error) {
93
- console.error('Failed to load demos:', error);
94
- setDemos([]);
95
- }
96
- };
97
-
98
- checkServices();
99
- loadDemos();
100
- const interval = setInterval(checkServices, 5000); // Check every 5 seconds
101
- return () => clearInterval(interval);
102
- }, [isDevelopment, shouldHide]);
103
-
104
- if (!isDevelopment || shouldHide) {
105
- return null;
106
- }
107
-
108
-
109
- const runDemo = async (demoId: string) => {
110
- if (isGenerating) return;
111
-
112
- // Dispatch prompt variations IMMEDIATELY for PromptDiff
113
- const demoPrompts = {
114
- fibonacci: {
115
- promptA: "def fibonacci(n):\n '''Calculate fibonacci number'''",
116
- promptB: "def fibonacci(n):\n '''Calculate fibonacci number with memoization'''"
117
- },
118
- quicksort: {
119
- promptA: "def quicksort(arr):\n '''Sort array using quicksort'''",
120
- promptB: "def quicksort(arr):\n '''Sort array using optimized quicksort with pivot selection'''"
121
- },
122
- stack: {
123
- promptA: "class Stack:\n '''Simple stack implementation'''",
124
- promptB: "class Stack:\n '''Thread-safe stack implementation with size limit'''"
125
- },
126
- binary_search: {
127
- promptA: "def binary_search(arr, target):\n '''Find target in sorted array'''",
128
- promptB: "def binary_search(arr, target):\n '''Find target in sorted array using iterative approach'''"
129
- }
130
- };
131
-
132
- if (demoId in demoPrompts) {
133
- window.dispatchEvent(new CustomEvent('demo-prompts-selected', {
134
- detail: demoPrompts[demoId as keyof typeof demoPrompts]
135
- }));
136
- }
137
-
138
- // Also dispatch the primary prompt IMMEDIATELY for ConfidenceMeter
139
- const demoPrimaryPrompts = {
140
- fibonacci: "def fibonacci(n):\n '''Calculate fibonacci number'''",
141
- quicksort: "def quicksort(arr):\n '''Sort array using quicksort'''",
142
- stack: "class Stack:\n '''Simple stack implementation'''",
143
- binary_search: "def binary_search(arr, target):\n '''Find target in sorted array'''"
144
- };
145
-
146
- if (demoId in demoPrimaryPrompts) {
147
- window.dispatchEvent(new CustomEvent('demo-prompt-selected', {
148
- detail: { prompt: demoPrimaryPrompts[demoId as keyof typeof demoPrimaryPrompts], demoId }
149
- }));
150
- }
151
-
152
- // Dispatch event to indicate demo is starting (for clearing tokens)
153
- window.dispatchEvent(new CustomEvent('demo-starting', {
154
- detail: { demoId }
155
- }));
156
-
157
- setIsGenerating(true);
158
- setGeneratingDemoId(demoId);
159
- try {
160
- const response = await fetch(`${getApiUrl()}/demos/run`, {
161
- method: 'POST',
162
- headers: {
163
- 'Content-Type': 'application/json',
164
- },
165
- body: JSON.stringify({ demo_id: demoId })
166
- });
167
-
168
- if (!response.ok) {
169
- throw new Error(`HTTP error! status: ${response.status}`);
170
- }
171
-
172
- const data = await response.json();
173
- console.log('Demo completed:', data);
174
-
175
- // Dispatch custom event to notify AttentionExplorer
176
- window.dispatchEvent(new CustomEvent('demo-completed', { detail: data }));
177
- } catch (error) {
178
- console.error('Failed to run demo:', error);
179
- alert(`Failed to run demo: ${error}`);
180
- } finally {
181
- setIsGenerating(false);
182
- setGeneratingDemoId(null);
183
- }
184
- };
185
-
186
- if (minimized) {
187
- return (
188
- <div className="fixed bottom-4 right-4 bg-gray-900 border border-gray-700 rounded-lg p-2 cursor-pointer"
189
- onClick={() => setMinimized(false)}>
190
- <Terminal className="w-5 h-5" />
191
- </div>
192
- );
193
- }
194
-
195
- return (
196
- <div className="fixed bottom-4 right-4 bg-gray-900 border border-gray-700 rounded-lg p-4 w-80 shadow-2xl z-50">
197
- <div className="flex items-center justify-between mb-3">
198
- <h3 className="text-lg font-bold flex items-center gap-2">
199
- <Terminal className="w-5 h-5 text-blue-400" />
200
- Local Development
201
- </h3>
202
- <button
203
- onClick={() => setMinimized(true)}
204
- className="text-gray-400 hover:text-white transition-colors"
205
- >
206
- ×
207
- </button>
208
- </div>
209
-
210
- {/* Service Status */}
211
- <div className="space-y-2 mb-4">
212
- <ServiceIndicator name="Model Service" status={serviceStatus.modelService} />
213
- <ServiceIndicator name="WebSocket" status={serviceStatus.websocket} />
214
- <ServiceIndicator name="Frontend" status={serviceStatus.frontend} />
215
- </div>
216
-
217
- {/* Device Info */}
218
- {deviceInfo && (
219
- <div className="mb-4 p-2 bg-gray-800 rounded text-xs">
220
- <div className="flex items-center gap-2">
221
- <Server className="w-3 h-3 text-gray-400" />
222
- <span className="text-gray-400">Device:</span>
223
- <span className="text-white">{deviceInfo}</span>
224
- </div>
225
- <div className="flex items-center gap-2 mt-1">
226
- <Database className="w-3 h-3 text-gray-400" />
227
- <span className="text-gray-400">Model:</span>
228
- <span className={modelLoaded ? "text-green-400" : "text-yellow-400"}>
229
- {modelLoaded ? "Loaded" : "Loading..."}
230
- </span>
231
- </div>
232
- </div>
233
- )}
234
-
235
- {/* Quick Actions */}
236
- <div className="space-y-2 mb-4">
237
- <button
238
- onClick={() => {
239
- // checkServices and loadDemos are auto-refreshed
240
- // Manual refresh removed to fix scope issue
241
- }}
242
- className="w-full px-3 py-2 bg-gray-800 hover:bg-gray-700 rounded flex items-center justify-center gap-2 transition-colors"
243
- >
244
- <RefreshCw className="w-4 h-4" />
245
- Refresh Status
246
- </button>
247
- </div>
248
-
249
- {/* Demo Runners */}
250
- <div className="space-y-2">
251
- <h4 className="text-sm font-semibold text-gray-400">Run Demos</h4>
252
- {demos.length > 0 ? (
253
- demos.map(demo => {
254
- const isThisDemoGenerating = generatingDemoId === demo.id;
255
- return (
256
- <button
257
- key={demo.id}
258
- onClick={() => runDemo(demo.id)}
259
- disabled={isGenerating || serviceStatus.modelService !== 'running'}
260
- className={`w-full px-3 py-2 rounded text-sm flex items-center gap-2 transition-colors ${
261
- isThisDemoGenerating
262
- ? 'bg-blue-700 cursor-wait'
263
- : isGenerating
264
- ? 'bg-gray-700 cursor-not-allowed'
265
- : 'bg-blue-600 hover:bg-blue-700'
266
- } ${serviceStatus.modelService !== 'running' ? 'disabled:bg-gray-700 disabled:cursor-not-allowed' : ''}`}
267
- >
268
- {isThisDemoGenerating ? (
269
- <>
270
- <RefreshCw className="w-3 h-3 animate-spin" />
271
- <span>Generating code...</span>
272
- </>
273
- ) : (
274
- <>
275
- <Play className="w-3 h-3" />
276
- <span>{demo.name}</span>
277
- </>
278
- )}
279
- </button>
280
- );
281
- })
282
- ) : (
283
- <div className="text-xs text-gray-500 text-center py-2">
284
- No demos available
285
- </div>
286
- )}
287
- </div>
288
-
289
- {/* Generation Status */}
290
- {isGenerating && (
291
- <div className="mt-4 p-2 bg-blue-900/30 border border-blue-700 rounded">
292
- <div className="flex items-center gap-2 text-sm text-blue-400">
293
- <Zap className="w-4 h-4 animate-pulse" />
294
- Generating code...
295
- </div>
296
- </div>
297
- )}
298
- </div>
299
- );
300
- }
301
-
302
- function ServiceIndicator({ name, status }: { name: string; status: string }) {
303
- const colors = {
304
- running: 'text-green-400',
305
- stopped: 'text-red-400',
306
- checking: 'text-yellow-400',
307
- error: 'text-red-400'
308
- };
309
-
310
- const icons = {
311
- running: <Wifi className="w-3 h-3 inline mr-1" />,
312
- stopped: <WifiOff className="w-3 h-3 inline mr-1" />,
313
- checking: <Activity className="w-3 h-3 inline mr-1 animate-pulse" />,
314
- error: <WifiOff className="w-3 h-3 inline mr-1" />
315
- };
316
-
317
- return (
318
- <div className="flex items-center justify-between p-2 bg-gray-800 rounded">
319
- <span className="text-sm">{name}</span>
320
- <span className={`text-xs ${colors[status as keyof typeof colors]} flex items-center`}>
321
- {icons[status as keyof typeof icons]}
322
- {status}
323
- </span>
324
- </div>
325
- );
326
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/ModelArchitecture3D.tsx DELETED
@@ -1,712 +0,0 @@
1
- /**
2
- * 3D Model Architecture Visualization
3
- *
4
- * Interactive 3D visualization of the transformer model architecture,
5
- * showing layers, attention heads, and data flow with real values.
6
- * Inspired by neural network architecture diagrams.
7
- *
8
- * @component
9
- */
10
-
11
- "use client";
12
-
13
- import { useRef, useState, useEffect, Suspense } from "react";
14
- import { Canvas, useFrame, useThree } from "@react-three/fiber";
15
- import { getApiUrl } from "@/lib/config";
16
- import {
17
- OrbitControls,
18
- Text,
19
- Box,
20
- Plane,
21
- Line,
22
- Billboard,
23
- PerspectiveCamera,
24
- Environment,
25
- Float
26
- } from "@react-three/drei";
27
- import * as THREE from "three";
28
- import {
29
- Brain,
30
- Layers,
31
- Activity,
32
- Zap,
33
- Eye,
34
- GitBranch,
35
- Maximize2,
36
- Move3D,
37
- HelpCircle,
38
- X,
39
- Info
40
- } from "lucide-react";
41
-
42
- // Layer component representing a transformer layer
43
- function TransformerLayer({
44
- position,
45
- layerIndex,
46
- attentionValues,
47
- onClick,
48
- isActive
49
- }: {
50
- position: [number, number, number];
51
- layerIndex: number;
52
- attentionValues?: number[][];
53
- onClick?: () => void;
54
- isActive?: boolean;
55
- }) {
56
- const meshRef = useRef<THREE.Mesh>(null);
57
- const [hovered, setHovered] = useState(false);
58
-
59
- useFrame((state) => {
60
- if (meshRef.current) {
61
- // NO floating animation - layers stay at their exact positions
62
- // meshRef.current.position.y = position[1]; // Keep at exact position
63
-
64
- // Only pulse when active
65
- if (isActive) {
66
- const scale = 1 + Math.sin(state.clock.elapsedTime * 3) * 0.05;
67
- meshRef.current.scale.set(scale, scale, scale);
68
- }
69
- }
70
- });
71
-
72
- return (
73
- <group position={position}>
74
- {/* Main layer box */}
75
- <Box
76
- ref={meshRef}
77
- args={[4, 0.3, 3]}
78
- onClick={onClick}
79
- onPointerOver={() => setHovered(true)}
80
- onPointerOut={() => setHovered(false)}
81
- >
82
- <meshStandardMaterial
83
- color={isActive ? "#3b82f6" : hovered ? "#4b5563" : "#1f2937"}
84
- emissive={isActive ? "#3b82f6" : "#000000"}
85
- emissiveIntensity={isActive ? 0.2 : 0}
86
- metalness={0.8}
87
- roughness={0.2}
88
- transparent
89
- opacity={0.9}
90
- />
91
- </Box>
92
-
93
- {/* Layer label */}
94
- <Text
95
- position={[0, 0.3, 0]}
96
- fontSize={0.15}
97
- color="white"
98
- anchorX="center"
99
- anchorY="middle"
100
- >
101
- Layer {layerIndex}
102
- </Text>
103
-
104
- {/* Debug: Show actual position */}
105
- <Text
106
- position={[2.5, 0, 0]}
107
- fontSize={0.08}
108
- color="#666"
109
- anchorX="center"
110
- >
111
- Y: {position[1].toFixed(1)}
112
- </Text>
113
-
114
- {/* Attention heads visualization */}
115
- <group position={[0, 0, 1.8]}>
116
- {Array.from({ length: 16 }).map((_, i) => (
117
- <Box
118
- key={i}
119
- position={[(i % 4 - 1.5) * 0.25, 0, Math.floor(i / 4) * 0.25 - 0.375]}
120
- args={[0.2, 0.1, 0.2]}
121
- >
122
- <meshStandardMaterial
123
- color={`hsl(${120 + i * 10}, 70%, 50%)`}
124
- emissive={`hsl(${120 + i * 10}, 70%, 50%)`}
125
- emissiveIntensity={0.3}
126
- />
127
- </Box>
128
- ))}
129
- <Text
130
- position={[0, 0.2, 0]}
131
- fontSize={0.1}
132
- color="#9ca3af"
133
- anchorX="center"
134
- >
135
- 16 Attention Heads
136
- </Text>
137
- </group>
138
-
139
- {/* FFN visualization */}
140
- <group position={[0, 0, -1.8]}>
141
- <Box args={[3, 0.1, 0.5]}>
142
- <meshStandardMaterial
143
- color="#8b5cf6"
144
- emissive="#8b5cf6"
145
- emissiveIntensity={0.2}
146
- metalness={0.6}
147
- roughness={0.3}
148
- />
149
- </Box>
150
- <Text
151
- position={[0, 0.15, 0]}
152
- fontSize={0.1}
153
- color="#9ca3af"
154
- anchorX="center"
155
- >
156
- FFN (4096d)
157
- </Text>
158
- </group>
159
- </group>
160
- );
161
- }
162
-
163
- // Attention flow visualization
164
- function AttentionFlow({
165
- startPos,
166
- endPos,
167
- intensity = 1,
168
- color = "#3b82f6"
169
- }: {
170
- startPos: [number, number, number];
171
- endPos: [number, number, number];
172
- intensity?: number;
173
- color?: string;
174
- }) {
175
- const lineRef = useRef<THREE.BufferGeometry>(null);
176
-
177
- useFrame((state) => {
178
- // Animate the flow
179
- const time = state.clock.elapsedTime;
180
- // You could add particle effects here
181
- });
182
-
183
- const points = [
184
- new THREE.Vector3(...startPos),
185
- new THREE.Vector3(...endPos)
186
- ];
187
-
188
- return (
189
- <Line
190
- points={points}
191
- color={color}
192
- lineWidth={intensity * 2}
193
- transparent
194
- opacity={0.6}
195
- />
196
- );
197
- }
198
-
199
- // Token embedding visualization
200
- function TokenEmbedding({ position }: { position: [number, number, number] }) {
201
- const meshRef = useRef<THREE.Mesh>(null);
202
-
203
- useFrame((state) => {
204
- if (meshRef.current) {
205
- meshRef.current.rotation.y = state.clock.elapsedTime * 0.5;
206
- }
207
- });
208
-
209
- return (
210
- <group position={position}>
211
- <Box ref={meshRef} args={[5, 0.2, 2]}>
212
- <meshStandardMaterial
213
- color="#10b981"
214
- emissive="#10b981"
215
- emissiveIntensity={0.3}
216
- metalness={0.7}
217
- roughness={0.2}
218
- />
219
- </Box>
220
- <Text
221
- position={[0, 0.3, 0]}
222
- fontSize={0.15}
223
- color="white"
224
- anchorX="center"
225
- >
226
- Token Embeddings
227
- </Text>
228
- <Text
229
- position={[0, -0.3, 0]}
230
- fontSize={0.1}
231
- color="#9ca3af"
232
- anchorX="center"
233
- >
234
- 51,200 × 1,024
235
- </Text>
236
- </group>
237
- );
238
- }
239
-
240
- // Output layer visualization
241
- function OutputLayer({ position, modelInfo }: { position: [number, number, number]; modelInfo: { layers: number; heads: number; vocabSize: number; hiddenSize: number; totalParams: number } }) {
242
- const meshRef = useRef<THREE.Mesh>(null);
243
- const [probabilities, setProbabilities] = useState<number[]>([]);
244
-
245
- // Debug log position
246
- useEffect(() => {
247
- console.log(`OutputLayer rendered at position: [${position[0]}, ${position[1]}, ${position[2]}]`);
248
- }, [position]);
249
-
250
- useEffect(() => {
251
- // Simulate probability distribution
252
- const probs = Array.from({ length: modelInfo.heads }, () => Math.random());
253
- setProbabilities(probs);
254
- }, []);
255
-
256
- useFrame((state) => {
257
- if (meshRef.current) {
258
- meshRef.current.rotation.y = state.clock.elapsedTime * 0.3;
259
- }
260
- });
261
-
262
- return (
263
- <group position={position}>
264
- <Box ref={meshRef} args={[5, 0.2, 2]}>
265
- <meshStandardMaterial
266
- color="#f59e0b"
267
- emissive="#f59e0b"
268
- emissiveIntensity={0.3}
269
- metalness={0.7}
270
- roughness={0.2}
271
- />
272
- </Box>
273
- <Text
274
- position={[0, 0.3, 0]}
275
- fontSize={0.15}
276
- color="white"
277
- anchorX="center"
278
- >
279
- Output Probabilities
280
- </Text>
281
- <Text
282
- position={[0, -0.3, 0]}
283
- fontSize={0.1}
284
- color="#9ca3af"
285
- anchorX="center"
286
- >
287
- 51,200 tokens
288
- </Text>
289
-
290
- {/* Probability bars */}
291
- <group position={[0, 0.6, 0]}>
292
- {probabilities.slice(0, 10).map((prob, i) => (
293
- <Box
294
- key={i}
295
- position={[(i - 4.5) * 0.5, prob * 0.5, 0]}
296
- args={[0.3, prob, 0.1]}
297
- >
298
- <meshStandardMaterial
299
- color={`hsl(${prob * 120}, 70%, 50%)`}
300
- emissive={`hsl(${prob * 120}, 70%, 50%)`}
301
- emissiveIntensity={0.3}
302
- />
303
- </Box>
304
- ))}
305
- </group>
306
- </group>
307
- );
308
- }
309
-
310
- // Main 3D scene
311
- function Scene({ modelInfo }: { modelInfo: { layers: number; heads: number; vocabSize: number; hiddenSize: number; totalParams: number } }) {
312
- const [selectedLayer, setSelectedLayer] = useState<number | null>(null);
313
- const { camera } = useThree();
314
-
315
- // Model configuration from fetched data
316
- const numLayers = modelInfo.layers;
317
- const layerSpacing = 3.5; // Much larger spacing for clear separation
318
-
319
- // Calculate positions
320
- const outputYPosition = numLayers * layerSpacing + 5;
321
- const inputYPosition = -5;
322
-
323
- // Log to verify we're creating 20 layers
324
- useEffect(() => {
325
- console.log(`Creating ${numLayers} transformer layers`);
326
- console.log(`Layer spacing: ${layerSpacing}`);
327
- console.log(`Layer positions: `, Array.from({ length: numLayers }, (_, i) => i * layerSpacing));
328
- console.log(`Last layer (19) position: ${(numLayers - 1) * layerSpacing}`);
329
- console.log(`Output position calculated: ${outputYPosition}`);
330
- console.log(`Input position: ${inputYPosition}`);
331
- }, []);
332
-
333
- return (
334
- <>
335
- {/* Lighting */}
336
- <ambientLight intensity={0.3} />
337
- <pointLight position={[10, 10, 10]} intensity={1} />
338
- <pointLight position={[-10, -10, -10]} intensity={0.5} />
339
- <spotLight position={[0, 20, 0]} angle={0.3} penumbra={1} intensity={1} />
340
-
341
- {/* Token Embeddings (Input Layer) */}
342
- <TokenEmbedding position={[0, inputYPosition, 0]} />
343
-
344
- {/* Transformer Layers (0-19) */}
345
- {Array.from({ length: numLayers }).map((_, i) => {
346
- const yPosition = i * layerSpacing;
347
- return (
348
- <TransformerLayer
349
- key={`layer-${i}`}
350
- position={[0, yPosition, 0]}
351
- layerIndex={i}
352
- onClick={() => setSelectedLayer(i)}
353
- isActive={selectedLayer === i}
354
- />
355
- );
356
- })}
357
-
358
- {/* Output Layer (After Layer 19) */}
359
- <OutputLayer position={[0, outputYPosition, 0]} modelInfo={modelInfo} />
360
-
361
- {/* Attention flows between layers */}
362
- {Array.from({ length: numLayers - 1 }).map((_, i) => (
363
- <AttentionFlow
364
- key={`flow-${i}`}
365
- startPos={[0, i * layerSpacing + 0.3, 0]}
366
- endPos={[0, (i + 1) * layerSpacing - 0.3, 0]}
367
- intensity={0.5}
368
- />
369
- ))}
370
-
371
- {/* Flow from input to first layer */}
372
- <AttentionFlow
373
- startPos={[0, -4.5, 0]}
374
- endPos={[0, -0.3, 0]}
375
- intensity={0.5}
376
- color="#10b981"
377
- />
378
-
379
- {/* Flow from last layer to output */}
380
- <AttentionFlow
381
- startPos={[0, (numLayers - 1) * layerSpacing + 0.3, 0]}
382
- endPos={[0, outputYPosition - 0.5, 0]}
383
- intensity={0.5}
384
- color="#f59e0b"
385
- />
386
-
387
- {/* Grid for reference */}
388
- <gridHelper args={[150, 150, 0x444444, 0x222222]} />
389
-
390
- {/* Layer info display */}
391
- {selectedLayer !== null && (
392
- <Billboard position={[5, selectedLayer * layerSpacing, 0]}>
393
- <Text fontSize={0.2} color="white">
394
- Layer {selectedLayer} Details
395
- </Text>
396
- <Text position={[0, -0.3, 0]} fontSize={0.1} color="#9ca3af">
397
- • 16 attention heads
398
- </Text>
399
- <Text position={[0, -0.5, 0]} fontSize={0.1} color="#9ca3af">
400
- • 1024 hidden dimensions
401
- </Text>
402
- <Text position={[0, -0.7, 0]} fontSize={0.1} color="#9ca3af">
403
- • 4096 FFN dimensions
404
- </Text>
405
- <Text position={[0, -0.9, 0]} fontSize={0.1} color="#9ca3af">
406
- • Y Position: {(selectedLayer * layerSpacing).toFixed(1)}
407
- </Text>
408
- </Billboard>
409
- )}
410
-
411
- {/* Debug: Show output actual position */}
412
- <Billboard position={[8, outputYPosition, 0]}>
413
- <Text fontSize={0.15} color="#f59e0b">
414
- Output Y: {outputYPosition.toFixed(1)}
415
- </Text>
416
- </Billboard>
417
-
418
- {/* Debug: Show highest layer position */}
419
- <Billboard position={[-8, (numLayers - 1) * layerSpacing, 0]}>
420
- <Text fontSize={0.15} color="#3b82f6">
421
- Layer 19 Y: {((numLayers - 1) * layerSpacing).toFixed(1)}
422
- </Text>
423
- </Billboard>
424
- </>
425
- );
426
- }
427
-
428
- export default function ModelArchitecture3D() {
429
- const [viewMode, setViewMode] = useState<"perspective" | "top" | "side">("perspective");
430
- const [showLabels, setShowLabels] = useState(true);
431
- const [autoRotate, setAutoRotate] = useState(false); // Start without auto-rotate for better control
432
- const [showExplanation, setShowExplanation] = useState(false);
433
-
434
- // Fetch real model data
435
- const [modelInfo, setModelInfo] = useState({
436
- layers: 20,
437
- heads: 16,
438
- vocabSize: 51200,
439
- hiddenSize: 1024,
440
- totalParams: 356712448
441
- });
442
-
443
- useEffect(() => {
444
- fetch(`${getApiUrl()}/model/info`)
445
- .then(res => res.json())
446
- .then(data => {
447
- setModelInfo({
448
- layers: data.layers,
449
- heads: data.heads,
450
- vocabSize: data.vocabSize,
451
- hiddenSize: data.hiddenSize,
452
- totalParams: data.totalParams
453
- });
454
- })
455
- .catch(err => console.log('Using default model info'));
456
- }, []);
457
-
458
- // Generate contextual explanation for current visualization
459
- const generateExplanation = () => {
460
- return {
461
- title: "Transformer Architecture Visualization",
462
- description: `Interactive 3D view of a ${modelInfo.layers}-layer transformer model with ${modelInfo.heads} attention heads per layer.`,
463
- details: [
464
- {
465
- heading: "What is a Transformer?",
466
- content: `Transformers are the foundation of modern LLMs. They process text by passing it through multiple layers, each applying attention mechanisms to understand relationships between words. This model has ${modelInfo.layers} layers stacked vertically.`
467
- },
468
- {
469
- heading: "Reading the 3D Structure",
470
- content: `Bottom (green): Input embeddings convert text to vectors. Middle (blue): ${modelInfo.layers} transformer layers process information. Top (orange): Output layer predicts next tokens. The vertical flow shows how data moves through the network.`
471
- },
472
- {
473
- heading: "Attention Heads (Small Blocks)",
474
- content: `Each layer contains ${modelInfo.heads} attention heads (colored blocks). These heads learn different aspects of language: grammar, semantics, context, etc. They work in parallel, each focusing on different patterns.`
475
- },
476
- {
477
- heading: "Feed-Forward Networks (Purple)",
478
- content: `The purple blocks are FFN components with 4096 dimensions. After attention, these networks transform the data further, adding non-linearity and learning complex patterns.`
479
- },
480
- {
481
- heading: "Information Flow",
482
- content: `Data flows upward from input to output. Each layer refines the understanding, building from simple patterns (early layers) to complex concepts (later layers). The lines show this sequential processing.`
483
- },
484
- {
485
- heading: "Model Scale",
486
- content: `This architecture represents ~${Math.round(modelInfo.totalParams / 1e6)}M parameters. With ${modelInfo.vocabSize.toLocaleString()} token vocabulary, ${modelInfo.hiddenSize} hidden dimensions, and ${modelInfo.layers} layers, it can generate coherent code by learning patterns from training data.`
487
- }
488
- ]
489
- };
490
- };
491
-
492
- const explanation = generateExplanation();
493
-
494
- return (
495
- <div className="bg-gray-900 rounded-xl p-6 h-[800px]">
496
- {/* Header */}
497
- <div className="flex items-center justify-between mb-4">
498
- <div>
499
- <h2 className="text-2xl font-bold flex items-center gap-2">
500
- <Move3D className="w-6 h-6 text-blue-400" />
501
- 3D Model Architecture
502
- </h2>
503
- <p className="text-gray-400 mt-1">
504
- Interactive 3D visualization of the transformer architecture
505
- </p>
506
- </div>
507
-
508
- {/* Controls */}
509
- <div className="flex items-center gap-2">
510
- <button
511
- onClick={() => setAutoRotate(!autoRotate)}
512
- className={`px-3 py-1.5 rounded-lg text-sm ${
513
- autoRotate ? 'bg-blue-600 text-white' : 'bg-gray-800 text-gray-300'
514
- }`}
515
- >
516
- Auto-rotate
517
- </button>
518
- <button
519
- onClick={() => setShowLabels(!showLabels)}
520
- className={`px-3 py-1.5 rounded-lg text-sm ${
521
- showLabels ? 'bg-blue-600 text-white' : 'bg-gray-800 text-gray-300'
522
- }`}
523
- >
524
- Labels
525
- </button>
526
- </div>
527
- </div>
528
-
529
- {/* Main Content Area with Side Panel */}
530
- <div className="flex gap-4">
531
- {/* 3D Canvas */}
532
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
533
- <div className="h-[700px] bg-black rounded-lg relative">
534
- <Canvas camera={{ position: [50, 40, 70], fov: 45 }}> {/* Camera much further back for full view */}
535
- <Suspense fallback={null}>
536
- <Scene modelInfo={modelInfo} />
537
- <OrbitControls
538
- enablePan={true}
539
- enableZoom={true}
540
- enableRotate={true}
541
- autoRotate={autoRotate}
542
- autoRotateSpeed={0.5}
543
- minDistance={20}
544
- maxDistance={200}
545
- target={[0, 35, 0]}
546
- />
547
- <Environment preset="city" />
548
- </Suspense>
549
- </Canvas>
550
-
551
- {/* Help Toggle Button */}
552
- <button
553
- onClick={() => setShowExplanation(!showExplanation)}
554
- className="absolute top-4 left-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
555
- >
556
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
557
- <span className="text-sm font-medium">
558
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
559
- </span>
560
- </button>
561
-
562
- {/* Instructions */}
563
- <div className="absolute bottom-4 left-4 bg-gray-800/80 backdrop-blur rounded-lg p-3 text-xs text-gray-400">
564
- <div className="flex items-center gap-2 mb-1">
565
- <Eye className="w-3 h-3" />
566
- <span>Click layers to inspect</span>
567
- </div>
568
- <div className="flex items-center gap-2 mb-1">
569
- <Move3D className="w-3 h-3" />
570
- <span>Drag to rotate • Scroll to zoom</span>
571
- </div>
572
- <div className="flex items-center gap-2">
573
- <Layers className="w-3 h-3" />
574
- <span>{modelInfo.layers} layers × {modelInfo.heads} heads = {modelInfo.layers * modelInfo.heads} attention patterns</span>
575
- </div>
576
- <div className="mt-2 pt-2 border-t border-gray-600">
577
- <div className="font-semibold text-white">Architecture Stack:</div>
578
- <div className="text-green-400">↑ Output Probabilities (51,200 tokens)</div>
579
- <div className="text-blue-400">↑ Layers 0-19 (20 transformer blocks)</div>
580
- <div className="text-green-400">↑ Input Embeddings (51,200 × 1,024)</div>
581
- </div>
582
- </div>
583
-
584
- {/* Legend */}
585
- <div className="absolute top-4 right-4 bg-gray-800/80 backdrop-blur rounded-lg p-3 text-xs">
586
- <div className="font-semibold text-white mb-2">Components</div>
587
- <div className="space-y-1">
588
- <div className="flex items-center gap-2">
589
- <div className="w-3 h-3 bg-green-500 rounded"></div>
590
- <span className="text-gray-300">Token Embeddings</span>
591
- </div>
592
- <div className="flex items-center gap-2">
593
- <div className="w-3 h-3 bg-blue-500 rounded"></div>
594
- <span className="text-gray-300">Attention Layers</span>
595
- </div>
596
- <div className="flex items-center gap-2">
597
- <div className="w-3 h-3 bg-purple-500 rounded"></div>
598
- <span className="text-gray-300">Feed-Forward</span>
599
- </div>
600
- <div className="flex items-center gap-2">
601
- <div className="w-3 h-3 bg-amber-500 rounded"></div>
602
- <span className="text-gray-300">Output Layer</span>
603
- </div>
604
- </div>
605
- </div>
606
- </div>
607
- </div>
608
-
609
- {/* Explanation Side Panel */}
610
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
611
- <div className="w-96 h-[700px] bg-gray-900 rounded-lg border border-gray-700">
612
- {/* Panel Header */}
613
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
614
- <div className="flex items-center gap-2">
615
- <Info className="w-5 h-5 text-blue-400" />
616
- <h3 className="text-lg font-semibold text-white">Understanding the Architecture</h3>
617
- </div>
618
- </div>
619
-
620
- {/* Panel Content */}
621
- <div className="px-4 py-4 overflow-y-auto h-[calc(700px-60px)]">
622
- {/* Main Description */}
623
- <div className="mb-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
624
- <h4 className="text-sm font-semibold text-blue-400 mb-1">{explanation.title}</h4>
625
- <p className="text-xs text-gray-300">{explanation.description}</p>
626
- </div>
627
-
628
- {/* Explanation Sections */}
629
- <div className="space-y-3">
630
- {explanation.details.map((section, idx) => (
631
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
632
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
633
- <Zap className="w-3 h-3 text-yellow-400" />
634
- {section.heading}
635
- </h5>
636
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
637
- </div>
638
- ))}
639
- </div>
640
-
641
- {/* Visual Guide */}
642
- <div className="mt-4 p-3 bg-purple-900/20 border border-purple-800 rounded-lg">
643
- <h4 className="font-medium text-sm text-purple-400 mb-2">Layer Components</h4>
644
- <div className="space-y-2 text-xs">
645
- <div className="flex items-start gap-2">
646
- <div className="w-3 h-3 bg-green-500 rounded mt-0.5"></div>
647
- <span className="text-gray-300">Input: Token embeddings (51,200 × 1,024)</span>
648
- </div>
649
- <div className="flex items-start gap-2">
650
- <div className="w-3 h-3 bg-blue-500 rounded mt-0.5"></div>
651
- <span className="text-gray-300">Attention: {modelInfo.layers} layers × {modelInfo.heads} heads</span>
652
- </div>
653
- <div className="flex items-start gap-2">
654
- <div className="w-3 h-3 bg-purple-500 rounded mt-0.5"></div>
655
- <span className="text-gray-300">FFN: 4096 dimensional processing</span>
656
- </div>
657
- <div className="flex items-start gap-2">
658
- <div className="w-3 h-3 bg-amber-500 rounded mt-0.5"></div>
659
- <span className="text-gray-300">Output: Probability over 51,200 tokens</span>
660
- </div>
661
- </div>
662
- </div>
663
-
664
- {/* Model Statistics */}
665
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
666
- <h4 className="font-medium text-sm text-gray-300 mb-2">Model Statistics</h4>
667
- <div className="space-y-1 text-xs">
668
- <div className="flex justify-between">
669
- <span className="text-gray-400">Total Layers:</span>
670
- <span className="text-white">20</span>
671
- </div>
672
- <div className="flex justify-between">
673
- <span className="text-gray-400">Attention Heads:</span>
674
- <span className="text-white">16 per layer</span>
675
- </div>
676
- <div className="flex justify-between">
677
- <span className="text-gray-400">Hidden Dimensions:</span>
678
- <span className="text-white">1,024</span>
679
- </div>
680
- <div className="flex justify-between">
681
- <span className="text-gray-400">FFN Dimensions:</span>
682
- <span className="text-white">4,096</span>
683
- </div>
684
- <div className="flex justify-between">
685
- <span className="text-gray-400">Vocabulary Size:</span>
686
- <span className="text-white">51,200 tokens</span>
687
- </div>
688
- <div className="flex justify-between">
689
- <span className="text-gray-400">Parameters:</span>
690
- <span className="text-blue-400">~350M</span>
691
- </div>
692
- </div>
693
- </div>
694
-
695
- {/* Interaction Guide */}
696
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
697
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Interactive Features</h4>
698
- <ul className="text-xs text-gray-400 space-y-1">
699
- <li>• Click layers to see details</li>
700
- <li>• Drag to rotate the model</li>
701
- <li>• Scroll to zoom in/out</li>
702
- <li>• Enable auto-rotate for 360° view</li>
703
- <li>• Each layer processes all tokens in parallel</li>
704
- </ul>
705
- </div>
706
- </div>
707
- </div>
708
- </div>
709
- </div>
710
- </div>
711
- );
712
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/ModelInspector.tsx DELETED
@@ -1,495 +0,0 @@
1
- /**
2
- * Model Inspector Component
3
- *
4
- * Displays detailed architecture information about the loaded model,
5
- * including layers, parameters, attention heads, and accessible components.
6
- * Makes the "black box" transparent by showing what can be visualized.
7
- *
8
- * @component
9
- */
10
-
11
- "use client";
12
-
13
- import { useState, useEffect, lazy, Suspense } from "react";
14
- import { getApiUrl } from "@/lib/config";
15
- import {
16
- Brain,
17
- Layers,
18
- Eye,
19
- Cpu,
20
- Database,
21
- GitBranch,
22
- Zap,
23
- ChevronRight,
24
- ChevronDown,
25
- Activity,
26
- Info,
27
- Box,
28
- Network,
29
- Move3D
30
- } from "lucide-react";
31
-
32
- // Lazy load the 3D components to avoid SSR issues
33
- const ModelArchitecture3D = lazy(() => import('./ModelArchitecture3D'));
34
- const DecisionPath3D = lazy(() => import('./DecisionPath3DEnhanced'));
35
-
36
- interface ModelInfo {
37
- name: string;
38
- type: string;
39
- totalParams: number;
40
- layers: number;
41
- heads: number;
42
- hiddenSize: number;
43
- vocabSize: number;
44
- maxPositions: number;
45
- architecture: string[];
46
- accessible: string[];
47
- }
48
-
49
- export default function ModelInspector({ hideControlPanel = false }: { hideControlPanel?: boolean }) {
50
- const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set(['overview']));
51
- const [isConnected, setIsConnected] = useState(false);
52
- const [isLoading, setIsLoading] = useState(true);
53
- const [modelInfo, setModelInfo] = useState<ModelInfo>({
54
- name: "Loading...",
55
- type: "unknown",
56
- totalParams: 0,
57
- layers: 0,
58
- heads: 0,
59
- hiddenSize: 0,
60
- vocabSize: 0,
61
- maxPositions: 0,
62
- architecture: [],
63
- accessible: []
64
- });
65
- const [deviceInfo, setDeviceInfo] = useState<string>("");
66
- const [modelConfig, setModelConfig] = useState<Record<string, unknown> | null>(null);
67
-
68
- // Fetch model information from backend
69
- useEffect(() => {
70
- const fetchModelInfo = async () => {
71
- try {
72
- const response = await fetch(`${getApiUrl()}/model/info`);
73
- if (response.ok) {
74
- const data = await response.json();
75
- setModelInfo({
76
- name: data.name,
77
- type: data.type,
78
- totalParams: data.totalParams,
79
- layers: data.layers,
80
- heads: data.heads,
81
- hiddenSize: data.hiddenSize,
82
- vocabSize: data.vocabSize,
83
- maxPositions: data.maxPositions,
84
- architecture: [data.architecture],
85
- accessible: data.accessible
86
- });
87
- setDeviceInfo(data.device);
88
- setModelConfig(data.config);
89
- setIsConnected(true);
90
- }
91
- } catch (error) {
92
- console.error('Failed to fetch model info:', error);
93
- // Keep default/mock data if fetch fails
94
- setIsConnected(false);
95
- } finally {
96
- setIsLoading(false);
97
- }
98
- };
99
-
100
- fetchModelInfo();
101
- // Refresh model info every 10 seconds
102
- const interval = setInterval(fetchModelInfo, 10000);
103
- return () => clearInterval(interval);
104
- }, []);
105
-
106
- const toggleSection = (section: string) => {
107
- const newExpanded = new Set(expandedSections);
108
- if (newExpanded.has(section)) {
109
- newExpanded.delete(section);
110
- } else {
111
- newExpanded.add(section);
112
- }
113
- setExpandedSections(newExpanded);
114
- };
115
-
116
- const formatNumber = (num: number) => {
117
- if (num >= 1e9) return `${(num / 1e9).toFixed(1)}B`;
118
- if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
119
- if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
120
- return num.toString();
121
- };
122
-
123
- return (
124
- <div className="bg-gray-900 rounded-xl p-6">
125
- {/* Header */}
126
- <div className="flex items-center justify-between mb-6">
127
- <div>
128
- <h2 className="text-2xl font-bold flex items-center gap-2">
129
- <Brain className="w-6 h-6 text-purple-400" />
130
- Model Inspector
131
- </h2>
132
- <p className="text-gray-400 mt-1">
133
- Explore the complete architecture of the loaded model
134
- </p>
135
- </div>
136
-
137
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
138
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
139
- }`}>
140
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
141
- {isLoading ? 'Loading...' : (isConnected ? 'Model Connected' : 'Disconnected')}
142
- </div>
143
- </div>
144
-
145
- {/* Model Overview Section */}
146
- <div className="bg-gray-800 rounded-lg mb-4">
147
- <button
148
- onClick={() => toggleSection('overview')}
149
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
150
- >
151
- <div className="flex items-center gap-3">
152
- {expandedSections.has('overview') ?
153
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
154
- <ChevronRight className="w-5 h-5 text-gray-400" />
155
- }
156
- <Cpu className="w-5 h-5 text-blue-400" />
157
- <span className="font-semibold">Model Overview</span>
158
- </div>
159
- <span className="text-sm text-gray-400">{modelInfo.name}</span>
160
- </button>
161
-
162
- {expandedSections.has('overview') && (
163
- <div className="px-4 pb-4">
164
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
165
- <div className="bg-gray-900 rounded-lg p-3">
166
- <div className="text-xs text-gray-400">Total Parameters</div>
167
- <div className="text-2xl font-bold text-white">{formatNumber(modelInfo.totalParams)}</div>
168
- <div className="text-xs text-gray-500">
169
- {modelInfo.totalParams > 1e9 ? `${(modelInfo.totalParams / 1e9).toFixed(1)} Billion` :
170
- modelInfo.totalParams > 1e6 ? `${(modelInfo.totalParams / 1e6).toFixed(1)} Million` :
171
- formatNumber(modelInfo.totalParams)}
172
- </div>
173
- </div>
174
-
175
- <div className="bg-gray-900 rounded-lg p-3">
176
- <div className="text-xs text-gray-400">Vocabulary Size</div>
177
- <div className="text-2xl font-bold text-purple-400">{formatNumber(modelInfo.vocabSize)}</div>
178
- <div className="text-xs text-gray-500">Unique tokens</div>
179
- </div>
180
-
181
- <div className="bg-gray-900 rounded-lg p-3">
182
- <div className="text-xs text-gray-400">Context Length</div>
183
- <div className="text-2xl font-bold text-green-400">{formatNumber(modelInfo.maxPositions)}</div>
184
- <div className="text-xs text-gray-500">Max tokens</div>
185
- </div>
186
-
187
- <div className="bg-gray-900 rounded-lg p-3">
188
- <div className="text-xs text-gray-400">Architecture</div>
189
- <div className="text-lg font-bold text-yellow-400">Transformer</div>
190
- <div className="text-xs text-gray-500">GPT-style</div>
191
- </div>
192
- </div>
193
-
194
- {/* Device Information */}
195
- {deviceInfo && (
196
- <div className="mt-4 p-3 bg-gray-900 rounded-lg">
197
- <div className="flex items-center gap-2 mb-2">
198
- <Cpu className="w-4 h-4 text-blue-400" />
199
- <span className="text-sm font-semibold">Device Information</span>
200
- </div>
201
- <div className="text-sm text-gray-400">
202
- Running on: <span className="text-white font-mono">{deviceInfo}</span>
203
- </div>
204
- </div>
205
- )}
206
-
207
- {/* Model Configuration */}
208
- {modelConfig && (
209
- <div className="mt-4 p-3 bg-gray-900 rounded-lg">
210
- <div className="flex items-center gap-2 mb-2">
211
- <Database className="w-4 h-4 text-purple-400" />
212
- <span className="text-sm font-semibold">Configuration</span>
213
- </div>
214
- <div className="grid grid-cols-2 gap-2 text-xs">
215
- <div className="flex justify-between">
216
- <span className="text-gray-400">Activation:</span>
217
- <span className="text-white font-mono">{String(modelConfig.activation_function)}</span>
218
- </div>
219
- <div className="flex justify-between">
220
- <span className="text-gray-400">Cache:</span>
221
- <span className="text-white font-mono">{modelConfig.use_cache ? 'Enabled' : 'Disabled'}</span>
222
- </div>
223
- </div>
224
- </div>
225
- )}
226
- </div>
227
- )}
228
- </div>
229
-
230
- {/* Architecture Details Section */}
231
- <div className="bg-gray-800 rounded-lg mb-4">
232
- <button
233
- onClick={() => toggleSection('architecture')}
234
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
235
- >
236
- <div className="flex items-center gap-3">
237
- {expandedSections.has('architecture') ?
238
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
239
- <ChevronRight className="w-5 h-5 text-gray-400" />
240
- }
241
- <Layers className="w-5 h-5 text-green-400" />
242
- <span className="font-semibold">Architecture Details</span>
243
- </div>
244
- <span className="text-sm text-gray-400">{modelInfo.layers} layers × {modelInfo.heads} heads</span>
245
- </button>
246
-
247
- {expandedSections.has('architecture') && (
248
- <div className="px-4 pb-4">
249
- <div className="space-y-4">
250
- {/* Layer Structure */}
251
- <div className="bg-gray-900 rounded-lg p-4">
252
- <h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
253
- <GitBranch className="w-4 h-4 text-blue-400" />
254
- Layer Structure (×{modelInfo.layers})
255
- </h4>
256
- <div className="space-y-2 text-sm">
257
- <div className="flex items-center gap-2">
258
- <div className="w-2 h-2 bg-green-400 rounded-full"></div>
259
- <span className="text-gray-300">Multi-Head Attention</span>
260
- <span className="text-gray-500">({modelInfo.heads} heads, {modelInfo.hiddenSize / modelInfo.heads} dims/head)</span>
261
- </div>
262
- <div className="flex items-center gap-2 ml-4">
263
- <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
264
- <span className="text-gray-400">QKV Projection: {modelInfo.hiddenSize} → {modelInfo.hiddenSize * 3}</span>
265
- </div>
266
- <div className="flex items-center gap-2 ml-4">
267
- <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
268
- <span className="text-gray-400">Output Projection: {modelInfo.hiddenSize} → {modelInfo.hiddenSize}</span>
269
- </div>
270
-
271
- <div className="flex items-center gap-2 mt-2">
272
- <div className="w-2 h-2 bg-purple-400 rounded-full"></div>
273
- <span className="text-gray-300">Feed-Forward Network</span>
274
- <span className="text-gray-500">(4× expansion)</span>
275
- </div>
276
- <div className="flex items-center gap-2 ml-4">
277
- <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
278
- <span className="text-gray-400">FC1: {modelInfo.hiddenSize} → {modelInfo.hiddenSize * 4}</span>
279
- </div>
280
- <div className="flex items-center gap-2 ml-4">
281
- <div className="w-1 h-1 bg-gray-400 rounded-full"></div>
282
- <span className="text-gray-400">FC2: {modelInfo.hiddenSize * 4} → {modelInfo.hiddenSize}</span>
283
- </div>
284
-
285
- <div className="flex items-center gap-2 mt-2">
286
- <div className="w-2 h-2 bg-yellow-400 rounded-full"></div>
287
- <span className="text-gray-300">Layer Normalization</span>
288
- </div>
289
-
290
- <div className="flex items-center gap-2">
291
- <div className="w-2 h-2 bg-red-400 rounded-full"></div>
292
- <span className="text-gray-300">Residual Connections</span>
293
- </div>
294
- </div>
295
- </div>
296
-
297
- {/* Data Flow */}
298
- <div className="bg-gray-900 rounded-lg p-4">
299
- <h4 className="text-sm font-semibold mb-3 flex items-center gap-2">
300
- <Network className="w-4 h-4 text-purple-400" />
301
- Data Flow Through Model
302
- </h4>
303
- <div className="font-mono text-xs text-gray-400 whitespace-pre">
304
- {`Input Text
305
-
306
- [Token Embeddings] (${modelInfo.vocabSize.toLocaleString()} × ${modelInfo.hiddenSize.toLocaleString()})
307
-
308
- [+ Rotary Position Embeddings]
309
-
310
- ╔═══════════════════════╗
311
- ║ Layer 0 ║
312
- ║ ├─ Attention (${modelInfo.heads}h) ║
313
- ║ └─ FFN (${modelInfo.hiddenSize * 4}d) ║
314
- ╚═══════════════════════╝
315
-
316
- ... (${modelInfo.layers - 2} more layers)
317
-
318
- ╔═══════════════════════╗
319
- ║ Layer ${modelInfo.layers - 1} ║
320
- ║ ├─ Attention (${modelInfo.heads}h) ║
321
- ║ └─ FFN (${modelInfo.hiddenSize * 4}d) ║
322
- ╚═══════════════════════╝
323
-
324
- [Layer Norm]
325
-
326
- [Language Model Head]
327
-
328
- ${modelInfo.vocabSize.toLocaleString()} Token Probabilities`}
329
- </div>
330
- </div>
331
- </div>
332
- </div>
333
- )}
334
- </div>
335
-
336
- {/* Accessible Components Section */}
337
- <div className="bg-gray-800 rounded-lg mb-4">
338
- <button
339
- onClick={() => toggleSection('accessible')}
340
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
341
- >
342
- <div className="flex items-center gap-3">
343
- {expandedSections.has('accessible') ?
344
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
345
- <ChevronRight className="w-5 h-5 text-gray-400" />
346
- }
347
- <Eye className="w-5 h-5 text-yellow-400" />
348
- <span className="font-semibold">What We Can Visualize</span>
349
- </div>
350
- <span className="text-sm text-gray-400">Everything is accessible</span>
351
- </button>
352
-
353
- {expandedSections.has('accessible') && (
354
- <div className="px-4 pb-4">
355
- <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
356
- {modelInfo.accessible.map((item, idx) => (
357
- <div key={idx} className="flex items-start gap-2">
358
- <Zap className="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
359
- <span className="text-sm text-gray-300">{item}</span>
360
- </div>
361
- ))}
362
- </div>
363
-
364
- <div className="mt-4 p-4 bg-gray-900 rounded-lg border border-blue-900">
365
- <div className="flex items-start gap-2">
366
- <Info className="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
367
- <div className="text-sm">
368
- <p className="text-blue-400 font-semibold mb-1">Complete Transparency</p>
369
- <p className="text-gray-400">
370
- Every computation, weight, and decision in the model&apos;s {formatNumber(modelInfo.totalParams)} parameters
371
- is accessible. The &quot;black box&quot; becomes a &quot;glass box&quot; - we can visualize the
372
- entire thinking process as tokens flow through the network.
373
- </p>
374
- </div>
375
- </div>
376
- </div>
377
- </div>
378
- )}
379
- </div>
380
-
381
- {/* Decision Path Visualization Section */}
382
- <div className="bg-gray-800 rounded-lg mb-4">
383
- <button
384
- onClick={() => toggleSection('decision-path')}
385
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
386
- >
387
- <div className="flex items-center gap-3">
388
- {expandedSections.has('decision-path') ?
389
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
390
- <ChevronRight className="w-5 h-5 text-gray-400" />
391
- }
392
- <GitBranch className="w-5 h-5 text-yellow-400" />
393
- <span className="font-semibold">Decision Path Analysis</span>
394
- </div>
395
- <span className="text-sm text-gray-400">Glass Box view</span>
396
- </button>
397
-
398
- {expandedSections.has('decision-path') && (
399
- <div className="px-4 pb-4">
400
- <Suspense fallback={
401
- <div className="h-[900px] bg-gray-900 rounded-lg flex items-center justify-center">
402
- <div className="text-gray-400">Loading decision path visualization...</div>
403
- </div>
404
- }>
405
- <DecisionPath3D />
406
- </Suspense>
407
- </div>
408
- )}
409
- </div>
410
-
411
- {/* 3D Visualization Section */}
412
- <div className="bg-gray-800 rounded-lg mb-4">
413
- <button
414
- onClick={() => toggleSection('3d')}
415
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
416
- >
417
- <div className="flex items-center gap-3">
418
- {expandedSections.has('3d') ?
419
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
420
- <ChevronRight className="w-5 h-5 text-gray-400" />
421
- }
422
- <Move3D className="w-5 h-5 text-blue-400" />
423
- <span className="font-semibold">3D Architecture Visualization</span>
424
- </div>
425
- <span className="text-sm text-gray-400">Interactive 3D model</span>
426
- </button>
427
-
428
- {expandedSections.has('3d') && (
429
- <div className="px-4 pb-4">
430
- <Suspense fallback={
431
- <div className="h-[800px] bg-gray-900 rounded-lg flex items-center justify-center">
432
- <div className="text-gray-400">Loading 3D visualization...</div>
433
- </div>
434
- }>
435
- <ModelArchitecture3D />
436
- </Suspense>
437
- </div>
438
- )}
439
- </div>
440
-
441
- {/* Computation Stats Section */}
442
- <div className="bg-gray-800 rounded-lg">
443
- <button
444
- onClick={() => toggleSection('computation')}
445
- className="w-full p-4 flex items-center justify-between hover:bg-gray-700/50 transition-colors rounded-lg"
446
- >
447
- <div className="flex items-center gap-3">
448
- {expandedSections.has('computation') ?
449
- <ChevronDown className="w-5 h-5 text-gray-400" /> :
450
- <ChevronRight className="w-5 h-5 text-gray-400" />
451
- }
452
- <Database className="w-5 h-5 text-red-400" />
453
- <span className="font-semibold">Computation Scale</span>
454
- </div>
455
- <span className="text-sm text-gray-400">Per token generation</span>
456
- </button>
457
-
458
- {expandedSections.has('computation') && (
459
- <div className="px-4 pb-4">
460
- <div className="space-y-3">
461
- <div className="flex items-center justify-between">
462
- <span className="text-sm text-gray-400">Operations per token</span>
463
- <span className="text-sm font-mono text-white">~{formatNumber(modelInfo.totalParams * 2)}</span>
464
- </div>
465
- <div className="flex items-center justify-between">
466
- <span className="text-sm text-gray-400">Attention computations</span>
467
- <span className="text-sm font-mono text-white">{modelInfo.layers * modelInfo.heads} heads</span>
468
- </div>
469
- <div className="flex items-center justify-between">
470
- <span className="text-sm text-gray-400">Probability calculations</span>
471
- <span className="text-sm font-mono text-white">{modelInfo.vocabSize.toLocaleString()} tokens</span>
472
- </div>
473
- <div className="flex items-center justify-between">
474
- <span className="text-sm text-gray-400">Memory footprint (FP32)</span>
475
- <span className="text-sm font-mono text-white">{((modelInfo.totalParams * 4) / (1024 * 1024 * 1024)).toFixed(2)} GB</span>
476
- </div>
477
- <div className="flex items-center justify-between">
478
- <span className="text-sm text-gray-400">Memory footprint (FP16)</span>
479
- <span className="text-sm font-mono text-white">{((modelInfo.totalParams * 2) / (1024 * 1024 * 1024)).toFixed(2)} GB</span>
480
- </div>
481
- </div>
482
-
483
- <div className="mt-4 p-3 bg-gray-900 rounded-lg">
484
- <p className="text-xs text-gray-500">
485
- Each token generation involves passing through all {modelInfo.layers} layers,
486
- computing attention across {modelInfo.layers * modelInfo.heads} heads, and producing probabilities
487
- for {modelInfo.vocabSize.toLocaleString()} possible next tokens.
488
- </p>
489
- </div>
490
- </div>
491
- )}
492
- </div>
493
- </div>
494
- );
495
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/Navigation.tsx DELETED
@@ -1,44 +0,0 @@
1
- "use client";
2
-
3
- import { Brain, Code, GitBranch, Book } from "lucide-react";
4
-
5
- export default function Navigation() {
6
- return (
7
- <nav className="border-b border-gray-800 bg-gray-900/50 backdrop-blur-sm sticky top-0 z-50">
8
- <div className="container mx-auto px-4">
9
- <div className="flex items-center justify-between h-16">
10
- <div className="flex items-center gap-8">
11
- <div className="flex items-center gap-2">
12
- <Brain className="w-8 h-8 text-blue-500" />
13
- <span className="text-xl font-bold">Visualisable.ai</span>
14
- </div>
15
-
16
- <div className="hidden md:flex gap-6">
17
- <a href="#explorer" className="flex items-center gap-2 text-gray-300 hover:text-white transition-colors">
18
- <Code className="w-4 h-4" />
19
- <span>Explorer</span>
20
- </a>
21
- <a href="#lab" className="flex items-center gap-2 text-gray-300 hover:text-white transition-colors">
22
- <GitBranch className="w-4 h-4" />
23
- <span>Lab</span>
24
- </a>
25
- <a href="#docs" className="flex items-center gap-2 text-gray-300 hover:text-white transition-colors">
26
- <Book className="w-4 h-4" />
27
- <span>Docs</span>
28
- </a>
29
- </div>
30
- </div>
31
-
32
- <div className="flex items-center gap-4">
33
- <button className="px-4 py-2 text-sm text-gray-300 hover:text-white transition-colors">
34
- Sign In
35
- </button>
36
- <button className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
37
- Get Started
38
- </button>
39
- </div>
40
- </div>
41
- </div>
42
- </nav>
43
- );
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/PromptDiff.tsx DELETED
@@ -1,1196 +0,0 @@
1
- /**
2
- * Prompt Diff Analyzer Component
3
- *
4
- * Visualizes and compares attention patterns between different prompts
5
- * to show how prompt changes affect model behavior.
6
- *
7
- * CURRENT STATUS: Demo Mode
8
- * - Uses existing traces from working_demo.py
9
- * - Simulates differences between prompts
10
- * - TODO: Integrate with real LLM models for actual prompt comparison
11
- *
12
- * @component
13
- */
14
-
15
- "use client";
16
-
17
- import { useState, useEffect, useRef } from "react";
18
- import * as d3 from "d3";
19
- import { useWebSocket } from "@/lib/websocket-client";
20
- import { PromptServiceClient } from "@/lib/prompt-service-client";
21
- import { getApiUrl } from "@/lib/config";
22
- import { ArrowRight, AlertTriangle, CheckCircle, Minus, Plus, GitCompare, Activity, Download, HelpCircle, X, Info, Zap } from "lucide-react";
23
-
24
- // Attention data structure for a single layer
25
- interface AttentionData {
26
- layer: string;
27
- weights: number[][];
28
- tokens?: string[];
29
- max_weight: number;
30
- entropy?: number;
31
- timestamp: number;
32
- }
33
-
34
- // Comparison data structure for two prompts
35
- interface PromptComparison {
36
- promptA: string;
37
- promptB: string;
38
- attentionA: AttentionData[];
39
- attentionB: AttentionData[];
40
- timestamp: number;
41
- }
42
-
43
- export default function PromptDiff() {
44
- const { traces, isConnected } = useWebSocket();
45
- const [prompt1, setPrompt1] = useState("def fibonacci(n):\n '''Calculate fibonacci number'''");
46
- const [prompt2, setPrompt2] = useState("def fibonacci(n):\n '''Calculate fibonacci number with memoization'''");
47
- const [shouldAutoAnalyze, setShouldAutoAnalyze] = useState(false);
48
-
49
- // Listen for demo selections from LocalControlPanel
50
- useEffect(() => {
51
- const handleDemoPromptsSelected = (event: Event) => {
52
- const customEvent = event as CustomEvent;
53
- const { promptA, promptB } = customEvent.detail;
54
- console.log('PromptDiff: Demo prompts received -', { promptA, promptB });
55
-
56
- if (promptA && promptB) {
57
- setPrompt1(promptA);
58
- setPrompt2(promptB);
59
- setShouldAutoAnalyze(true);
60
- }
61
- };
62
-
63
- window.addEventListener('demo-prompts-selected', handleDemoPromptsSelected);
64
- return () => window.removeEventListener('demo-prompts-selected', handleDemoPromptsSelected);
65
- }, []);
66
-
67
- // We'll add the auto-analyze effect after analyzePrompts is defined
68
-
69
- const [comparison, setComparison] = useState<PromptComparison | null>(null);
70
- const [selectedLayer, setSelectedLayer] = useState<string>("");
71
- const [isAnalyzing, setIsAnalyzing] = useState(false);
72
- const [savedComparisons, setSavedComparisons] = useState<PromptComparison[]>([]);
73
- const [isPromptServiceConnected, setIsPromptServiceConnected] = useState(false);
74
- const [useRealModel, setUseRealModel] = useState(true); // Default to using real model
75
- const [generatedTexts, setGeneratedTexts] = useState<{a?: string, b?: string}>({});
76
- const [showExplanation, setShowExplanation] = useState(false);
77
-
78
- const diffSvgRef = useRef<SVGSVGElement>(null);
79
- const heatmapARef = useRef<SVGSVGElement>(null);
80
- const heatmapBRef = useRef<SVGSVGElement>(null);
81
- const promptServiceRef = useRef<PromptServiceClient | null>(null);
82
-
83
- // Initialize prompt service connection
84
- useEffect(() => {
85
- const client = new PromptServiceClient();
86
- promptServiceRef.current = client;
87
-
88
- // Try to connect to the prompt service
89
- client.connect()
90
- .then(() => {
91
- console.log('✅ Connected to prompt service');
92
- setIsPromptServiceConnected(true);
93
- setUseRealModel(true);
94
- })
95
- .catch((error) => {
96
- // This is expected if the service isn't running - no need to log error
97
- console.log('ℹ️ Using demo mode (start prompt_service.py for real model)');
98
- setIsPromptServiceConnected(false);
99
- setUseRealModel(false);
100
- });
101
-
102
- // Set up message handler
103
- client.onMessage('prompt-diff', (data) => {
104
- handlePromptServiceMessage(data as unknown as PromptServiceMessage);
105
- });
106
-
107
- return () => {
108
- client.offMessage('prompt-diff');
109
- client.disconnect();
110
- };
111
- }, []);
112
-
113
- // Handle messages from prompt service
114
- interface PromptServiceMessage {
115
- type: string;
116
- comparison_group?: string;
117
- layer?: string;
118
- weights?: number[][];
119
- max_weight?: number;
120
- entropy?: number;
121
- timestamp?: number;
122
- tokens?: string[];
123
- }
124
-
125
- const handlePromptServiceMessage = (data: PromptServiceMessage) => {
126
- if (data.type === 'attention') {
127
- // Store attention traces temporarily
128
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
- if (!(window as any).tempAttentionStorage) {
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
- (window as any).tempAttentionStorage = { prompt_a: [], prompt_b: [] };
132
- }
133
-
134
- if (data.comparison_group === 'prompt_a') {
135
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
- (window as any).tempAttentionStorage.prompt_a.push(data);
137
- } else if (data.comparison_group === 'prompt_b') {
138
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
- (window as any).tempAttentionStorage.prompt_b.push(data);
140
- }
141
- } else if (data.type === 'prompt_comparison') {
142
- // Handle comparison summary
143
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
- const storage = (window as any).tempAttentionStorage || { prompt_a: [], prompt_b: [] };
145
-
146
- // Create comparison from received data
147
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
- const comparisonData = data as any; // Type assertion for comparison-specific fields
149
- const newComparison: PromptComparison = {
150
- promptA: comparisonData.prompt_a?.text || prompt1,
151
- promptB: comparisonData.prompt_b?.text || prompt2,
152
- attentionA: storage.prompt_a.map((t: PromptServiceMessage) => ({
153
- layer: t.layer || '',
154
- weights: t.weights || [],
155
- max_weight: t.max_weight || 0,
156
- entropy: t.entropy || 0,
157
- timestamp: t.timestamp || 0,
158
- tokens: t.tokens || []
159
- })),
160
- attentionB: storage.prompt_b.map((t: PromptServiceMessage) => ({
161
- layer: t.layer || '',
162
- weights: t.weights || [],
163
- max_weight: t.max_weight || 0,
164
- entropy: t.entropy || 0,
165
- timestamp: t.timestamp || 0,
166
- tokens: t.tokens || []
167
- })),
168
- timestamp: Date.now()
169
- };
170
-
171
- // Store generated texts
172
- setGeneratedTexts({
173
- a: comparisonData.prompt_a?.generated || '',
174
- b: comparisonData.prompt_b?.generated || ''
175
- });
176
-
177
- setComparison(newComparison);
178
- setSavedComparisons(prev => [...prev, newComparison]);
179
-
180
- // Auto-select first layer
181
- if (storage.prompt_a.length > 0) {
182
- const layers = Array.from(new Set(storage.prompt_a.map((a: PromptServiceMessage) => a.layer)));
183
- if (layers[0]) {
184
- setSelectedLayer(layers[0] as string);
185
- }
186
- }
187
-
188
- // Clear temporary storage
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- (window as any).tempAttentionStorage = null;
191
- setIsAnalyzing(false);
192
- }
193
- };
194
-
195
- // Window type declarations removed - using (window as any) for tempAttentionStorage instead
196
-
197
- /**
198
- * Analyzes attention patterns for two prompts
199
- *
200
- * Uses real model service if available, otherwise falls back to demo mode
201
- */
202
- const analyzePrompts = async () => {
203
- setIsAnalyzing(true);
204
-
205
- try {
206
- // Generate traces for both prompts using the unified backend
207
- console.log('Generating traces for Prompt A...');
208
- const responseA = await fetch(`${getApiUrl()}/generate`, {
209
- method: 'POST',
210
- headers: { 'Content-Type': 'application/json' },
211
- body: JSON.stringify({
212
- prompt: prompt1,
213
- max_tokens: 50,
214
- temperature: 0.7,
215
- sampling_rate: 0.5 // Higher sampling for better comparison
216
- })
217
- });
218
-
219
- if (!responseA.ok) throw new Error(`HTTP error! status: ${responseA.status}`);
220
- const dataA = await responseA.json();
221
-
222
- console.log('Generating traces for Prompt B...');
223
- const responseB = await fetch(`${getApiUrl()}/generate`, {
224
- method: 'POST',
225
- headers: { 'Content-Type': 'application/json' },
226
- body: JSON.stringify({
227
- prompt: prompt2,
228
- max_tokens: 50,
229
- temperature: 0.7,
230
- sampling_rate: 0.5
231
- })
232
- });
233
-
234
- if (!responseB.ok) throw new Error(`HTTP error! status: ${responseB.status}`);
235
- const dataB = await responseB.json();
236
-
237
- // Store generated texts
238
- setGeneratedTexts({
239
- a: dataA.generated_text,
240
- b: dataB.generated_text
241
- });
242
-
243
- // Extract attention traces from both responses
244
- const attentionA = dataA.traces
245
- ?.filter((t: PromptServiceMessage) => t.type === 'attention' && t.weights)
246
- .map((t: PromptServiceMessage) => ({
247
- layer: t.layer || 'unknown',
248
- weights: t.weights || [],
249
- max_weight: t.max_weight || 1,
250
- entropy: t.entropy,
251
- timestamp: t.timestamp || Date.now(),
252
- tokens: t.tokens
253
- })) || [];
254
-
255
- const attentionB = dataB.traces
256
- ?.filter((t: PromptServiceMessage) => t.type === 'attention' && t.weights)
257
- .map((t: PromptServiceMessage) => ({
258
- layer: t.layer || 'unknown',
259
- weights: t.weights || [],
260
- max_weight: t.max_weight || 1,
261
- entropy: t.entropy,
262
- timestamp: t.timestamp || Date.now(),
263
- tokens: t.tokens
264
- })) || [];
265
-
266
- if (attentionA.length === 0 || attentionB.length === 0) {
267
- alert('No attention traces captured. Try adjusting the prompts or sampling rate.');
268
- setIsAnalyzing(false);
269
- return;
270
- }
271
-
272
- // Create comparison
273
- const newComparison: PromptComparison = {
274
- promptA: prompt1,
275
- promptB: prompt2,
276
- attentionA,
277
- attentionB,
278
- timestamp: Date.now()
279
- };
280
-
281
- setComparison(newComparison);
282
-
283
- // Find common layers between both sets
284
- const layersA = new Set(attentionA.map((a: AttentionData) => a.layer));
285
- const layersB = new Set(attentionB.map((a: AttentionData) => a.layer));
286
- const commonLayers = Array.from(layersA).filter(l => layersB.has(l)).sort();
287
-
288
- if (commonLayers.length > 0) {
289
- setSelectedLayer(commonLayers[0] as string);
290
- } else if (attentionA.length > 0) {
291
- // No common layers, just use first layer from A
292
- setSelectedLayer(attentionA[0].layer);
293
- }
294
-
295
- console.log(`Comparison complete. Found ${commonLayers.length} common layers.`);
296
- } catch (error) {
297
- console.error('Error comparing prompts:', error);
298
- alert(`Failed to compare prompts: ${error}`);
299
- } finally {
300
- setIsAnalyzing(false);
301
- }
302
- };
303
-
304
- // Auto-analyze when triggered by demo selection
305
- useEffect(() => {
306
- if (shouldAutoAnalyze && !isAnalyzing) {
307
- setShouldAutoAnalyze(false);
308
- // Small delay to ensure state is updated
309
- setTimeout(() => {
310
- analyzePrompts();
311
- }, 100);
312
- }
313
- }, [shouldAutoAnalyze, isAnalyzing, prompt1, prompt2]);
314
-
315
- const analyzeDemoMode = () => {
316
- // Original demo mode implementation
317
- const attentionTraces = traces.filter(t => t.type === 'attention' && t.weights);
318
-
319
- console.log('[PromptDiff] Available attention traces:', attentionTraces.length);
320
-
321
- if (attentionTraces.length === 0) {
322
- // No traces available - show error message
323
- alert('No attention data available. Please run a model first to capture attention patterns.\n\nTry running: python python-sdk/working_demo.py');
324
- setIsAnalyzing(false);
325
- return;
326
- }
327
-
328
- if (attentionTraces.length < 2) {
329
- // Not enough traces for comparison
330
- alert(`Only ${attentionTraces.length} attention trace(s) available. Need at least 2 for comparison.\n\nTry running the demo again to generate more traces.`);
331
- setIsAnalyzing(false);
332
- return;
333
- }
334
-
335
- // Simulate having different attention patterns for each prompt
336
- const halfPoint = Math.floor(attentionTraces.length / 2);
337
- const attentionA = attentionTraces.slice(0, halfPoint).map(t => ({
338
- layer: t.layer || 'unknown',
339
- weights: t.weights || [],
340
- max_weight: t.max_weight || 1,
341
- entropy: t.entropy,
342
- timestamp: t.timestamp || Date.now(),
343
- tokens: t.tokens
344
- }));
345
-
346
- const attentionB = attentionTraces.slice(halfPoint).map(t => ({
347
- layer: t.layer || 'unknown',
348
- weights: t.weights || [],
349
- max_weight: t.max_weight || 1,
350
- entropy: t.entropy,
351
- timestamp: t.timestamp || Date.now(),
352
- tokens: t.tokens
353
- }));
354
-
355
- // Add some variation to attentionB to simulate different prompt
356
- attentionB.forEach(attention => {
357
- attention.weights = attention.weights.map(row =>
358
- row.map(val => Math.min(1, Math.max(0, val + (Math.random() - 0.5) * 0.2)))
359
- );
360
- attention.max_weight = Math.max(...attention.weights.flat());
361
- attention.entropy = (attention.entropy || 0) + (Math.random() - 0.5) * 0.1;
362
- });
363
-
364
- const newComparison: PromptComparison = {
365
- promptA: prompt1,
366
- promptB: prompt2,
367
- attentionA,
368
- attentionB,
369
- timestamp: Date.now()
370
- };
371
-
372
- setComparison(newComparison);
373
- setSavedComparisons(prev => [...prev, newComparison]);
374
-
375
- // Auto-select first layer
376
- if (attentionA.length > 0) {
377
- const layers = Array.from(new Set(attentionA.map(a => a.layer)));
378
- setSelectedLayer(layers[0]);
379
- }
380
-
381
- setTimeout(() => setIsAnalyzing(false), 1000);
382
- };
383
-
384
- /**
385
- * Render attention heatmap for Prompt A
386
- */
387
- useEffect(() => {
388
- if (!comparison || !selectedLayer || !heatmapARef.current) return;
389
-
390
- const attention = comparison.attentionA.find(a => a.layer === selectedLayer);
391
- if (!attention || !attention.weights) return;
392
-
393
- const margin = { top: 40, right: 40, bottom: 60, left: 60 };
394
- const cellSize = 12;
395
- const weights = attention.weights;
396
- const numRows = weights.length;
397
- const numCols = weights[0]?.length || 0;
398
-
399
- if (numRows === 0 || numCols === 0) return;
400
-
401
- const width = numCols * cellSize;
402
- const height = numRows * cellSize;
403
-
404
- // Clear previous
405
- d3.select(heatmapARef.current).selectAll("*").remove();
406
-
407
- const svg = d3.select(heatmapARef.current)
408
- .attr("width", width + margin.left + margin.right)
409
- .attr("height", height + margin.top + margin.bottom);
410
-
411
- const g = svg.append("g")
412
- .attr("transform", `translate(${margin.left},${margin.top})`);
413
-
414
- // Color scale
415
- const colorScale = d3.scaleSequential(d3.interpolateBlues)
416
- .domain([0, attention.max_weight || 1]);
417
-
418
- // Draw cells
419
- g.selectAll(".cell")
420
- .data(weights.flatMap((row, i) =>
421
- row.map((value, j) => ({ row: i, col: j, value }))
422
- ))
423
- .enter().append("rect")
424
- .attr("class", "cell")
425
- .attr("x", d => d.col * cellSize)
426
- .attr("y", d => d.row * cellSize)
427
- .attr("width", cellSize - 1)
428
- .attr("height", cellSize - 1)
429
- .attr("fill", d => colorScale(d.value))
430
- .attr("stroke", "#1f2937")
431
- .attr("stroke-width", 0.5);
432
-
433
- // Title
434
- svg.append("text")
435
- .attr("x", (width + margin.left + margin.right) / 2)
436
- .attr("y", 20)
437
- .attr("text-anchor", "middle")
438
- .style("font-size", "14px")
439
- .style("font-weight", "bold")
440
- .style("fill", "#fff")
441
- .text("Prompt A");
442
- }, [comparison, selectedLayer]);
443
-
444
- /**
445
- * Render attention heatmap for Prompt B
446
- */
447
- useEffect(() => {
448
- if (!comparison || !selectedLayer || !heatmapBRef.current) return;
449
-
450
- const attention = comparison.attentionB.find(a => a.layer === selectedLayer);
451
- if (!attention || !attention.weights) return;
452
-
453
- const margin = { top: 40, right: 40, bottom: 60, left: 60 };
454
- const cellSize = 12;
455
- const weights = attention.weights;
456
- const numRows = weights.length;
457
- const numCols = weights[0]?.length || 0;
458
-
459
- if (numRows === 0 || numCols === 0) return;
460
-
461
- const width = numCols * cellSize;
462
- const height = numRows * cellSize;
463
-
464
- // Clear previous
465
- d3.select(heatmapBRef.current).selectAll("*").remove();
466
-
467
- const svg = d3.select(heatmapBRef.current)
468
- .attr("width", width + margin.left + margin.right)
469
- .attr("height", height + margin.top + margin.bottom);
470
-
471
- const g = svg.append("g")
472
- .attr("transform", `translate(${margin.left},${margin.top})`);
473
-
474
- // Color scale
475
- const colorScale = d3.scaleSequential(d3.interpolateBlues)
476
- .domain([0, attention.max_weight || 1]);
477
-
478
- // Draw cells
479
- g.selectAll(".cell")
480
- .data(weights.flatMap((row, i) =>
481
- row.map((value, j) => ({ row: i, col: j, value }))
482
- ))
483
- .enter().append("rect")
484
- .attr("class", "cell")
485
- .attr("x", d => d.col * cellSize)
486
- .attr("y", d => d.row * cellSize)
487
- .attr("width", cellSize - 1)
488
- .attr("height", cellSize - 1)
489
- .attr("fill", d => colorScale(d.value))
490
- .attr("stroke", "#1f2937")
491
- .attr("stroke-width", 0.5);
492
-
493
- // Title
494
- svg.append("text")
495
- .attr("x", (width + margin.left + margin.right) / 2)
496
- .attr("y", 20)
497
- .attr("text-anchor", "middle")
498
- .style("font-size", "14px")
499
- .style("font-weight", "bold")
500
- .style("fill", "#fff")
501
- .text("Prompt B");
502
- }, [comparison, selectedLayer]);
503
-
504
- /**
505
- * Creates D3.js visualization of attention differences
506
- * Uses diverging color scale (RdBu) to show increases/decreases
507
- * KNOWN ISSUE: Title text may be cut off at top of visualization
508
- */
509
- useEffect(() => {
510
- if (!comparison || !selectedLayer || !diffSvgRef.current) return;
511
-
512
- const attentionA = comparison.attentionA.find(a => a.layer === selectedLayer);
513
- const attentionB = comparison.attentionB.find(a => a.layer === selectedLayer);
514
-
515
- if (!attentionA || !attentionB) return;
516
-
517
- const margin = { top: 130, right: 60, bottom: 40, left: 60 };
518
- const cellSize = 18;
519
-
520
- // Calculate difference matrix
521
- const diffMatrix: number[][] = [];
522
- const minRows = Math.min(attentionA.weights.length, attentionB.weights.length);
523
- const minCols = Math.min(attentionA.weights[0]?.length || 0, attentionB.weights[0]?.length || 0);
524
-
525
- for (let i = 0; i < minRows; i++) {
526
- diffMatrix[i] = [];
527
- for (let j = 0; j < minCols; j++) {
528
- diffMatrix[i][j] = (attentionB.weights[i]?.[j] || 0) - (attentionA.weights[i]?.[j] || 0);
529
- }
530
- }
531
-
532
- const width = minCols * cellSize;
533
- const height = minRows * cellSize;
534
-
535
- // Clear previous visualization
536
- d3.select(diffSvgRef.current).selectAll("*").remove();
537
-
538
- const totalWidth = width + margin.left + margin.right;
539
- const totalHeight = height + margin.top + margin.bottom;
540
-
541
- const svg = d3.select(diffSvgRef.current)
542
- .attr("width", totalWidth)
543
- .attr("height", totalHeight)
544
- .attr("viewBox", `0 0 ${totalWidth} ${totalHeight}`)
545
- .style("display", "block");
546
-
547
- const g = svg.append("g")
548
- .attr("transform", `translate(${margin.left},${margin.top})`);
549
-
550
- // Create diverging color scale for differences
551
- const maxDiff = Math.max(...diffMatrix.flat().map(Math.abs));
552
- const colorScale = d3.scaleDiverging<string>()
553
- .domain([-maxDiff, 0, maxDiff])
554
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
555
- .interpolator(d3.interpolateRdBu as any);
556
-
557
- // Create scales
558
- const xScale = d3.scaleBand()
559
- .domain(d3.range(minCols).map(String))
560
- .range([0, width])
561
- .padding(0.01);
562
-
563
- const yScale = d3.scaleBand()
564
- .domain(d3.range(minRows).map(String))
565
- .range([0, height])
566
- .padding(0.01);
567
-
568
- // Create tooltip
569
- const tooltip = d3.select("body").append("div")
570
- .attr("class", "diff-tooltip")
571
- .style("opacity", 0)
572
- .style("position", "absolute")
573
- .style("background", "rgba(0, 0, 0, 0.9)")
574
- .style("color", "white")
575
- .style("padding", "10px")
576
- .style("border-radius", "6px")
577
- .style("font-size", "12px")
578
- .style("pointer-events", "none")
579
- .style("z-index", "1000");
580
-
581
- // Draw cells
582
- g.selectAll(".diff-cell")
583
- .data(diffMatrix.flatMap((row, i) =>
584
- row.map((value, j) => ({ row: i, col: j, value }))
585
- ))
586
- .enter().append("rect")
587
- .attr("class", "diff-cell")
588
- .attr("x", d => xScale(String(d.col))!)
589
- .attr("y", d => yScale(String(d.row))!)
590
- .attr("width", xScale.bandwidth())
591
- .attr("height", yScale.bandwidth())
592
- .attr("fill", d => colorScale(d.value))
593
- .attr("stroke", "#1f2937")
594
- .attr("stroke-width", 0.5)
595
- .style("cursor", "pointer")
596
- .on("mouseover", function(event, d) {
597
- tooltip.transition().duration(200).style("opacity", .95);
598
-
599
- const change = d.value > 0 ? '+' : '';
600
- const tokenFrom = attentionA.tokens?.[d.row] || `T${d.row}`;
601
- const tokenTo = attentionA.tokens?.[d.col] || `T${d.col}`;
602
-
603
- tooltip.html(`
604
- <div style="font-weight: bold; margin-bottom: 5px;">Attention Change</div>
605
- <div>From: ${tokenFrom} → To: ${tokenTo}</div>
606
- <div style="margin-top: 5px;">
607
- <span>Prompt A: ${(attentionA.weights[d.row]?.[d.col] || 0).toFixed(4)}</span><br>
608
- <span>Prompt B: ${(attentionB.weights[d.row]?.[d.col] || 0).toFixed(4)}</span><br>
609
- <span style="color: ${d.value > 0 ? '#60a5fa' : '#f87171'}; font-weight: bold;">
610
- Change: ${change}${d.value.toFixed(4)}
611
- </span>
612
- </div>
613
- `)
614
- .style("left", (event.pageX + 10) + "px")
615
- .style("top", (event.pageY - 28) + "px");
616
-
617
- d3.select(this)
618
- .attr("stroke", "#3b82f6")
619
- .attr("stroke-width", 2);
620
- })
621
- .on("mouseout", function() {
622
- tooltip.transition().duration(500).style("opacity", 0);
623
- d3.select(this)
624
- .attr("stroke", "#1f2937")
625
- .attr("stroke-width", 0.5);
626
- });
627
-
628
- // Add title
629
- svg.append("text")
630
- .attr("x", totalWidth / 2)
631
- .attr("y", 25)
632
- .attr("text-anchor", "middle")
633
- .style("font-size", "14px")
634
- .style("font-weight", "bold")
635
- .style("fill", "#fff")
636
- .text(`Attention Difference - ${selectedLayer}`);
637
-
638
- // Add color legend
639
- const legendWidth = 150;
640
- const legendHeight = 15;
641
-
642
- const legendScale = d3.scaleLinear()
643
- .domain([-maxDiff, maxDiff])
644
- .range([0, legendWidth]);
645
-
646
- const legendAxis = d3.axisBottom(legendScale)
647
- .ticks(5)
648
- .tickFormat(d => (d as number).toFixed(2));
649
-
650
- const legend = svg.append("g")
651
- .attr("transform", `translate(${(totalWidth - legendWidth) / 2}, ${50})`);
652
-
653
- // Create gradient for legend
654
- const gradientId = `diff-gradient-${Date.now()}`;
655
- const gradient = svg.append("defs")
656
- .append("linearGradient")
657
- .attr("id", gradientId)
658
- .attr("x1", "0%")
659
- .attr("x2", "100%");
660
-
661
- for (let i = 0; i <= 20; i++) {
662
- const t = i / 20;
663
- const value = -maxDiff + (2 * maxDiff * t);
664
- gradient.append("stop")
665
- .attr("offset", `${t * 100}%`)
666
- .attr("stop-color", colorScale(value));
667
- }
668
-
669
- legend.append("rect")
670
- .attr("width", legendWidth)
671
- .attr("height", legendHeight)
672
- .style("fill", `url(#${gradientId})`);
673
-
674
- legend.append("g")
675
- .attr("transform", `translate(0, ${legendHeight})`)
676
- .call(legendAxis)
677
- .selectAll("text")
678
- .style("fill", "#9ca3af")
679
- .style("font-size", "10px");
680
-
681
- legend.append("text")
682
- .attr("x", legendWidth / 2)
683
- .attr("y", -5)
684
- .attr("text-anchor", "middle")
685
- .style("font-size", "11px")
686
- .style("fill", "#9ca3af")
687
- .text("← Less Attention | More Attention →");
688
-
689
- // Cleanup
690
- return () => {
691
- tooltip.remove();
692
- };
693
- }, [comparison, selectedLayer]);
694
-
695
- /**
696
- * Calculates statistical metrics for attention comparison
697
- * Returns average change, max increase/decrease, and entropy metrics
698
- */
699
- const calculateStats = () => {
700
- if (!comparison || !selectedLayer) return null;
701
-
702
- const attentionA = comparison.attentionA.find(a => a.layer === selectedLayer);
703
- const attentionB = comparison.attentionB.find(a => a.layer === selectedLayer);
704
-
705
- if (!attentionA || !attentionB) return null;
706
-
707
- // Calculate average attention change
708
- let totalChange = 0;
709
- let count = 0;
710
- let maxIncrease = 0;
711
- let maxDecrease = 0;
712
-
713
- const minRows = Math.min(attentionA.weights.length, attentionB.weights.length);
714
- const minCols = Math.min(attentionA.weights[0]?.length || 0, attentionB.weights[0]?.length || 0);
715
-
716
- for (let i = 0; i < minRows; i++) {
717
- for (let j = 0; j < minCols; j++) {
718
- const diff = (attentionB.weights[i]?.[j] || 0) - (attentionA.weights[i]?.[j] || 0);
719
- totalChange += Math.abs(diff);
720
- count++;
721
- if (diff > maxIncrease) maxIncrease = diff;
722
- if (diff < maxDecrease) maxDecrease = diff;
723
- }
724
- }
725
-
726
- return {
727
- avgChange: count > 0 ? totalChange / count : 0,
728
- maxIncrease,
729
- maxDecrease: Math.abs(maxDecrease),
730
- entropyChangeA: attentionA.entropy || 0,
731
- entropyChangeB: attentionB.entropy || 0
732
- };
733
- };
734
-
735
- const stats = calculateStats();
736
- const uniqueLayers = comparison ?
737
- Array.from(new Set(comparison.attentionA.map(a => a.layer))) : [];
738
-
739
- // Generate contextual explanation for current visualization
740
- const generateExplanation = () => {
741
- if (!comparison) {
742
- return {
743
- title: "No Comparison Data",
744
- description: "Enter two different prompts and analyze to see how attention patterns differ.",
745
- details: []
746
- };
747
- }
748
-
749
- const numLayers = uniqueLayers.length;
750
- const hasRealModel = useRealModel;
751
- const avgChange = stats?.avgChange ? (stats.avgChange * 100).toFixed(1) : "0";
752
- const maxInc = stats?.maxIncrease ? (stats.maxIncrease * 100).toFixed(1) : "0";
753
- const maxDec = stats?.maxDecrease ? (stats.maxDecrease * 100).toFixed(1) : "0";
754
-
755
- return {
756
- title: `Prompt Comparison: ${numLayers} layers analyzed`,
757
- description: `Comparing attention patterns between two prompts to identify behavioral differences.`,
758
- details: [
759
- {
760
- heading: "What is Prompt Diff?",
761
- content: `This tool compares how the model's attention mechanism responds to different prompts. Three visualizations show: raw attention for each prompt (left/right) and the difference between them (center).`
762
- },
763
- {
764
- heading: "Reading the Three Maps",
765
- content: `Left: Attention patterns for Prompt A. Center: Difference map (blue = increased in B, red = decreased in B). Right: Attention patterns for Prompt B. Compare side panels to see the full context of changes.`
766
- },
767
- {
768
- heading: "Current Analysis",
769
- content: `Average attention change: ${avgChange}%. Maximum increase: +${maxInc}%. Maximum decrease: -${maxDec}%. ${hasRealModel ? 'Using real model for authentic patterns.' : 'Demo mode with simulated differences.'}`
770
- },
771
- {
772
- heading: "Why Compare Prompts?",
773
- content: `Different prompts can dramatically change model behavior. Adding words like 'detailed' or 'concise' shifts attention patterns. This visualization reveals these hidden changes.`
774
- },
775
- {
776
- heading: "Entropy Changes",
777
- content: `Entropy measures uncertainty in attention distribution. Higher entropy means more scattered attention, lower means more focused. Compare entropy values to see which prompt creates clearer attention patterns.`
778
- },
779
- {
780
- heading: "Practical Applications",
781
- content: `Use this to: Optimize prompts for better results, understand why certain prompts work better, debug unexpected model behavior, and design more effective prompt templates.`
782
- }
783
- ]
784
- };
785
- };
786
-
787
- const explanation = generateExplanation();
788
-
789
- // Export comparison
790
- const exportComparison = () => {
791
- if (!comparison) return;
792
-
793
- const dataStr = JSON.stringify(comparison, null, 2);
794
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
795
-
796
- const exportFileDefaultName = `prompt_diff_${Date.now()}.json`;
797
-
798
- const linkElement = document.createElement('a');
799
- linkElement.setAttribute('href', dataUri);
800
- linkElement.setAttribute('download', exportFileDefaultName);
801
- linkElement.click();
802
- };
803
-
804
- return (
805
- <div className="bg-gray-900 rounded-xl p-6">
806
- <div className="flex items-center justify-between mb-6">
807
- <div>
808
- <h2 className="text-2xl font-bold flex items-center gap-2">
809
- <GitCompare className="w-6 h-6 text-purple-400" />
810
- Prompt Diff Analyzer
811
- </h2>
812
- <p className="text-gray-400 mt-1">
813
- Compare how different prompts affect attention patterns and model behavior
814
- </p>
815
- </div>
816
-
817
- <div className="flex items-center gap-4">
818
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
819
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
820
- }`}>
821
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
822
- {isConnected ? 'Connected' : 'Disconnected'}
823
- </div>
824
- </div>
825
- </div>
826
-
827
- {/* Prompt Input Section */}
828
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
829
- <div>
830
- <label className="block text-sm font-medium text-gray-300 mb-2">
831
- Prompt A
832
- </label>
833
- <textarea
834
- value={prompt1}
835
- onChange={(e) => setPrompt1(e.target.value)}
836
- className="w-full h-32 px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
837
- placeholder="Enter first prompt..."
838
- />
839
- </div>
840
-
841
- <div>
842
- <label className="block text-sm font-medium text-gray-300 mb-2">
843
- Prompt B
844
- </label>
845
- <textarea
846
- value={prompt2}
847
- onChange={(e) => setPrompt2(e.target.value)}
848
- className="w-full h-32 px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
849
- placeholder="Enter second prompt..."
850
- />
851
- </div>
852
- </div>
853
-
854
- {/* Mode Indicator */}
855
- <div className="mb-4 p-3 bg-blue-900/20 border border-blue-700 rounded-lg">
856
- <div className="flex items-center justify-between">
857
- <div>
858
- <p className="text-xs text-blue-400 font-semibold mb-1">
859
- {useRealModel ? '🤖 Real Model Mode' : '📝 Demo Mode'}
860
- </p>
861
- <p className="text-xs text-gray-300">
862
- {useRealModel
863
- ? 'Connected to CodeGPT model service - generating real attention patterns'
864
- : 'Using simulated data - start prompt_service.py for real model'}
865
- </p>
866
- </div>
867
- {useRealModel && (
868
- <div className="flex items-center gap-2 px-3 py-1 bg-green-900/30 rounded-full">
869
- <div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
870
- <span className="text-xs text-green-400">Model Ready</span>
871
- </div>
872
- )}
873
- </div>
874
- </div>
875
-
876
- {/* Action Buttons */}
877
- <div className="flex gap-4 mb-6">
878
- <button
879
- onClick={analyzePrompts}
880
- disabled={isAnalyzing || !isConnected}
881
- className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 flex items-center gap-2"
882
- data-analyze-button
883
- >
884
- {isAnalyzing ? (
885
- <>
886
- <Activity className="w-4 h-4 animate-spin" />
887
- Analyzing...
888
- </>
889
- ) : (
890
- <>
891
- Analyze Difference
892
- <ArrowRight className="w-4 h-4" />
893
- </>
894
- )}
895
- </button>
896
-
897
- {comparison && (
898
- <button
899
- onClick={exportComparison}
900
- className="px-4 py-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2"
901
- >
902
- <Download className="w-4 h-4" />
903
- Export
904
- </button>
905
- )}
906
- </div>
907
-
908
- {comparison && (
909
- <>
910
- {/* Layer Selector */}
911
- <div className="flex items-center gap-4 mb-6">
912
- <span className="text-gray-400">Layer:</span>
913
- <div className="flex gap-2">
914
- {uniqueLayers.map(layer => (
915
- <button
916
- key={layer}
917
- onClick={() => setSelectedLayer(layer)}
918
- className={`px-3 py-1 text-sm rounded-lg transition-colors ${
919
- selectedLayer === layer
920
- ? 'bg-purple-600 text-white'
921
- : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
922
- }`}
923
- >
924
- {layer}
925
- </button>
926
- ))}
927
- </div>
928
- </div>
929
-
930
- {/* Main Content Area with Side Panel */}
931
- <div className="flex gap-4 mb-6">
932
- {/* Difference Visualization */}
933
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
934
- <div className="bg-gray-800 rounded-lg p-4 relative">
935
- {/* Help Toggle Button */}
936
- <button
937
- onClick={() => setShowExplanation(!showExplanation)}
938
- className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
939
- >
940
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
941
- <span className="text-sm font-medium">
942
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
943
- </span>
944
- </button>
945
-
946
- <div className="w-full flex justify-center gap-4 overflow-x-auto">
947
- {/* Prompt A Heatmap */}
948
- <div className="flex flex-col items-center">
949
- <svg ref={heatmapARef}></svg>
950
- </div>
951
-
952
- {/* Difference Map */}
953
- <div className="flex flex-col items-center">
954
- <svg ref={diffSvgRef}></svg>
955
- </div>
956
-
957
- {/* Prompt B Heatmap */}
958
- <div className="flex flex-col items-center">
959
- <svg ref={heatmapBRef}></svg>
960
- </div>
961
- </div>
962
- </div>
963
- </div>
964
-
965
- {/* Explanation Side Panel */}
966
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
967
- <div className="w-96 h-[600px] bg-gray-900 rounded-lg border border-gray-700">
968
- {/* Panel Header */}
969
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
970
- <div className="flex items-center gap-2">
971
- <Info className="w-5 h-5 text-blue-400" />
972
- <h3 className="text-lg font-semibold text-white">Understanding Prompt Diff</h3>
973
- </div>
974
- </div>
975
-
976
- {/* Panel Content */}
977
- <div className="px-4 py-4 overflow-y-auto h-[calc(600px-60px)]">
978
- {/* Main Description */}
979
- <div className="mb-4 p-3 bg-purple-900/20 border border-purple-800 rounded-lg">
980
- <h4 className="text-sm font-semibold text-purple-400 mb-1">{explanation.title}</h4>
981
- <p className="text-xs text-gray-300">{explanation.description}</p>
982
- </div>
983
-
984
- {/* Explanation Sections */}
985
- <div className="space-y-3">
986
- {explanation.details.map((section, idx) => (
987
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
988
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
989
- <Zap className="w-3 h-3 text-yellow-400" />
990
- {section.heading}
991
- </h5>
992
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
993
- </div>
994
- ))}
995
- </div>
996
-
997
- {/* Visual Guide */}
998
- <div className="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
999
- <h4 className="font-medium text-sm text-blue-400 mb-2">Color Legend</h4>
1000
- <div className="space-y-2 text-xs">
1001
- <div className="flex items-start gap-2">
1002
- <div className="w-3 h-3 bg-blue-500 rounded mt-0.5"></div>
1003
- <span className="text-gray-300">Increased attention in Prompt B</span>
1004
- </div>
1005
- <div className="flex items-start gap-2">
1006
- <div className="w-3 h-3 bg-red-500 rounded mt-0.5"></div>
1007
- <span className="text-gray-300">Decreased attention in Prompt B</span>
1008
- </div>
1009
- <div className="flex items-start gap-2">
1010
- <div className="w-3 h-3 bg-gray-500 rounded mt-0.5"></div>
1011
- <span className="text-gray-300">Similar attention levels</span>
1012
- </div>
1013
- </div>
1014
- </div>
1015
-
1016
- {/* Current Metrics */}
1017
- {stats && (
1018
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
1019
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Metrics</h4>
1020
- <div className="space-y-1 text-xs">
1021
- <div className="flex justify-between">
1022
- <span className="text-gray-400">Layer:</span>
1023
- <span className="text-white">{selectedLayer || 'None'}</span>
1024
- </div>
1025
- <div className="flex justify-between">
1026
- <span className="text-gray-400">Avg Change:</span>
1027
- <span className="text-yellow-400">{(stats.avgChange * 100).toFixed(2)}%</span>
1028
- </div>
1029
- <div className="flex justify-between">
1030
- <span className="text-gray-400">Max Increase:</span>
1031
- <span className="text-green-400">+{(stats.maxIncrease * 100).toFixed(2)}%</span>
1032
- </div>
1033
- <div className="flex justify-between">
1034
- <span className="text-gray-400">Max Decrease:</span>
1035
- <span className="text-red-400">-{(stats.maxDecrease * 100).toFixed(2)}%</span>
1036
- </div>
1037
- </div>
1038
- </div>
1039
- )}
1040
-
1041
- {/* Tips */}
1042
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
1043
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
1044
- <ul className="text-xs text-gray-400 space-y-1">
1045
- <li>• Try adding adjectives to see attention shifts</li>
1046
- <li>• Compare with/without instructions</li>
1047
- <li>• Test different prompt formats</li>
1048
- <li>• Look for consistent patterns across layers</li>
1049
- </ul>
1050
- </div>
1051
- </div>
1052
- </div>
1053
- </div>
1054
- </div>
1055
-
1056
- {/* Generated Code (if using real model) */}
1057
- {useRealModel && (generatedTexts.a || generatedTexts.b) && (
1058
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
1059
- {generatedTexts.a && (
1060
- <div className="bg-gray-800 rounded-lg p-4">
1061
- <h4 className="text-sm font-semibold text-gray-300 mb-2">Generated Code A:</h4>
1062
- <pre className="text-xs text-gray-400 font-mono overflow-x-auto">
1063
- <code>{generatedTexts.a.substring(0, 200)}...</code>
1064
- </pre>
1065
- </div>
1066
- )}
1067
- {generatedTexts.b && (
1068
- <div className="bg-gray-800 rounded-lg p-4">
1069
- <h4 className="text-sm font-semibold text-gray-300 mb-2">Generated Code B:</h4>
1070
- <pre className="text-xs text-gray-400 font-mono overflow-x-auto">
1071
- <code>{generatedTexts.b.substring(0, 200)}...</code>
1072
- </pre>
1073
- </div>
1074
- )}
1075
- </div>
1076
- )}
1077
-
1078
- {/* Statistics Panel */}
1079
- {stats && (
1080
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
1081
- <div className="bg-gray-800 rounded-lg p-4">
1082
- <h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
1083
- <AlertTriangle className="w-5 h-5 text-yellow-500" />
1084
- Attention Changes
1085
- </h3>
1086
-
1087
- <div className="space-y-3">
1088
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1089
- <span className="text-sm text-gray-400">Average Change</span>
1090
- <span className="text-sm font-mono text-yellow-400">
1091
- {(stats.avgChange * 100).toFixed(2)}%
1092
- </span>
1093
- </div>
1094
-
1095
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1096
- <span className="text-sm text-gray-400">Max Increase</span>
1097
- <span className="text-sm font-mono text-green-400">
1098
- +{(stats.maxIncrease * 100).toFixed(2)}%
1099
- </span>
1100
- </div>
1101
-
1102
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1103
- <span className="text-sm text-gray-400">Max Decrease</span>
1104
- <span className="text-sm font-mono text-red-400">
1105
- -{(stats.maxDecrease * 100).toFixed(2)}%
1106
- </span>
1107
- </div>
1108
- </div>
1109
- </div>
1110
-
1111
- <div className="bg-gray-800 rounded-lg p-4">
1112
- <h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
1113
- <CheckCircle className="w-5 h-5 text-green-500" />
1114
- Entropy Analysis
1115
- </h3>
1116
-
1117
- <div className="space-y-3">
1118
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1119
- <span className="text-sm text-gray-400">Prompt A Entropy</span>
1120
- <span className="text-sm font-mono">
1121
- {stats.entropyChangeA.toFixed(3)}
1122
- </span>
1123
- </div>
1124
-
1125
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1126
- <span className="text-sm text-gray-400">Prompt B Entropy</span>
1127
- <span className="text-sm font-mono">
1128
- {stats.entropyChangeB.toFixed(3)}
1129
- </span>
1130
- </div>
1131
-
1132
- <div className="flex justify-between items-center p-2 bg-gray-900 rounded">
1133
- <span className="text-sm text-gray-400">Entropy Change</span>
1134
- <span className={`text-sm font-mono ${
1135
- stats.entropyChangeB > stats.entropyChangeA ? 'text-yellow-400' : 'text-green-400'
1136
- }`}>
1137
- {stats.entropyChangeB > stats.entropyChangeA ? <Plus className="w-3 h-3 inline" /> : <Minus className="w-3 h-3 inline" />}
1138
- {Math.abs(stats.entropyChangeB - stats.entropyChangeA).toFixed(3)}
1139
- </span>
1140
- </div>
1141
- </div>
1142
- </div>
1143
- </div>
1144
- )}
1145
-
1146
- {/* Saved Comparisons */}
1147
- {savedComparisons.length > 0 && (
1148
- <div className="bg-gray-800 rounded-lg p-4">
1149
- <h3 className="text-lg font-semibold mb-3">Recent Comparisons</h3>
1150
- <div className="space-y-2">
1151
- {savedComparisons.slice(-3).reverse().map((comp, idx) => (
1152
- <div key={idx} className="flex items-center justify-between p-2 bg-gray-900 rounded">
1153
- <div className="text-sm">
1154
- <span className="text-gray-400">Compare {idx + 1}:</span>
1155
- <span className="ml-2 text-xs text-gray-500">
1156
- {new Date(comp.timestamp).toLocaleTimeString()}
1157
- </span>
1158
- </div>
1159
- <button
1160
- onClick={() => setComparison(comp)}
1161
- className="text-xs px-2 py-1 bg-gray-700 rounded hover:bg-gray-600"
1162
- >
1163
- Load
1164
- </button>
1165
- </div>
1166
- ))}
1167
- </div>
1168
- </div>
1169
- )}
1170
- </>
1171
- )}
1172
-
1173
- {/* Empty State */}
1174
- {!comparison && (
1175
- <div className="bg-gray-800 rounded-lg p-8 text-center">
1176
- <GitCompare className="w-12 h-12 mx-auto mb-4 text-gray-600" />
1177
- <p className="text-gray-400 mb-2">No comparison yet</p>
1178
- <p className="text-sm text-gray-500 mb-4">
1179
- Enter two different prompts and click &quot;Analyze Difference&quot; to compare attention patterns
1180
- </p>
1181
- {traces.filter(t => t.type === 'attention').length === 0 && (
1182
- <div className="mt-4 p-3 bg-yellow-900/30 border border-yellow-700 rounded-lg text-left max-w-md mx-auto">
1183
- <p className="text-xs text-yellow-400 font-semibold mb-1">⚠️ No attention data available</p>
1184
- <p className="text-xs text-gray-300">
1185
- First, generate some traces by running:
1186
- </p>
1187
- <code className="block mt-1 text-xs bg-gray-900 px-2 py-1 rounded text-gray-300">
1188
- python python-sdk/working_demo.py
1189
- </code>
1190
- </div>
1191
- )}
1192
- </div>
1193
- )}
1194
- </div>
1195
- );
1196
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/TokenFlowVisualizer.tsx DELETED
@@ -1,960 +0,0 @@
1
- /**
2
- * Token Flow Visualizer Component
3
- *
4
- * Visualizes how tokens flow through transformer layers,
5
- * showing attention paths and information propagation
6
- *
7
- * @component
8
- */
9
-
10
- "use client";
11
-
12
- import { useState, useEffect, useRef } from "react";
13
- import * as d3 from "d3";
14
- import { getApiUrl, getWsUrl } from "@/lib/config";
15
- import {
16
- GitBranch,
17
- Activity,
18
- Layers,
19
- Play,
20
- Pause,
21
- RotateCcw,
22
- ZoomIn,
23
- ZoomOut,
24
- Download,
25
- Info,
26
- HelpCircle,
27
- X,
28
- Zap,
29
- RefreshCw
30
- } from "lucide-react";
31
-
32
- // Token data structure
33
- interface Token {
34
- id: string;
35
- text: string;
36
- position: number;
37
- embedding?: number[];
38
- }
39
-
40
- // Layer data structure
41
- interface LayerData {
42
- layerIndex: number;
43
- layerName: string;
44
- tokens: TokenState[];
45
- attention: number[][];
46
- timestamp: number;
47
- }
48
-
49
- // Token state at a specific layer
50
- interface TokenState {
51
- tokenId: string;
52
- text: string;
53
- position: number;
54
- activation: number;
55
- attention_received: number;
56
- attention_given: number;
57
- importance: number;
58
- }
59
-
60
- // Flow connection between tokens across layers
61
- interface FlowConnection {
62
- source: { layer: number; token: number };
63
- target: { layer: number; token: number };
64
- strength: number;
65
- type: 'attention' | 'residual' | 'feedforward';
66
- }
67
-
68
- export default function TokenFlowVisualizer() {
69
- const [tokens, setTokens] = useState<Token[]>([]);
70
- const [layers, setLayers] = useState<LayerData[]>([]);
71
- const [flowConnections, setFlowConnections] = useState<FlowConnection[]>([]);
72
- const [selectedToken, setSelectedToken] = useState<number | null>(null);
73
- const [selectedLayer, setSelectedLayer] = useState<number | null>(null);
74
- const [isPlaying, setIsPlaying] = useState(false);
75
- const [currentStep, setCurrentStep] = useState(0);
76
- const [zoom, setZoom] = useState(1);
77
- const [showResidual, setShowResidual] = useState(true);
78
- const [showAttention, setShowAttention] = useState(true);
79
- const [showExplanation, setShowExplanation] = useState(false);
80
- const [isConnected, setIsConnected] = useState(false);
81
- const [prompt, setPrompt] = useState("def fibonacci(n):\n '''Calculate fibonacci number'''");
82
- const [isGenerating, setIsGenerating] = useState(false);
83
- const [traces, setTraces] = useState<Record<string, unknown>[]>([]);
84
-
85
- const svgRef = useRef<SVGSVGElement>(null);
86
- const animationRef = useRef<number | null>(null);
87
- const wsRef = useRef<WebSocket | null>(null);
88
-
89
- // Connect to WebSocket for real-time updates
90
- useEffect(() => {
91
- let mounted = true;
92
- let reconnectTimeout: NodeJS.Timeout;
93
-
94
- const connectWS = () => {
95
- if (!mounted) return;
96
-
97
- try {
98
- const ws = new WebSocket(getWsUrl());
99
-
100
- ws.onopen = () => {
101
- if (!mounted) return;
102
- console.log('TokenFlow: WebSocket connected');
103
- setIsConnected(true);
104
- };
105
-
106
- ws.onmessage = (event) => {
107
- if (!mounted) return;
108
-
109
- let data;
110
- try {
111
- data = JSON.parse(event.data);
112
- } catch (e) {
113
- // Skip non-JSON messages
114
- return;
115
- }
116
-
117
- // Collect traces for visualization
118
- if (data.type === 'attention' || data.type === 'activation') {
119
- setTraces(prev => [...prev, data]);
120
- } else if (data.type === 'generated_token') {
121
- // Handle token generation
122
- setTokens(prev => {
123
- const newToken: Token = {
124
- id: `token_${prev.length}`,
125
- text: data.token,
126
- position: prev.length
127
- };
128
- return [...prev, newToken];
129
- });
130
- }
131
- };
132
-
133
- ws.onerror = () => {
134
- if (mounted) {
135
- setIsConnected(false);
136
- }
137
- };
138
-
139
- ws.onclose = () => {
140
- if (!mounted) return;
141
- console.log('TokenFlow: WebSocket disconnected, will reconnect...');
142
- setIsConnected(false);
143
- reconnectTimeout = setTimeout(() => {
144
- if (mounted) connectWS();
145
- }, 3000);
146
- };
147
-
148
- wsRef.current = ws;
149
- } catch (error) {
150
- console.log('WebSocket connection attempt failed, will retry...');
151
- if (mounted) {
152
- setIsConnected(false);
153
- reconnectTimeout = setTimeout(() => {
154
- if (mounted) connectWS();
155
- }, 3000);
156
- }
157
- }
158
- };
159
-
160
- connectWS();
161
-
162
- return () => {
163
- mounted = false;
164
- if (reconnectTimeout) {
165
- clearTimeout(reconnectTimeout);
166
- }
167
- if (wsRef.current) {
168
- wsRef.current.close();
169
- }
170
- };
171
- }, []);
172
-
173
- // Listen for demo events from LocalControlPanel
174
- useEffect(() => {
175
- const handleDemoPromptSelected = (event: CustomEvent) => {
176
- const { prompt, demoId } = event.detail;
177
- console.log('TokenFlow: Demo prompt selected -', demoId);
178
-
179
- if (prompt) {
180
- setPrompt(prompt);
181
- }
182
- };
183
-
184
- const handleDemoStarting = (event: CustomEvent) => {
185
- const { demoId } = event.detail;
186
- console.log('TokenFlow: Demo starting, clearing data -', demoId);
187
-
188
- // Clear all data when demo starts
189
- setTokens([]);
190
- setLayers([]);
191
- setFlowConnections([]);
192
- setTraces([]);
193
- setSelectedToken(null);
194
- setSelectedLayer(null);
195
- };
196
-
197
- const handleDemoCompleted = (event: CustomEvent) => {
198
- const data = event.detail;
199
- console.log('TokenFlow: Demo completed', data);
200
-
201
- // Process the completed demo data
202
- if (data && data.traces) {
203
- setTraces(data.traces);
204
- }
205
- };
206
-
207
- window.addEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
208
- window.addEventListener('demo-starting', handleDemoStarting as EventListener);
209
- window.addEventListener('demo-completed', handleDemoCompleted as EventListener);
210
-
211
- return () => {
212
- window.removeEventListener('demo-prompt-selected', handleDemoPromptSelected as EventListener);
213
- window.removeEventListener('demo-starting', handleDemoStarting as EventListener);
214
- window.removeEventListener('demo-completed', handleDemoCompleted as EventListener);
215
- };
216
- }, []);
217
-
218
- // Process traces to extract token flow data
219
- useEffect(() => {
220
- const attentionTraces = traces.filter(t => t.type === 'attention' && t.weights);
221
- const activationTraces = traces.filter(t => t.type === 'activation');
222
-
223
- console.log('[TokenFlow] Total traces:', traces.length);
224
- console.log('[TokenFlow] Attention traces:', attentionTraces.length);
225
- console.log('[TokenFlow] First attention trace:', attentionTraces[0]);
226
-
227
- if (attentionTraces.length > 0 || tokens.length > 0) {
228
- // Use existing tokens if available (from streaming), otherwise extract from traces
229
- if (tokens.length === 0) {
230
- const traceWithTokens = attentionTraces.find(t => t.tokens);
231
- if (traceWithTokens?.tokens && Array.isArray(traceWithTokens.tokens)) {
232
- const extractedTokens: Token[] = (traceWithTokens.tokens as string[]).map((text: string, idx: number) => ({
233
- id: `token_${idx}`,
234
- text,
235
- position: idx
236
- }));
237
- setTokens(extractedTokens);
238
- }
239
- }
240
-
241
- // Build layer data
242
- const layerMap = new Map<string, Record<string, unknown>[]>();
243
- attentionTraces.forEach(trace => {
244
- const layer = String(trace.layer || 'unknown');
245
- if (!layerMap.has(layer)) {
246
- layerMap.set(layer, []);
247
- }
248
- layerMap.get(layer)?.push(trace);
249
- });
250
-
251
- const layerDataArray: LayerData[] = Array.from(layerMap.entries())
252
- .map(([layerName, traces], idx) => {
253
- const latestTrace = traces[traces.length - 1];
254
- const weights = (latestTrace.weights || []) as number[][];
255
-
256
- // Calculate token states for this layer
257
- // Use the tokens we have collected (either from streaming or from traces)
258
- const tokenTexts = tokens.length > 0 ? tokens.map(t => t.text) : ((latestTrace.tokens || []) as string[]);
259
- const tokenStates: TokenState[] = tokenTexts.map((text: string, tokenIdx: number) => {
260
- // Calculate attention received (sum of column)
261
- const attention_received = weights.reduce((sum: number, row: number[]) =>
262
- sum + (row[tokenIdx] || 0), 0
263
- );
264
-
265
- // Calculate attention given (sum of row)
266
- const attention_given = weights[tokenIdx]?.reduce((sum: number, val: number) =>
267
- sum + val, 0
268
- ) || 0;
269
-
270
- // Calculate importance as combination of received and given attention
271
- const importance = (attention_received + attention_given) / 2;
272
-
273
- return {
274
- tokenId: `token_${tokenIdx}`,
275
- text,
276
- position: tokenIdx,
277
- activation: Math.random(), // Would come from activation traces
278
- attention_received,
279
- attention_given,
280
- importance
281
- };
282
- });
283
-
284
- return {
285
- layerIndex: idx,
286
- layerName,
287
- tokens: tokenStates,
288
- attention: weights,
289
- timestamp: (latestTrace.timestamp || Date.now()) as number
290
- };
291
- })
292
- .sort((a, b) => {
293
- // Extract layer numbers for proper numerical sorting
294
- const aNum = parseInt(a.layerName.replace(/[^0-9]/g, '')) || 0;
295
- const bNum = parseInt(b.layerName.replace(/[^0-9]/g, '')) || 0;
296
- return aNum - bNum;
297
- });
298
-
299
- setLayers(layerDataArray);
300
-
301
- // Generate flow connections
302
- generateFlowConnections(layerDataArray);
303
- }
304
- }, [traces, tokens]);
305
-
306
- // Generate flow connections between layers
307
- const generateFlowConnections = (layerData: LayerData[]) => {
308
- const connections: FlowConnection[] = [];
309
-
310
- for (let i = 0; i < layerData.length - 1; i++) {
311
- const currentLayer = layerData[i];
312
- const nextLayer = layerData[i + 1];
313
-
314
- // Add attention connections
315
- if (currentLayer.attention && showAttention) {
316
- currentLayer.attention.forEach((row, srcToken) => {
317
- row.forEach((weight, tgtToken) => {
318
- if (weight > 0.1) { // Threshold for visibility
319
- connections.push({
320
- source: { layer: i, token: srcToken },
321
- target: { layer: i + 1, token: tgtToken },
322
- strength: weight,
323
- type: 'attention'
324
- });
325
- }
326
- });
327
- });
328
- }
329
-
330
- // Add residual connections
331
- if (showResidual) {
332
- currentLayer.tokens.forEach((token, idx) => {
333
- if (idx < nextLayer.tokens.length) {
334
- connections.push({
335
- source: { layer: i, token: idx },
336
- target: { layer: i + 1, token: idx },
337
- strength: 0.5,
338
- type: 'residual'
339
- });
340
- }
341
- });
342
- }
343
- }
344
-
345
- setFlowConnections(connections);
346
- };
347
-
348
- // D3 Visualization
349
- useEffect(() => {
350
- if (!svgRef.current || layers.length === 0) return;
351
-
352
- const margin = { top: 60, right: 200, bottom: 60, left: 100 };
353
- const width = 1300;
354
- const height = 600;
355
-
356
- // Clear previous visualization
357
- d3.select(svgRef.current).selectAll("*").remove();
358
-
359
- const svg = d3.select(svgRef.current)
360
- .attr("width", width)
361
- .attr("height", height)
362
- .attr("viewBox", `0 0 ${width} ${height}`);
363
-
364
- const g = svg.append("g")
365
- .attr("transform", `translate(${margin.left},${margin.top}) scale(${zoom})`);
366
-
367
- // Calculate positions
368
- const layerWidth = (width - margin.left - margin.right) / (layers.length || 1);
369
- const tokenHeight = 40;
370
- const tokenWidth = 80;
371
-
372
- // Create layer groups
373
- const layerGroups = g.selectAll(".layer-group")
374
- .data(layers)
375
- .enter()
376
- .append("g")
377
- .attr("class", "layer-group")
378
- .attr("transform", (d, i) => `translate(${i * layerWidth}, 0)`);
379
-
380
- // Add layer labels
381
- layerGroups.append("text")
382
- .attr("x", layerWidth / 2)
383
- .attr("y", -20)
384
- .attr("text-anchor", "middle")
385
- .attr("fill", "#9ca3af")
386
- .attr("font-size", "12px")
387
- .attr("font-weight", "bold")
388
- .text(d => d.layerName);
389
-
390
- // Function to get token position
391
- const getTokenPosition = (layerIdx: number, tokenIdx: number) => {
392
- const x = layerIdx * layerWidth + layerWidth / 2;
393
- const y = tokenIdx * (tokenHeight + 10) + tokenHeight / 2;
394
- return { x, y };
395
- };
396
-
397
- // Draw flow connections
398
- const connectionPaths = g.selectAll(".flow-connection")
399
- .data(flowConnections)
400
- .enter()
401
- .append("path")
402
- .attr("class", d => `flow-connection flow-${d.type}`)
403
- .attr("d", d => {
404
- const source = getTokenPosition(d.source.layer, d.source.token);
405
- const target = getTokenPosition(d.target.layer, d.target.token);
406
-
407
- // Create curved path
408
- const midX = (source.x + target.x) / 2;
409
- return `M ${source.x} ${source.y} Q ${midX} ${source.y} ${midX} ${(source.y + target.y) / 2} T ${target.x} ${target.y}`;
410
- })
411
- .attr("stroke", d => {
412
- if (d.type === 'attention') return "#3b82f6";
413
- if (d.type === 'residual') return "#10b981";
414
- return "#8b5cf6";
415
- })
416
- .attr("stroke-width", d => Math.max(0.5, d.strength * 3))
417
- .attr("stroke-opacity", d => d.strength * 0.6)
418
- .attr("fill", "none");
419
-
420
- // Add animation to connections if playing
421
- if (isPlaying) {
422
- connectionPaths
423
- .attr("stroke-dasharray", "5,5")
424
- .append("animate")
425
- .attr("attributeName", "stroke-dashoffset")
426
- .attr("from", "10")
427
- .attr("to", "0")
428
- .attr("dur", "1s")
429
- .attr("repeatCount", "indefinite");
430
- }
431
-
432
- // Draw tokens
433
- const tokenGroups = layerGroups.selectAll(".token")
434
- .data(d => d.tokens)
435
- .enter()
436
- .append("g")
437
- .attr("class", "token-group")
438
- .attr("transform", (d, i) => {
439
- const pos = getTokenPosition(0, i);
440
- return `translate(${layerWidth / 2 - tokenWidth / 2}, ${i * (tokenHeight + 10)})`;
441
- });
442
-
443
- // Token rectangles
444
- tokenGroups.append("rect")
445
- .attr("width", tokenWidth)
446
- .attr("height", tokenHeight)
447
- .attr("rx", 6)
448
- .attr("fill", d => {
449
- const importance = d.importance || 0;
450
- return d3.interpolateYlOrRd(importance);
451
- })
452
- .attr("stroke", d => selectedToken === d.position ? "#3b82f6" : "#4b5563")
453
- .attr("stroke-width", d => selectedToken === d.position ? 2 : 1)
454
- .style("cursor", "pointer")
455
- .on("click", (event, d) => {
456
- setSelectedToken(d.position === selectedToken ? null : d.position);
457
- });
458
-
459
- // Token text
460
- tokenGroups.append("text")
461
- .attr("x", tokenWidth / 2)
462
- .attr("y", tokenHeight / 2)
463
- .attr("text-anchor", "middle")
464
- .attr("dominant-baseline", "middle")
465
- .attr("fill", d => d.importance > 0.5 ? "#fff" : "#1f2937")
466
- .attr("font-size", "11px")
467
- .attr("font-family", "monospace")
468
- .attr("pointer-events", "none")
469
- .text(d => d.text.substring(0, 8));
470
-
471
- // Add importance indicator
472
- tokenGroups.append("circle")
473
- .attr("cx", tokenWidth - 10)
474
- .attr("cy", 10)
475
- .attr("r", d => Math.max(2, d.importance * 6))
476
- .attr("fill", "#fbbf24")
477
- .attr("opacity", 0.8);
478
-
479
- // Add title
480
- svg.append("text")
481
- .attr("x", width / 2)
482
- .attr("y", 30)
483
- .attr("text-anchor", "middle")
484
- .attr("font-size", "16px")
485
- .attr("font-weight", "bold")
486
- .attr("fill", "#fff")
487
- .text("Token Flow Through Transformer Layers");
488
-
489
- // Add legend - positioned in the right margin area, clear of the visualization
490
- // The visualization ends at width - margin.right (1100), legend goes in the margin
491
- const legend = svg.append("g")
492
- .attr("transform", `translate(${width - 180}, 100)`);
493
-
494
- const legendItems = [
495
- { color: "#3b82f6", label: "Attention Flow", type: "attention" },
496
- { color: "#10b981", label: "Residual Connection", type: "residual" },
497
- { color: "#fbbf24", label: "Token Importance", type: "importance" }
498
- ];
499
-
500
- legendItems.forEach((item, i) => {
501
- const legendItem = legend.append("g")
502
- .attr("transform", `translate(0, ${i * 25})`);
503
-
504
- if (item.type === "importance") {
505
- legendItem.append("circle")
506
- .attr("cx", 10)
507
- .attr("cy", 10)
508
- .attr("r", 6)
509
- .attr("fill", item.color);
510
- } else {
511
- legendItem.append("line")
512
- .attr("x1", 0)
513
- .attr("y1", 10)
514
- .attr("x2", 20)
515
- .attr("y2", 10)
516
- .attr("stroke", item.color)
517
- .attr("stroke-width", 2);
518
- }
519
-
520
- legendItem.append("text")
521
- .attr("x", 30)
522
- .attr("y", 10)
523
- .attr("dominant-baseline", "middle")
524
- .attr("fill", "#9ca3af")
525
- .attr("font-size", "11px")
526
- .text(item.label);
527
- });
528
-
529
- }, [layers, flowConnections, selectedToken, zoom, isPlaying, showAttention, showResidual]);
530
-
531
- // Animation control
532
- const toggleAnimation = () => {
533
- setIsPlaying(!isPlaying);
534
- if (!isPlaying) {
535
- animateFlow();
536
- } else {
537
- if (animationRef.current) {
538
- cancelAnimationFrame(animationRef.current);
539
- }
540
- }
541
- };
542
-
543
- const animateFlow = () => {
544
- setCurrentStep(prev => (prev + 1) % (layers.length || 1));
545
- animationRef.current = requestAnimationFrame(() => {
546
- if (isPlaying) {
547
- setTimeout(animateFlow, 1000);
548
- }
549
- });
550
- };
551
-
552
- const reset = () => {
553
- setCurrentStep(0);
554
- setSelectedToken(null);
555
- setSelectedLayer(null);
556
- setIsPlaying(false);
557
- };
558
-
559
- // Generate contextual explanation for current visualization
560
- const generateExplanation = () => {
561
- if (layers.length === 0) {
562
- return {
563
- title: "No Token Flow Data",
564
- description: "Run a model to see how tokens flow through transformer layers.",
565
- details: []
566
- };
567
- }
568
-
569
- const numLayers = layers.length;
570
- const numTokens = tokens.length;
571
- const activeConnections = flowConnections.filter(c => c.strength > 0.1).length;
572
- const totalConnections = flowConnections.length;
573
- const connectionDensity = totalConnections > 0 ? ((activeConnections / totalConnections) * 100).toFixed(1) : "0";
574
-
575
- return {
576
- title: `Token Flow Analysis: ${numTokens} tokens, ${numLayers} layers`,
577
- description: `Visualizing information flow through the transformer's attention mechanism.`,
578
- details: [
579
- {
580
- heading: "What is Token Flow?",
581
- content: `This visualization shows how tokens are processed through transformer layers in real-time. Each column represents a layer, each box is a token at that layer. The visualization builds progressively as tokens are generated.`
582
- },
583
- {
584
- heading: "Reading the Flow",
585
- content: `Tokens flow from left (layer 0, input) to right (final layer, output). Each column is a transformer layer processing all tokens. Color intensity (yellow→orange→red) shows token importance/activation strength.`
586
- },
587
- {
588
- heading: "Real-time Generation",
589
- content: `The visualization starts with one column and expands horizontally as new tokens are generated. You're watching the model build its understanding token by token, layer by layer.`
590
- },
591
- {
592
- heading: "Current Network Stats",
593
- content: `${numTokens} tokens × ${numLayers} layers = ${numTokens * numLayers} nodes. ${connectionDensity}% of possible connections are active (strength > 0.1). Blue lines show attention flow between tokens.`
594
- },
595
- {
596
- heading: "Connection Types",
597
- content: `Blue lines: Attention connections showing which tokens attend to which. Green lines: Residual connections (when enabled). Line thickness indicates connection strength.`
598
- },
599
- {
600
- heading: "Color Meaning",
601
- content: `Token color represents activation level: Light yellow (low activation), Orange (medium), Red (high activation). This shows which tokens are most important at each processing stage.`
602
- }
603
- ]
604
- };
605
- };
606
-
607
- const explanation = generateExplanation();
608
-
609
- // Export functionality
610
- const exportVisualization = () => {
611
- if (!svgRef.current) return;
612
-
613
- const svgData = new XMLSerializer().serializeToString(svgRef.current);
614
- const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
615
- const svgUrl = URL.createObjectURL(svgBlob);
616
-
617
- const link = document.createElement("a");
618
- link.href = svgUrl;
619
- link.download = `token_flow_${Date.now()}.svg`;
620
- link.click();
621
- };
622
-
623
- return (
624
- <div className="bg-gray-900 rounded-xl p-6">
625
- <div className="flex items-center justify-between mb-6">
626
- <div>
627
- <h2 className="text-2xl font-bold flex items-center gap-2">
628
- <GitBranch className="w-6 h-6 text-purple-400" />
629
- Token Flow Visualizer
630
- </h2>
631
- <p className="text-gray-400 mt-1">
632
- Track how information flows through transformer layers
633
- </p>
634
- </div>
635
-
636
- <div className="flex items-center gap-4">
637
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full ${
638
- isConnected ? 'bg-green-900/30 text-green-400' : 'bg-red-900/30 text-red-400'
639
- }`}>
640
- <Activity className={`w-4 h-4 ${isConnected ? 'animate-pulse' : ''}`} />
641
- {isConnected ? 'Connected' : 'Disconnected'}
642
- </div>
643
- </div>
644
- </div>
645
-
646
- {/* Generation Controls */}
647
- <div className="mb-6">
648
- <div className="flex gap-4">
649
- <input
650
- type="text"
651
- value={prompt}
652
- onChange={(e) => setPrompt(e.target.value)}
653
- className="flex-1 px-4 py-2 bg-gray-800 text-white rounded-lg border border-gray-700 focus:border-blue-500 focus:outline-none font-mono text-sm"
654
- placeholder="Enter prompt to analyze token flow..."
655
- />
656
- <button
657
- onClick={async () => {
658
- setIsGenerating(true);
659
- setTokens([]);
660
- setLayers([]);
661
- setFlowConnections([]);
662
- setTraces([]);
663
-
664
- try {
665
- const response = await fetch(`${getApiUrl()}/generate`, {
666
- method: 'POST',
667
- headers: { 'Content-Type': 'application/json' },
668
- body: JSON.stringify({
669
- prompt,
670
- max_tokens: 50,
671
- temperature: 0.7,
672
- extract_traces: true,
673
- sampling_rate: 0.3
674
- })
675
- });
676
-
677
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
678
- const data = await response.json();
679
-
680
- // Process the response
681
- if (data.traces) {
682
- setTraces(data.traces);
683
- }
684
- } catch (error) {
685
- console.error('Generation error:', error);
686
- alert(`Failed to generate: ${error}`);
687
- } finally {
688
- setIsGenerating(false);
689
- }
690
- }}
691
- disabled={isGenerating || !isConnected}
692
- className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
693
- >
694
- {isGenerating ? (
695
- <>
696
- <RefreshCw className="w-4 h-4 animate-spin" />
697
- Analyzing...
698
- </>
699
- ) : (
700
- <>
701
- Generate & Visualize
702
- <Zap className="w-4 h-4" />
703
- </>
704
- )}
705
- </button>
706
- </div>
707
- </div>
708
-
709
- {/* Controls */}
710
- <div className="flex flex-wrap items-center gap-4 mb-4">
711
- {/* Playback Controls */}
712
- <div className="flex items-center gap-2">
713
- <button
714
- onClick={toggleAnimation}
715
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
716
- title={isPlaying ? "Pause" : "Play"}
717
- >
718
- {isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
719
- </button>
720
- <button
721
- onClick={reset}
722
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
723
- title="Reset"
724
- >
725
- <RotateCcw className="w-4 h-4" />
726
- </button>
727
- <span className="text-sm text-gray-400 px-2">
728
- Layer {currentStep + 1} / {layers.length || 1}
729
- </span>
730
- </div>
731
-
732
- {/* View Options */}
733
- <div className="flex items-center gap-2">
734
- <label className="flex items-center gap-2 text-sm text-gray-400">
735
- <input
736
- type="checkbox"
737
- checked={showAttention}
738
- onChange={(e) => setShowAttention(e.target.checked)}
739
- className="rounded"
740
- />
741
- Attention
742
- </label>
743
- <label className="flex items-center gap-2 text-sm text-gray-400">
744
- <input
745
- type="checkbox"
746
- checked={showResidual}
747
- onChange={(e) => setShowResidual(e.target.checked)}
748
- className="rounded"
749
- />
750
- Residual
751
- </label>
752
- </div>
753
-
754
- {/* Zoom Controls */}
755
- <div className="flex items-center gap-2">
756
- <button
757
- onClick={() => setZoom(Math.max(0.5, zoom - 0.1))}
758
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
759
- title="Zoom Out"
760
- >
761
- <ZoomOut className="w-4 h-4" />
762
- </button>
763
- <span className="text-sm text-gray-400 min-w-[50px] text-center">
764
- {(zoom * 100).toFixed(0)}%
765
- </span>
766
- <button
767
- onClick={() => setZoom(Math.min(2, zoom + 0.1))}
768
- className="p-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors"
769
- title="Zoom In"
770
- >
771
- <ZoomIn className="w-4 h-4" />
772
- </button>
773
- </div>
774
-
775
- {/* Export */}
776
- <button
777
- onClick={exportVisualization}
778
- className="ml-auto px-3 py-1.5 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2"
779
- >
780
- <Download className="w-4 h-4" />
781
- Export
782
- </button>
783
- </div>
784
-
785
- {/* Main Content Area with Side Panel */}
786
- <div className="flex gap-4">
787
- {/* Visualization Container */}
788
- <div className="flex-1 min-w-0 transition-all duration-500 ease-in-out">
789
- <div className="bg-gray-800 rounded-lg p-4 overflow-auto relative">
790
- {/* Help Toggle Button */}
791
- <button
792
- onClick={() => setShowExplanation(!showExplanation)}
793
- className="absolute top-4 right-4 z-10 p-2 bg-blue-600/90 hover:bg-blue-700 text-white rounded-lg transition-colors flex items-center gap-2 backdrop-blur"
794
- >
795
- {showExplanation ? <X className="w-5 h-5" /> : <HelpCircle className="w-5 h-5" />}
796
- <span className="text-sm font-medium">
797
- {showExplanation ? 'Hide Info' : 'What am I seeing?'}
798
- </span>
799
- </button>
800
-
801
- {layers.length > 0 ? (
802
- <svg ref={svgRef}></svg>
803
- ) : (
804
- <div className="flex items-center justify-center h-96 text-gray-500">
805
- <div className="text-center">
806
- <GitBranch className="w-12 h-12 mx-auto mb-4 opacity-50" />
807
- <p className="text-lg mb-2">No Token Flow Data</p>
808
- <p className="text-sm">Run a model to visualize token flow through layers</p>
809
- </div>
810
- </div>
811
- )}
812
- </div>
813
- </div>
814
-
815
- {/* Explanation Side Panel */}
816
- <div className={`${showExplanation ? 'w-96' : 'w-0'} transition-all duration-500 ease-in-out overflow-hidden`}>
817
- <div className="w-96 h-[600px] bg-gray-900 rounded-lg border border-gray-700">
818
- {/* Panel Header */}
819
- <div className="bg-gray-800 px-4 py-3 border-b border-gray-700">
820
- <div className="flex items-center gap-2">
821
- <Info className="w-5 h-5 text-blue-400" />
822
- <h3 className="text-lg font-semibold text-white">Understanding Token Flow</h3>
823
- </div>
824
- </div>
825
-
826
- {/* Panel Content */}
827
- <div className="px-4 py-4 overflow-y-auto h-[calc(600px-60px)]">
828
- {/* Main Description */}
829
- <div className="mb-4 p-3 bg-purple-900/20 border border-purple-800 rounded-lg">
830
- <h4 className="text-sm font-semibold text-purple-400 mb-1">{explanation.title}</h4>
831
- <p className="text-xs text-gray-300">{explanation.description}</p>
832
- </div>
833
-
834
- {/* Explanation Sections */}
835
- <div className="space-y-3">
836
- {explanation.details.map((section, idx) => (
837
- <div key={idx} className="bg-gray-800 rounded-lg p-3">
838
- <h5 className="font-medium text-sm text-white mb-1 flex items-center gap-1">
839
- <Zap className="w-3 h-3 text-yellow-400" />
840
- {section.heading}
841
- </h5>
842
- <p className="text-xs text-gray-300 leading-relaxed">{section.content}</p>
843
- </div>
844
- ))}
845
- </div>
846
-
847
- {/* Visual Guide */}
848
- <div className="mt-4 p-3 bg-blue-900/20 border border-blue-800 rounded-lg">
849
- <h4 className="font-medium text-sm text-blue-400 mb-2">Visual Elements</h4>
850
- <div className="space-y-2 text-xs">
851
- <div className="flex items-start gap-2">
852
- <span className="text-blue-300">•</span>
853
- <span className="text-gray-300">Nodes = Tokens at each layer</span>
854
- </div>
855
- <div className="flex items-start gap-2">
856
- <span className="text-blue-300">•</span>
857
- <span className="text-gray-300">Lines = Attention connections</span>
858
- </div>
859
- <div className="flex items-start gap-2">
860
- <span className="text-blue-300">•</span>
861
- <span className="text-gray-300">Thickness = Connection strength</span>
862
- </div>
863
- <div className="flex items-start gap-2">
864
- <span className="text-blue-300">•</span>
865
- <span className="text-gray-300">Color intensity = Token importance</span>
866
- </div>
867
- </div>
868
- </div>
869
-
870
- {/* Current Metrics */}
871
- {layers.length > 0 && (
872
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
873
- <h4 className="font-medium text-sm text-gray-300 mb-2">Current Metrics</h4>
874
- <div className="space-y-1 text-xs">
875
- <div className="flex justify-between">
876
- <span className="text-gray-400">Tokens:</span>
877
- <span className="text-white">{tokens.length}</span>
878
- </div>
879
- <div className="flex justify-between">
880
- <span className="text-gray-400">Layers:</span>
881
- <span className="text-white">{layers.length}</span>
882
- </div>
883
- <div className="flex justify-between">
884
- <span className="text-gray-400">Total Nodes:</span>
885
- <span className="text-white">{tokens.length * layers.length}</span>
886
- </div>
887
- <div className="flex justify-between">
888
- <span className="text-gray-400">Active Connections:</span>
889
- <span className="text-blue-400">{flowConnections.filter(c => c.strength > 0.1).length}</span>
890
- </div>
891
- </div>
892
- </div>
893
- )}
894
-
895
- {/* Tips */}
896
- <div className="mt-4 p-3 bg-gray-800 rounded-lg">
897
- <h4 className="font-medium text-sm text-gray-300 mb-2">💡 Tips</h4>
898
- <ul className="text-xs text-gray-400 space-y-1">
899
- <li>• Click tokens to trace their path</li>
900
- <li>• Use animation to see flow evolution</li>
901
- <li>• Zoom for different perspectives</li>
902
- <li>• Toggle connection types with controls</li>
903
- </ul>
904
- </div>
905
- </div>
906
- </div>
907
- </div>
908
- </div>
909
-
910
- {/* Info Panel */}
911
- {selectedToken !== null && tokens[selectedToken] && (
912
- <div className="mt-4 p-4 bg-gray-800 rounded-lg">
913
- <h3 className="text-lg font-semibold mb-3 flex items-center gap-2">
914
- <Info className="w-5 h-5 text-blue-400" />
915
- Selected Token: &quot;{tokens[selectedToken].text}&quot;
916
- </h3>
917
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
918
- <div>
919
- <span className="text-gray-400">Position:</span>
920
- <div className="font-mono text-white mt-1">{selectedToken}</div>
921
- </div>
922
- <div>
923
- <span className="text-gray-400">Layers Processed:</span>
924
- <div className="font-mono text-white mt-1">{layers.length}</div>
925
- </div>
926
- <div>
927
- <span className="text-gray-400">Connections:</span>
928
- <div className="font-mono text-white mt-1">
929
- {flowConnections.filter(c =>
930
- c.source.token === selectedToken || c.target.token === selectedToken
931
- ).length}
932
- </div>
933
- </div>
934
- <div>
935
- <span className="text-gray-400">Max Importance:</span>
936
- <div className="font-mono text-blue-400 mt-1">
937
- {Math.max(...layers.map(l =>
938
- l.tokens[selectedToken]?.importance || 0
939
- )).toFixed(3)}
940
- </div>
941
- </div>
942
- </div>
943
- </div>
944
- )}
945
-
946
- {/* Instructions */}
947
- {layers.length === 0 && (
948
- <div className="mt-4 p-4 bg-yellow-900/20 border border-yellow-700 rounded-lg">
949
- <h4 className="text-yellow-400 font-semibold mb-2">How to Use</h4>
950
- <ol className="text-sm text-gray-300 space-y-1 list-decimal list-inside">
951
- <li>Run a model to generate attention traces</li>
952
- <li>Token flow will automatically visualize</li>
953
- <li>Click tokens to see their flow details</li>
954
- <li>Use controls to animate the flow</li>
955
- </ol>
956
- </div>
957
- )}
958
- </div>
959
- );
960
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/WebSocketTest.tsx DELETED
@@ -1,81 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect, useRef } from "react";
4
-
5
- export default function WebSocketTest() {
6
- const [isConnected, setIsConnected] = useState(false);
7
- const [messages, setMessages] = useState<string[]>([]);
8
- const [mounted, setMounted] = useState(false);
9
- const wsRef = useRef<WebSocket | null>(null);
10
-
11
- // Ensure we're mounted on client before connecting
12
- useEffect(() => {
13
- setMounted(true);
14
- }, []);
15
-
16
- useEffect(() => {
17
- // Only connect after component is mounted on client
18
- if (!mounted) return;
19
-
20
- // Try connecting to test server on port 8770
21
- try {
22
- const ws = new WebSocket('ws://localhost:8770');
23
-
24
- ws.onopen = () => {
25
- console.log('Test WebSocket connected!');
26
- setIsConnected(true);
27
- wsRef.current = ws;
28
-
29
- // Send test message
30
- ws.send(JSON.stringify({ type: 'test', message: 'Hello from client!' }));
31
- };
32
-
33
- ws.onmessage = (event) => {
34
- console.log('Test WebSocket received:', event.data);
35
- setMessages(prev => [...prev, event.data]);
36
- };
37
-
38
- ws.onerror = (error) => {
39
- console.error('Test WebSocket error:', error);
40
- setIsConnected(false);
41
- };
42
-
43
- ws.onclose = () => {
44
- console.log('Test WebSocket closed');
45
- setIsConnected(false);
46
- };
47
- } catch (error) {
48
- console.error('Failed to create WebSocket:', error);
49
- }
50
-
51
- return () => {
52
- if (wsRef.current) {
53
- wsRef.current.close();
54
- }
55
- };
56
- }, [mounted]);
57
-
58
- // Don't render WebSocket status until mounted to avoid hydration issues
59
- if (!mounted) {
60
- return (
61
- <div className="p-4 bg-gray-800 rounded">
62
- <h3 className="text-white mb-2">WebSocket Test</h3>
63
- <div className="text-sm text-gray-400">
64
- Status: Loading...
65
- </div>
66
- </div>
67
- );
68
- }
69
-
70
- return (
71
- <div className="p-4 bg-gray-800 rounded">
72
- <h3 className="text-white mb-2">WebSocket Test</h3>
73
- <div className={`text-sm ${isConnected ? 'text-green-400' : 'text-red-400'}`}>
74
- Status: {isConnected ? 'Connected' : 'Disconnected'}
75
- </div>
76
- <div className="mt-2 text-xs text-gray-400">
77
- Messages: {messages.length}
78
- </div>
79
- </div>
80
- );
81
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/config-with-auth.ts DELETED
@@ -1,53 +0,0 @@
1
- // Configuration with authentication support for private HF Spaces
2
-
3
- export function getApiUrl(): string {
4
- // Check for explicit URL first
5
- if (process.env.NEXT_PUBLIC_API_URL) {
6
- return process.env.NEXT_PUBLIC_API_URL;
7
- }
8
-
9
- // Default for local development
10
- if (typeof window === 'undefined') {
11
- return 'http://localhost:8000';
12
- }
13
-
14
- // Auto-detect based on current location
15
- const currentHost = window.location.hostname;
16
-
17
- if (currentHost === 'localhost' || currentHost === '127.0.0.1') {
18
- return 'http://localhost:8000';
19
- }
20
-
21
- // For production, use the backend URL
22
- return process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
23
- }
24
-
25
- export function getAuthHeaders(): HeadersInit {
26
- const headers: HeadersInit = {
27
- 'Content-Type': 'application/json',
28
- };
29
-
30
- // Add HF token if available (for private spaces)
31
- if (process.env.NEXT_PUBLIC_HF_TOKEN) {
32
- headers['Authorization'] = `Bearer ${process.env.NEXT_PUBLIC_HF_TOKEN}`;
33
- }
34
-
35
- // Add custom API key if configured
36
- if (process.env.NEXT_PUBLIC_API_KEY) {
37
- headers['X-API-Key'] = process.env.NEXT_PUBLIC_API_KEY;
38
- }
39
-
40
- return headers;
41
- }
42
-
43
- export async function fetchWithAuth(url: string, options: RequestInit = {}): Promise<Response> {
44
- const authHeaders = getAuthHeaders();
45
-
46
- return fetch(url, {
47
- ...options,
48
- headers: {
49
- ...authHeaders,
50
- ...options.headers,
51
- },
52
- });
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/config.ts DELETED
@@ -1,95 +0,0 @@
1
- /**
2
- * Centralized configuration for dynamic backend URL detection
3
- * Automatically detects environment and uses appropriate backend URLs
4
- */
5
-
6
- /**
7
- * Get the API URL based on environment
8
- * Priority:
9
- * 1. Explicit environment variable (NEXT_PUBLIC_API_URL)
10
- * 2. Auto-detect based on browser location
11
- * 3. Default to localhost for development
12
- */
13
- export function getApiUrl(): string {
14
- // If explicitly set via environment variable, use it
15
- if (process.env.NEXT_PUBLIC_API_URL) {
16
- return process.env.NEXT_PUBLIC_API_URL;
17
- }
18
-
19
- // In browser environment
20
- if (typeof window !== 'undefined') {
21
- // Local development
22
- if (window.location.hostname === 'localhost' ||
23
- window.location.hostname === '127.0.0.1') {
24
- return 'http://localhost:8000';
25
- }
26
-
27
- // Production: assume backend is on same domain with /api prefix
28
- // This works for Vercel with API routes or proxied backend
29
- const protocol = window.location.protocol;
30
- const host = window.location.host;
31
- return `${protocol}//${host}/api`;
32
- }
33
-
34
- // Server-side rendering or build time: default to localhost
35
- // This will be overridden by client-side detection
36
- return 'http://localhost:8000';
37
- }
38
-
39
- /**
40
- * Get the WebSocket URL based on the API URL
41
- * Converts http:// to ws:// and https:// to wss://
42
- */
43
- export function getWsUrl(): string {
44
- const apiUrl = getApiUrl();
45
-
46
- // Handle the /api suffix for production deployments
47
- const baseUrl = apiUrl.endsWith('/api')
48
- ? apiUrl.slice(0, -4) // Remove /api suffix for WebSocket
49
- : apiUrl;
50
-
51
- // Convert protocol and add WebSocket path
52
- const wsUrl = baseUrl
53
- .replace('http://', 'ws://')
54
- .replace('https://', 'wss://');
55
-
56
- return `${wsUrl}/ws`;
57
- }
58
-
59
- /**
60
- * Check if we're in local development mode
61
- */
62
- export function isLocalDevelopment(): boolean {
63
- if (typeof window === 'undefined') {
64
- return process.env.NODE_ENV === 'development';
65
- }
66
-
67
- return window.location.hostname === 'localhost' ||
68
- window.location.hostname === '127.0.0.1';
69
- }
70
-
71
- /**
72
- * Get the old WebSocket URL (for backward compatibility)
73
- * This is for the old trace monitoring WebSocket that some components might still use
74
- */
75
- export function getLegacyWsUrl(): string {
76
- // If explicitly set via environment variable, use it
77
- if (process.env.NEXT_PUBLIC_WS_URL) {
78
- return process.env.NEXT_PUBLIC_WS_URL;
79
- }
80
-
81
- // Default to the old port for backward compatibility
82
- return 'ws://localhost:8765';
83
- }
84
-
85
- /**
86
- * Configuration object with all URLs
87
- */
88
- export const config = {
89
- get apiUrl() { return getApiUrl(); },
90
- get wsUrl() { return getWsUrl(); },
91
- get legacyWsUrl() { return getLegacyWsUrl(); },
92
- get isLocal() { return isLocalDevelopment(); }
93
- };
94
-
95
- export default config;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/favicon.ico DELETED
Binary file (25.9 kB)
 
frontend/file.svg DELETED
frontend/globals.css DELETED
@@ -1,68 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- :root {
4
- --background: #ffffff;
5
- --foreground: #171717;
6
- }
7
-
8
- @theme inline {
9
- --color-background: var(--background);
10
- --color-foreground: var(--foreground);
11
- --font-sans: var(--font-geist-sans);
12
- --font-mono: var(--font-geist-mono);
13
- }
14
-
15
- @media (prefers-color-scheme: dark) {
16
- :root {
17
- --background: #0a0a0a;
18
- --foreground: #ededed;
19
- }
20
- }
21
-
22
- body {
23
- background: var(--background);
24
- color: var(--foreground);
25
- font-family: Arial, Helvetica, sans-serif;
26
- }
27
-
28
- @keyframes slideIn {
29
- from {
30
- opacity: 0;
31
- transform: translateY(-10px);
32
- }
33
- to {
34
- opacity: 1;
35
- transform: translateY(0);
36
- }
37
- }
38
-
39
- .animate-slideIn {
40
- animation: slideIn 0.3s ease-out;
41
- }
42
-
43
- @keyframes slideInFromRight {
44
- from {
45
- opacity: 0;
46
- transform: translateX(100%);
47
- }
48
- to {
49
- opacity: 1;
50
- transform: translateX(0);
51
- }
52
- }
53
-
54
- @keyframes expandFromRight {
55
- from {
56
- width: 0;
57
- opacity: 0;
58
- }
59
- to {
60
- width: 24rem;
61
- opacity: 1;
62
- }
63
- }
64
-
65
- .animate-slide-in {
66
- animation: expandFromRight 0.4s cubic-bezier(0.4, 0.0, 0.2, 1) forwards;
67
- overflow: hidden;
68
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/globe.svg DELETED
frontend/layout.tsx DELETED
@@ -1,36 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { Inter, JetBrains_Mono } from "next/font/google";
3
- import "./globals.css";
4
- import { LocalControlPanel } from "@/components/LocalControlPanel";
5
-
6
- const inter = Inter({
7
- variable: "--font-inter",
8
- subsets: ["latin"],
9
- });
10
-
11
- const jetbrainsMono = JetBrains_Mono({
12
- variable: "--font-mono",
13
- subsets: ["latin"],
14
- });
15
-
16
- export const metadata: Metadata = {
17
- title: "Visualisable.ai - Glass Box LLM Code Generation",
18
- description: "Transform opaque LLM code generation into transparent, interpretable visualizations for software engineers",
19
- };
20
-
21
- export default function RootLayout({
22
- children,
23
- }: Readonly<{
24
- children: React.ReactNode;
25
- }>) {
26
- return (
27
- <html lang="en">
28
- <body
29
- className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased bg-gray-950 text-gray-100`}
30
- >
31
- {children}
32
- <LocalControlPanel />
33
- </body>
34
- </html>
35
- );
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/next.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import type { NextConfig } from "next";
2
-
3
- const nextConfig: NextConfig = {
4
- output: 'standalone',
5
- eslint: {
6
- ignoreDuringBuilds: true,
7
- },
8
- };
9
-
10
- export default nextConfig;
 
 
 
 
 
 
 
 
 
 
 
frontend/next.svg DELETED
frontend/package-lock.json DELETED
The diff for this file is too large to render. See raw diff
 
frontend/package.json DELETED
@@ -1,45 +0,0 @@
1
- {
2
- "name": "visualisable-ai",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev --turbopack",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "next lint",
10
- "docker:build": "docker-compose -f docker-compose.dev.yml build",
11
- "docker:up": "docker-compose -f docker-compose.dev.yml up",
12
- "docker:down": "docker-compose -f docker-compose.dev.yml down",
13
- "docker:logs": "docker-compose -f docker-compose.dev.yml logs -f",
14
- "docker:clean": "docker-compose -f docker-compose.dev.yml down -v --remove-orphans"
15
- },
16
- "dependencies": {
17
- "@radix-ui/react-slider": "^1.3.5",
18
- "@radix-ui/react-tabs": "^1.1.12",
19
- "@react-three/drei": "^10.6.1",
20
- "@react-three/fiber": "^9.3.0",
21
- "@types/d3": "^7.4.3",
22
- "@types/ws": "^8.18.1",
23
- "d3": "^7.9.0",
24
- "framer-motion": "^12.23.12",
25
- "lucide-react": "^0.539.0",
26
- "next": "15.4.6",
27
- "react": "19.1.0",
28
- "react-dom": "19.1.0",
29
- "recharts": "^3.1.2",
30
- "socket.io-client": "^4.8.1",
31
- "three": "^0.179.1",
32
- "ws": "^8.18.3"
33
- },
34
- "devDependencies": {
35
- "@eslint/eslintrc": "^3",
36
- "@tailwindcss/postcss": "^4",
37
- "@types/node": "^20",
38
- "@types/react": "^19",
39
- "@types/react-dom": "^19",
40
- "eslint": "^9",
41
- "eslint-config-next": "15.4.6",
42
- "tailwindcss": "^4",
43
- "typescript": "^5"
44
- }
45
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/page.tsx DELETED
@@ -1,103 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useEffect } from "react";
4
- import Hero from "@/components/Hero";
5
- import AttentionExplorer from "@/components/AttentionExplorer";
6
- import PromptDiff from "@/components/PromptDiff";
7
- import ConfidenceMeter from "@/components/ConfidenceMeter";
8
- import TokenFlowVisualizer from "@/components/TokenFlowVisualizer";
9
- import CodeGenerationTracker from "@/components/CodeGenerationTracker";
10
- import ModelInspector from "@/components/ModelInspector";
11
- import Navigation from "@/components/Navigation";
12
- // import WebSocketTest from "@/components/WebSocketTest"; // Removed - test component
13
- import ClientOnly from "@/components/ClientOnly";
14
-
15
- export default function Home() {
16
- const [activeView, setActiveView] = useState<"explorer" | "diff" | "confidence" | "flow" | "generation" | "inspector">("explorer");
17
-
18
- // Dispatch event when view changes
19
- useEffect(() => {
20
- window.dispatchEvent(new CustomEvent('viewChanged', { detail: { view: activeView } }));
21
- }, [activeView]);
22
-
23
- return (
24
- <div className="min-h-screen">
25
- <Navigation />
26
- <Hero />
27
-
28
- <main className="container mx-auto px-4 py-8">
29
- <div className="flex justify-center gap-4 mb-8">
30
- <button
31
- onClick={() => setActiveView("explorer")}
32
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
33
- activeView === "explorer"
34
- ? "bg-blue-600 text-white"
35
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
36
- }`}
37
- >
38
- Attention Explorer
39
- </button>
40
- <button
41
- onClick={() => setActiveView("diff")}
42
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
43
- activeView === "diff"
44
- ? "bg-blue-600 text-white"
45
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
46
- }`}
47
- >
48
- Prompt Diff
49
- </button>
50
- <button
51
- onClick={() => setActiveView("confidence")}
52
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
53
- activeView === "confidence"
54
- ? "bg-blue-600 text-white"
55
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
56
- }`}
57
- >
58
- Confidence Meter
59
- </button>
60
- <button
61
- onClick={() => setActiveView("flow")}
62
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
63
- activeView === "flow"
64
- ? "bg-blue-600 text-white"
65
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
66
- }`}
67
- >
68
- Token Flow
69
- </button>
70
- <button
71
- onClick={() => setActiveView("generation")}
72
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
73
- activeView === "generation"
74
- ? "bg-blue-600 text-white"
75
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
76
- }`}
77
- >
78
- Generation
79
- </button>
80
- <button
81
- onClick={() => setActiveView("inspector")}
82
- className={`px-6 py-3 rounded-lg font-medium transition-all ${
83
- activeView === "inspector"
84
- ? "bg-blue-600 text-white"
85
- : "bg-gray-800 text-gray-300 hover:bg-gray-700"
86
- }`}
87
- >
88
- Model Inspector
89
- </button>
90
- </div>
91
-
92
- <div className="bg-gray-900 rounded-xl p-6 min-h-[600px]">
93
- {activeView === "explorer" && <AttentionExplorer />}
94
- {activeView === "diff" && <PromptDiff />}
95
- {activeView === "confidence" && <ConfidenceMeter />}
96
- {activeView === "flow" && <TokenFlowVisualizer />}
97
- {activeView === "generation" && <CodeGenerationTracker />}
98
- {activeView === "inspector" && <ModelInspector />}
99
- </div>
100
- </main>
101
- </div>
102
- );
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/postcss.config.mjs DELETED
@@ -1,5 +0,0 @@
1
- const config = {
2
- plugins: ["@tailwindcss/postcss"],
3
- };
4
-
5
- export default config;
 
 
 
 
 
 
frontend/prompt-service-client.ts DELETED
@@ -1,201 +0,0 @@
1
- /**
2
- * Prompt Service Client
3
- * Handles communication with the Python prompt service for real model generation
4
- */
5
-
6
- export interface PromptComparisonRequest {
7
- type: 'prompt_comparison';
8
- prompt_a: string;
9
- prompt_b: string;
10
- }
11
-
12
- export interface SinglePromptRequest {
13
- type: 'single_prompt';
14
- prompt: string;
15
- prompt_id?: string;
16
- max_tokens?: number;
17
- temperature?: number;
18
- }
19
-
20
- export interface AttentionTrace {
21
- type: 'attention';
22
- layer: string;
23
- weights: number[][];
24
- max_weight: number;
25
- entropy?: number;
26
- prompt_id: string;
27
- comparison_group?: 'prompt_a' | 'prompt_b';
28
- timestamp: number;
29
- }
30
-
31
- export interface ComparisonSummary {
32
- type: 'prompt_comparison';
33
- prompt_a: {
34
- id: string;
35
- text: string;
36
- generated: string;
37
- num_traces: number;
38
- };
39
- prompt_b: {
40
- id: string;
41
- text: string;
42
- generated: string;
43
- num_traces: number;
44
- };
45
- timestamp: number;
46
- }
47
-
48
- export class PromptServiceClient {
49
- private ws: WebSocket | null = null;
50
- private readonly url: string;
51
- private messageHandlers: Map<string, (data: Record<string, unknown>) => void> = new Map();
52
- private connectionPromise: Promise<void> | null = null;
53
-
54
- constructor(url: string = 'ws://localhost:8767') {
55
- this.url = url;
56
- }
57
-
58
- /**
59
- * Connect to the prompt service
60
- */
61
- async connect(): Promise<void> {
62
- if (this.ws?.readyState === WebSocket.OPEN) {
63
- return;
64
- }
65
-
66
- if (this.connectionPromise) {
67
- return this.connectionPromise;
68
- }
69
-
70
- this.connectionPromise = new Promise((resolve, reject) => {
71
- const timeout = setTimeout(() => {
72
- if (this.ws?.readyState !== WebSocket.OPEN) {
73
- console.log('⏱️ Connection timeout - using demo mode');
74
- this.ws?.close();
75
- reject(new Error('Connection timeout'));
76
- }
77
- }, 3000); // 3 second timeout
78
-
79
- try {
80
- this.ws = new WebSocket(this.url);
81
-
82
- this.ws.onopen = () => {
83
- clearTimeout(timeout);
84
- console.log('✅ Connected to prompt service');
85
- resolve();
86
- };
87
-
88
- this.ws.onerror = (error) => {
89
- clearTimeout(timeout);
90
- console.log('⚠️ Prompt service not available - will use demo mode');
91
- reject(new Error('Prompt service not available'));
92
- };
93
-
94
- this.ws.onclose = () => {
95
- console.log('🔌 Disconnected from prompt service');
96
- this.ws = null;
97
- this.connectionPromise = null;
98
- };
99
-
100
- this.ws.onmessage = (event) => {
101
- try {
102
- const data = JSON.parse(event.data);
103
- this.handleMessage(data);
104
- } catch (error) {
105
- console.error('Error parsing message:', error);
106
- }
107
- };
108
- } catch (error) {
109
- reject(error);
110
- }
111
- });
112
-
113
- return this.connectionPromise;
114
- }
115
-
116
- /**
117
- * Handle incoming messages from the prompt service
118
- */
119
- private handleMessage(data: Record<string, unknown>) {
120
- // Notify all registered handlers
121
- this.messageHandlers.forEach((handler) => {
122
- handler(data);
123
- });
124
- }
125
-
126
- /**
127
- * Register a message handler
128
- */
129
- onMessage(id: string, handler: (data: Record<string, unknown>) => void) {
130
- this.messageHandlers.set(id, handler);
131
- }
132
-
133
- /**
134
- * Unregister a message handler
135
- */
136
- offMessage(id: string) {
137
- this.messageHandlers.delete(id);
138
- }
139
-
140
- /**
141
- * Request prompt comparison from the service
142
- */
143
- async comparePrompts(promptA: string, promptB: string): Promise<void> {
144
- await this.connect();
145
-
146
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
147
- throw new Error('Not connected to prompt service');
148
- }
149
-
150
- const request: PromptComparisonRequest = {
151
- type: 'prompt_comparison',
152
- prompt_a: promptA,
153
- prompt_b: promptB
154
- };
155
-
156
- this.ws.send(JSON.stringify(request));
157
- }
158
-
159
- /**
160
- * Generate for a single prompt
161
- */
162
- async generateSingle(
163
- prompt: string,
164
- options?: {
165
- prompt_id?: string;
166
- max_tokens?: number;
167
- temperature?: number;
168
- }
169
- ): Promise<void> {
170
- await this.connect();
171
-
172
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
173
- throw new Error('Not connected to prompt service');
174
- }
175
-
176
- const request: SinglePromptRequest = {
177
- type: 'single_prompt',
178
- prompt,
179
- ...options
180
- };
181
-
182
- this.ws.send(JSON.stringify(request));
183
- }
184
-
185
- /**
186
- * Disconnect from the service
187
- */
188
- disconnect() {
189
- if (this.ws) {
190
- this.ws.close();
191
- this.ws = null;
192
- }
193
- }
194
-
195
- /**
196
- * Check if connected
197
- */
198
- isConnected(): boolean {
199
- return this.ws?.readyState === WebSocket.OPEN;
200
- }
201
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/tailwind.config.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { Config } from "tailwindcss";
2
-
3
- export default {
4
- content: [
5
- "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6
- "./components/**/*.{js,ts,jsx,tsx,mdx}",
7
- "./app/**/*.{js,ts,jsx,tsx,mdx}",
8
- ],
9
- theme: {
10
- extend: {
11
- fontFamily: {
12
- sans: ["var(--font-inter)"],
13
- mono: ["var(--font-mono)"],
14
- },
15
- colors: {
16
- background: "var(--background)",
17
- foreground: "var(--foreground)",
18
- },
19
- animation: {
20
- 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
21
- },
22
- },
23
- },
24
- plugins: [],
25
- } satisfies Config;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/trace-buffer.ts DELETED
@@ -1,249 +0,0 @@
1
- import { TraceData } from './types';
2
-
3
- export type PlaybackMode = 'live' | 'paused' | 'slow' | 'fast' | 'catchup';
4
-
5
- export interface BufferState {
6
- mode: PlaybackMode;
7
- speed: number; // Multiplier: 0.25, 0.5, 1, 2, 4
8
- bufferSize: number;
9
- currentSize: number;
10
- writePosition: number;
11
- readPosition: number;
12
- droppedCount: number;
13
- isPaused: boolean;
14
- }
15
-
16
- export interface TraceBufferConfig {
17
- maxSize?: number;
18
- updateInterval?: number;
19
- batchSize?: number;
20
- }
21
-
22
- type BufferListener = (traces: TraceData[], state: BufferState) => void;
23
-
24
- export class TraceBuffer {
25
- private buffer: (TraceData | null)[];
26
- private maxSize: number;
27
- private writePos: number = 0;
28
- private readPos: number = 0;
29
- private currentSize: number = 0;
30
- private droppedCount: number = 0;
31
-
32
- private mode: PlaybackMode = 'live';
33
- private speed: number = 1;
34
- private isPaused: boolean = false;
35
-
36
- private listeners: Set<BufferListener> = new Set();
37
- private updateTimer: NodeJS.Timeout | null = null;
38
- private updateInterval: number;
39
- private batchSize: number;
40
-
41
- private pendingTraces: TraceData[] = [];
42
- private lastUpdateTime: number = 0;
43
- private targetFPS: number = 60;
44
-
45
- constructor(config: TraceBufferConfig = {}) {
46
- this.maxSize = config.maxSize || 10000;
47
- this.updateInterval = config.updateInterval || 16; // ~60fps
48
- this.batchSize = config.batchSize || 10;
49
- this.buffer = new Array(this.maxSize).fill(null);
50
-
51
- this.startUpdateLoop();
52
- }
53
-
54
- private startUpdateLoop(): void {
55
- const update = () => {
56
- const now = Date.now();
57
- const deltaTime = now - this.lastUpdateTime;
58
-
59
- if (!this.isPaused && this.pendingTraces.length > 0) {
60
- const effectiveInterval = this.updateInterval / this.speed;
61
-
62
- if (deltaTime >= effectiveInterval) {
63
- this.processPendingTraces();
64
- this.lastUpdateTime = now;
65
- }
66
- }
67
-
68
- this.updateTimer = setTimeout(update, Math.max(1, this.updateInterval));
69
- };
70
-
71
- update();
72
- }
73
-
74
- private processPendingTraces(): void {
75
- if (this.pendingTraces.length === 0) return;
76
-
77
- let tracesToProcess: TraceData[];
78
-
79
- switch (this.mode) {
80
- case 'live':
81
- // Process in real-time batches
82
- tracesToProcess = this.pendingTraces.splice(0, this.batchSize);
83
- break;
84
-
85
- case 'slow':
86
- // Process one at a time for slow motion
87
- tracesToProcess = this.pendingTraces.splice(0, 1);
88
- break;
89
-
90
- case 'fast':
91
- // Process larger batches for fast forward
92
- tracesToProcess = this.pendingTraces.splice(0, this.batchSize * 4);
93
- break;
94
-
95
- case 'catchup':
96
- // Process all pending to catch up
97
- tracesToProcess = this.pendingTraces.splice(0, this.pendingTraces.length);
98
- this.mode = 'live'; // Return to live after catching up
99
- break;
100
-
101
- default:
102
- tracesToProcess = [];
103
- }
104
-
105
- if (tracesToProcess.length > 0) {
106
- this.notifyListeners(tracesToProcess);
107
- }
108
- }
109
-
110
- public push(trace: TraceData): void {
111
- // Add to ring buffer
112
- if (this.currentSize >= this.maxSize) {
113
- // Buffer is full, overwrite oldest
114
- this.droppedCount++;
115
- } else {
116
- this.currentSize++;
117
- }
118
-
119
- this.buffer[this.writePos] = trace;
120
- this.writePos = (this.writePos + 1) % this.maxSize;
121
-
122
- // Add to pending traces for processing
123
- if (!this.isPaused) {
124
- this.pendingTraces.push(trace);
125
- }
126
- }
127
-
128
- public setMode(mode: PlaybackMode): void {
129
- this.mode = mode;
130
-
131
- switch (mode) {
132
- case 'paused':
133
- this.isPaused = true;
134
- break;
135
-
136
- case 'catchup':
137
- this.isPaused = false;
138
- // Will process all pending traces
139
- break;
140
-
141
- default:
142
- this.isPaused = false;
143
- }
144
- }
145
-
146
- public setSpeed(speed: number): void {
147
- this.speed = Math.max(0.25, Math.min(4, speed));
148
- }
149
-
150
- public pause(): void {
151
- this.isPaused = true;
152
- this.mode = 'paused';
153
- }
154
-
155
- public resume(): void {
156
- this.isPaused = false;
157
- this.mode = 'live';
158
- }
159
-
160
- public clear(): void {
161
- this.buffer.fill(null);
162
- this.writePos = 0;
163
- this.readPos = 0;
164
- this.currentSize = 0;
165
- this.droppedCount = 0;
166
- this.pendingTraces = [];
167
- this.notifyListeners([]);
168
- }
169
-
170
- public getState(): BufferState {
171
- return {
172
- mode: this.mode,
173
- speed: this.speed,
174
- bufferSize: this.maxSize,
175
- currentSize: this.currentSize,
176
- writePosition: this.writePos,
177
- readPosition: this.readPos,
178
- droppedCount: this.droppedCount,
179
- isPaused: this.isPaused,
180
- };
181
- }
182
-
183
- public getRecentTraces(count: number = 100): TraceData[] {
184
- const traces: TraceData[] = [];
185
- let pos = (this.writePos - 1 + this.maxSize) % this.maxSize;
186
- let collected = 0;
187
-
188
- while (collected < count && collected < this.currentSize) {
189
- const trace = this.buffer[pos];
190
- if (trace) {
191
- traces.unshift(trace);
192
- collected++;
193
- }
194
- pos = (pos - 1 + this.maxSize) % this.maxSize;
195
- }
196
-
197
- return traces;
198
- }
199
-
200
- public subscribe(listener: BufferListener): () => void {
201
- this.listeners.add(listener);
202
-
203
- // Send current state immediately
204
- listener(this.getRecentTraces(), this.getState());
205
-
206
- // Return unsubscribe function
207
- return () => {
208
- this.listeners.delete(listener);
209
- };
210
- }
211
-
212
- private notifyListeners(newTraces: TraceData[]): void {
213
- const state = this.getState();
214
- this.listeners.forEach(listener => {
215
- try {
216
- listener(newTraces, state);
217
- } catch (error) {
218
- console.error('Error in buffer listener:', error);
219
- }
220
- });
221
- }
222
-
223
- public destroy(): void {
224
- if (this.updateTimer) {
225
- clearTimeout(this.updateTimer);
226
- this.updateTimer = null;
227
- }
228
- this.listeners.clear();
229
- this.buffer = [];
230
- this.pendingTraces = [];
231
- }
232
- }
233
-
234
- // Singleton instance
235
- let bufferInstance: TraceBuffer | null = null;
236
-
237
- export function getTraceBuffer(config?: TraceBufferConfig): TraceBuffer {
238
- if (!bufferInstance) {
239
- bufferInstance = new TraceBuffer(config);
240
- }
241
- return bufferInstance;
242
- }
243
-
244
- export function destroyTraceBuffer(): void {
245
- if (bufferInstance) {
246
- bufferInstance.destroy();
247
- bufferInstance = null;
248
- }
249
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/tsconfig.json DELETED
@@ -1,27 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2017",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "allowJs": true,
6
- "skipLibCheck": true,
7
- "strict": true,
8
- "noEmit": true,
9
- "esModuleInterop": true,
10
- "module": "esnext",
11
- "moduleResolution": "bundler",
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "jsx": "preserve",
15
- "incremental": true,
16
- "plugins": [
17
- {
18
- "name": "next"
19
- }
20
- ],
21
- "paths": {
22
- "@/*": ["./*"]
23
- }
24
- },
25
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
- "exclude": ["node_modules"]
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/types.ts DELETED
@@ -1,28 +0,0 @@
1
- // Shared types for WebSocket and trace handling
2
-
3
- export interface TraceData {
4
- type: 'attention' | 'activation' | 'routing' | 'confidence';
5
- layer?: string;
6
- weights?: number[][];
7
- max_weight?: number;
8
- entropy?: number;
9
- mean?: number;
10
- std?: number;
11
- max?: number;
12
- selected_expert?: number;
13
- timestamp?: number;
14
- tokens?: string[]; // Added for attention visualization
15
- // Confidence-specific fields
16
- confidence_score?: number;
17
- hallucination_risk?: number;
18
- attention_count?: number;
19
- activation_count?: number;
20
- max_attention?: number;
21
- }
22
-
23
- export interface WebSocketState {
24
- isConnected: boolean;
25
- lastMessage: TraceData | null;
26
- traces: TraceData[];
27
- buffer?: Record<string, unknown>; // TraceBuffer type
28
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/vercel.svg DELETED
frontend/websocket-client.ts DELETED
@@ -1,214 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
4
- import { getTraceBuffer, TraceBuffer } from './trace-buffer';
5
- import { getLegacyWsUrl } from './config';
6
- import { TraceData, WebSocketState } from './types';
7
-
8
- export type { TraceData, WebSocketState } from './types';
9
-
10
- export function useWebSocket(url?: string) {
11
- const defaultUrl = url || getLegacyWsUrl();
12
- const [state, setState] = useState<WebSocketState>({
13
- isConnected: false,
14
- lastMessage: null,
15
- traces: [],
16
- });
17
-
18
- const [isClient, setIsClient] = useState(false);
19
- const ws = useRef<WebSocket | null>(null);
20
- const reconnectTimeout = useRef<NodeJS.Timeout | null>(null);
21
- const urlRef = useRef(defaultUrl);
22
- const bufferRef = useRef<TraceBuffer | null>(null);
23
- const bufferUnsubscribe = useRef<(() => void) | null>(null);
24
- const isCleared = useRef(false);
25
-
26
- // Ensure we're on the client
27
- useEffect(() => {
28
- setIsClient(true);
29
- }, []);
30
-
31
- // Initialize trace buffer
32
- useEffect(() => {
33
- if (isClient && !bufferRef.current) {
34
- bufferRef.current = getTraceBuffer({
35
- maxSize: 10000,
36
- updateInterval: 25,
37
- batchSize: 10,
38
- });
39
-
40
- // Subscribe to buffer updates
41
- bufferUnsubscribe.current = bufferRef.current.subscribe((newTraces, bufferState) => {
42
- // If we've cleared and there are no new traces, don't restore the old lastMessage
43
- if (isCleared.current && newTraces.length === 0) {
44
- setState(prev => ({
45
- ...prev,
46
- traces: bufferRef.current?.getRecentTraces(500) || [],
47
- lastMessage: null,
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- buffer: bufferRef.current as any,
50
- }));
51
- } else {
52
- // Reset cleared flag if we have new traces
53
- if (newTraces.length > 0) {
54
- isCleared.current = false;
55
- }
56
- setState(prev => ({
57
- ...prev,
58
- traces: bufferRef.current?.getRecentTraces(500) || [],
59
- lastMessage: newTraces.length > 0 ? newTraces[newTraces.length - 1] : prev.lastMessage,
60
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
- buffer: bufferRef.current as any,
62
- }));
63
- }
64
- });
65
-
66
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
- setState(prev => ({ ...prev, buffer: bufferRef.current as any }));
68
- }
69
-
70
- return () => {
71
- if (bufferUnsubscribe.current) {
72
- bufferUnsubscribe.current();
73
- bufferUnsubscribe.current = null;
74
- }
75
- };
76
- }, [isClient]);
77
-
78
- // Update URL ref when URL prop changes
79
- useEffect(() => {
80
- urlRef.current = defaultUrl;
81
- }, [defaultUrl]);
82
-
83
- const connect = useCallback(() => {
84
- // Double-check we're in browser and WebSocket is available
85
- if (typeof window === 'undefined' || !window.WebSocket) {
86
- console.log('WebSocket not available');
87
- return;
88
- }
89
-
90
- try {
91
- console.log('Attempting to connect to:', urlRef.current);
92
- ws.current = new window.WebSocket(urlRef.current);
93
-
94
- ws.current.onopen = () => {
95
- console.log('WebSocket connected to:', urlRef.current);
96
- setState(prev => ({ ...prev, isConnected: true }));
97
-
98
- // Clear any reconnection timeout
99
- if (reconnectTimeout.current) {
100
- clearTimeout(reconnectTimeout.current);
101
- reconnectTimeout.current = null;
102
- }
103
- };
104
-
105
- ws.current.onmessage = async (event) => {
106
- try {
107
- let messageData: string;
108
-
109
- // Handle both string and Blob data
110
- if (event.data instanceof Blob) {
111
- messageData = await event.data.text();
112
- } else {
113
- messageData = event.data;
114
- }
115
-
116
- const data = JSON.parse(messageData) as TraceData;
117
- console.log('Received trace:', data.type);
118
-
119
- // Push to trace buffer instead of direct state update
120
- if (bufferRef.current) {
121
- bufferRef.current.push(data);
122
- }
123
- } catch (error) {
124
- console.error('Error parsing WebSocket message:', error, 'Data:', event.data);
125
- }
126
- };
127
-
128
- ws.current.onerror = (error) => {
129
- console.warn('WebSocket error (this is normal if server is not running):', error);
130
- };
131
-
132
- ws.current.onclose = () => {
133
- console.log('WebSocket disconnected');
134
- setState(prev => ({ ...prev, isConnected: false }));
135
-
136
- // Attempt to reconnect after 3 seconds
137
- if (reconnectTimeout.current) {
138
- clearTimeout(reconnectTimeout.current);
139
- }
140
- reconnectTimeout.current = setTimeout(() => {
141
- console.log('Attempting to reconnect...');
142
- connect();
143
- }, 3000);
144
- };
145
- } catch (error) {
146
- console.warn('Failed to connect WebSocket:', error);
147
- // Retry connection after delay
148
- if (reconnectTimeout.current) {
149
- clearTimeout(reconnectTimeout.current);
150
- }
151
- reconnectTimeout.current = setTimeout(() => {
152
- connect();
153
- }, 3000);
154
- }
155
- }, []); // Remove url dependency to prevent reconnection loops
156
-
157
- const disconnect = useCallback(() => {
158
- if (reconnectTimeout.current) {
159
- clearTimeout(reconnectTimeout.current);
160
- reconnectTimeout.current = null;
161
- }
162
-
163
- if (ws.current && ws.current.readyState === ws.current.OPEN) {
164
- ws.current.close();
165
- }
166
- ws.current = null;
167
- }, []);
168
-
169
- const sendMessage = useCallback((message: Record<string, unknown>) => {
170
- if (ws.current && ws.current.readyState === ws.current.OPEN) {
171
- ws.current.send(JSON.stringify(message));
172
- } else {
173
- console.warn('WebSocket is not connected');
174
- }
175
- }, []);
176
-
177
- const clearTraces = useCallback(() => {
178
- if (bufferRef.current) {
179
- bufferRef.current.clear();
180
- }
181
- isCleared.current = true;
182
- setState(prev => ({ ...prev, traces: [], lastMessage: null }));
183
- }, []);
184
-
185
- // Connect when component mounts and we're on client
186
- useEffect(() => {
187
- if (!isClient) return;
188
-
189
- const timer = setTimeout(() => {
190
- connect();
191
- }, 100); // Small delay to ensure client is ready
192
-
193
- return () => {
194
- clearTimeout(timer);
195
- disconnect();
196
- };
197
- // eslint-disable-next-line react-hooks/exhaustive-deps
198
- }, [isClient]); // Only depend on isClient, not connect/disconnect to avoid loops
199
-
200
- // Memoize connect to prevent infinite loops
201
- const memoizedConnect = useMemo(() => connect, []);
202
- const memoizedDisconnect = useMemo(() => disconnect, []);
203
-
204
- return {
205
- ...state,
206
- isClient,
207
- sendMessage,
208
- clearTraces,
209
- reconnect: memoizedConnect,
210
- disconnect: memoizedDisconnect,
211
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
- buffer: bufferRef.current as any,
213
- };
214
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/websocket.ts DELETED
@@ -1,114 +0,0 @@
1
- import { useEffect, useRef, useState, useCallback } from 'react';
2
- import { getLegacyWsUrl } from './config';
3
- import { TraceData, WebSocketState } from './types';
4
-
5
- export type { TraceData, WebSocketState } from './types';
6
-
7
- // DEPRECATED: Use websocket-client.ts instead
8
- export function useWebSocket(url?: string) {
9
- const defaultUrl = url || getLegacyWsUrl();
10
- const [state, setState] = useState<WebSocketState>({
11
- isConnected: false,
12
- lastMessage: null,
13
- traces: [],
14
- });
15
-
16
- const ws = useRef<WebSocket | null>(null);
17
- const reconnectTimeout = useRef<NodeJS.Timeout | null>(null);
18
-
19
- const connect = useCallback(() => {
20
- // Only run in browser environment
21
- if (typeof window === 'undefined') {
22
- return;
23
- }
24
-
25
- try {
26
- ws.current = new WebSocket(defaultUrl);
27
-
28
- ws.current.onopen = () => {
29
- console.log('WebSocket connected to:', defaultUrl);
30
- setState(prev => ({ ...prev, isConnected: true }));
31
-
32
- // Clear any reconnection timeout
33
- if (reconnectTimeout.current) {
34
- clearTimeout(reconnectTimeout.current);
35
- reconnectTimeout.current = null;
36
- }
37
- };
38
-
39
- ws.current.onmessage = (event) => {
40
- try {
41
- const data = JSON.parse(event.data) as TraceData;
42
- console.log('Received trace:', data.type);
43
-
44
- setState(prev => ({
45
- ...prev,
46
- lastMessage: data,
47
- traces: [...prev.traces, data].slice(-100), // Keep last 100 traces
48
- }));
49
- } catch (error) {
50
- console.error('Error parsing WebSocket message:', error);
51
- }
52
- };
53
-
54
- ws.current.onerror = (error) => {
55
- console.error('WebSocket error:', error);
56
- };
57
-
58
- ws.current.onclose = () => {
59
- console.log('WebSocket disconnected');
60
- setState(prev => ({ ...prev, isConnected: false }));
61
-
62
- // Attempt to reconnect after 3 seconds
63
- reconnectTimeout.current = setTimeout(() => {
64
- console.log('Attempting to reconnect...');
65
- connect();
66
- }, 3000);
67
- };
68
- } catch (error) {
69
- console.error('Failed to connect WebSocket:', error);
70
- }
71
- }, [url]);
72
-
73
- const disconnect = useCallback(() => {
74
- if (reconnectTimeout.current) {
75
- clearTimeout(reconnectTimeout.current);
76
- reconnectTimeout.current = null;
77
- }
78
-
79
- if (ws.current) {
80
- ws.current.close();
81
- ws.current = null;
82
- }
83
- }, []);
84
-
85
- const sendMessage = useCallback((message: Record<string, unknown>) => {
86
- if (ws.current && ws.current.readyState === WebSocket.OPEN) {
87
- ws.current.send(JSON.stringify(message));
88
- } else {
89
- console.warn('WebSocket is not connected');
90
- }
91
- }, []);
92
-
93
- const clearTraces = useCallback(() => {
94
- setState(prev => ({ ...prev, traces: [], lastMessage: null }));
95
- }, []);
96
-
97
- useEffect(() => {
98
- // Only run in browser environment
99
- if (typeof window === 'undefined') {
100
- return;
101
- }
102
-
103
- connect();
104
- return () => disconnect();
105
- }, [connect, disconnect]);
106
-
107
- return {
108
- ...state,
109
- sendMessage,
110
- clearTraces,
111
- reconnect: connect,
112
- disconnect,
113
- };
114
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/window.svg DELETED