cdpearlman commited on
Commit
5c05f37
·
1 Parent(s): 5f164c5

Replace Cytoscape with accordion-based layer view

Browse files

- Added html.Details/Summary accordion panels for each layer
- Display plain-language headers: 'Layer L{N}: likely {token} (p={prob})'
- Added token chips showing top-3 predictions between layers
- Gated old Cytoscape graph behind USE_LEGACY_CYTOSCAPE feature flag
- Added CSS for accordion styling with ellipsis truncation
- Created extract_layer_data() utility function
- All panels closed by default for compact flow view

Files changed (6) hide show
  1. app.py +64 -1
  2. assets/style.css +85 -0
  3. components/main_panel.py +223 -217
  4. todo.md +5 -5
  5. utils/__init__.py +3 -2
  6. utils/model_patterns.py +44 -0
app.py CHANGED
@@ -9,7 +9,7 @@ import dash
9
  from dash import html, dcc, Input, Output, State, callback, no_update
10
  import dash_cytoscape as cyto
11
  from utils import (load_model_and_get_patterns, execute_forward_pass, format_data_for_cytoscape,
12
- categorize_single_layer_heads, format_categorization_summary,
13
  compare_attention_layers, compare_output_probabilities, format_comparison_summary,
14
  get_check_token_probabilities)
15
  from utils.model_config import get_auto_selections, get_model_family
@@ -528,6 +528,69 @@ def update_check_token_graph(check_token_data):
528
 
529
  return figure, {'flex': '1', 'minWidth': '300px', 'display': 'block'}
