tfrere HF Staff commited on
Commit
2eff380
·
1 Parent(s): 489ae14
app/src/components/Hero.astro CHANGED
@@ -96,7 +96,7 @@ const pdfFilename = `${slugify(pdfBase)}.pdf`;
96
  <section class="hero">
97
  <h1 class="hero-title" set:html={title} />
98
  <div class="hero-banner">
99
- <HtmlEmbed src="banner.html" frameless />
100
  {description && <p class="hero-desc">{description}</p>}
101
  </div>
102
  </section>
 
96
  <section class="hero">
97
  <h1 class="hero-title" set:html={title} />
98
  <div class="hero-banner">
99
+ <HtmlEmbed src="banner-sequence-alignment-svg.html" frameless />
100
  {description && <p class="hero-desc">{description}</p>}
101
  </div>
102
  </section>
app/src/content/embeds/banner-sequence-alignment-svg.html CHANGED
@@ -41,45 +41,45 @@
41
  const height = container.clientHeight || 220;
42
 
43
  // Calculate scale based on available width
44
- const baseTotalWidth = 180 + 110 + 40 + 90 + 180 + (15 * 4);
45
  const scale = Math.min(1, (width - 40) / baseTotalWidth);
46
 
47
- const padding = 15 * scale;
48
- const tokenHeight = 35 * scale;
49
- const spacing = 15 * scale;
50
- const subTokenSmallSize = 24 * scale;
51
- const subTokenLargeWidth = 70 * scale;
52
- const subTokenLargeHeight = 24 * scale;
53
 
54
  const originalWords = [
55
  { text: '<think>', subTokens: [
56
  { id: 0, type: 'small' },
57
  { id: 1, type: 'small' },
58
  { id: 2, type: 'small' }
59
- ], width: 180 * scale },
60
  { text: 'Hugging Face', subTokens: [
61
  { id: 3, type: 'large' }
62
- ], width: 110 * scale },
63
  { text: 'is', subTokens: [
64
  { id: 4, type: 'small' }
65
- ], width: 40 * scale },
66
  { text: 'awesome!', subTokens: [
67
  { id: 5, type: 'large' }
68
- ], width: 90 * scale },
69
  { text: '</think>', subTokens: [
70
  { id: 6, type: 'small' },
71
  { id: 7, type: 'small' },
72
  { id: 8, type: 'small' },
73
  { id: 9, type: 'small' }
74
- ], width: 180 * scale }
75
  ];
76
 
77
  // Store word centers for alignment
78
  const wordCenters = [];
79
  let currentX = padding;
80
- const originalY = height * 0.18;
81
- const subTokenY = height * 0.45;
82
- const mergedY = height * 0.72;
83
 
84
  // Draw original words and sub-tokens
85
  originalWords.forEach((word, wordIdx) => {
@@ -89,22 +89,25 @@
89
  // Original word (green)
90
  draw.rect(word.width, tokenHeight)
91
  .move(currentX, originalY - tokenHeight / 2)
92
- .radius(10 * scale)
93
  .fill(colors.originalToken)
94
- .stroke({ color: colors.line, width: 1.5 * scale });
95
 
96
- draw.text(word.text)
97
- .move(currentX + word.width / 2, originalY)
98
  .font({
99
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
100
- size: 11 * scale,
101
  anchor: 'middle',
102
  weight: '500'
103
  })
104
  .fill(colors.text);
 
 
 
 
105
 
106
  // Sub-tokens (yellow) below - small squares or large rectangles
107
- const subTokenGap = 4 * scale;
108
 
109
  // Calculate total width for centering
110
  let subTokensTotalWidth = 0;
@@ -121,26 +124,29 @@
121
 
122
  draw.rect(stWidth, stHeight)
123
  .move(subTokenX, subTokenY - stHeight / 2)
124
- .radius(6 * scale)
125
  .fill(colors.subToken)
126
- .stroke({ color: colors.line, width: 1.5 * scale });
127
 
128
- draw.text(st.id.toString())
129
- .move(subTokenX + stWidth / 2, subTokenY)
130
  .font({
131
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
132
- size: 10 * scale,
133
  anchor: 'middle',
134
  weight: '600'
135
  })
136
  .fill(colors.text);
137
 
 
 
 
 
138
  subTokenX += stWidth + subTokenGap;
139
  });
140
 
141
  // Vertical dotted line from word center
142
- draw.line(wordCenterX, originalY + tokenHeight / 2 + (3 * scale), wordCenterX, mergedY - tokenHeight / 2 - (3 * scale))
143
- .stroke({ color: colors.line, width: 1.5 * scale, dasharray: '4,4' });
144
 
