Jellyfish042 commited on
Commit
9a0518c
·
1 Parent(s): ebfc0e8

Initial commit

Browse files
Files changed (3) hide show
  1. .gitignore +2 -0
  2. app.py +1496 -0
  3. requirements.txt +1 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__/
2
+ *.pyc
app.py ADDED
@@ -0,0 +1,1496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import html
2
+ import json
3
+ import random
4
+ import re
5
+ import time
6
+
7
+ import gradio as gr
8
+
9
+ MAX_DEMO_LEN = 20
10
+
11
+ ROSA_CODE = """
12
+ def rosa_qkv(qqq, kkk, vvv):
13
+ n=len(qqq); out=[-1]*n
14
+ for i in range(n):
15
+ for w in range(i+1,0,-1):
16
+ t=qqq[i+1-w:i+1]
17
+ for j in range(i-w,-1,-1):
18
+ if kkk[j:j+w]==t:
19
+ out[i]=vvv[j+w]
20
+ break
21
+ if out[i]!=-1:
22
+ break
23
+ return out
24
+ """.strip("\n")
25
+
26
+
27
+ KEYWORDS = {
28
+ "def",
29
+ "for",
30
+ "in",
31
+ "if",
32
+ "break",
33
+ "return",
34
+ "assert",
35
+ }
36
+ BUILTINS = {"len", "range"}
37
+ KEYWORD_RE = re.compile(r"\b(" + "|".join(sorted(KEYWORDS)) + r")\b")
38
+ BUILTIN_RE = re.compile(r"\b(" + "|".join(sorted(BUILTINS)) + r")\b")
39
+ NUMBER_RE = re.compile(r"(?<![\w.])(-?\d+)(?![\w.])")
40
+
41
+
42
+ def split_comment(line: str) -> tuple[str, str]:
43
+ in_single = False
44
+ in_double = False
45
+ escaped = False
46
+ for index, ch in enumerate(line):
47
+ if escaped:
48
+ escaped = False
49
+ continue
50
+ if ch == "\\":
51
+ escaped = True
52
+ continue
53
+ if ch == "'" and not in_double:
54
+ in_single = not in_single
55
+ continue
56
+ if ch == '"' and not in_single:
57
+ in_double = not in_double
58
+ continue
59
+ if ch == "#" and not in_single and not in_double:
60
+ return line[:index], line[index:]
61
+ return line, ""
62
+
63
+
64
+ def tokenize_strings(code: str) -> list[tuple[str, str]]:
65
+ segments: list[tuple[str, str]] = []
66
+ index = 0
67
+ while index < len(code):
68
+ ch = code[index]
69
+ if ch in ("'", '"'):
70
+ quote = ch
71
+ start = index
72
+ index += 1
73
+ escaped = False
74
+ while index < len(code):
75
+ if escaped:
76
+ escaped = False
77
+ index += 1
78
+ continue
79
+ if code[index] == "\\":
80
+ escaped = True
81
+ index += 1
82
+ continue
83
+ if code[index] == quote:
84
+ index += 1
85
+ break
86
+ index += 1
87
+ segments.append(("string", code[start:index]))
88
+ continue
89
+ start = index
90
+ while index < len(code) and code[index] not in ("'", '"'):
91
+ index += 1
92
+ segments.append(("text", code[start:index]))
93
+ return segments
94
+
95
+
96
+ def highlight_text_segment(text: str) -> str:
97
+ escaped = html.escape(text)
98
+ escaped = KEYWORD_RE.sub(r'<span class="tok-keyword">\1</span>', escaped)
99
+ escaped = BUILTIN_RE.sub(r'<span class="tok-builtin">\1</span>', escaped)
100
+ escaped = NUMBER_RE.sub(r'<span class="tok-number">\1</span>', escaped)
101
+ return escaped
102
+
103
+
104
+ def highlight_python_line(line: str) -> str:
105
+ code, comment = split_comment(line)
106
+ segments = tokenize_strings(code)
107
+ rendered: list[str] = []
108
+ for kind, text in segments:
109
+ if kind == "string":
110
+ rendered.append('<span class="tok-string">{}</span>'.format(html.escape(text)))
111
+ else:
112
+ rendered.append(highlight_text_segment(text))
113
+ if comment:
114
+ rendered.append('<span class="tok-comment">{}</span>'.format(html.escape(comment)))
115
+ return "".join(rendered)
116
+
117
+
118
+ def build_code_html(code: str) -> str:
119
+ lines = code.splitlines()
120
+ rendered = ['<div id="rosa-code" class="rosa-code">']
121
+ marker_t = "__TOK_T__"
122
+ marker_k = "__TOK_K__"
123
+ marker_v = "__TOK_V__"
124
+ for index, line in enumerate(lines, start=1):
125
+ line_with_markers = line
126
+ if index == LINE_TRY:
127
+ line_with_markers = line_with_markers.replace("kkk[j:j+w]", marker_k, 1)
128
+ line_with_markers = line_with_markers.replace("t", marker_t, 1)
129
+ if index == LINE_ASSIGN:
130
+ line_with_markers = line_with_markers.replace("vvv[j+w]", marker_v, 1)
131
+ highlighted = highlight_python_line(line_with_markers)
132
+ highlighted = highlighted.replace(
133
+ marker_t, '<span class="code-token" data-token="t">t</span>'
134
+ )
135
+ highlighted = highlighted.replace(
136
+ marker_k, '<span class="code-token" data-token="k">kkk[j:j+w]</span>'
137
+ )
138
+ highlighted = highlighted.replace(
139
+ marker_v, '<span class="code-token" data-token="v">vvv[j+w]</span>'
140
+ )
141
+ rendered.append(
142
+ '<div class="code-line" data-line="{line}">'
143
+ '<span class="line-no">{line}</span>'
144
+ '<span class="line-text">{text}</span>'
145
+ "</div>".format(line=index, text=highlighted)
146
+ )
147
+ rendered.append("</div>")
148
+ return "\n".join(rendered)
149
+
150
+
151
+ def find_line_number(lines: list[str], needle: str, fallback: int = 1) -> int:
152
+ for index, line in enumerate(lines, start=1):
153
+ if needle in line:
154
+ return index
155
+ return fallback
156
+
157
+
158
+ ROSA_LINES = ROSA_CODE.splitlines()
159
+ def find_line_after(lines: list[str], start_line: int, needle: str, fallback: int) -> int:
160
+ start_index = min(max(start_line, 0), len(lines))
161
+ for index in range(start_index, len(lines)):
162
+ if needle in lines[index]:
163
+ return index + 1
164
+ return fallback
165
+
166
+
167
+ LINE_FOR_I = find_line_number(ROSA_LINES, "for i in range(n):")
168
+ LINE_FOR_W = find_line_number(ROSA_LINES, "for w in range(i+1,0,-1):")
169
+ LINE_T_ASSIGN = find_line_number(ROSA_LINES, "t=qqq[i+1-w:i+1]")
170
+ LINE_FOR_J = find_line_number(ROSA_LINES, "for j in range(i-w,-1,-1):")
171
+ LINE_TRY = find_line_number(ROSA_LINES, "if kkk[j:j+w]==t:")
172
+ LINE_ASSIGN = find_line_number(ROSA_LINES, "out[i]=vvv[j+w]")
173
+ LINE_BREAK_INNER = find_line_after(ROSA_LINES, LINE_ASSIGN, "break", LINE_ASSIGN)
174
+ LINE_IF_OUT = find_line_number(ROSA_LINES, "if out[i]!=-1:")
175
+ LINE_BREAK_OUTER = find_line_after(ROSA_LINES, LINE_IF_OUT, "break", LINE_IF_OUT)
176
+ LINE_RETURN = find_line_number(ROSA_LINES, "return out")
177
+ CODE_HTML = build_code_html(ROSA_CODE)
178
+
179
+
180
+ def parse_bits(text: str, name: str) -> list[int]:
181
+ cleaned = re.sub(r"[,\s]+", "", (text or "").strip())
182
+ if not cleaned:
183
+ raise gr.Error(f"{name} cannot be empty")
184
+ if re.search(r"[^01]", cleaned):
185
+ raise gr.Error(f"{name} must contain only 0/1 (spaces or commas allowed)")
186
+ return [int(c) for c in cleaned]
187
+
188
+
189
+ def rosa_steps(q: list[int], k: list[int], v: list[int]) -> tuple[list[dict], list[int]]:
190
+ n = len(q)
191
+ out = [-1] * n
192
+ steps: list[dict] = []
193
+ for i in range(n):
194
+ steps.append({"phase": "loop_i", "i": i, "line": LINE_FOR_I})
195
+ matched = False
196
+ match_j = -1
197
+ match_w = -1
198
+ for w in range(i + 1, 0, -1):
199
+ steps.append({"phase": "loop_w", "i": i, "w": w, "line": LINE_FOR_W})
200
+ t = q[i + 1 - w : i + 1]
201
+ t_str = "".join(str(x) for x in t)
202
+ steps.append({"phase": "assign_t", "i": i, "w": w, "t": t_str, "line": LINE_T_ASSIGN})
203
+ for j in range(i - w, -1, -1):
204
+ steps.append({"phase": "loop_j", "i": i, "w": w, "j": j, "line": LINE_FOR_J})
205
+ is_match = k[j : j + w] == t
206
+ steps.append(
207
+ {
208
+ "phase": "try",
209
+ "i": i,
210
+ "w": w,
211
+ "j": j,
212
+ "t": t_str,
213
+ "matched": is_match,
214
+ "line": LINE_TRY,
215
+ }
216
+ )
217
+ if is_match:
218
+ value = v[j + w]
219
+ out[i] = value
220
+ match_j = j
221
+ match_w = w
222
+ steps.append(
223
+ {
224
+ "phase": "assign",
225
+ "i": i,
226
+ "w": w,
227
+ "j": j,
228
+ "t": t_str,
229
+ "v_index": j + w,
230
+ "value": value,
231
+ "line": LINE_ASSIGN,
232
+ }
233
+ )
234
+ steps.append(
235
+ {
236
+ "phase": "break_inner",
237
+ "i": i,
238
+ "w": w,
239
+ "j": j,
240
+ "line": LINE_BREAK_INNER,
241
+ }
242
+ )
243
+ matched = True
244
+ break
245
+ if matched:
246
+ steps.append(
247
+ {
248
+ "phase": "check",
249
+ "i": i,
250
+ "w": match_w,
251
+ "j": match_j,
252
+ "matched": True,
253
+ "line": LINE_IF_OUT,
254
+ }
255
+ )
256
+ if i == n - 1:
257
+ steps.append(
258
+ {
259
+ "phase": "break_outer",
260
+ "i": i,
261
+ "w": match_w,
262
+ "j": match_j,
263
+ "line": LINE_BREAK_OUTER,
264
+ }
265
+ )
266
+ break
267
+ if not matched:
268
+ out[i] = -1
269
+ steps.append(
270
+ {
271
+ "phase": "output",
272
+ "i": i,
273
+ "value": out[i],
274
+ "matched": matched,
275
+ }
276
+ )
277
+ if i == n - 1:
278
+ steps.append({"phase": "return", "line": LINE_RETURN})
279
+ return steps, out
280
+
281
+
282
+ def format_out(out: list[int]) -> str:
283
+ if any(value < 0 or value > 1 for value in out):
284
+ return " ".join(str(value) for value in out)
285
+ return "".join(str(value) for value in out)
286
+
287
+
288
+ def build_payload(q: list[int], k: list[int], v: list[int]) -> tuple[str, str]:
289
+ steps, out = rosa_steps(q, k, v)
290
+ payload = {
291
+ "q": q,
292
+ "k": k,
293
+ "v": v,
294
+ "steps": steps,
295
+ "out": out,
296
+ "run_id": time.time_ns(),
297
+ }
298
+ return json.dumps(payload, ensure_ascii=False), format_out(out)
299
+
300
+
301
+ def on_demo(q_text: str, k_text: str, v_text: str) -> tuple[str, str]:
302
+ q = parse_bits(q_text, "q")
303
+ k = parse_bits(k_text, "k")
304
+ v = parse_bits(v_text, "v")
305
+ if not (len(q) == len(k) == len(v)):
306
+ raise gr.Error("q, k, v must have the same length")
307
+ if len(q) > MAX_DEMO_LEN:
308
+ raise gr.Error(f"For smooth playback, length should be <= {MAX_DEMO_LEN}")
309
+ return build_payload(q, k, v)
310
+
311
+
312
+ def on_random(length: int) -> tuple[str, str, str]:
313
+ length = max(1, int(length))
314
+ q = [random.randint(0, 1) for _ in range(length)]
315
+ k = [random.randint(0, 1) for _ in range(length)]
316
+ v = [random.randint(0, 1) for _ in range(length)]
317
+ return "".join(str(x) for x in q), "".join(str(x) for x in k), "".join(str(x) for x in v)
318
+
319
+
320
+ CSS = """
321
+ .page-header {
322
+ text-align: center;
323
+ margin-bottom: 18px;
324
+ color: #0f172a;
325
+ }
326
+ .page-title {
327
+ font-size: 30px;
328
+ font-weight: 700;
329
+ letter-spacing: 0.4px;
330
+ margin-bottom: 6px;
331
+ }
332
+ .page-subtitle {
333
+ font-size: 14px;
334
+ color: #475569;
335
+ }
336
+ .rosa-shell {
337
+ display: flex;
338
+ gap: 24px;
339
+ align-items: flex-start;
340
+ justify-content: center;
341
+ flex-wrap: wrap;
342
+ position: relative;
343
+ }
344
+ .rosa-pane {
345
+ flex: 3 1 520px;
346
+ min-width: 320px;
347
+ }
348
+ .rosa-code-pane {
349
+ flex: 2 1 320px;
350
+ min-width: 300px;
351
+ }
352
+ #rosa-vis {
353
+ --rosa-blue: #3b82f6;
354
+ --rosa-sky: #38bdf8;
355
+ --rosa-violet: #a855f7;
356
+ --rosa-amber: #f59e0b;
357
+ --rosa-cyan: #06b6d4;
358
+ --rosa-green: #22c55e;
359
+ --rosa-green-soft: rgba(34, 197, 94, 0.16);
360
+ --rosa-red: #ef4444;
361
+ --rosa-red-soft: rgba(239, 68, 68, 0.14);
362
+ --rosa-blue-soft: rgba(59, 130, 246, 0.08);
363
+ --rosa-violet-soft: rgba(168, 85, 247, 0.08);
364
+ }
365
+ #rosa-vis .rosa-card {
366
+ background: #ffffff;
367
+ border-radius: 18px;
368
+ padding: 24px;
369
+ color: #0f172a;
370
+ box-shadow: none;
371
+ border: 1px solid #e2e8f0;
372
+ text-align: center;
373
+ position: relative;
374
+ }
375
+ #rosa-vis .rosa-rows {
376
+ display: flex;
377
+ flex-direction: column;
378
+ gap: 20px;
379
+ align-items: center;
380
+ }
381
+ #rosa-vis .rosa-row {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 14px;
385
+ flex-wrap: wrap;
386
+ justify-content: center;
387
+ width: 100%;
388
+ }
389
+ #rosa-vis .rosa-row.k-row {
390
+ margin-bottom: 0;
391
+ }
392
+ #rosa-vis .row-label {
393
+ min-width: 36px;
394
+ font-size: 12px;
395
+ color: #475569;
396
+ text-transform: uppercase;
397
+ letter-spacing: 0.2px;
398
+ text-align: center;
399
+ }
400
+ #rosa-vis .row-cells {
401
+ display: flex;
402
+ flex-wrap: wrap;
403
+ gap: 6px;
404
+ justify-content: center;
405
+ max-width: 100%;
406
+ min-width: 0;
407
+ }
408
+ #rosa-vis .cell {
409
+ width: 30px;
410
+ height: 30px;
411
+ border-radius: 8px;
412
+ background: #f1f5f9;
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: center;
416
+ font-weight: 600;
417
+ transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
418
+ color: #0f172a;
419
+ box-shadow: none;
420
+ }
421
+ #rosa-vis .cell.active {
422
+ background: var(--rosa-blue);
423
+ color: #f8fafc;
424
+ box-shadow: none;
425
+ }
426
+ #rosa-vis .cell.suffix {
427
+ background: var(--rosa-sky);
428
+ color: #f8fafc;
429
+ }
430
+ #rosa-vis .cell.k-window {
431
+ background: var(--rosa-violet);
432
+ color: #f8fafc;
433
+ }
434
+ #rosa-vis .cell.v-pick {
435
+ background: var(--rosa-amber);
436
+ color: #1f2937;
437
+ }
438
+ #rosa-vis .cell.out,
439
+ #rosa-vis .cell.out-fixed {
440
+ background: var(--rosa-amber);
441
+ color: #1f2937;
442
+ }
443
+ #rosa-vis .cell.filled {
444
+ box-shadow: none;
445
+ }
446
+ #rosa-vis .rosa-overlay {
447
+ position: absolute;
448
+ inset: 0;
449
+ pointer-events: none;
450
+ z-index: 5;
451
+ }
452
+ #rosa-vis .overlay-svg {
453
+ position: absolute;
454
+ inset: 0;
455
+ width: 100%;
456
+ height: 100%;
457
+ pointer-events: none;
458
+ }
459
+ #rosa-vis .overlay-box-layer {
460
+ position: absolute;
461
+ inset: 0;
462
+ pointer-events: none;
463
+ }
464
+ #rosa-vis .overlay-box {
465
+ position: absolute;
466
+ border: 2px solid rgba(59, 130, 246, 0.7);
467
+ border-radius: 10px;
468
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
469
+ box-sizing: border-box;
470
+ background: transparent;
471
+ transition: border-color 0.18s ease, background 0.18s ease;
472
+ }
473
+ #rosa-vis .overlay-hover-layer {
474
+ position: absolute;
475
+ inset: 0;
476
+ pointer-events: auto;
477
+ z-index: 6;
478
+ }
479
+ #rosa-vis .overlay-hover-box {
480
+ position: absolute;
481
+ background: transparent;
482
+ pointer-events: auto;
483
+ cursor: pointer;
484
+ }
485
+ #rosa-vis .overlay-box[data-label="t"] {
486
+ --overlay-label: "t";
487
+ }
488
+ #rosa-vis .overlay-box[data-label="kkk[j:j+w]"] {
489
+ --overlay-label: "kkk[j:j+w]";
490
+ }
491
+ #rosa-vis .overlay-box.t-box {
492
+ border-color: var(--rosa-blue);
493
+ background: var(--rosa-blue-soft);
494
+ }
495
+ #rosa-vis .overlay-box.k-box {
496
+ border-color: var(--rosa-violet);
497
+ background: var(--rosa-violet-soft);
498
+ }
499
+ #rosa-vis .overlay-box.try-match {
500
+ border-color: var(--rosa-green);
501
+ background: var(--rosa-green-soft);
502
+ }
503
+ #rosa-vis .overlay-box.try-miss {
504
+ border-color: var(--rosa-red);
505
+ background: var(--rosa-red-soft);
506
+ }
507
+ #rosa-vis .overlay-ray {
508
+ position: absolute;
509
+ inset: 0;
510
+ width: 100%;
511
+ height: 100%;
512
+ pointer-events: none;
513
+ }
514
+ #rosa-vis .overlay-ray-line {
515
+ stroke: var(--rosa-cyan);
516
+ stroke-width: 2;
517
+ fill: none;
518
+ stroke-linecap: round;
519
+ }
520
+ #rosa-vis .overlay-label {
521
+ position: absolute;
522
+ top: -12px;
523
+ right: 0;
524
+ font-size: 11px;
525
+ padding: 0;
526
+ background: transparent;
527
+ color: #0f172a;
528
+ letter-spacing: 0.2px;
529
+ white-space: nowrap;
530
+ display: none;
531
+ }
532
+ #rosa-vis .overlay-label.t-label {
533
+ color: #1d4ed8;
534
+ }
535
+ #rosa-vis .overlay-label.k-label {
536
+ color: #6d28d9;
537
+ }
538
+ .v-float {
539
+ position: fixed;
540
+ z-index: 9999;
541
+ border-radius: 8px;
542
+ background: #f59e0b;
543
+ color: #1f2937;
544
+ display: flex;
545
+ align-items: center;
546
+ justify-content: center;
547
+ font-weight: 600;
548
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.2);
549
+ pointer-events: none;
550
+ transition: transform 0.35s ease, opacity 0.35s ease;
551
+ opacity: 1;
552
+ }
553
+ #rosa-vis .rosa-legend {
554
+ display: flex;
555
+ flex-wrap: wrap;
556
+ gap: 12px;
557
+ margin-bottom: 12px;
558
+ font-size: 12px;
559
+ color: #475569;
560
+ justify-content: center;
561
+ }
562
+ #rosa-vis .legend-item {
563
+ display: inline-flex;
564
+ align-items: center;
565
+ gap: 6px;
566
+ }
567
+ #rosa-vis .legend-dot {
568
+ width: 12px;
569
+ height: 12px;
570
+ border-radius: 4px;
571
+ }
572
+ #rosa-vis .legend-suffix {
573
+ background: var(--rosa-sky);
574
+ }
575
+ #rosa-vis .legend-window {
576
+ background: var(--rosa-violet);
577
+ }
578
+ #rosa-vis .legend-match {
579
+ background: var(--rosa-green);
580
+ }
581
+ #rosa-vis .legend-v {
582
+ background: var(--rosa-amber);
583
+ }
584
+ #rosa-vis .legend-out {
585
+ background: var(--rosa-amber);
586
+ }
587
+ #rosa-vis .index-row {
588
+ padding-bottom: 8px;
589
+ border-bottom: 1px dashed #e2e8f0;
590
+ }
591
+ #rosa-vis .index-cells {
592
+ gap: 6px;
593
+ }
594
+ #rosa-vis .index-cell {
595
+ width: 30px;
596
+ text-align: center;
597
+ font-size: 11px;
598
+ color: #64748b;
599
+ font-variant-numeric: tabular-nums;
600
+ }
601
+ #rosa-code {
602
+ background: #ffffff;
603
+ border: 1px solid #e2e8f0;
604
+ border-radius: 12px;
605
+ padding: 12px;
606
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
607
+ font-size: 12px;
608
+ color: #0f172a;
609
+ max-height: 520px;
610
+ overflow: auto;
611
+ }
612
+ #rosa-code .code-line {
613
+ display: flex;
614
+ gap: 10px;
615
+ padding: 2px 6px;
616
+ border-radius: 6px;
617
+ }
618
+ #rosa-code .line-no {
619
+ width: 28px;
620
+ text-align: right;
621
+ color: #94a3b8;
622
+ user-select: none;
623
+ }
624
+ #rosa-code .line-text {
625
+ white-space: pre;
626
+ flex: 1;
627
+ }
628
+ #rosa-code .code-line.active {
629
+ background: #dbeafe;
630
+ color: #1d4ed8;
631
+ }
632
+ #rosa-code .code-line.active .line-no {
633
+ color: #1d4ed8;
634
+ }
635
+ #rosa-code .tok-keyword {
636
+ color: #7c3aed;
637
+ font-weight: 600;
638
+ }
639
+ #rosa-code .tok-builtin {
640
+ color: #0ea5e9;
641
+ }
642
+ #rosa-code .tok-number {
643
+ color: #f97316;
644
+ }
645
+ #rosa-code .tok-string {
646
+ color: #10b981;
647
+ }
648
+ #rosa-code .tok-comment {
649
+ color: #64748b;
650
+ font-style: italic;
651
+ }
652
+ #rosa-code .code-token {
653
+ border-bottom: 1px dashed #94a3b8;
654
+ padding: 0 1px;
655
+ }
656
+ #rosa-code .code-token.active {
657
+ background: #fef3c7;
658
+ border-radius: 4px;
659
+ border-bottom-color: #f59e0b;
660
+ }
661
+ """
662
+
663
+ JS = """
664
+ () => {
665
+ if (window.__rosaDemoInit) return;
666
+ window.__rosaDemoInit = true;
667
+
668
+ const rootId = "rosa-vis";
669
+ const stepsId = "steps_json";
670
+ const speedId = "speed_slider";
671
+ const baseDelay = 650;
672
+ const transferPauseMs = 200;
673
+ const transferMs = 750;
674
+ const debugAnim = true;
675
+ let runToken = 0;
676
+ let linkLayer = null;
677
+ let activeTokenEl = null;
678
+
679
+ function getAppRoot() {
680
+ const app = document.querySelector("gradio-app");
681
+ if (app && app.shadowRoot) return app.shadowRoot;
682
+ return document;
683
+ }
684
+
685
+ function findInputById(id, selector) {
686
+ const root = getAppRoot();
687
+ const direct = root.querySelector(`#${id}`) || document.getElementById(id);
688
+ if (!direct) return null;
689
+ if (direct.matches && direct.matches(selector)) return direct;
690
+ const nested = direct.querySelector(selector);
691
+ if (nested) return nested;
692
+ if (direct.shadowRoot) {
693
+ const shadowEl = direct.shadowRoot.querySelector(selector);
694
+ if (shadowEl) return shadowEl;
695
+ }
696
+ return null;
697
+ }
698
+
699
+ function getStepsBox() {
700
+ return findInputById(stepsId, "textarea, input");
701
+ }
702
+
703
+ function getSpeed() {
704
+ const slider = findInputById(speedId, 'input[type="range"], input');
705
+ const value = slider ? parseFloat(slider.value) : 1;
706
+ if (!Number.isFinite(value) || value <= 0) return 1;
707
+ return value;
708
+ }
709
+
710
+ function sleep(ms) {
711
+ return new Promise((resolve) => setTimeout(resolve, ms));
712
+ }
713
+
714
+ function getOverlayHost() {
715
+ const root = getAppRoot();
716
+ if (root === document) return document.body;
717
+ return root;
718
+ }
719
+
720
+ function animateTransfer(fromEl, toEl, value, onFinish) {
721
+ if (!fromEl || !toEl) {
722
+ if (debugAnim) {
723
+ console.warn("[ROSA] animateTransfer missing element", {
724
+ hasFrom: !!fromEl,
725
+ hasTo: !!toEl,
726
+ });
727
+ }
728
+ return;
729
+ }
730
+ const fromRect = fromEl.getBoundingClientRect();
731
+ const toRect = toEl.getBoundingClientRect();
732
+ if (!fromRect || !toRect) {
733
+ if (debugAnim) {
734
+ console.warn("[ROSA] animateTransfer missing rect", {
735
+ fromRect,
736
+ toRect,
737
+ });
738
+ }
739
+ return;
740
+ }
741
+ const bubble = document.createElement("div");
742
+ bubble.className = "v-float";
743
+ bubble.textContent = value != null ? String(value) : fromEl.textContent;
744
+ bubble.style.position = "fixed";
745
+ bubble.style.zIndex = "9999";
746
+ bubble.style.borderRadius = "8px";
747
+ bubble.style.background = "#f59e0b";
748
+ bubble.style.color = "#1f2937";
749
+ bubble.style.display = "flex";
750
+ bubble.style.alignItems = "center";
751
+ bubble.style.justifyContent = "center";
752
+ bubble.style.fontWeight = "600";
753
+ bubble.style.boxShadow = "0 10px 24px rgba(15, 23, 42, 0.2)";
754
+ bubble.style.pointerEvents = "none";
755
+ bubble.style.transform = "translate(0px, 0px) scale(1)";
756
+ bubble.style.left = `${fromRect.left}px`;
757
+ bubble.style.top = `${fromRect.top}px`;
758
+ bubble.style.width = `${fromRect.width}px`;
759
+ bubble.style.height = `${fromRect.height}px`;
760
+ const host = getOverlayHost();
761
+ if (debugAnim) {
762
+ console.log("[ROSA] animateTransfer", {
763
+ from: {
764
+ left: fromRect.left,
765
+ top: fromRect.top,
766
+ width: fromRect.width,
767
+ height: fromRect.height,
768
+ },
769
+ to: {
770
+ left: toRect.left,
771
+ top: toRect.top,
772
+ width: toRect.width,
773
+ height: toRect.height,
774
+ },
775
+ host: host === document.body ? "document.body" : "shadowRoot",
776
+ value,
777
+ });
778
+ if (fromRect.width === 0 || fromRect.height === 0 || toRect.width === 0 || toRect.height === 0) {
779
+ console.warn("[ROSA] animateTransfer zero rect", {
780
+ fromRect,
781
+ toRect,
782
+ });
783
+ }
784
+ }
785
+ host.appendChild(bubble);
786
+ const dx = toRect.left - fromRect.left;
787
+ const dy = toRect.top - fromRect.top;
788
+ const toTransform = `translate(${dx}px, ${dy}px) scale(0.95)`;
789
+ const duration = transferMs;
790
+ const startAnimation = () => {
791
+ if (bubble.animate) {
792
+ const anim = bubble.animate(
793
+ [
794
+ { transform: "translate(0px, 0px) scale(1)" },
795
+ { transform: toTransform },
796
+ ],
797
+ { duration, easing: "ease-out", fill: "forwards" }
798
+ );
799
+ anim.addEventListener("finish", () => {
800
+ if (onFinish) onFinish();
801
+ bubble.remove();
802
+ });
803
+ return;
804
+ }
805
+ bubble.style.transition = `transform ${duration}ms ease`;
806
+ bubble.style.willChange = "transform";
807
+ requestAnimationFrame(() => {
808
+ bubble.style.transform = toTransform;
809
+ });
810
+ setTimeout(() => {
811
+ if (onFinish) onFinish();
812
+ bubble.remove();
813
+ }, duration + 80);
814
+ };
815
+ bubble.getBoundingClientRect();
816
+ requestAnimationFrame(() => {
817
+ requestAnimationFrame(startAnimation);
818
+ });
819
+ }
820
+
821
+ function getCodeLines() {
822
+ const root = getAppRoot();
823
+ const container = root.querySelector("#rosa-code") || document.getElementById("rosa-code");
824
+ if (!container) return {};
825
+ const lines = {};
826
+ container.querySelectorAll(".code-line").forEach((line) => {
827
+ const index = parseInt(line.dataset.line, 10);
828
+ if (Number.isFinite(index)) {
829
+ lines[index] = line;
830
+ }
831
+ });
832
+ return lines;
833
+ }
834
+
835
+ function resetCodeHighlight(codeLines) {
836
+ Object.values(codeLines).forEach((line) => {
837
+ line.classList.remove("active");
838
+ });
839
+ }
840
+
841
+ function setActiveCodeLine(state, line) {
842
+ if (!state.codeLines) return;
843
+ if (Number.isInteger(state.activeLine) && state.codeLines[state.activeLine]) {
844
+ state.codeLines[state.activeLine].classList.remove("active");
845
+ }
846
+ if (Number.isInteger(line) && state.codeLines[line]) {
847
+ state.codeLines[line].classList.add("active");
848
+ state.activeLine = line;
849
+ }
850
+ }
851
+
852
+ function buildRow(label, values, className) {
853
+ const row = document.createElement("div");
854
+ row.className = `rosa-row ${className || ""}`;
855
+ const labelEl = document.createElement("div");
856
+ labelEl.className = "row-label";
857
+ labelEl.textContent = label;
858
+ const cellsEl = document.createElement("div");
859
+ cellsEl.className = "row-cells";
860
+ const cells = values.map((val, idx) => {
861
+ const cell = document.createElement("div");
862
+ cell.className = "cell";
863
+ cell.textContent = String(val);
864
+ cell.dataset.idx = String(idx);
865
+ cellsEl.appendChild(cell);
866
+ return cell;
867
+ });
868
+ row.appendChild(labelEl);
869
+ row.appendChild(cellsEl);
870
+ return { row, cells };
871
+ }
872
+
873
+ function getCodeToken(rootEl, tokenKey) {
874
+ const root = rootEl || getAppRoot();
875
+ return root.querySelector(`[data-token="${tokenKey}"]`);
876
+ }
877
+
878
+ function ensureLinkLayer(shellEl) {
879
+ const host = shellEl || getAppRoot().querySelector("#rosa-shell") || document.body;
880
+ if (linkLayer && linkLayer.host === host) return linkLayer;
881
+ if (linkLayer && linkLayer.svg) {
882
+ linkLayer.svg.remove();
883
+ }
884
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
885
+ svg.setAttribute("width", "100%");
886
+ svg.setAttribute("height", "100%");
887
+ svg.style.position = "absolute";
888
+ svg.style.left = "0";
889
+ svg.style.top = "0";
890
+ svg.style.pointerEvents = "none";
891
+ svg.style.zIndex = "10000";
892
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
893
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
894
+ marker.setAttribute("id", "rosa-link-arrow");
895
+ marker.setAttribute("markerWidth", "8");
896
+ marker.setAttribute("markerHeight", "8");
897
+ marker.setAttribute("refX", "6");
898
+ marker.setAttribute("refY", "3");
899
+ marker.setAttribute("orient", "auto");
900
+ const markerPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
901
+ markerPath.setAttribute("d", "M0,0 L6,3 L0,6 Z");
902
+ markerPath.setAttribute("fill", "#94a3b8");
903
+ marker.appendChild(markerPath);
904
+ defs.appendChild(marker);
905
+ svg.appendChild(defs);
906
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
907
+ path.setAttribute("stroke", "#94a3b8");
908
+ path.setAttribute("stroke-width", "1.5");
909
+ path.setAttribute("fill", "none");
910
+ path.setAttribute("marker-end", "url(#rosa-link-arrow)");
911
+ path.style.display = "none";
912
+ svg.appendChild(path);
913
+ host.appendChild(svg);
914
+ linkLayer = { svg, path, host };
915
+ return linkLayer;
916
+ }
917
+
918
+ function showLink(state, fromEl, tokenKey) {
919
+ if (!state || !state.shellEl) return;
920
+ const tokenEl = getCodeToken(state.shellEl, tokenKey);
921
+ if (!fromEl || !tokenEl) return;
922
+ const shellRect = state.shellEl.getBoundingClientRect();
923
+ const fromRect = fromEl.getBoundingClientRect();
924
+ const toRect = tokenEl.getBoundingClientRect();
925
+ const link = ensureLinkLayer(state.shellEl);
926
+ let startX = fromRect.right - shellRect.left;
927
+ let endX = toRect.left - shellRect.left;
928
+ if (toRect.left < fromRect.left) {
929
+ startX = fromRect.left - shellRect.left;
930
+ endX = toRect.right - shellRect.left;
931
+ }
932
+ const startY = fromRect.top + fromRect.height / 2 - shellRect.top;
933
+ const endY = toRect.top + toRect.height / 2 - shellRect.top;
934
+ link.path.setAttribute("d", `M ${startX} ${startY} L ${endX} ${endY}`);
935
+ link.path.style.display = "block";
936
+ if (activeTokenEl && activeTokenEl !== tokenEl) {
937
+ activeTokenEl.classList.remove("active");
938
+ }
939
+ tokenEl.classList.add("active");
940
+ activeTokenEl = tokenEl;
941
+ }
942
+
943
+ function hideLink() {
944
+ if (linkLayer) {
945
+ linkLayer.path.style.display = "none";
946
+ }
947
+ if (activeTokenEl) {
948
+ activeTokenEl.classList.remove("active");
949
+ activeTokenEl = null;
950
+ }
951
+ }
952
+
953
+ function getRangeRect(cells, start, end) {
954
+ const rects = [];
955
+ for (let idx = start; idx <= end; idx += 1) {
956
+ const cell = cells[idx];
957
+ if (!cell) continue;
958
+ rects.push(cell.getBoundingClientRect());
959
+ }
960
+ if (!rects.length) return null;
961
+ let left = rects[0].left;
962
+ let right = rects[0].right;
963
+ let top = rects[0].top;
964
+ let bottom = rects[0].bottom;
965
+ rects.forEach((rect) => {
966
+ left = Math.min(left, rect.left);
967
+ right = Math.max(right, rect.right);
968
+ top = Math.min(top, rect.top);
969
+ bottom = Math.max(bottom, rect.bottom);
970
+ });
971
+ return {
972
+ left,
973
+ top,
974
+ right,
975
+ bottom,
976
+ width: right - left,
977
+ height: bottom - top,
978
+ };
979
+ }
980
+
981
+ function toLocalRect(rect, containerRect) {
982
+ return {
983
+ left: rect.left - containerRect.left,
984
+ top: rect.top - containerRect.top,
985
+ width: rect.width,
986
+ height: rect.height,
987
+ right: rect.right - containerRect.left,
988
+ bottom: rect.bottom - containerRect.top,
989
+ };
990
+ }
991
+
992
+ function padRect(rect, pad, bounds) {
993
+ const left = Math.max(0, rect.left - pad);
994
+ const top = Math.max(0, rect.top - pad);
995
+ const right = Math.min(bounds.width, rect.left + rect.width + pad);
996
+ const bottom = Math.min(bounds.height, rect.top + rect.height + pad);
997
+ return {
998
+ left,
999
+ top,
1000
+ width: Math.max(0, right - left),
1001
+ height: Math.max(0, bottom - top),
1002
+ right,
1003
+ bottom,
1004
+ };
1005
+ }
1006
+
1007
+ function createOverlayBox(layer, rect, label, className, labelClass) {
1008
+ const box = document.createElement("div");
1009
+ box.className = `overlay-box ${className || ""}`;
1010
+ box.style.left = `${rect.left}px`;
1011
+ box.style.top = `${rect.top}px`;
1012
+ box.style.width = `${rect.width}px`;
1013
+ box.style.height = `${rect.height}px`;
1014
+ const labelEl = document.createElement("div");
1015
+ labelEl.className = `overlay-label ${labelClass || ""}`;
1016
+ labelEl.textContent = label;
1017
+ box.appendChild(labelEl);
1018
+ box.dataset.label = label;
1019
+ layer.appendChild(box);
1020
+ }
1021
+
1022
+ function clearHoverBoxes(state) {
1023
+ if (state.overlayHover) {
1024
+ state.overlayHover.innerHTML = "";
1025
+ }
1026
+ state.hoverBoxes = [];
1027
+ hideLink();
1028
+ }
1029
+
1030
+ function clearOverlay(state) {
1031
+ if (state.overlayBoxes) {
1032
+ state.overlayBoxes.innerHTML = "";
1033
+ }
1034
+ if (state.rayLine) {
1035
+ state.rayLine.style.display = "none";
1036
+ }
1037
+ clearHoverBoxes(state);
1038
+ }
1039
+
1040
+ function updateOverlay(state, step) {
1041
+ const hasI = Number.isInteger(step.i);
1042
+ const hasW = Number.isInteger(step.w);
1043
+ const hasJ = Number.isInteger(step.j);
1044
+ if (step.phase === "loop_i") {
1045
+ state.lastOverlay = null;
1046
+ }
1047
+ const showOverlay = [
1048
+ "loop_w",
1049
+ "assign_t",
1050
+ "loop_j",
1051
+ "try",
1052
+ "assign",
1053
+ "break_inner",
1054
+ "check",
1055
+ "break_outer",
1056
+ ].includes(step.phase);
1057
+ let overlay = null;
1058
+ if (hasI && hasW) {
1059
+ const sameWindow =
1060
+ state.lastOverlay &&
1061
+ state.lastOverlay.i === step.i &&
1062
+ state.lastOverlay.w === step.w;
1063
+ const jValue = hasJ ? step.j : (sameWindow ? state.lastOverlay.j : null);
1064
+ state.lastOverlay = { i: step.i, w: step.w, j: jValue };
1065
+ overlay = state.lastOverlay;
1066
+ } else if (state.lastOverlay) {
1067
+ overlay = state.lastOverlay;
1068
+ }
1069
+ clearOverlay(state);
1070
+ if (!showOverlay) {
1071
+ return;
1072
+ }
1073
+ if (!overlay) return;
1074
+ const tStart = overlay.i - overlay.w + 1;
1075
+ const tEnd = overlay.i;
1076
+ const kStart = overlay.j;
1077
+ const kEnd = Number.isInteger(overlay.j) ? overlay.j + overlay.w - 1 : null;
1078
+ const tRect = getRangeRect(state.qCells, tStart, tEnd);
1079
+ const kRect =
1080
+ Number.isInteger(kStart) && Number.isInteger(kEnd)
1081
+ ? getRangeRect(state.kCells, kStart, kEnd)
1082
+ : null;
1083
+ const vIndex = Number.isInteger(kStart) ? kStart + overlay.w : null;
1084
+ const vCell = Number.isInteger(vIndex) ? state.vCells[vIndex] : null;
1085
+ const vRect = vCell ? vCell.getBoundingClientRect() : null;
1086
+ const cardRect = state.cardEl.getBoundingClientRect();
1087
+ const cardStyle = window.getComputedStyle(state.cardEl);
1088
+ const borderLeft = parseFloat(cardStyle.borderLeftWidth) || 0;
1089
+ const borderTop = parseFloat(cardStyle.borderTopWidth) || 0;
1090
+ const borderRight = parseFloat(cardStyle.borderRightWidth) || 0;
1091
+ const borderBottom = parseFloat(cardStyle.borderBottomWidth) || 0;
1092
+ const originLeft = cardRect.left + borderLeft;
1093
+ const originTop = cardRect.top + borderTop;
1094
+ const boxPad = 4;
1095
+ const bounds = {
1096
+ width: cardRect.width - borderLeft - borderRight,
1097
+ height: cardRect.height - borderTop - borderBottom,
1098
+ };
1099
+ const toOverlayRect = (rect) => ({
1100
+ left: rect.left - originLeft,
1101
+ top: rect.top - originTop,
1102
+ width: rect.width,
1103
+ height: rect.height,
1104
+ right: rect.right - originLeft,
1105
+ bottom: rect.bottom - originTop,
1106
+ });
1107
+ const isTry = step.phase === "try";
1108
+ const tryClass = isTry ? (step.matched ? "try-match" : "try-miss") : "";
1109
+
1110
+ if (tRect) {
1111
+ const local = padRect(toOverlayRect(tRect), boxPad, bounds);
1112
+ createOverlayBox(state.overlayBoxes, local, "t", `t-box ${tryClass}`, "t-label");
1113
+ const hoverBox = document.createElement("div");
1114
+ hoverBox.className = "overlay-hover-box";
1115
+ hoverBox.style.left = `${local.left}px`;
1116
+ hoverBox.style.top = `${local.top}px`;
1117
+ hoverBox.style.width = `${local.width}px`;
1118
+ hoverBox.style.height = `${local.height}px`;
1119
+ hoverBox.addEventListener("mouseenter", () => showLink(state, hoverBox, "t"));
1120
+ hoverBox.addEventListener("mouseleave", hideLink);
1121
+ state.overlayHover.appendChild(hoverBox);
1122
+ state.hoverBoxes.push(hoverBox);
1123
+ }
1124
+
1125
+ if (kRect) {
1126
+ const local = padRect(toOverlayRect(kRect), boxPad, bounds);
1127
+ createOverlayBox(state.overlayBoxes, local, "kkk[j:j+w]", `k-box ${tryClass}`, "k-label");
1128
+ const hoverBox = document.createElement("div");
1129
+ hoverBox.className = "overlay-hover-box";
1130
+ hoverBox.style.left = `${local.left}px`;
1131
+ hoverBox.style.top = `${local.top}px`;
1132
+ hoverBox.style.width = `${local.width}px`;
1133
+ hoverBox.style.height = `${local.height}px`;
1134
+ hoverBox.addEventListener("mouseenter", () => showLink(state, hoverBox, "k"));
1135
+ hoverBox.addEventListener("mouseleave", hideLink);
1136
+ state.overlayHover.appendChild(hoverBox);
1137
+ state.hoverBoxes.push(hoverBox);
1138
+ }
1139
+
1140
+ if (step.phase === "assign" && kRect && vRect && state.rayLine) {
1141
+ const kLocal = padRect(toOverlayRect(kRect), boxPad, bounds);
1142
+ const vLocal = toOverlayRect(vRect);
1143
+ const startX = kLocal.right;
1144
+ const startY = kLocal.bottom;
1145
+ const endX = vLocal.left + vLocal.width / 2;
1146
+ const endY = vLocal.top + vLocal.height / 2;
1147
+ state.rayLine.setAttribute("x1", startX);
1148
+ state.rayLine.setAttribute("y1", startY);
1149
+ state.rayLine.setAttribute("x2", endX);
1150
+ state.rayLine.setAttribute("y2", endY);
1151
+ state.rayLine.style.display = "block";
1152
+ }
1153
+ }
1154
+
1155
+ function render(data) {
1156
+ const root = getAppRoot().querySelector(`#${rootId}`);
1157
+ if (!root) return null;
1158
+ root.innerHTML = "";
1159
+
1160
+ const card = document.createElement("div");
1161
+ card.className = "rosa-card";
1162
+
1163
+ const legend = document.createElement("div");
1164
+ legend.className = "rosa-legend";
1165
+ legend.innerHTML = `
1166
+ <span class="legend-item"><span class="legend-dot legend-suffix"></span>Current suffix</span>
1167
+ <span class="legend-item"><span class="legend-dot legend-window"></span>k window</span>
1168
+ <span class="legend-item"><span class="legend-dot legend-match"></span>Match</span>
1169
+ <span class="legend-item"><span class="legend-dot legend-v"></span>Read v</span>
1170
+ <span class="legend-item"><span class="legend-dot legend-out"></span>Output</span>
1171
+ `;
1172
+ card.appendChild(legend);
1173
+
1174
+ const rowsWrap = document.createElement("div");
1175
+ rowsWrap.className = "rosa-rows";
1176
+
1177
+ const indexRow = document.createElement("div");
1178
+ indexRow.className = "rosa-row index-row";
1179
+ const indexLabel = document.createElement("div");
1180
+ indexLabel.className = "row-label";
1181
+ indexLabel.textContent = "#";
1182
+ const indexCells = document.createElement("div");
1183
+ indexCells.className = "row-cells index-cells";
1184
+ data.q.forEach((_, idx) => {
1185
+ const cell = document.createElement("div");
1186
+ cell.className = "index-cell";
1187
+ cell.textContent = String(idx);
1188
+ indexCells.appendChild(cell);
1189
+ });
1190
+ indexRow.appendChild(indexLabel);
1191
+ indexRow.appendChild(indexCells);
1192
+ rowsWrap.appendChild(indexRow);
1193
+
1194
+ const qRow = buildRow("q", data.q, "q-row");
1195
+ const kRow = buildRow("k", data.k, "k-row");
1196
+ const vRow = buildRow("v", data.v, "v-row");
1197
+ const outRow = buildRow("out", data.q.map(() => "."), "out-row");
1198
+
1199
+ rowsWrap.appendChild(qRow.row);
1200
+ rowsWrap.appendChild(kRow.row);
1201
+ rowsWrap.appendChild(vRow.row);
1202
+ rowsWrap.appendChild(outRow.row);
1203
+
1204
+ card.appendChild(rowsWrap);
1205
+ const overlay = document.createElement("div");
1206
+ overlay.className = "rosa-overlay";
1207
+ const overlayRay = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1208
+ overlayRay.classList.add("overlay-ray");
1209
+ const rayDefs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
1210
+ const rayMarker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
1211
+ rayMarker.setAttribute("id", "rosa-ray-head");
1212
+ rayMarker.setAttribute("markerWidth", "8");
1213
+ rayMarker.setAttribute("markerHeight", "8");
1214
+ rayMarker.setAttribute("refX", "6");
1215
+ rayMarker.setAttribute("refY", "3");
1216
+ rayMarker.setAttribute("orient", "auto");
1217
+ const rayMarkerPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
1218
+ rayMarkerPath.setAttribute("d", "M0,0 L6,3 L0,6 Z");
1219
+ rayMarkerPath.setAttribute("fill", "var(--rosa-cyan)");
1220
+ rayMarker.appendChild(rayMarkerPath);
1221
+ rayDefs.appendChild(rayMarker);
1222
+ overlayRay.appendChild(rayDefs);
1223
+ const rayLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
1224
+ rayLine.classList.add("overlay-ray-line");
1225
+ rayLine.setAttribute("marker-end", "url(#rosa-ray-head)");
1226
+ rayLine.style.display = "none";
1227
+ overlayRay.appendChild(rayLine);
1228
+ const overlayBoxes = document.createElement("div");
1229
+ overlayBoxes.className = "overlay-box-layer";
1230
+ overlay.appendChild(overlayBoxes);
1231
+ overlay.appendChild(overlayRay);
1232
+ card.appendChild(overlay);
1233
+ const overlayHover = document.createElement("div");
1234
+ overlayHover.className = "overlay-hover-layer";
1235
+ card.appendChild(overlayHover);
1236
+ root.appendChild(card);
1237
+
1238
+ const codeLines = getCodeLines();
1239
+ resetCodeHighlight(codeLines);
1240
+
1241
+ return {
1242
+ shellEl: root.closest("#rosa-shell") || getAppRoot().querySelector("#rosa-shell"),
1243
+ cardEl: card,
1244
+ qCells: qRow.cells,
1245
+ kCells: kRow.cells,
1246
+ vCells: vRow.cells,
1247
+ outCells: outRow.cells,
1248
+ outFixed: new Set(),
1249
+ outPending: new Set(),
1250
+ overlayBoxes,
1251
+ rayLine,
1252
+ overlayHover,
1253
+ hoverBoxes: [],
1254
+ lastOverlay: null,
1255
+ codeLines,
1256
+ activeLine: null,
1257
+ };
1258
+ }
1259
+
1260
+ function clearHighlights(state) {
1261
+ const holdActive =
1262
+ state.vHold &&
1263
+ Number.isInteger(state.vHold.index) &&
1264
+ performance.now() < state.vHold.until;
1265
+ const holdIndex = holdActive ? state.vHold.index : null;
1266
+ const all = [...state.qCells, ...state.kCells, ...state.vCells, ...state.outCells];
1267
+ all.forEach((cell) => {
1268
+ cell.classList.remove("active", "suffix", "k-window", "v-pick", "out");
1269
+ });
1270
+ if (holdActive && state.vCells[holdIndex]) {
1271
+ state.vCells[holdIndex].classList.add("v-pick");
1272
+ }
1273
+ }
1274
+
1275
+ function applyStep(state, step) {
1276
+ clearHighlights(state);
1277
+ const { qCells, kCells, vCells, outCells } = state;
1278
+ if (Number.isInteger(step.line)) {
1279
+ setActiveCodeLine(state, step.line);
1280
+ }
1281
+
1282
+ const showWindow = [
1283
+ "loop_w",
1284
+ "assign_t",
1285
+ "loop_j",
1286
+ "try",
1287
+ "assign",
1288
+ "break_inner",
1289
+ "check",
1290
+ "break_outer",
1291
+ ].includes(step.phase);
1292
+ if (showWindow && Number.isInteger(step.i) && qCells[step.i]) {
1293
+ qCells[step.i].classList.add("active");
1294
+ }
1295
+ if (showWindow && Number.isInteger(step.w)) {
1296
+ for (let idx = step.i - step.w + 1; idx <= step.i; idx += 1) {
1297
+ if (qCells[idx]) qCells[idx].classList.add("suffix");
1298
+ }
1299
+ }
1300
+ if (showWindow && Number.isInteger(step.j)) {
1301
+ for (let idx = step.j; idx <= step.j + step.w - 1; idx += 1) {
1302
+ if (kCells[idx]) kCells[idx].classList.add("k-window");
1303
+ }
1304
+ }
1305
+ updateOverlay(state, step);
1306
+
1307
+ if (step.phase === "try") {
1308
+ return;
1309
+ }
1310
+
1311
+ if (step.phase === "assign") {
1312
+ if (debugAnim) {
1313
+ console.log("[ROSA] assign step", {
1314
+ i: step.i,
1315
+ j: step.j,
1316
+ w: step.w,
1317
+ v_index: step.v_index,
1318
+ value: step.value,
1319
+ });
1320
+ }
1321
+ if (Number.isInteger(step.v_index) && vCells[step.v_index]) {
1322
+ vCells[step.v_index].classList.add("v-pick");
1323
+ }
1324
+ if (
1325
+ Number.isInteger(step.v_index) &&
1326
+ Number.isInteger(step.i) &&
1327
+ vCells[step.v_index] &&
1328
+ outCells[step.i]
1329
+ ) {
1330
+ const holdToken = state.runToken;
1331
+ if (state.outPending) {
1332
+ state.outPending.add(step.i);
1333
+ }
1334
+ const totalWait = transferPauseMs + transferMs;
1335
+ if (state.outPending) {
1336
+ state.outPending.add(step.i);
1337
+ }
1338
+ const startTransfer = () => {
1339
+ if (state.runToken !== holdToken) return;
1340
+ animateTransfer(vCells[step.v_index], outCells[step.i], step.value, () => {
1341
+ if (state.runToken !== holdToken) return;
1342
+ if (state.outPending) {
1343
+ state.outPending.delete(step.i);
1344
+ }
1345
+ if (state.outFixed) {
1346
+ state.outFixed.add(step.i);
1347
+ }
1348
+ const outCell = outCells[step.i];
1349
+ if (outCell) {
1350
+ outCell.textContent = String(step.value);
1351
+ outCell.classList.add("out-fixed", "filled");
1352
+ }
1353
+ });
1354
+ };
1355
+ if (transferPauseMs > 0) {
1356
+ setTimeout(startTransfer, transferPauseMs);
1357
+ } else {
1358
+ startTransfer();
1359
+ }
1360
+ state.vHold = {
1361
+ index: step.v_index,
1362
+ until: performance.now() + totalWait,
1363
+ };
1364
+ const holdIndex = step.v_index;
1365
+ setTimeout(() => {
1366
+ if (state.runToken !== holdToken) return;
1367
+ if (!state.vHold || state.vHold.index !== holdIndex) return;
1368
+ if (performance.now() < state.vHold.until) return;
1369
+ const cell = state.vCells[holdIndex];
1370
+ if (cell) cell.classList.remove("v-pick");
1371
+ }, totalWait + 60);
1372
+ return totalWait;
1373
+ }
1374
+ return 0;
1375
+ }
1376
+
1377
+ if (step.phase === "break_inner") {
1378
+ return;
1379
+ }
1380
+
1381
+ if (step.phase === "check") {
1382
+ return;
1383
+ }
1384
+
1385
+ if (step.phase === "break_outer") {
1386
+ return;
1387
+ }
1388
+
1389
+ if (step.phase === "output") {
1390
+ if (
1391
+ (state.outPending && state.outPending.has(step.i)) ||
1392
+ (state.outFixed && state.outFixed.has(step.i))
1393
+ ) {
1394
+ return;
1395
+ }
1396
+ if (outCells[step.i]) {
1397
+ outCells[step.i].textContent = String(step.value);
1398
+ outCells[step.i].classList.add("out", "out-fixed", "filled");
1399
+ if (state.outFixed) {
1400
+ state.outFixed.add(step.i);
1401
+ }
1402
+ }
1403
+ return;
1404
+ }
1405
+
1406
+ if (step.phase === "return") {
1407
+ return;
1408
+ }
1409
+ }
1410
+
1411
+ async function play(state, steps, token) {
1412
+ if (!steps || !steps.length) return;
1413
+ for (let idx = 0; idx < steps.length; idx += 1) {
1414
+ if (token !== runToken) return;
1415
+ const extraWait = applyStep(state, steps[idx]);
1416
+ const delay = baseDelay / getSpeed();
1417
+ const waitMs = Math.max(delay, Number.isFinite(extraWait) ? extraWait : 0);
1418
+ await sleep(waitMs);
1419
+ }
1420
+ }
1421
+
1422
+ function start(data) {
1423
+ runToken += 1;
1424
+ const token = runToken;
1425
+ const state = render(data);
1426
+ if (!state) return;
1427
+ state.runToken = token;
1428
+ state.vHold = null;
1429
+ if (state.outFixed) state.outFixed.clear();
1430
+ if (state.outPending) state.outPending.clear();
1431
+ play(state, data.steps, token);
1432
+ }
1433
+
1434
+ let lastValue = "";
1435
+ setInterval(() => {
1436
+ const box = getStepsBox();
1437
+ if (!box) return;
1438
+ const value = box.value || "";
1439
+ if (value && value !== lastValue) {
1440
+ lastValue = value;
1441
+ try {
1442
+ const data = JSON.parse(value);
1443
+ start(data);
1444
+ } catch (err) {
1445
+ console.error("Invalid ROSA steps payload", err);
1446
+ }
1447
+ }
1448
+ }, 300);
1449
+ }
1450
+ """
1451
+
1452
+
1453
+ with gr.Blocks(css=CSS, js=JS) as demo:
1454
+ gr.HTML(
1455
+ '<div class="page-header">'
1456
+ '<div class="page-title">ROSA QKV Demo</div>'
1457
+ '<div class="page-subtitle">Enter or randomize q/k/v (0/1 only). Click Start Demo to see suffix matching and retrieval.</div>'
1458
+ "</div>"
1459
+ )
1460
+
1461
+ with gr.Row():
1462
+ q_text = gr.Textbox(label="q sequence", value="01010101010101010101", lines=1)
1463
+ k_text = gr.Textbox(label="k sequence", value="10100110100110100110", lines=1)
1464
+ v_text = gr.Textbox(label="v sequence", value="11001100110011001100", lines=1)
1465
+
1466
+ with gr.Row():
1467
+ length = gr.Slider(4, 20, value=20, step=1, label="Random length")
1468
+ random_btn = gr.Button("Randomize")
1469
+ demo_btn = gr.Button("Start Demo", variant="primary")
1470
+ speed = gr.Slider(
1471
+ 0.1,
1472
+ 5.0,
1473
+ value=2.0,
1474
+ step=0.05,
1475
+ label="Playback speed",
1476
+ elem_id="speed_slider",
1477
+ interactive=True,
1478
+ )
1479
+
1480
+ out_text = gr.Textbox(label="Output", interactive=False)
1481
+ steps_json = gr.Textbox(visible=False, elem_id="steps_json")
1482
+
1483
+ gr.HTML(
1484
+ f'<div id="rosa-shell" class="rosa-shell">'
1485
+ f'<div class="rosa-pane"><div id="rosa-vis"></div></div>'
1486
+ f'<div class="rosa-code-pane">{CODE_HTML}</div>'
1487
+ f"</div>"
1488
+ )
1489
+
1490
+ random_btn.click(on_random, inputs=[length], outputs=[q_text, k_text, v_text])
1491
+ demo_btn.click(on_demo, inputs=[q_text, k_text, v_text], outputs=[steps_json, out_text])
1492
+ demo.load(on_demo, inputs=[q_text, k_text, v_text], outputs=[steps_json, out_text])
1493
+
1494
+
1495
+ if __name__ == "__main__":
1496
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio>=5.0.0,<6.0.0