530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  # Enable Run Analysis button when requirements are met
532
  @app.callback(
533
  Output('run-analysis-btn', 'disabled'),
 
9
  from dash import html, dcc, Input, Output, State, callback, no_update
10
  import dash_cytoscape as cyto
11
  from utils import (load_model_and_get_patterns, execute_forward_pass, format_data_for_cytoscape,
12
+ extract_layer_data, categorize_single_layer_heads, format_categorization_summary,
13
  compare_attention_layers, compare_output_probabilities, format_comparison_summary,
14
  get_check_token_probabilities)
15
  from utils.model_config import get_auto_selections, get_model_family
 
528
 
529
  return figure, {'flex': '1', 'minWidth': '300px', 'display': 'block'}
530
 
531
+ # Callback to create accordion panels from layer data
532
+ @app.callback(
533
+ Output('layer-accordions-container', 'children'),
534
+ [Input('session-activation-store', 'data')],
535
+ [State('model-dropdown', 'value')]
536
+ )
537
+ def create_layer_accordions(activation_data, model_name):
538
+ """Create accordion panels for each layer."""
539
+ if not activation_data or not model_name:
540
+ return html.P("Run analysis to see layer-by-layer predictions.", className="placeholder-text")
541
+
542
+ try:
543
+ from transformers import AutoModelForCausalLM, AutoTokenizer
544
+ model = AutoModelForCausalLM.from_pretrained(model_name, attn_implementation='eager')
545
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
546
+
547
+ # Extract layer data
548
+ layer_data = extract_layer_data(activation_data, model, tokenizer)
549
+
550
+ if not layer_data:
551
+ return html.P("No layer data available.", className="placeholder-text")
552
+
553
+ # Create accordion panels
554
+ accordions = []
555
+ for i, layer in enumerate(layer_data):
556
+ layer_num = layer['layer_num']
557
+ top_token = layer.get('top_token', 'N/A')
558
+ top_prob = layer.get('top_prob', 0.0)
559
+ top_3 = layer.get('top_3_tokens', [])
560
+
561
+ # Create summary header
562
+ if top_token:
563
+ summary_text = f"Layer L{layer_num}: likely '{top_token}' (p={top_prob:.3f})"
564
+ else:
565
+ summary_text = f"Layer L{layer_num}: (no prediction)"
566
+
567
+ # Create accordion panel
568
+ panel = html.Details([
569
+ html.Summary(summary_text, className="layer-summary"),
570
+ html.Div([
571
+ html.P(f"Layer {layer_num} details (placeholder for future content)")
572
+ ], className="layer-content")
573
+ ], className="layer-accordion")
574
+
575
+ accordions.append(panel)
576
+
577
+ # Add token chips between adjacent layers (not after last layer)
578
+ if i < len(layer_data) - 1 and top_3:
579
+ chips = html.Div([
580
+ html.Span("→", className="token-arrow"),
581
+ *[html.Span(f"{tok} ({prob:.2f})", className="token-chip")
582
+ for tok, prob in top_3]
583
+ ], className="token-chips-row")
584
+ accordions.append(chips)
585
+
586
+ return html.Div(accordions)
587
+
588
+ except Exception as e:
589
+ print(f"Error creating accordions: {e}")
590
+ import traceback
591
+ traceback.print_exc()
592
+ return html.P(f"Error creating layer view: {str(e)}", className="placeholder-text")
593
+
594
  # Enable Run Analysis button when requirements are met
595
  @app.callback(
596
  Output('run-analysis-btn', 'disabled'),
assets/style.css CHANGED
@@ -434,6 +434,87 @@ body {
434
  }
435
 
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  /* Responsive design */
438
  @media (max-width: 768px) {
439
  .content-container {
@@ -453,4 +534,8 @@ body {
453
  .header-title {
454
  font-size: 2rem;
455
  }
 
 
 
 
456
  }
 
434
  }
435
 
436
 
437
+ /* Layer accordion styles */
438
+ .layer-accordions {
439
+ margin-top: 1rem;
440
+ }
441
+
442
+ .layer-accordion {
443
+ margin-bottom: 0.5rem;
444
+ border: 1px solid #e9ecef;
445
+ border-radius: 6px;
446
+ overflow: hidden;
447
+ }
448
+
449
+ .layer-summary {
450
+ padding: 0.75rem 1rem;
451
+ background-color: #f8f9fa;
452
+ cursor: pointer;
453
+ font-size: 14px;
454
+ font-weight: 500;
455
+ color: #495057;
456
+ list-style: none;
457
+ transition: background-color 0.2s ease;
458
+ white-space: nowrap;
459
+ overflow: hidden;
460
+ text-overflow: ellipsis;
461
+ user-select: none;
462
+ }
463
+
464
+ .layer-summary:hover {
465
+ background-color: #e9ecef;
466
+ }
467
+
468
+ .layer-summary::-webkit-details-marker {
469
+ display: none;
470
+ }
471
+
472
+ .layer-summary::before {
473
+ content: '▶';
474
+ display: inline-block;
475
+ margin-right: 0.5rem;
476
+ transition: transform 0.2s ease;
477
+ font-size: 0.8em;
478
+ }
479
+
480
+ details[open] .layer-summary::before {
481
+ transform: rotate(90deg);
482
+ }
483
+
484
+ .layer-content {
485
+ padding: 1rem;
486
+ background-color: white;
487
+ border-top: 1px solid #e9ecef;
488
+ }
489
+
490
+ /* Token chips between layers */
491
+ .token-chips-row {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: 0.5rem;
495
+ padding: 0.5rem 1rem;
496
+ margin: 0.25rem 0;
497
+ background-color: #f8f9fa;
498
+ border-left: 3px solid #667eea;
499
+ }
500
+
501
+ .token-arrow {
502
+ color: #667eea;
503
+ font-size: 1.2em;
504
+ font-weight: bold;
505
+ }
506
+
507
+ .token-chip {
508
+ display: inline-block;
509
+ padding: 0.25rem 0.5rem;
510
+ background-color: white;
511
+ border: 1px solid #dee2e6;
512
+ border-radius: 4px;
513
+ font-size: 12px;
514
+ color: #495057;
515
+ white-space: nowrap;
516
+ }
517
+
518
  /* Responsive design */
519
  @media (max-width: 768px) {
520
  .content-container {
 
534
  .header-title {
535
  font-size: 2rem;
536
  }
537
+
538
+ .token-chips-row {
539
+ flex-wrap: wrap;
540
+ }
541
  }
components/main_panel.py CHANGED
@@ -1,217 +1,223 @@
1
- """
2
- Main panel component for the dashboard.
3
-
4
- This component contains:
5
- - Model selector and prompt input
6
- - Visualization area (placeholder for now)
7
- - Status and results display
8
- """
9
-
10
- from dash import html, dcc
11
- import dash_cytoscape as cyto
12
- from .model_selector import create_model_selector
13
-
14
- def create_main_panel():
15
- """Create the main content panel."""
16
- return html.Div([
17
- # Model selection section
18
- html.Div([
19
- html.H3("Model Configuration", className="section-title"),
20
- create_model_selector()
21
- ], className="config-section"),
22
-
23
- # Analysis loading indicator
24
- html.Div(id="analysis-loading-indicator", className="loading-container"),
25
-
26
- # Check Token input with probability graph
27
- html.Div([
28
- html.Div([
29
- html.Label("Check Token (optional):", className="input-label"),
30
- dcc.Input(
31
- id='check-token-input',
32
- type='text',
33
- placeholder="Enter a token to track its probability...",
34
- value="",
35
- style={"width": "300px"},
36
- className="prompt-input"
37
- )
38
- ], style={"flex": "0 0 auto"}),
39
- html.Div([
40
- dcc.Graph(
41
- id='check-token-graph',
42
- figure={},
43
- style={'height': '450px', 'width': '100%'},
44
- config={'displayModeBar': False}
45
- )
46
- ], id='check-token-graph-container', style={'flex': '1', 'minWidth': '300px', 'display': 'none'})
47
- ], className="input-container", style={"marginBottom": "1.5rem", "display": "flex", "gap": "1.5rem", "alignItems": "flex-start"}),
48
-
49
- # Visualization section (first prompt)
50
- html.Div([
51
- html.H3("Model Flow Visualization", className="section-title"),
52
- html.Div([
53
- cyto.Cytoscape(
54
- id='model-flow-graph',
55
- elements=[],
56
- layout={'name': 'preset'},
57
- style={'width': '100%', 'height': '400px'},
58
- zoom=1.0,
59
- pan={'x': 100, 'y': 200},
60
- stylesheet=[
61
- # Node styles
62
- {
63
- 'selector': 'node',
64
- 'style': {
65
- 'width': '60px',
66
- 'height': '60px',
67
- 'background-color': '#667eea',
68
- 'border-color': '#5a67d8',
69
- 'border-width': '2px',
70
- 'label': 'data(label)',
71
- 'text-valign': 'center',
72
- 'color': 'white',
73
- 'font-size': '10px',
74
- 'text-wrap': 'wrap'
75
- }
76
- },
77
- # Divergent layer node style (red border for different prompts)
78
- {
79
- 'selector': 'node.divergent-layer',
80
- 'style': {
81
- 'border-color': '#e53e3e',
82
- 'border-width': '4px'
83
- }
84
- },
85
- # Output node style (distinct from layer nodes)
86
- {
87
- 'selector': 'node[id="output_node"]',
88
- 'style': {
89
- 'width': '80px',
90
- 'height': '80px',
91
- 'background-color': '#48bb78',
92
- 'border-color': '#38a169',
93
- 'border-width': '3px',
94
- 'label': 'data(label)',
95
- 'text-valign': 'center',
96
- 'color': 'white',
97
- 'font-size': '11px',
98
- 'font-weight': 'bold',
99
- 'text-wrap': 'wrap',
100
- 'shape': 'round-rectangle'
101
- }
102
- },
103
- # Edge styles
104
- {
105
- 'selector': 'edge',
106
- 'style': {
107
- 'width': 'data(width)',
108
- 'opacity': 'data(opacity)',
109
- 'line-color': 'data(color)',
110
- 'target-arrow-color': 'data(color)',
111
- 'target-arrow-shape': 'triangle',
112
- 'curve-style': 'bezier'
113
- }
114
- }
115
- ]
116
- ),
117
- # Tooltip for edge hover
118
- html.Div(id='edge-tooltip', style={'display': 'none'})
119
- ], style={'position': 'relative'})
120
- ], className="visualization-section"),
121
-
122
- # Second visualization (for comparison - initially hidden)
123
- html.Div([
124
- html.H3("Model Flow Visualization (Prompt 2)", className="section-title"),
125
- html.Div([
126
- cyto.Cytoscape(
127
- id='model-flow-graph-2',
128
- elements=[],
129
- layout={'name': 'preset'},
130
- style={'width': '100%', 'height': '400px'},
131
- zoom=1.0,
132
- pan={'x': 100, 'y': 200},
133
- stylesheet=[
134
- # Node styles
135
- {
136
- 'selector': 'node',
137
- 'style': {
138
- 'width': '60px',
139
- 'height': '60px',
140
- 'background-color': '#667eea',
141
- 'border-color': '#5a67d8',
142
- 'border-width': '2px',
143
- 'label': 'data(label)',
144
- 'text-valign': 'center',
145
- 'color': 'white',
146
- 'font-size': '10px',
147
- 'text-wrap': 'wrap'
148
- }
149
- },
150
- # Divergent layer node style (red border for different prompts)
151
- {
152
- 'selector': 'node.divergent-layer',
153
- 'style': {
154
- 'border-color': '#e53e3e',
155
- 'border-width': '4px'
156
- }
157
- },
158
- # Output node style (distinct from layer nodes)
159
- {
160
- 'selector': 'node[id="output_node"]',
161
- 'style': {
162
- 'width': '80px',
163
- 'height': '80px',
164
- 'background-color': '#48bb78',
165
- 'border-color': '#38a169',
166
- 'border-width': '3px',
167
- 'label': 'data(label)',
168
- 'text-valign': 'center',
169
- 'color': 'white',
170
- 'font-size': '11px',
171
- 'font-weight': 'bold',
172
- 'text-wrap': 'wrap',
173
- 'shape': 'round-rectangle'
174
- }
175
- },
176
- # Edge styles
177
- {
178
- 'selector': 'edge',
179
- 'style': {
180
- 'width': 'data(width)',
181
- 'opacity': 'data(opacity)',
182
- 'line-color': 'data(color)',
183
- 'target-arrow-color': 'data(color)',
184
- 'target-arrow-shape': 'triangle',
185
- 'curve-style': 'bezier'
186
- }
187
- }
188
- ]
189
- ),
190
- # Tooltip for edge hover
191
- html.Div(id='edge-tooltip-2', style={'display': 'none'})
192
- ], style={'position': 'relative'})
193
- ], id="second-visualization-section", className="visualization-section", style={'display': 'none'}),
194
-
195
- # Two-Prompt Comparison section (shown when comparing)
196
- html.Div([
197
- html.H3("Two-Prompt Comparison Analysis", className="section-title"),
198
- html.Div([
199
- html.P(
200
- "Comparison analysis will appear here when two prompts are provided.",
201
- className="placeholder-text"
202
- )
203
- ], id="comparison-container", className="results-area")
204
- ], id="comparison-section", className="results-section", style={'display': 'none'}),
205
-
206
- # Analysis Results section (layer-specific analysis on node click)
207
- html.Div([
208
- html.H3("Analysis Results", className="section-title"),
209
- html.Div([
210
- html.P(
211
- "Click a layer node to see detailed attention analysis and head categorization.",
212
- className="placeholder-text"
213
- )
214
- ], id="results-container", className="results-area")
215
- ], className="results-section")
216
-
217
- ], className="main-panel-content")
 
 
 
 
 
 
 
1
+ """
2
+ Main panel component for the dashboard.
3
+
4
+ This component contains:
5
+ - Model selector and prompt input
6
+ - Visualization area (placeholder for now)
7
+ - Status and results display
8
+ """
9
+
10
+ from dash import html, dcc
11
+ import dash_cytoscape as cyto
12
+ from .model_selector import create_model_selector
13
+
14
+ # Feature flag: set to True to show legacy Cytoscape graph
15
+ USE_LEGACY_CYTOSCAPE = False
16
+
17
+ def create_main_panel():
18
+ """Create the main content panel."""
19
+ return html.Div([
20
+ # Model selection section
21
+ html.Div([
22
+ html.H3("Model Configuration", className="section-title"),
23
+ create_model_selector()
24
+ ], className="config-section"),
25
+
26
+ # Analysis loading indicator
27
+ html.Div(id="analysis-loading-indicator", className="loading-container"),
28
+
29
+ # Check Token input with probability graph
30
+ html.Div([
31
+ html.Div([
32
+ html.Label("Check Token (optional):", className="input-label"),
33
+ dcc.Input(
34
+ id='check-token-input',
35
+ type='text',
36
+ placeholder="Enter a token to track its probability...",
37
+ value="",
38
+ style={"width": "300px"},
39
+ className="prompt-input"
40
+ )
41
+ ], style={"flex": "0 0 auto"}),
42
+ html.Div([
43
+ dcc.Graph(
44
+ id='check-token-graph',
45
+ figure={},
46
+ style={'height': '450px', 'width': '100%'},
47
+ config={'displayModeBar': False}
48
+ )
49
+ ], id='check-token-graph-container', style={'flex': '1', 'minWidth': '300px', 'display': 'none'})
50
+ ], className="input-container", style={"marginBottom": "1.5rem", "display": "flex", "gap": "1.5rem", "alignItems": "flex-start"}),
51
+
52
+ # Visualization section (first prompt)
53
+ html.Div([
54
+ html.H3("Model Flow Visualization", className="section-title"),
55
+ # New accordion-based layer view
56
+ html.Div(id='layer-accordions-container', className="layer-accordions"),
57
+ # Legacy Cytoscape graph (gated by feature flag)
58
+ html.Div([
59
+ cyto.Cytoscape(
60
+ id='model-flow-graph',
61
+ elements=[],
62
+ layout={'name': 'preset'},
63
+ style={'width': '100%', 'height': '400px'},
64
+ zoom=1.0,
65
+ pan={'x': 100, 'y': 200},
66
+ stylesheet=[
67
+ # Node styles
68
+ {
69
+ 'selector': 'node',
70
+ 'style': {
71
+ 'width': '60px',
72
+ 'height': '60px',
73
+ 'background-color': '#667eea',
74
+ 'border-color': '#5a67d8',
75
+ 'border-width': '2px',
76
+ 'label': 'data(label)',
77
+ 'text-valign': 'center',
78
+ 'color': 'white',
79
+ 'font-size': '10px',
80
+ 'text-wrap': 'wrap'
81
+ }
82
+ },
83
+ # Divergent layer node style (red border for different prompts)
84
+ {
85
+ 'selector': 'node.divergent-layer',
86
+ 'style': {
87
+ 'border-color': '#e53e3e',
88
+ 'border-width': '4px'
89
+ }
90
+ },
91
+ # Output node style (distinct from layer nodes)
92
+ {
93
+ 'selector': 'node[id="output_node"]',
94
+ 'style': {
95
+ 'width': '80px',
96
+ 'height': '80px',
97
+ 'background-color': '#48bb78',
98
+ 'border-color': '#38a169',
99
+ 'border-width': '3px',
100
+ 'label': 'data(label)',
101
+ 'text-valign': 'center',
102
+ 'color': 'white',
103
+ 'font-size': '11px',
104
+ 'font-weight': 'bold',
105
+ 'text-wrap': 'wrap',
106
+ 'shape': 'round-rectangle'
107
+ }
108
+ },
109
+ # Edge styles
110
+ {
111
+ 'selector': 'edge',
112
+ 'style': {
113
+ 'width': 'data(width)',
114
+ 'opacity': 'data(opacity)',
115
+ 'line-color': 'data(color)',
116
+ 'target-arrow-color': 'data(color)',
117
+ 'target-arrow-shape': 'triangle',
118
+ 'curve-style': 'bezier'
119
+ }
120
+ }
121
+ ]
122
+ ),
123
+ # Tooltip for edge hover
124
+ html.Div(id='edge-tooltip', style={'display': 'none'})
125
+ ], id='legacy-cytoscape-container', style={'position': 'relative', 'display': 'block' if USE_LEGACY_CYTOSCAPE else 'none'})
126
+ ], className="visualization-section"),
127
+
128
+ # Second visualization (for comparison - initially hidden, gated by feature flag)
129
+ html.Div([
130
+ html.H3("Model Flow Visualization (Prompt 2)", className="section-title"),
131
+ html.Div([
132
+ cyto.Cytoscape(
133
+ id='model-flow-graph-2',
134
+ elements=[],
135
+ layout={'name': 'preset'},
136
+ style={'width': '100%', 'height': '400px'},
137
+ zoom=1.0,
138
+ pan={'x': 100, 'y': 200},
139
+ stylesheet=[
140
+ # Node styles
141
+ {
142
+ 'selector': 'node',
143
+ 'style': {
144
+ 'width': '60px',
145
+ 'height': '60px',
146
+ 'background-color': '#667eea',
147
+ 'border-color': '#5a67d8',
148
+ 'border-width': '2px',
149
+ 'label': 'data(label)',
150
+ 'text-valign': 'center',
151
+ 'color': 'white',
152
+ 'font-size': '10px',
153
+ 'text-wrap': 'wrap'
154
+ }
155
+ },
156
+ # Divergent layer node style (red border for different prompts)
157
+ {
158
+ 'selector': 'node.divergent-layer',
159
+ 'style': {
160
+ 'border-color': '#e53e3e',
161
+ 'border-width': '4px'
162
+ }
163
+ },
164
+ # Output node style (distinct from layer nodes)
165
+ {
166
+ 'selector': 'node[id="output_node"]',
167
+ 'style': {
168
+ 'width': '80px',
169
+ 'height': '80px',
170
+ 'background-color': '#48bb78',
171
+ 'border-color': '#38a169',
172
+ 'border-width': '3px',
173
+ 'label': 'data(label)',
174
+ 'text-valign': 'center',
175
+ 'color': 'white',
176
+ 'font-size': '11px',
177
+ 'font-weight': 'bold',
178
+ 'text-wrap': 'wrap',
179
+ 'shape': 'round-rectangle'
180
+ }
181
+ },
182
+ # Edge styles
183
+ {
184
+ 'selector': 'edge',
185
+ 'style': {
186
+ 'width': 'data(width)',
187
+ 'opacity': 'data(opacity)',
188
+ 'line-color': 'data(color)',
189
+ 'target-arrow-color': 'data(color)',
190
+ 'target-arrow-shape': 'triangle',
191
+ 'curve-style': 'bezier'
192
+ }
193
+ }
194
+ ]
195
+ ),
196
+ # Tooltip for edge hover
197
+ html.Div(id='edge-tooltip-2', style={'display': 'none'})
198
+ ], style={'position': 'relative'})
199
+ ], id="second-visualization-section", className="visualization-section", style={'display': 'none' if not USE_LEGACY_CYTOSCAPE else 'none'}),
200
+
201
+ # Two-Prompt Comparison section (shown when comparing)
202
+ html.Div([
203
+ html.H3("Two-Prompt Comparison Analysis", className="section-title"),
204
+ html.Div([
205
+ html.P(
206
+ "Comparison analysis will appear here when two prompts are provided.",
207
+ className="placeholder-text"
208
+ )
209
+ ], id="comparison-container", className="results-area")
210
+ ], id="comparison-section", className="results-section", style={'display': 'none'}),
211
+
212
+ # Analysis Results section (layer-specific analysis on node click)
213
+ html.Div([
214
+ html.H3("Analysis Results", className="section-title"),
215
+ html.Div([
216
+ html.P(
217
+ "Click a layer node to see detailed attention analysis and head categorization.",
218
+ className="placeholder-text"
219
+ )
220
+ ], id="results-container", className="results-area")
221
+ ], className="results-section")
222
+
223
+ ], className="main-panel-content")
todo.md CHANGED
@@ -3,11 +3,11 @@
3
  Note: Minimal-change approach. Reuse existing files (`app.py`, `components/main_panel.py`, `utils/*`). Avoid new dependencies; use native `html.Details`/`html.Summary`, existing `dcc.Graph`, and current BertViz integration.
4
 
5
  ## Feature: Switch to panels with plain-language headers
6
- - [ ] Replace per-layer node display with `html.Details` (accordion) per layer in `components/main_panel.py`
7
- - [ ] Use `html.Summary` as header: `Layer L{N}: likely '{token}' (p={prob})`
8
- - [ ] Truncate long tokens in header with CSS (ellipsis); keep one-line summary
9
- - [ ] Add lightweight top-3 tokens chips/arrows between adjacent panel headers (no Cytoscape)
10
- - [ ] Gate old Cytoscape graph behind a feature flag (keep code path but hidden by default)
11
 
12
  ## Feature: Keep initial panels small to preserve flow view
13
  - [ ] Style summary rows compactly (single-line, small font, consistent height)
 
3
  Note: Minimal-change approach. Reuse existing files (`app.py`, `components/main_panel.py`, `utils/*`). Avoid new dependencies; use native `html.Details`/`html.Summary`, existing `dcc.Graph`, and current BertViz integration.
4
 
5
  ## Feature: Switch to panels with plain-language headers
6
+ - [x] Replace per-layer node display with `html.Details` (accordion) per layer in `components/main_panel.py`
7
+ - [x] Use `html.Summary` as header: `Layer L{N}: likely '{token}' (p={prob})`
8
+ - [x] Truncate long tokens in header with CSS (ellipsis); keep one-line summary
9
+ - [x] Add lightweight top-3 tokens "chips/arrows" between adjacent panel headers (no Cytoscape)
10
+ - [x] Gate old Cytoscape graph behind a feature flag (keep code path but hidden by default)
11
 
12
  ## Feature: Keep initial panels small to preserve flow view
13
  - [ ] Style summary rows compactly (single-line, small font, consistent height)
utils/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .model_patterns import load_model_and_get_patterns, execute_forward_pass, logit_lens_transformation, format_data_for_cytoscape, generate_bertviz_html, generate_category_bertviz_html, get_check_token_probabilities
2
  from .model_config import get_model_family, get_family_config, get_auto_selections, MODEL_TO_FAMILY, MODEL_FAMILIES
3
  from .head_detection import categorize_all_heads, categorize_single_layer_heads, format_categorization_summary, HeadCategorizationConfig
4
  from .prompt_comparison import compare_attention_layers, compare_output_probabilities, format_comparison_summary, ComparisonConfig
@@ -7,7 +7,8 @@ __all__ = [
7
  'load_model_and_get_patterns',
8
  'execute_forward_pass',
9
  'logit_lens_transformation',
10
- 'format_data_for_cytoscape',
 
11
  'generate_bertviz_html',
12
  'generate_category_bertviz_html',
13
  'get_check_token_probabilities',
 
1
+ from .model_patterns import load_model_and_get_patterns, execute_forward_pass, logit_lens_transformation, format_data_for_cytoscape, extract_layer_data, generate_bertviz_html, generate_category_bertviz_html, get_check_token_probabilities
2
  from .model_config import get_model_family, get_family_config, get_auto_selections, MODEL_TO_FAMILY, MODEL_FAMILIES
3
  from .head_detection import categorize_all_heads, categorize_single_layer_heads, format_categorization_summary, HeadCategorizationConfig
4
  from .prompt_comparison import compare_attention_layers, compare_output_probabilities, format_comparison_summary, ComparisonConfig
 
7
  'load_model_and_get_patterns',
8
  'execute_forward_pass',
9
  'logit_lens_transformation',
10
+ 'format_data_for_cytoscape',
11
+ 'extract_layer_data',
12
  'generate_bertviz_html',
13
  'generate_category_bertviz_html',
14
  'get_check_token_probabilities',
utils/model_patterns.py CHANGED
@@ -486,6 +486,50 @@ def format_data_for_cytoscape(activation_data: Dict[str, Any], model, tokenizer)
486
  return elements
487
 
488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  def generate_bertviz_html(activation_data: Dict[str, Any], layer_index: int, view_type: str = 'full') -> str:
490
  """
491
  Generate BertViz attention visualization HTML using model_view.
 
486
  return elements
487
 
488
 
489
+ def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> List[Dict[str, Any]]:
490
+ """
491
+ Extract layer-by-layer data for accordion display.
492
+
493
+ Returns:
494
+ List of dicts with: layer_num, top_token, top_prob, top_3_tokens (list of (token, prob))
495
+ """
496
+ layer_modules = activation_data.get('block_modules', [])
497
+ if not layer_modules:
498
+ return []
499
+
500
+ # Extract and sort layers by layer number
501
+ layer_info = sorted(
502
+ [(int(re.findall(r'\d+', name)[0]), name)
503
+ for name in layer_modules if re.findall(r'\d+', name)]
504
+ )
505
+
506
+ logit_lens_enabled = activation_data.get('logit_lens_parameter') is not None
507
+ layer_data = []
508
+
509
+ for layer_num, module_name in layer_info:
510
+ top_tokens = _get_top_tokens(activation_data, module_name, model, tokenizer) if logit_lens_enabled else None
511
+
512
+ if top_tokens:
513
+ top_token, top_prob = top_tokens[0]
514
+ layer_data.append({
515
+ 'layer_num': layer_num,
516
+ 'module_name': module_name,
517
+ 'top_token': top_token,
518
+ 'top_prob': top_prob,
519
+ 'top_3_tokens': top_tokens[:3] # Get top 3 for chips
520
+ })
521
+ else:
522
+ layer_data.append({
523
+ 'layer_num': layer_num,
524
+ 'module_name': module_name,
525
+ 'top_token': None,
526
+ 'top_prob': None,
527
+ 'top_3_tokens': []
528
+ })
529
+
530
+ return layer_data
531
+
532
+
533
  def generate_bertviz_html(activation_data: Dict[str, Any], layer_index: int, view_type: str = 'full') -> str:
534
  """
535
  Generate BertViz attention visualization HTML using model_view.