145
  currentX += word.width + spacing;
146
  });
@@ -149,14 +155,14 @@
149
  // Groups of tokens: some have multiple rectangles side-by-side
150
  const mergedTokenGroups = [
151
  { ids: [0], alignToWord: 0 }, // Single token "0" under <think>
152
- { ids: [1, 2], alignToWord: null, x: wordCenters[0] + (wordCenters[1] - wordCenters[0]) * 0.55 }, // Two tokens "1", "2" between words
153
- { ids: [3], alignToWord: 1 }, // Single token "3" under Hugging Face
154
- { ids: [4], alignToWord: 2 }, // Single token "4" under is
155
- { ids: [5], alignToWord: 3 } // Single token "5" under awesome!
156
  ];
157
 
158
- const mergedTokenWidth = 55 * scale;
159
- const mergedTokenGap = 2 * scale;
160
 
161
  mergedTokenGroups.forEach((group) => {
162
  const groupTotalWidth = group.ids.length * mergedTokenWidth + (group.ids.length - 1) * mergedTokenGap;
@@ -175,19 +181,22 @@
175
 
176
  draw.rect(mergedTokenWidth, tokenHeight)
177
  .move(tokenX, mergedY - tokenHeight / 2)
178
- .radius(8 * scale)
179
  .fill(colors.mergedToken)
180
- .stroke({ color: colors.line, width: 1.5 * scale });
181
 
182
- draw.text(id.toString())
183
- .move(tokenX + mergedTokenWidth / 2, mergedY)
184
  .font({
185
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
186
- size: 11 * scale,
187
  anchor: 'middle',
188
  weight: '600'
189
  })
190
  .fill(colors.text);
 
 
 
 
191
  });
192
  });
193
  }
@@ -204,135 +213,184 @@
204
  const height = container.clientHeight || 220;
205
 
206
  // Calculate scale based on available width
207
- const baseTotalWidth = 180 + 110 + 35 + 80 + 160 + (15 * 4);
 
208
  const scale = Math.min(1, (width - 40) / baseTotalWidth);
209
 
210
- const padding = 15 * scale;
211
- const logprobStackHeight = 55 * scale;
212
- const logprobRectSize = 14 * scale;
213
- const logprobSpacing = 4 * scale;
214
  const rectsPerStack = 4; // Input stacks have 4 rectangles
215
- const stackWidth = (logprobRectSize + logprobSpacing) * rectsPerStack - logprobSpacing;
216
- const spacing = 15 * scale;
217
-
218
- const words = [
219
- { text: '<think>', stacks: 3, width: 180 * scale },
220
- { text: 'Hugging Face', stacks: 1, width: 110 * scale },
221
- { text: 'is', stacks: 1, width: 35 * scale },
222
- { text: 'awesome!', stacks: 1, width: 80 * scale },
223
- { text: '</think>', stacks: 4, width: 160 * scale }
 
 
 
 
 
 
 
224
  ];
225
 
226
- const wordCenters = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  let currentX = padding;
228
- const inputY = height * 0.18;
229
- const outputY = height * 0.72;
 
230
 
231
  // Draw input logprob tensors (yellow stacks)
