Mike0021 commited on
Commit
dbfa873
·
verified ·
1 Parent(s): a8890fb

Probe Gradio startup

Browse files
Files changed (1) hide show
  1. app.py +7 -1418
app.py CHANGED
@@ -1,1428 +1,17 @@
1
  import os
2
 
3
- os.environ.setdefault("HF_HOME", "/tmp/.cache/huggingface")
4
- os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
5
- os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
6
  os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
7
  os.environ.setdefault("GRADIO_SSR_MODE", "false")
8
- if not os.environ.get("HF_TOKEN"):
9
- for _token_key in ("HUGGING_FACE_HUB_TOKEN", "HUGGINGFACE_HUB_TOKEN"):
10
- if os.environ.get(_token_key):
11
- os.environ["HF_TOKEN"] = os.environ[_token_key]
12
- break
13
 
14
- print("codex-traces startup: env configured", flush=True)
15
-
16
- import html
17
- import json
18
- import math
19
- import re
20
- from datetime import datetime, timezone
21
- from functools import lru_cache
22
- from pathlib import Path
23
- from typing import Any
24
 
25
  import gradio as gr
26
- from huggingface_hub import hf_hub_download, list_repo_files
27
-
28
- print("codex-traces startup: imports complete", flush=True)
29
-
30
-
31
- APP_TITLE = "Codex Traces"
32
- APP_REVISION = "parser-v2"
33
- DATASET_REPO = "Mike0021/codex-sessions"
34
- REPO_TYPE = "dataset"
35
- FILE_SUFFIX = "_rollout.jsonl"
36
- SESSION_PATH_PREFIX = "sessions/rollout-"
37
- SESSION_PATH_SUFFIX = ".jsonl"
38
- PAGE_SIZE = 120
39
- MAX_OUTPUT_CHARS = 22000
40
- MAX_MESSAGE_CHARS = 30000
41
- KNOWN_SESSIONS = (
42
- "agents-a1-demo",
43
- "ai-agent-soccer",
44
- "anime-soccer-generator",
45
- "asasr-space",
46
- "codex-traces-viewer",
47
- "craft-agents-oss",
48
- "deepspec-space",
49
- "edit-anything",
50
- "fractal-pi-extension",
51
- "gaussian-splat-demo",
52
- "hf-history-article",
53
- "hf-motion-video",
54
- "horus-hiero-space",
55
- "locate-anything-space",
56
- "ltx-3dreal-space",
57
- "microworld-space",
58
- "olmoearth-demo",
59
- "paris-13-landing",
60
- "pulpie-demo",
61
- "pulpie-gguf",
62
- "pulpie-mlx",
63
- "pulpie-onnx",
64
- "pulpie-web-demo",
65
- "qwen3-asr-space",
66
- "rampart-demo",
67
- "sync-lora-space",
68
- "tabfm-arena",
69
- )
70
-
71
- ANSI_RE = re.compile(r"\x1b\[[0-9;?]*[ -/]*[@-~]")
72
- TIMESTAMP_KEYS = (
73
- "timestamp",
74
- "time",
75
- "created_at",
76
- "updated_at",
77
- "started_at",
78
- "completed_at",
79
- )
80
-
81
-
82
- def _hub_token() -> str | None:
83
- return (
84
- os.environ.get("HF_TOKEN")
85
- or os.environ.get("HUGGING_FACE_HUB_TOKEN")
86
- or os.environ.get("HUGGINGFACE_HUB_TOKEN")
87
- or None
88
- )
89
-
90
-
91
- def _display_name(file_name: str) -> str:
92
- if file_name.startswith(SESSION_PATH_PREFIX) and file_name.endswith(SESSION_PATH_SUFFIX):
93
- return file_name[len(SESSION_PATH_PREFIX) : -len(SESSION_PATH_SUFFIX)]
94
- return file_name[: -len(FILE_SUFFIX)] if file_name.endswith(FILE_SUFFIX) else file_name
95
-
96
-
97
- def _is_rollout_file(file_name: str) -> bool:
98
- return (
99
- file_name.startswith(SESSION_PATH_PREFIX)
100
- and file_name.endswith(SESSION_PATH_SUFFIX)
101
- ) or file_name.endswith(FILE_SUFFIX)
102
-
103
-
104
- def _file_candidates(session_name: str | None) -> list[str]:
105
- if not session_name:
106
- return []
107
- name = str(session_name).strip()
108
- if name.endswith(".jsonl"):
109
- return [name]
110
-
111
- candidates = [
112
- f"{SESSION_PATH_PREFIX}{name}{SESSION_PATH_SUFFIX}",
113
- f"{name}{FILE_SUFFIX}",
114
- ]
115
- return list(dict.fromkeys(candidates))
116
-
117
-
118
- def _fmt_number(value: int | None) -> str:
119
- return f"{int(value or 0):,}"
120
-
121
-
122
- def _fmt_bytes(value: int | None) -> str:
123
- value = int(value or 0)
124
- units = ("B", "KB", "MB", "GB")
125
- size = float(value)
126
- for unit in units:
127
- if size < 1024 or unit == units[-1]:
128
- return f"{size:.1f} {unit}" if unit != "B" else f"{value} B"
129
- size /= 1024
130
- return f"{value} B"
131
-
132
-
133
- def _strip_ansi(text: Any) -> str:
134
- return ANSI_RE.sub("", str(text or ""))
135
-
136
-
137
- def _escape(text: Any) -> str:
138
- return html.escape(str(text or ""), quote=True)
139
-
140
-
141
- def _limit_text(text: str, limit: int, label: str) -> tuple[str, str]:
142
- if len(text) <= limit:
143
- return text, ""
144
- hidden = len(text) - limit
145
- note = f"\n\n[{label} truncated by {hidden:,} characters for browser responsiveness.]"
146
- return text[:limit] + note, f" truncated {hidden:,} chars"
147
-
148
-
149
- def _parse_time(value: Any) -> datetime | None:
150
- if value is None:
151
- return None
152
- if isinstance(value, (int, float)):
153
- seconds = float(value) / 1000 if value > 10_000_000_000 else float(value)
154
- try:
155
- return datetime.fromtimestamp(seconds, tz=timezone.utc)
156
- except (OSError, OverflowError, ValueError):
157
- return None
158
- if isinstance(value, str):
159
- raw = value.strip()
160
- if not raw:
161
- return None
162
- try:
163
- if raw.endswith("Z"):
164
- raw = raw[:-1] + "+00:00"
165
- parsed = datetime.fromisoformat(raw)
166
- return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
167
- except ValueError:
168
- return None
169
- return None
170
-
171
-
172
- def _duration_label(timestamps: list[datetime]) -> str:
173
- if len(timestamps) < 2:
174
- return "not recorded"
175
- delta = max(timestamps) - min(timestamps)
176
- seconds = max(0, int(delta.total_seconds()))
177
- if seconds < 60:
178
- return f"{seconds}s"
179
- minutes, sec = divmod(seconds, 60)
180
- if minutes < 60:
181
- return f"{minutes}m {sec}s"
182
- hours, minutes = divmod(minutes, 60)
183
- return f"{hours}h {minutes}m"
184
-
185
-
186
- @lru_cache(maxsize=1)
187
- def _list_sessions_cached() -> tuple[tuple[str, ...], str]:
188
- try:
189
- files = list_repo_files(DATASET_REPO, repo_type=REPO_TYPE, token=_hub_token())
190
- except Exception as exc:
191
- return (), f"{type(exc).__name__}: {exc}"
192
- sessions = tuple(sorted({_display_name(f) for f in files if _is_rollout_file(f)}))
193
- if not sessions:
194
- return (), "No *_rollout.jsonl files were found in the dataset."
195
- return sessions, ""
196
-
197
-
198
- def _session_choices(live: bool = True) -> list[str]:
199
- if not live:
200
- return list(KNOWN_SESSIONS)
201
- sessions, _ = _list_sessions_cached()
202
- return list(sessions or KNOWN_SESSIONS)
203
-
204
-
205
- def _default_session() -> str | None:
206
- choices = _session_choices(live=False)
207
- return choices[0] if choices else None
208
-
209
-
210
- def _download_session(file_name: str) -> Path:
211
- return Path(
212
- hf_hub_download(
213
- repo_id=DATASET_REPO,
214
- filename=file_name,
215
- repo_type=REPO_TYPE,
216
- token=_hub_token(),
217
- )
218
- )
219
-
220
-
221
- def _command_status(item: dict[str, Any]) -> tuple[str, str]:
222
- status = str(item.get("status") or "").lower()
223
- exit_code = item.get("exit_code")
224
- if status in {"failed", "error"} or (exit_code not in (None, 0)):
225
- return "failed", "fail"
226
- if status in {"completed", "succeeded", "success"}:
227
- return "success", "success"
228
- if status in {"in_progress", "running"}:
229
- return "running", "running"
230
- return status or "unknown", "neutral"
231
-
232
-
233
- def _usage_total(usage: dict[str, Any]) -> int:
234
- return int(usage.get("input_tokens") or 0) + int(usage.get("output_tokens") or 0)
235
-
236
-
237
- def _collect_timestamps(obj: dict[str, Any], timestamps: list[datetime]) -> None:
238
- for key in TIMESTAMP_KEYS:
239
- parsed = _parse_time(obj.get(key))
240
- if parsed:
241
- timestamps.append(parsed)
242
- item = obj.get("item")
243
- if isinstance(item, dict):
244
- for key in TIMESTAMP_KEYS:
245
- parsed = _parse_time(item.get(key))
246
- if parsed:
247
- timestamps.append(parsed)
248
-
249
-
250
- def _payload_command(payload: dict[str, Any]) -> str:
251
- name = str(payload.get("name") or "function_call")
252
- arguments = payload.get("arguments")
253
- if isinstance(arguments, dict):
254
- command = arguments.get("command")
255
- if command:
256
- return str(command)
257
- return f"{name}({json.dumps(arguments, ensure_ascii=True)})"
258
- if arguments:
259
- return f"{name}({arguments})"
260
- return name
261
-
262
-
263
- @lru_cache(maxsize=6)
264
- def _load_session(file_name: str) -> dict[str, Any]:
265
- path = _download_session(file_name)
266
- events: list[dict[str, Any]] = []
267
- timestamps: list[datetime] = []
268
- pending_commands: list[str] = []
269
- command_ids: set[str] = set()
270
- failed_commands = 0
271
- turn_completed = 0
272
- turn_started = 0
273
- parse_errors = 0
274
- usage_summary = {
275
- "input_tokens": 0,
276
- "cached_input_tokens": 0,
277
- "output_tokens": 0,
278
- "reasoning_output_tokens": 0,
279
- }
280
-
281
- with path.open("r", encoding="utf-8", errors="replace") as handle:
282
- for line_no, raw_line in enumerate(handle, start=1):
283
- line = raw_line.strip()
284
- if not line.startswith("{"):
285
- parse_errors += 1
286
- continue
287
- try:
288
- obj = json.loads(line)
289
- except json.JSONDecodeError:
290
- parse_errors += 1
291
- continue
292
-
293
- obj_type = obj.get("type")
294
- payload = obj.get("payload") if isinstance(obj.get("payload"), dict) else {}
295
- payload_type = payload.get("type")
296
- item = obj.get("item") if isinstance(obj.get("item"), dict) else {}
297
- item_type = item.get("type")
298
- _collect_timestamps(obj, timestamps)
299
-
300
- if obj_type == "turn_context":
301
- turn_started += 1
302
- continue
303
-
304
- if obj_type == "event_msg":
305
- if payload_type in {"agent_message", "user_message"}:
306
- text = str(payload.get("message") or "")
307
- is_user = payload_type == "user_message"
308
- events.append(
309
- {
310
- "kind": "user" if is_user else "agent",
311
- "role": "USER" if is_user else "AGENT",
312
- "line": line_no,
313
- "id": f"line-{line_no}",
314
- "text": text,
315
- "search": text,
316
- }
317
- )
318
- continue
319
-
320
- if payload_type == "token_count":
321
- turn_completed += 1
322
- usage = {key: int(payload.get(key) or 0) for key in usage_summary}
323
- for key, value in usage.items():
324
- usage_summary[key] += value
325
- events.append(
326
- {
327
- "kind": "usage",
328
- "line": line_no,
329
- "usage": usage,
330
- "search": " ".join(
331
- [
332
- "turn completed usage tokens",
333
- " ".join(f"{k} {v}" for k, v in usage.items()),
334
- ]
335
- ),
336
- }
337
- )
338
- continue
339
-
340
- if payload_type == "item_completed" and payload.get("item_type") == "todo_list":
341
- todos = payload.get("todos") or payload.get("items") or []
342
- if isinstance(todos, list):
343
- text = " ".join(str(todo.get("text") or "") for todo in todos if isinstance(todo, dict))
344
- events.append(
345
- {
346
- "kind": "todo",
347
- "line": line_no,
348
- "id": f"line-{line_no}",
349
- "items": todos,
350
- "search": text,
351
- }
352
- )
353
- continue
354
-
355
- if payload_type == "patch_apply_end":
356
- path_value = str(payload.get("path") or "patch")
357
- events.append(
358
- {
359
- "kind": "files",
360
- "line": line_no,
361
- "id": f"line-{line_no}",
362
- "changes": [{"kind": "patch", "path": path_value}],
363
- "status": payload.get("status") or "",
364
- "search": f"patch {path_value} {payload.get('status') or ''}",
365
- }
366
- )
367
- continue
368
-
369
- if obj_type == "response_item":
370
- if payload_type == "function_call":
371
- pending_commands.append(_payload_command(payload))
372
- continue
373
-
374
- if payload_type == "function_call_output":
375
- command = pending_commands.pop(0) if pending_commands else "function_call_output"
376
- output = _strip_ansi(payload.get("output") or "")
377
- exit_code = payload.get("exit_code")
378
- status = "failed" if exit_code not in (None, 0) else "completed"
379
- label, status_class = _command_status({"status": status, "exit_code": exit_code})
380
- if status_class == "fail":
381
- failed_commands += 1
382
- item_id = f"line-{line_no}"
383
- command_ids.add(item_id)
384
- events.append(
385
- {
386
- "kind": "command",
387
- "line": line_no,
388
- "id": item_id,
389
- "command": command,
390
- "output": output,
391
- "exit_code": exit_code,
392
- "status": label,
393
- "status_class": status_class,
394
- "search": " ".join([command, output, str(exit_code), label]),
395
- }
396
- )
397
- continue
398
-
399
- if obj_type == "turn.started":
400
- turn_started += 1
401
- continue
402
-
403
- if obj_type == "turn.completed":
404
- turn_completed += 1
405
- usage = obj.get("usage") or {}
406
- if isinstance(usage, dict):
407
- for key in usage_summary:
408
- usage_summary[key] += int(usage.get(key) or 0)
409
- events.append(
410
- {
411
- "kind": "usage",
412
- "line": line_no,
413
- "usage": usage,
414
- "search": " ".join(
415
- [
416
- "turn completed usage tokens",
417
- " ".join(f"{k} {v}" for k, v in usage.items()),
418
- ]
419
- ),
420
- }
421
- )
422
- continue
423
-
424
- if obj_type not in {"item.completed", "item.updated"}:
425
- continue
426
-
427
- if item_type == "agent_message":
428
- text = str(item.get("text") or "")
429
- events.append(
430
- {
431
- "kind": "agent",
432
- "line": line_no,
433
- "id": item.get("id") or f"line-{line_no}",
434
- "text": text,
435
- "search": text,
436
- }
437
- )
438
- continue
439
-
440
- if item_type == "command_execution":
441
- item_id = str(item.get("id") or f"line-{line_no}")
442
- if item_id not in command_ids:
443
- command_ids.add(item_id)
444
- label, status_class = _command_status(item)
445
- if status_class == "fail":
446
- failed_commands += 1
447
- command = str(item.get("command") or "")
448
- output = _strip_ansi(item.get("aggregated_output") or "")
449
- exit_code = item.get("exit_code")
450
- events.append(
451
- {
452
- "kind": "command",
453
- "line": line_no,
454
- "id": item_id,
455
- "command": command,
456
- "output": output,
457
- "exit_code": exit_code,
458
- "status": label,
459
- "status_class": status_class,
460
- "search": " ".join([command, output, str(exit_code), label]),
461
- }
462
- )
463
- continue
464
-
465
- if item_type == "todo_list":
466
- todos = item.get("items") or []
467
- if isinstance(todos, list):
468
- text = " ".join(str(todo.get("text") or "") for todo in todos if isinstance(todo, dict))
469
- events.append(
470
- {
471
- "kind": "todo",
472
- "line": line_no,
473
- "id": item.get("id") or f"line-{line_no}",
474
- "items": todos,
475
- "search": text,
476
- }
477
- )
478
- continue
479
-
480
- if item_type == "file_change":
481
- changes = item.get("changes") or []
482
- if isinstance(changes, list):
483
- text = " ".join(
484
- f"{change.get('kind', '')} {change.get('path', '')}"
485
- for change in changes
486
- if isinstance(change, dict)
487
- )
488
- events.append(
489
- {
490
- "kind": "files",
491
- "line": line_no,
492
- "id": item.get("id") or f"line-{line_no}",
493
- "changes": changes,
494
- "status": item.get("status") or "",
495
- "search": text,
496
- }
497
- )
498
-
499
- total_turns = turn_completed or turn_started
500
- return {
501
- "file_name": file_name,
502
- "path": str(path),
503
- "bytes": path.stat().st_size,
504
- "events": events,
505
- "summary": {
506
- "turns": total_turns,
507
- "commands": len(command_ids),
508
- "failed_commands": failed_commands,
509
- "duration": _duration_label(timestamps),
510
- "parse_errors": parse_errors,
511
- **usage_summary,
512
- "total_tokens": usage_summary["input_tokens"] + usage_summary["output_tokens"],
513
- },
514
- }
515
-
516
-
517
- def _filter_events(events: list[dict[str, Any]], query: str | None) -> list[dict[str, Any]]:
518
- needle = (query or "").strip().lower()
519
- if not needle:
520
- return events
521
- terms = [part for part in needle.split() if part]
522
- return [
523
- event
524
- for event in events
525
- if all(term in str(event.get("search", "")).lower() for term in terms)
526
- ]
527
-
528
-
529
- def _metric(label: str, value: str, tone: str = "") -> str:
530
- return (
531
- f'<div class="metric {tone}">'
532
- f'<span class="metric-label">{_escape(label)}</span>'
533
- f'<span class="metric-value">{_escape(value)}</span>'
534
- "</div>"
535
- )
536
-
537
-
538
- def _render_summary(data: dict[str, Any], matches: int, query: str) -> str:
539
- summary = data["summary"]
540
- file_label = _display_name(data["file_name"])
541
- query_label = f"Filtered: {matches:,} matches" if query.strip() else f"{matches:,} visible events"
542
- failed = int(summary["failed_commands"])
543
- return (
544
- '<section class="summary-panel">'
545
- '<div class="summary-topline">'
546
- f'<div><h1>{_escape(file_label)}</h1>'
547
- f'<p>{_escape(data["file_name"])} · {_fmt_bytes(data["bytes"])} · {query_label}</p></div>'
548
- f'<div class="repo-pill">private dataset · {_escape(APP_REVISION)}</div>'
549
- "</div>"
550
- '<div class="metrics-grid">'
551
- f'{_metric("Turns", _fmt_number(summary["turns"]))}'
552
- f'{_metric("Total tokens", _fmt_number(summary["total_tokens"]))}'
553
- f'{_metric("Input", _fmt_number(summary["input_tokens"]))}'
554
- f'{_metric("Output", _fmt_number(summary["output_tokens"]))}'
555
- f'{_metric("Reasoning", _fmt_number(summary["reasoning_output_tokens"]))}'
556
- f'{_metric("Duration", str(summary["duration"]))}'
557
- f'{_metric("Commands", _fmt_number(summary["commands"]))}'
558
- f'{_metric("Failed", _fmt_number(failed), "danger" if failed else "ok")}'
559
- "</div>"
560
- "</section>"
561
- )
562
-
563
-
564
- def _render_agent(event: dict[str, Any]) -> str:
565
- text, note = _limit_text(str(event.get("text") or ""), MAX_MESSAGE_CHARS, "message")
566
- role = str(event.get("role") or "AGENT").upper()
567
- role_class = "user" if role == "USER" else "agent"
568
- return (
569
- f'<article class="event {role_class}-event">'
570
- f'<div class="event-rail {role_class}-rail">{_escape(role)}</div>'
571
- f'<div class="bubble {role_class}-bubble">'
572
- f'<div class="event-meta">line {_fmt_number(event["line"])}{_escape(note)}</div>'
573
- f'<div class="message-text">{_escape(text)}</div>'
574
- "</div>"
575
- "</article>"
576
- )
577
-
578
-
579
- def _render_command(event: dict[str, Any]) -> str:
580
- output = str(event.get("output") or "")
581
- output_limited, note = _limit_text(output, MAX_OUTPUT_CHARS, "output")
582
- exit_code = event.get("exit_code")
583
- exit_label = "pending" if exit_code is None else str(exit_code)
584
- status = str(event.get("status") or "unknown")
585
- status_class = str(event.get("status_class") or "neutral")
586
- output_count = f"{len(output):,} chars"
587
- return (
588
- '<article class="event command-event">'
589
- '<div class="event-rail command-rail">CMD</div>'
590
- '<div class="command-card">'
591
- '<div class="command-head">'
592
- f'<span class="status-badge {status_class}">{_escape(status)}</span>'
593
- f'<span class="exit-badge">exit {_escape(exit_label)}</span>'
594
- f'<span class="event-meta">line {_fmt_number(event["line"])}</span>'
595
- "</div>"
596
- f'<pre class="code command-code"><code>{_escape(event.get("command") or "")}</code></pre>'
597
- f'<details class="output-drawer"><summary>Command output · {output_count}{_escape(note)}</summary>'
598
- f'<pre class="code output-code"><code>{_escape(output_limited) if output_limited else "No output."}</code></pre>'
599
- "</details>"
600
- "</div>"
601
- "</article>"
602
- )
603
-
604
-
605
- def _render_usage(event: dict[str, Any]) -> str:
606
- usage = event.get("usage") or {}
607
- input_tokens = int(usage.get("input_tokens") or 0)
608
- cached = int(usage.get("cached_input_tokens") or 0)
609
- output = int(usage.get("output_tokens") or 0)
610
- reasoning = int(usage.get("reasoning_output_tokens") or 0)
611
- total = input_tokens + output
612
- return (
613
- '<article class="event usage-event">'
614
- '<div class="event-rail usage-rail">TURN</div>'
615
- '<div class="usage-card">'
616
- f'<span>Total {_fmt_number(total)}</span>'
617
- f'<span>Input {_fmt_number(input_tokens)}</span>'
618
- f'<span>Cached {_fmt_number(cached)}</span>'
619
- f'<span>Output {_fmt_number(output)}</span>'
620
- f'<span>Reasoning {_fmt_number(reasoning)}</span>'
621
- f'<span class="event-meta">line {_fmt_number(event["line"])}</span>'
622
- "</div>"
623
- "</article>"
624
- )
625
-
626
-
627
- def _render_todo(event: dict[str, Any]) -> str:
628
- items = event.get("items") or []
629
- rows = []
630
- for todo in items:
631
- if not isinstance(todo, dict):
632
- continue
633
- done = bool(todo.get("completed"))
634
- rows.append(
635
- '<li class="todo-row">'
636
- f'<span class="todo-check {"done" if done else "open"}">{"&#10003;" if done else "&#9711;"}</span>'
637
- f'<span>{_escape(todo.get("text") or "")}</span>'
638
- "</li>"
639
- )
640
- return (
641
- '<article class="event todo-event">'
642
- '<div class="event-rail todo-rail">TODO</div>'
643
- '<div class="todo-card">'
644
- f'<div class="event-meta">line {_fmt_number(event["line"])}</div>'
645
- f'<ul>{"".join(rows) if rows else "<li>No todo items.</li>"}</ul>'
646
- "</div>"
647
- "</article>"
648
- )
649
-
650
-
651
- def _render_files(event: dict[str, Any]) -> str:
652
- changes = event.get("changes") or []
653
- rows = []
654
- for change in changes:
655
- if not isinstance(change, dict):
656
- continue
657
- rows.append(
658
- '<li>'
659
- f'<span class="file-kind">{_escape(change.get("kind") or "change")}</span>'
660
- f'<code>{_escape(change.get("path") or "")}</code>'
661
- "</li>"
662
- )
663
- return (
664
- '<article class="event file-event">'
665
- '<div class="event-rail file-rail">FILE</div>'
666
- '<div class="file-card">'
667
- f'<div class="event-meta">line {_fmt_number(event["line"])} · {_escape(event.get("status") or "")}</div>'
668
- f'<ul>{"".join(rows) if rows else "<li>No file paths recorded.</li>"}</ul>'
669
- "</div>"
670
- "</article>"
671
- )
672
-
673
-
674
- def _render_event(event: dict[str, Any]) -> str:
675
- kind = event.get("kind")
676
- if kind in {"agent", "user"}:
677
- return _render_agent(event)
678
- if kind == "command":
679
- return _render_command(event)
680
- if kind == "usage":
681
- return _render_usage(event)
682
- if kind == "todo":
683
- return _render_todo(event)
684
- if kind == "files":
685
- return _render_files(event)
686
- return ""
687
-
688
-
689
- def _render_transcript(events: list[dict[str, Any]], page: int, total_pages: int) -> str:
690
- if not events:
691
- return (
692
- '<section class="empty-state">'
693
- "<h2>No matching transcript events</h2>"
694
- "<p>Try a broader search term or choose another session.</p>"
695
- "</section>"
696
- )
697
- start = (page - 1) * PAGE_SIZE
698
- visible = events[start : start + PAGE_SIZE]
699
- rendered = "".join(_render_event(event) for event in visible)
700
- return (
701
- '<section class="transcript">'
702
- f'<div class="page-marker">Page {page:,} of {total_pages:,}</div>'
703
- f"{rendered}"
704
- "</section>"
705
- )
706
-
707
-
708
- def _render_error(title: str, details: str) -> tuple[str, str, str, int]:
709
- return (
710
- '<section class="summary-panel error-panel">'
711
- f"<h1>{_escape(title)}</h1>"
712
- f"<p>{_escape(details)}</p>"
713
- "</section>",
714
- '<section class="empty-state"><h2>Session unavailable</h2></section>',
715
- "No session loaded.",
716
- 1,
717
- )
718
-
719
-
720
- def _initial_view(choices: list[str]) -> tuple[str, str, str, int]:
721
- choices_label = f"{len(choices):,} session files available" if choices else "No session files available"
722
- summary = (
723
- '<section class="summary-panel">'
724
- '<div class="summary-topline">'
725
- "<div><h1>Select a session</h1>"
726
- f"<p>{_escape(choices_label)} from {DATASET_REPO}</p></div>"
727
- f'<div class="repo-pill">private dataset · {_escape(APP_REVISION)}</div>'
728
- "</div>"
729
- '<div class="metrics-grid">'
730
- f'{_metric("Startup mode", "metadata only")}'
731
- f'{_metric("Files listed", _fmt_number(len(choices)))}'
732
- f'{_metric("Downloads", "on demand")}'
733
- f'{_metric("Page size", _fmt_number(PAGE_SIZE))}'
734
- "</div>"
735
- "</section>"
736
- )
737
- transcript = (
738
- '<section class="empty-state">'
739
- "<h2>No session loaded yet</h2>"
740
- "<p>Choose a session in the sidebar and select Load session. Files are downloaded lazily.</p>"
741
- "</section>"
742
- )
743
- return summary, transcript, "No session file downloaded yet.", 1
744
-
745
-
746
- def render_session(session_name: str | None, query: str | None = "", page: int | float | None = 1) -> tuple[str, str, str, int]:
747
- choices = KNOWN_SESSIONS
748
- list_error = ""
749
- if not choices:
750
- return _render_error("Dataset file list unavailable", list_error or "HF_TOKEN is not configured.")
751
-
752
- session_name = session_name or choices[0]
753
- if session_name not in choices and _display_name(str(session_name)) in choices:
754
- session_name = _display_name(str(session_name))
755
- candidates = _file_candidates(session_name)
756
- if not candidates:
757
- return _render_error("No session selected", "Choose a session from the dropdown.")
758
-
759
- last_error: Exception | None = None
760
- data = None
761
- for file_name in candidates:
762
- try:
763
- data = _load_session(file_name)
764
- break
765
- except Exception as exc:
766
- last_error = exc
767
- if data is None:
768
- details = f"{type(last_error).__name__}: {last_error}" if last_error else "No file candidates were available."
769
- return _render_error("Could not load session", details)
770
-
771
- query = query or ""
772
- filtered = _filter_events(data["events"], query)
773
- total_pages = max(1, math.ceil(len(filtered) / PAGE_SIZE))
774
- try:
775
- page_int = int(page or 1)
776
- except (TypeError, ValueError):
777
- page_int = 1
778
- page_int = min(max(1, page_int), total_pages)
779
- start = (page_int - 1) * PAGE_SIZE + 1 if filtered else 0
780
- end = min(page_int * PAGE_SIZE, len(filtered))
781
- page_info = (
782
- f"Showing {start:,}-{end:,} of {len(filtered):,} matching events "
783
- f"({len(data['events']):,} total parsed)."
784
- )
785
- if data["summary"]["parse_errors"]:
786
- page_info += f" Skipped {data['summary']['parse_errors']:,} non-JSON lines."
787
- summary = _render_summary(data, len(filtered), query)
788
- transcript = _render_transcript(filtered, page_int, total_pages)
789
- return summary, transcript, page_info, page_int
790
-
791
-
792
- def load_session(session_name: str | None) -> tuple[str, str, str, int, str]:
793
- summary, transcript, page_info, page = render_session(session_name, "", 1)
794
- return summary, transcript, page_info, page, ""
795
-
796
-
797
- def filter_session(session_name: str | None, query: str | None) -> tuple[str, str, str, int]:
798
- return render_session(session_name, query, 1)
799
-
800
-
801
- def next_page(session_name: str | None, query: str | None, page: int | float | None) -> tuple[str, str, str, int]:
802
- return render_session(session_name, query, int(page or 1) + 1)
803
-
804
-
805
- def previous_page(session_name: str | None, query: str | None, page: int | float | None) -> tuple[str, str, str, int]:
806
- return render_session(session_name, query, int(page or 1) - 1)
807
-
808
-
809
- def refresh_sessions() -> tuple[Any, str, str, str, int, str]:
810
- _list_sessions_cached.cache_clear()
811
- choices = _session_choices()
812
- value = None
813
- summary, transcript, page_info, page = _initial_view(choices)
814
- return gr.update(choices=choices, value=value), summary, transcript, page_info, page, ""
815
-
816
-
817
- CUSTOM_CSS = """
818
- :root {
819
- --ct-bg: #0b0d10;
820
- --ct-panel: #11151a;
821
- --ct-panel-2: #151a21;
822
- --ct-line: #30363d;
823
- --ct-text: #e6edf3;
824
- --ct-muted: #8b949e;
825
- --ct-green: #3fb950;
826
- --ct-red: #f85149;
827
- --ct-yellow: #d29922;
828
- --ct-blue: #58a6ff;
829
- --ct-cyan: #39c5cf;
830
- --ct-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
831
- --ct-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
832
- }
833
-
834
- html, body, .gradio-container {
835
- min-height: 100%;
836
- background: var(--ct-bg) !important;
837
- color: var(--ct-text) !important;
838
- font-family: var(--ct-sans) !important;
839
- }
840
-
841
- .gradio-container,
842
- .gradio-container .contain,
843
- .gradio-container main {
844
- max-width: none !important;
845
- width: 100% !important;
846
- padding: 0 !important;
847
- }
848
-
849
- #ct-app {
850
- min-height: 100vh;
851
- background:
852
- linear-gradient(180deg, rgba(88, 166, 255, 0.07), transparent 210px),
853
- var(--ct-bg);
854
- }
855
-
856
- .ct-shell {
857
- display: grid;
858
- grid-template-columns: minmax(260px, 330px) minmax(0, 1fr);
859
- gap: 0;
860
- min-height: 100vh;
861
- }
862
-
863
- .ct-sidebar {
864
- background: #0f1318;
865
- border-right: 1px solid var(--ct-line);
866
- padding: 22px 18px;
867
- position: sticky;
868
- top: 0;
869
- height: 100vh;
870
- overflow: auto;
871
- }
872
-
873
- .ct-brand {
874
- margin-bottom: 22px;
875
- }
876
-
877
- .ct-brand h1 {
878
- font-family: var(--ct-mono);
879
- font-size: 21px;
880
- line-height: 1.2;
881
- margin: 0 0 6px;
882
- color: var(--ct-text);
883
- letter-spacing: 0;
884
- }
885
-
886
- .ct-brand p {
887
- margin: 0;
888
- color: var(--ct-muted);
889
- font-size: 13px;
890
- line-height: 1.45;
891
- }
892
-
893
- .ct-sidebar .gradio-dropdown,
894
- .ct-sidebar .gradio-textbox,
895
- .ct-sidebar .gradio-button {
896
- font-family: var(--ct-mono) !important;
897
- }
898
-
899
- .ct-sidebar .block,
900
- .ct-sidebar .form,
901
- .ct-sidebar .wrap,
902
- .ct-sidebar .container,
903
- .ct-sidebar .secondary-wrap,
904
- .ct-sidebar .input-container,
905
- .ct-sidebar .block > div {
906
- background: transparent !important;
907
- border-color: var(--ct-line) !important;
908
- box-shadow: none !important;
909
- }
910
-
911
- .ct-sidebar .block {
912
- background: var(--ct-panel) !important;
913
- border: 1px solid var(--ct-line) !important;
914
- border-radius: 8px !important;
915
- }
916
-
917
- .ct-sidebar label,
918
- .ct-sidebar .block span {
919
- color: var(--ct-muted) !important;
920
- }
921
-
922
- .ct-sidebar input,
923
- .ct-sidebar textarea,
924
- .ct-sidebar .wrap,
925
- .ct-sidebar .container {
926
- background: var(--ct-panel) !important;
927
- color: var(--ct-text) !important;
928
- border-color: var(--ct-line) !important;
929
- }
930
-
931
- .ct-sidebar button {
932
- background: var(--ct-panel-2) !important;
933
- border: 1px solid var(--ct-line) !important;
934
- border-radius: 8px !important;
935
- color: var(--ct-text) !important;
936
- font-family: var(--ct-mono) !important;
937
- min-height: 42px;
938
- }
939
-
940
- .ct-sidebar button:hover {
941
- border-color: var(--ct-blue) !important;
942
- color: #ffffff !important;
943
- }
944
-
945
- .ct-sidebar button.primary,
946
- .ct-sidebar .primary button {
947
- background: #1f6feb !important;
948
- border-color: #388bfd !important;
949
- color: #ffffff !important;
950
- }
951
-
952
- .ct-main {
953
- padding: 22px;
954
- min-width: 0;
955
- }
956
-
957
- .summary-panel {
958
- background: var(--ct-panel);
959
- border: 1px solid var(--ct-line);
960
- border-radius: 8px;
961
- margin-bottom: 18px;
962
- padding: 18px;
963
- }
964
-
965
- .summary-topline {
966
- display: flex;
967
- align-items: flex-start;
968
- justify-content: space-between;
969
- gap: 16px;
970
- margin-bottom: 16px;
971
- }
972
-
973
- .summary-topline h1 {
974
- font-family: var(--ct-mono);
975
- font-size: 22px;
976
- margin: 0 0 6px;
977
- color: var(--ct-text);
978
- letter-spacing: 0;
979
- }
980
-
981
- .summary-topline p {
982
- margin: 0;
983
- color: var(--ct-muted);
984
- font-family: var(--ct-mono);
985
- font-size: 13px;
986
- }
987
-
988
- .repo-pill {
989
- border: 1px solid rgba(57, 197, 207, 0.45);
990
- color: var(--ct-cyan);
991
- border-radius: 999px;
992
- padding: 5px 10px;
993
- font-family: var(--ct-mono);
994
- font-size: 12px;
995
- white-space: nowrap;
996
- }
997
-
998
- .metrics-grid {
999
- display: grid;
1000
- grid-template-columns: repeat(auto-fit, minmax(135px, 1fr));
1001
- gap: 10px;
1002
- }
1003
-
1004
- .metric {
1005
- background: var(--ct-panel-2);
1006
- border: 1px solid var(--ct-line);
1007
- border-radius: 8px;
1008
- padding: 10px 12px;
1009
- min-height: 62px;
1010
- }
1011
-
1012
- .metric-label {
1013
- display: block;
1014
- color: var(--ct-muted);
1015
- font-size: 12px;
1016
- margin-bottom: 6px;
1017
- }
1018
-
1019
- .metric-value {
1020
- display: block;
1021
- color: var(--ct-text);
1022
- font-family: var(--ct-mono);
1023
- font-size: 17px;
1024
- overflow-wrap: anywhere;
1025
- }
1026
-
1027
- .metric.ok .metric-value {
1028
- color: var(--ct-green);
1029
- }
1030
-
1031
- .metric.danger .metric-value {
1032
- color: var(--ct-red);
1033
- }
1034
-
1035
- .transcript {
1036
- display: flex;
1037
- flex-direction: column;
1038
- gap: 14px;
1039
- }
1040
-
1041
- .page-marker {
1042
- color: var(--ct-muted);
1043
- font-family: var(--ct-mono);
1044
- font-size: 12px;
1045
- padding: 0 2px;
1046
- }
1047
-
1048
- .event {
1049
- display: grid;
1050
- grid-template-columns: 58px minmax(0, 1fr);
1051
- gap: 12px;
1052
- align-items: start;
1053
- }
1054
-
1055
- .event-rail {
1056
- position: sticky;
1057
- top: 18px;
1058
- border: 1px solid var(--ct-line);
1059
- border-radius: 8px;
1060
- padding: 8px 0;
1061
- text-align: center;
1062
- font-family: var(--ct-mono);
1063
- font-size: 11px;
1064
- color: var(--ct-muted);
1065
- background: #0f1318;
1066
- }
1067
-
1068
- .agent-rail { color: var(--ct-blue); }
1069
- .user-rail { color: var(--ct-cyan); }
1070
- .command-rail { color: var(--ct-yellow); }
1071
- .usage-rail { color: var(--ct-green); }
1072
- .todo-rail { color: var(--ct-cyan); }
1073
- .file-rail { color: #c9d1d9; }
1074
-
1075
- .bubble,
1076
- .command-card,
1077
- .usage-card,
1078
- .todo-card,
1079
- .file-card,
1080
- .empty-state {
1081
- border: 1px solid var(--ct-line);
1082
- border-radius: 8px;
1083
- background: var(--ct-panel);
1084
- min-width: 0;
1085
- }
1086
-
1087
- .agent-bubble {
1088
- padding: 14px 15px;
1089
- border-left: 3px solid var(--ct-blue);
1090
- }
1091
-
1092
- .user-bubble {
1093
- padding: 14px 15px;
1094
- border-left: 3px solid var(--ct-cyan);
1095
- background: #101820;
1096
- }
1097
-
1098
- .event-meta {
1099
- color: var(--ct-muted);
1100
- font-family: var(--ct-mono);
1101
- font-size: 12px;
1102
- margin-bottom: 8px;
1103
- }
1104
-
1105
- .message-text {
1106
- white-space: pre-wrap;
1107
- overflow-wrap: anywhere;
1108
- color: var(--ct-text);
1109
- line-height: 1.55;
1110
- font-size: 14px;
1111
- }
1112
-
1113
- .command-card {
1114
- overflow: hidden;
1115
- border-left: 3px solid var(--ct-yellow);
1116
- }
1117
-
1118
- .command-head {
1119
- display: flex;
1120
- align-items: center;
1121
- flex-wrap: wrap;
1122
- gap: 8px;
1123
- padding: 12px 12px 0;
1124
- }
1125
-
1126
- .status-badge,
1127
- .exit-badge {
1128
- font-family: var(--ct-mono);
1129
- font-size: 12px;
1130
- border-radius: 999px;
1131
- border: 1px solid var(--ct-line);
1132
- padding: 3px 8px;
1133
- }
1134
-
1135
- .status-badge.success { color: var(--ct-green); border-color: rgba(63, 185, 80, .5); }
1136
- .status-badge.fail { color: var(--ct-red); border-color: rgba(248, 81, 73, .55); }
1137
- .status-badge.running { color: var(--ct-yellow); border-color: rgba(210, 153, 34, .55); }
1138
- .status-badge.neutral { color: var(--ct-muted); }
1139
-
1140
- .exit-badge {
1141
- color: var(--ct-muted);
1142
- }
1143
-
1144
- .code {
1145
- margin: 0;
1146
- overflow: auto;
1147
- font-family: var(--ct-mono);
1148
- font-size: 12.5px;
1149
- line-height: 1.55;
1150
- color: #dbeafe;
1151
- background: #070a0f;
1152
- }
1153
-
1154
- .command-code {
1155
- margin: 12px;
1156
- padding: 12px;
1157
- border: 1px solid #1f2937;
1158
- border-radius: 8px;
1159
- }
1160
-
1161
- .output-drawer {
1162
- border-top: 1px solid var(--ct-line);
1163
- }
1164
-
1165
- .output-drawer summary {
1166
- cursor: pointer;
1167
- color: var(--ct-muted);
1168
- font-family: var(--ct-mono);
1169
- font-size: 12px;
1170
- padding: 10px 12px;
1171
- }
1172
-
1173
- .output-code {
1174
- max-height: 520px;
1175
- padding: 12px;
1176
- border-top: 1px solid #1f2937;
1177
- }
1178
-
1179
- .usage-card {
1180
- display: flex;
1181
- flex-wrap: wrap;
1182
- gap: 8px;
1183
- padding: 12px;
1184
- border-left: 3px solid var(--ct-green);
1185
- }
1186
-
1187
- .usage-card span {
1188
- font-family: var(--ct-mono);
1189
- font-size: 12px;
1190
- color: var(--ct-text);
1191
- background: #0b1117;
1192
- border: 1px solid var(--ct-line);
1193
- border-radius: 999px;
1194
- padding: 4px 9px;
1195
- }
1196
-
1197
- .todo-card,
1198
- .file-card {
1199
- padding: 12px;
1200
- }
1201
-
1202
- .todo-card {
1203
- border-left: 3px solid var(--ct-cyan);
1204
- }
1205
-
1206
- .file-card {
1207
- border-left: 3px solid #c9d1d9;
1208
- }
1209
-
1210
- .todo-card ul,
1211
- .file-card ul {
1212
- list-style: none;
1213
- padding: 0;
1214
- margin: 0;
1215
- display: grid;
1216
- gap: 8px;
1217
- }
1218
-
1219
- .todo-row {
1220
- display: grid;
1221
- grid-template-columns: 24px minmax(0, 1fr);
1222
- gap: 8px;
1223
- color: var(--ct-text);
1224
- line-height: 1.45;
1225
- }
1226
-
1227
- .todo-check {
1228
- font-family: var(--ct-mono);
1229
- color: var(--ct-muted);
1230
- }
1231
-
1232
- .todo-check.done {
1233
- color: var(--ct-green);
1234
- }
1235
-
1236
- .file-card li {
1237
- display: flex;
1238
- gap: 10px;
1239
- align-items: baseline;
1240
- min-width: 0;
1241
- }
1242
-
1243
- .file-kind {
1244
- color: var(--ct-yellow);
1245
- font-family: var(--ct-mono);
1246
- font-size: 12px;
1247
- min-width: 46px;
1248
- }
1249
-
1250
- .file-card code {
1251
- color: var(--ct-text);
1252
- font-family: var(--ct-mono);
1253
- overflow-wrap: anywhere;
1254
- }
1255
-
1256
- .empty-state {
1257
- padding: 32px;
1258
- color: var(--ct-muted);
1259
- }
1260
-
1261
- .empty-state h2 {
1262
- color: var(--ct-text);
1263
- margin: 0 0 8px;
1264
- font-family: var(--ct-mono);
1265
- font-size: 18px;
1266
- letter-spacing: 0;
1267
- }
1268
-
1269
- .error-panel {
1270
- border-color: rgba(248, 81, 73, .6);
1271
- }
1272
-
1273
- .ct-page-info {
1274
- color: var(--ct-muted);
1275
- font-family: var(--ct-mono);
1276
- font-size: 12px;
1277
- margin: 8px 0 12px;
1278
- }
1279
-
1280
- .ct-page-info,
1281
- .ct-page-info * {
1282
- color: var(--ct-muted) !important;
1283
- }
1284
-
1285
- .ct-pager {
1286
- display: grid;
1287
- grid-template-columns: 1fr 1fr;
1288
- gap: 8px;
1289
- }
1290
-
1291
- @media (max-width: 860px) {
1292
- .ct-shell {
1293
- grid-template-columns: 1fr;
1294
- }
1295
- .ct-sidebar {
1296
- position: relative;
1297
- height: auto;
1298
- border-right: 0;
1299
- border-bottom: 1px solid var(--ct-line);
1300
- }
1301
- .ct-main {
1302
- padding: 14px;
1303
- }
1304
- .event {
1305
- grid-template-columns: 1fr;
1306
- }
1307
- .event-rail {
1308
- position: relative;
1309
- top: 0;
1310
- width: 58px;
1311
- }
1312
- .summary-topline {
1313
- flex-direction: column;
1314
- }
1315
- }
1316
- """
1317
-
1318
-
1319
- def build_app() -> gr.Blocks:
1320
- print("codex-traces startup: building app", flush=True)
1321
- choices = _session_choices(live=False)
1322
- default = None
1323
- initial_summary, initial_transcript, initial_page_info, initial_page = _initial_view(choices)
1324
- theme = gr.themes.Base(
1325
- primary_hue="blue",
1326
- secondary_hue="cyan",
1327
- neutral_hue="slate",
1328
- radius_size="sm",
1329
- )
1330
-
1331
- with gr.Blocks(title=APP_TITLE, theme=theme, css=CUSTOM_CSS) as demo:
1332
- with gr.Row(elem_id="ct-app", elem_classes=["ct-shell"]):
1333
- with gr.Column(elem_classes=["ct-sidebar"], scale=0):
1334
- gr.HTML(
1335
- '<div class="ct-brand">'
1336
- "<h1>Codex Traces</h1>"
1337
- "<p>Private rollout.jsonl session viewer</p>"
1338
- "</div>"
1339
- )
1340
- session = gr.Dropdown(
1341
- choices=choices,
1342
- value=default,
1343
- label="Session file",
1344
- interactive=True,
1345
- allow_custom_value=False,
1346
- )
1347
- search = gr.Textbox(
1348
- label="Search transcript",
1349
- placeholder="Filter by keyword, command, output, token...",
1350
- lines=1,
1351
- max_lines=1,
1352
- )
1353
- filter_button = gr.Button("Filter", variant="primary")
1354
- load_button = gr.Button("Load session")
1355
- refresh_button = gr.Button("Refresh file list")
1356
- page_state = gr.State(initial_page)
1357
- page_info = gr.Markdown(initial_page_info, elem_classes=["ct-page-info"])
1358
- with gr.Row(elem_classes=["ct-pager"]):
1359
- prev_button = gr.Button("Previous")
1360
- next_button = gr.Button("Next")
1361
-
1362
- with gr.Column(elem_classes=["ct-main"], scale=1):
1363
- summary = gr.HTML(initial_summary)
1364
- transcript = gr.HTML(initial_transcript)
1365
-
1366
- session.change(
1367
- fn=load_session,
1368
- inputs=session,
1369
- outputs=[summary, transcript, page_info, page_state, search],
1370
- api_name="load_session",
1371
- show_progress="minimal",
1372
- )
1373
- load_button.click(
1374
- fn=load_session,
1375
- inputs=session,
1376
- outputs=[summary, transcript, page_info, page_state, search],
1377
- api_name=False,
1378
- show_progress="minimal",
1379
- )
1380
- filter_button.click(
1381
- fn=filter_session,
1382
- inputs=[session, search],
1383
- outputs=[summary, transcript, page_info, page_state],
1384
- api_name="filter_session",
1385
- show_progress="minimal",
1386
- )
1387
- search.submit(
1388
- fn=filter_session,
1389
- inputs=[session, search],
1390
- outputs=[summary, transcript, page_info, page_state],
1391
- api_name=False,
1392
- show_progress="minimal",
1393
- )
1394
- prev_button.click(
1395
- fn=previous_page,
1396
- inputs=[session, search, page_state],
1397
- outputs=[summary, transcript, page_info, page_state],
1398
- api_name="previous_page",
1399
- show_progress="minimal",
1400
- )
1401
- next_button.click(
1402
- fn=next_page,
1403
- inputs=[session, search, page_state],
1404
- outputs=[summary, transcript, page_info, page_state],
1405
- api_name="next_page",
1406
- show_progress="minimal",
1407
- )
1408
- refresh_button.click(
1409
- fn=refresh_sessions,
1410
- inputs=None,
1411
- outputs=[session, summary, transcript, page_info, page_state, search],
1412
- api_name="refresh_sessions",
1413
- show_progress="minimal",
1414
- )
1415
-
1416
- print("codex-traces startup: app built", flush=True)
1417
- return demo
1418
 
 
1419
 
1420
- demo = build_app()
1421
- print("codex-traces startup: demo ready", flush=True)
 
1422
 
1423
- print("codex-traces startup: launching", flush=True)
1424
- demo.launch(
1425
- server_name="0.0.0.0",
1426
- server_port=7860,
1427
- show_error=True,
1428
- )
 
1
  import os
2
 
 
 
 
3
  os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
4
  os.environ.setdefault("GRADIO_SSR_MODE", "false")
 
 
 
 
 
5
 
6
+ print("probe startup: before gradio import", flush=True)
 
 
 
 
 
 
 
 
 
7
 
8
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ print("probe startup: after gradio import", flush=True)
11
 
12
+ with gr.Blocks(title="Codex Traces Probe") as demo:
13
+ gr.Markdown("# Codex Traces Probe\n\nRuntime startup check.")
14
+ out = gr.Textbox(value="ready", label="Status")
15
 
16
+ print("probe startup: launching", flush=True)
17
+ demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)