232
- words.forEach((word, wordIdx) => {
233
- const wordCenterX = currentX + word.width / 2;
234
- wordCenters.push(wordCenterX);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
- // Draw multiple stacks for words that merge
237
- const stackGap = 12 * scale;
238
- const totalStacksWidth = word.stacks * stackWidth + (word.stacks - 1) * stackGap;
239
- const stacksStartX = wordCenterX - totalStacksWidth / 2;
240
-
241
- for (let s = 0; s < word.stacks; s++) {
242
- const stackX = stacksStartX + s * (stackWidth + stackGap);
243
-
244
- // Draw stack of rectangles (4 rectangles per stack)
245
- for (let i = 0; i < rectsPerStack; i++) {
246
- const rectY = inputY - logprobStackHeight / 2 + i * (logprobRectSize + logprobSpacing);
247
-
248
- draw.rect(logprobRectSize, logprobRectSize)
249
- .move(stackX, rectY)
250
- .radius(4 * scale)
251
- .fill(colors.subToken)
252
- .stroke({ color: colors.line, width: 1 * scale });
253
- }
254
-
255
- // Draw plus signs between stacks (except last)
256
- if (s < word.stacks - 1) {
257
- const plusX = stackX + stackWidth + stackGap / 2;
258
- draw.text('+')
259
- .move(plusX, inputY)
260
- .font({
261
- family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
262
- size: 18 * scale,
263
- anchor: 'middle',
264
- weight: 'bold'
265
- })
266
- .fill(colors.plus);
267
- }
268
  }
269
 
270
- // Word label below input stacks
271
- const labelHeight = 20 * scale;
272
- const labelY = inputY + logprobStackHeight / 2 + (18 * scale);
273
 
274
- draw.rect(word.width, labelHeight)
275
  .move(currentX, labelY - labelHeight / 2)
276
- .radius(5 * scale)
277
- .fill(colors.originalToken)
278
- .stroke({ color: colors.line, width: 1 * scale });
279
 
280
- draw.text(word.text)
281
- .move(currentX + word.width / 2, labelY)
282
  .font({
283
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
284
- size: 10 * scale,
285
  anchor: 'middle',
286
  weight: '500'
287
  })
288
  .fill(colors.text);
 
 
289
 
290
- // Vertical dotted line from label to output
291
- draw.line(wordCenterX, labelY + labelHeight / 2 + (5 * scale), wordCenterX, outputY - logprobStackHeight / 2 - (5 * scale))
292
- .stroke({ color: colors.line, width: 1.5 * scale, dasharray: '4,4' });
293
 
294
- currentX += word.width + spacing;
 
 
 
 
 
295
  });
296
 
297
  // Draw output logprob tensors (blue stacks) - 5 rectangles per stack
298
- words.forEach((word, wordIdx) => {
299
- const wordCenterX = wordCenters[wordIdx];
 
 
300
 
301
- // Single output stack (merged) - 5 rectangles!
 
 
 
 
 
302
  const outputRectsPerStack = 5;
303
- const outputStackWidth = (logprobRectSize + logprobSpacing) * outputRectsPerStack - logprobSpacing;
304
- const adjustedStackX = wordCenterX - outputStackWidth / 2;
305
 
306
  for (let i = 0; i < outputRectsPerStack; i++) {
307
  const rectY = outputY - logprobStackHeight / 2 + i * (logprobRectSize + logprobSpacing);
308
- const rectX = adjustedStackX + i * (logprobRectSize + logprobSpacing);
309
 
310
  draw.rect(logprobRectSize, logprobRectSize)
311
- .move(rectX, rectY)
312
  .radius(4 * scale)
313
  .fill(colors.mergedToken)
314
- .stroke({ color: colors.line, width: 1 * scale });
315
  }
316
 
317
- // Word label below output stacks
318
- const labelHeight = 20 * scale;
319
- const labelY = outputY + logprobStackHeight / 2 + (18 * scale);
320
 
321
- draw.rect(word.width, labelHeight)
322
- .move(wordCenters[wordIdx] - word.width / 2, labelY - labelHeight / 2)
323
- .radius(5 * scale)
324
  .fill(colors.mergedToken)
325
- .stroke({ color: colors.line, width: 1 * scale });
326
 
327
- draw.text(word.text)
328
- .move(wordCenters[wordIdx], labelY)
329
  .font({
330
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
331
- size: 10 * scale,
332
  anchor: 'middle',
333
  weight: '500'
334
  })
335
  .fill(colors.text);
 
 
336
  });
337
  }
338
 
 
41
  const height = container.clientHeight || 220;
42
 
43
  // Calculate scale based on available width
44
+ const baseTotalWidth = 130 + 130 + 50 + 100 + 150 + (20 * 4);
45
  const scale = Math.min(1, (width - 40) / baseTotalWidth);
46
 
47
+ const padding = 20 * scale;
48
+ const tokenHeight = 32 * scale;
49
+ const spacing = 20 * scale;
50
+ const subTokenSmallSize = 26 * scale;
51
+ const subTokenLargeWidth = 65 * scale;
52
+ const subTokenLargeHeight = 26 * scale;
53
 
54
  const originalWords = [
55
  { text: '<think>', subTokens: [
56
  { id: 0, type: 'small' },
57
  { id: 1, type: 'small' },
58
  { id: 2, type: 'small' }
59
+ ], width: 130 * scale },
60
  { text: 'Hugging Face', subTokens: [
61
  { id: 3, type: 'large' }
62
+ ], width: 130 * scale },
63
  { text: 'is', subTokens: [
64
  { id: 4, type: 'small' }
65
+ ], width: 50 * scale },
66
  { text: 'awesome!', subTokens: [
67
  { id: 5, type: 'large' }
68
+ ], width: 100 * scale },
69
  { text: '</think>', subTokens: [
70
  { id: 6, type: 'small' },
71
  { id: 7, type: 'small' },
72
  { id: 8, type: 'small' },
73
  { id: 9, type: 'small' }
74
+ ], width: 150 * scale }
75
  ];
76
 
77
  // Store word centers for alignment
78
  const wordCenters = [];
79
  let currentX = padding;
80
+ const originalY = height * 0.20;
81
+ const subTokenY = height * 0.50;
82
+ const mergedY = height * 0.78;
83
 
84
  // Draw original words and sub-tokens
85
  originalWords.forEach((word, wordIdx) => {
 
89
  // Original word (green)
90
  draw.rect(word.width, tokenHeight)
91
  .move(currentX, originalY - tokenHeight / 2)
92
+ .radius(12 * scale)
93
  .fill(colors.originalToken)
94
+ .stroke({ color: colors.line, width: 2 * scale });
95
 
96
+ const textEl = draw.text(word.text)
 
97
  .font({
98
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
99
+ size: 12 * scale,
100
  anchor: 'middle',
101
  weight: '500'
102
  })
103
  .fill(colors.text);
104
+
105
+ // Center text in the box
106
+ textEl.cx(currentX + word.width / 2);
107
+ textEl.cy(originalY);
108
 
109
  // Sub-tokens (yellow) below - small squares or large rectangles
110
+ const subTokenGap = 3 * scale;
111
 
112
  // Calculate total width for centering
113
  let subTokensTotalWidth = 0;
 
124
 
125
  draw.rect(stWidth, stHeight)
126
  .move(subTokenX, subTokenY - stHeight / 2)
127
+ .radius(7 * scale)
128
  .fill(colors.subToken)
129
+ .stroke({ color: colors.line, width: 2 * scale });
130
 
131
+ const subTextEl = draw.text(st.id.toString())
 
132
  .font({
133
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
134
+ size: 11 * scale,
135
  anchor: 'middle',
136
  weight: '600'
137
  })
138
  .fill(colors.text);
139
 
140
+ // Center text in the box
141
+ subTextEl.cx(subTokenX + stWidth / 2);
142
+ subTextEl.cy(subTokenY);
143
+
144
  subTokenX += stWidth + subTokenGap;
145
  });
146
 
147
  // Vertical dotted line from word center
148
+ draw.line(wordCenterX, originalY + tokenHeight / 2 + (5 * scale), wordCenterX, mergedY - tokenHeight / 2 - (5 * scale))
149
+ .stroke({ color: colors.line, width: 2 * scale, dasharray: '5,5' });
150
 
151
  currentX += word.width + spacing;
152
  });
 
155
  // Groups of tokens: some have multiple rectangles side-by-side
156
  const mergedTokenGroups = [
157
  { ids: [0], alignToWord: 0 }, // Single token "0" under <think>
158
+ { ids: [1, 2], alignToWord: 1 }, // Two tokens "1", "2" under Hugging Face
159
+ { ids: [3], alignToWord: 2 }, // Single token "3" under is
160
+ { ids: [4], alignToWord: 3 }, // Single token "4" under awesome!
161
+ { ids: [5], alignToWord: 4 } // Single token "5" under </think>
162
  ];
163
 
164
+ const mergedTokenWidth = 52 * scale;
165
+ const mergedTokenGap = 3 * scale;
166
 
167
  mergedTokenGroups.forEach((group) => {
168
  const groupTotalWidth = group.ids.length * mergedTokenWidth + (group.ids.length - 1) * mergedTokenGap;
 
181
 
182
  draw.rect(mergedTokenWidth, tokenHeight)
183
  .move(tokenX, mergedY - tokenHeight / 2)
184
+ .radius(10 * scale)
185
  .fill(colors.mergedToken)
186
+ .stroke({ color: colors.line, width: 2 * scale });
187
 
188
+ const mergedTextEl = draw.text(id.toString())
 
189
  .font({
190
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
191
+ size: 12 * scale,
192
  anchor: 'middle',
193
  weight: '600'
194
  })
195
  .fill(colors.text);
196
+
197
+ // Center text in the box
198
+ mergedTextEl.cx(tokenX + mergedTokenWidth / 2);
199
+ mergedTextEl.cy(mergedY);
200
  });
201
  });
202
  }
 
213
  const height = container.clientHeight || 220;
214
 
215
  // Calculate scale based on available width
216
+ // Total: (22+48+22) + 130 + 50 + 100 + (22+22+48+22) = 92 + 130 + 50 + 100 + 114 = 486 base + gaps
217
+ const baseTotalWidth = 92 + 130 + 50 + 100 + 114 + (20 * 4);
218
  const scale = Math.min(1, (width - 40) / baseTotalWidth);
219
 
220
+ const padding = 20 * scale;
221
+ const logprobStackHeight = 60 * scale;
222
+ const logprobRectSize = 13 * scale;
223
+ const logprobSpacing = 3 * scale;
224
  const rectsPerStack = 4; // Input stacks have 4 rectangles
225
+ const singleStackWidth = logprobRectSize;
226
+ const spacing = 20 * scale;
227
+ const labelHeight = 26 * scale;
228
+
229
+ // Input groups (top, yellow) - with separate nodes for brackets/slashes
230
+ const inputGroups = [
231
+ { text: '<', width: 22 * scale, groupId: 0 },
232
+ { text: 'think', width: 48 * scale, groupId: 0 },
233
+ { text: '>', width: 22 * scale, groupId: 0 },
234
+ { text: 'Hugging Face', width: 130 * scale, groupId: 1 },
235
+ { text: 'is', width: 50 * scale, groupId: 2 },
236
+ { text: 'awesome!', width: 100 * scale, groupId: 3 },
237
+ { text: '<', width: 22 * scale, groupId: 4 },
238
+ { text: '/', width: 22 * scale, groupId: 4 },
239
+ { text: 'think', width: 48 * scale, groupId: 4 },
240
+ { text: '>', width: 22 * scale, groupId: 4 }
241
  ];
242
 
243
+ // Stack configuration per group
244
+ const groupStacks = {
245
+ 0: 3, // <think> has 3 stacks
246
+ 1: 1, // Hugging Face has 1 stack
247
+ 2: 1, // is has 1 stack
248
+ 3: 1, // awesome! has 1 stack
249
+ 4: 4 // </think> has 4 stacks
250
+ };
251
+
252
+ // Output groups (bottom, blue) - 6 separate words
253
+ const outputGroups = [
254
+ { text: '<think>', alignToInput: 0 },
255
+ { text: 'Hugging', alignToInput: 1, offset: -40 * scale },
256
+ { text: 'Face', alignToInput: 1, offset: 40 * scale },
257
+ { text: 'is', alignToInput: 2 },
258
+ { text: 'awesome!', alignToInput: 3 },
259
+ { text: '</think>', alignToInput: 4 }
260
+ ];
261
+
262
+ // Track centers for each unique group (for output alignment)
263
+ const inputCenters = [null, null, null, null, null]; // 5 groups: 0=<think>, 1=Hugging Face, 2=is, 3=awesome!, 4=</think>
264
+ const groupXPositions = {}; // Track X positions for each group
265
+
266
  let currentX = padding;
267
+ const inputY = height * 0.20;
268
+ const outputY = height * 0.68;
269
+ const nodeGap = 3 * scale; // Small gap between nodes in same group
270
 
271
  // Draw input logprob tensors (yellow stacks)
272
+ const nodePositions = []; // Track each node's position for stacks
273
+
274
+ inputGroups.forEach((group, groupIdx) => {
275
+ // Track start of each unique group
276
+ if (!groupXPositions[group.groupId]) {
277
+ groupXPositions[group.groupId] = { start: currentX, nodes: [] };
278
+ }
279
+
280
+ const nodeCenterX = currentX + group.width / 2;
281
+ groupXPositions[group.groupId].nodes.push({ x: currentX, width: group.width, centerX: nodeCenterX });
282
+ nodePositions.push({ centerX: nodeCenterX, groupId: group.groupId, text: group.text });
283
+
284
+ // Draw stack above this node (4 rectangles stacked vertically)
285
+ const stackX = nodeCenterX - singleStackWidth / 2;
286
+
287
+ for (let i = 0; i < rectsPerStack; i++) {
288
+ const rectY = inputY - logprobStackHeight / 2 + i * (logprobRectSize + logprobSpacing);
289
 
290
+ draw.rect(logprobRectSize, logprobRectSize)
291
+ .move(stackX, rectY)
292
+ .radius(4 * scale)
293
+ .fill(colors.subToken)
294
+ .stroke({ color: colors.line, width: 1.5 * scale });
295
+ }
296
+
297
+ // Draw plus sign to the right of stack if not last in group
298
+ const isLastInGroup = groupIdx === inputGroups.length - 1 || inputGroups[groupIdx + 1].groupId !== group.groupId;
299
+ if (!isLastInGroup) {
300
+ // Calculate next node's center
301
+ const nextGroup = inputGroups[groupIdx + 1];
302
+ const nextNodeCenterX = currentX + group.width + nodeGap + (nextGroup.width / 2);
303
+ // Center the + between this stack and the next stack
304
+ const plusX = (nodeCenterX + nextNodeCenterX) / 2;
305
+ const plusTextEl = draw.text('+')
306
+ .font({
307
+ family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
308
+ size: 16 * scale,
309
+ anchor: 'middle',
310
+ weight: 'bold'
311
+ })
312
+ .fill(colors.plus);
313
+ plusTextEl.cx(plusX);
314
+ plusTextEl.cy(inputY);
 
 
 
 
 
 
 
315
  }
316
 
317
+ // Word label
318
+ const labelY = inputY + logprobStackHeight / 2 + (22 * scale);
 
319
 
320
+ draw.rect(group.width, labelHeight)
321
  .move(currentX, labelY - labelHeight / 2)
322
+ .radius(8 * scale)
323
+ .fill(colors.subToken)
324
+ .stroke({ color: colors.line, width: 1.5 * scale });
325
 
326
+ const inputTextEl = draw.text(group.text)
 
327
  .font({
328
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
329
+ size: 11 * scale,
330
  anchor: 'middle',
331
  weight: '500'
332
  })
333
  .fill(colors.text);
334
+ inputTextEl.cx(nodeCenterX);
335
+ inputTextEl.cy(labelY);
336
 
337
+ // Move to next position
338
+ currentX += group.width + (isLastInGroup ? spacing : nodeGap);
339
+ });
340
 
341
+ // Calculate center for each group (for output alignment)
342
+ Object.keys(groupXPositions).forEach(groupId => {
343
+ const group = groupXPositions[groupId];
344
+ const totalWidth = group.nodes.reduce((sum, node) => sum + node.width, 0) + (group.nodes.length - 1) * nodeGap;
345
+ const groupCenter = group.start + totalWidth / 2;
346
+ inputCenters[groupId] = groupCenter;
347
  });
348
 
349
  // Draw output logprob tensors (blue stacks) - 5 rectangles per stack
350
+ outputGroups.forEach((output) => {
351
+ // Calculate position based on input alignment
352
+ const inputCenter = inputCenters[output.alignToInput];
353
+ const outputCenterX = inputCenter + (output.offset || 0);
354
 
355
+ // Draw vertical dotted line from input to output
356
+ const inputLabelY = inputY + logprobStackHeight / 2 + (22 * scale);
357
+ draw.line(outputCenterX, inputLabelY + labelHeight / 2 + (3 * scale), outputCenterX, outputY - logprobStackHeight / 2 - (3 * scale))
358
+ .stroke({ color: colors.line, width: 2 * scale, dasharray: '5,5' });
359
+
360
+ // Single output stack (merged) - 5 rectangles stacked vertically
361
  const outputRectsPerStack = 5;
362
+ const stackX = outputCenterX - singleStackWidth / 2;
 
363
 
364
  for (let i = 0; i < outputRectsPerStack; i++) {
365
  const rectY = outputY - logprobStackHeight / 2 + i * (logprobRectSize + logprobSpacing);
 
366
 
367
  draw.rect(logprobRectSize, logprobRectSize)
368
+ .move(stackX, rectY)
369
  .radius(4 * scale)
370
  .fill(colors.mergedToken)
371
+ .stroke({ color: colors.line, width: 1.5 * scale });
372
  }
373
 
374
+ // Word label below output stack
375
+ const outputLabelY = outputY + logprobStackHeight / 2 + (35 * scale);
376
+ const labelWidth = output.text.length * 9 * scale + 15 * scale; // Dynamic width based on text
377
 
378
+ draw.rect(labelWidth, labelHeight)
379
+ .move(outputCenterX - labelWidth / 2, outputLabelY - labelHeight / 2)
380
+ .radius(8 * scale)
381
  .fill(colors.mergedToken)
382
+ .stroke({ color: colors.line, width: 1.5 * scale });
383
 
384
+ const outputTextEl = draw.text(output.text)
 
385
  .font({
386
  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
387
+ size: 11 * scale,
388
  anchor: 'middle',
389
  weight: '500'
390
  })
391
  .fill(colors.text);
392
+ outputTextEl.cx(outputCenterX);
393
+ outputTextEl.cy(outputLabelY);
394
  });
395
  }
396