ndurner commited on
Commit
6350667
·
1 Parent(s): caf42f6

add health check

Browse files
Files changed (3) hide show
  1. demo/app.py +13 -2
  2. demo/health.py +145 -0
  3. demo/layout.py +52 -0
demo/app.py CHANGED
@@ -1,12 +1,23 @@
 
 
1
  import os
2
 
3
  import gradio as gr
4
 
 
 
 
 
 
 
 
5
 
6
  def create_app() -> gr.Blocks:
7
- """Return a blank Gradio interface for placeholder purposes."""
8
  with gr.Blocks(title="Aileen3 Demo") as demo:
9
- gr.HTML("")
 
 
10
  return demo
11
 
12
 
 
1
+ from __future__ import annotations
2
+
3
  import os
4
 
5
  import gradio as gr
6
 
7
+ import health
8
+ from layout import CELL_CSS, wrap_cell
9
+
10
+
11
+ def render_health_panel() -> str:
12
+ return wrap_cell(health.render_health_notice())
13
+
14
 
15
  def create_app() -> gr.Blocks:
16
+ """Create the Gradio application with a minimal notebook-like health cell."""
17
  with gr.Blocks(title="Aileen3 Demo") as demo:
18
+ gr.HTML(f"<style>{CELL_CSS}</style>")
19
+ health_panel = gr.HTML(value=wrap_cell(health.render_placeholder_notice()))
20
+ demo.load(fn=render_health_panel, outputs=health_panel, queue=False)
21
  return demo
22
 
23
 
demo/health.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import html
4
+ import importlib.util
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ from dataclasses import dataclass
9
+ from itertools import zip_longest
10
+ from typing import Iterable
11
+
12
+
13
+ MIN_DENO_VERSION = (2, 0, 0)
14
+ MIN_YTDLP_VERSION = (2025, 11, 12)
15
+
16
+
17
+ @dataclass
18
+ class ToolStatus:
19
+ label: str
20
+ ok: bool
21
+ detail: str
22
+
23
+
24
+ @dataclass
25
+ class HealthReport:
26
+ ok: bool
27
+ summary: str
28
+ tools: list[ToolStatus]
29
+
30
+
31
+ def _split_version(raw: str) -> Iterable[int]:
32
+ return tuple(int(part) for part in re.findall(r"\d+", raw))
33
+
34
+
35
+ def _version_at_least(raw: str, expected: tuple[int, ...]) -> bool:
36
+ parsed = _split_version(raw)
37
+ if not parsed:
38
+ return False
39
+ for current, required in zip_longest(parsed, expected, fillvalue=0):
40
+ if current > required:
41
+ return True
42
+ if current < required:
43
+ return False
44
+ return True
45
+
46
+
47
+ def _check_deno() -> ToolStatus:
48
+ label = "🦕 Deno"
49
+ binary = shutil.which("deno")
50
+ if not binary:
51
+ return ToolStatus(label, False, "`deno` command not found on PATH")
52
+ try:
53
+ completed = subprocess.run(
54
+ [binary, "--version"],
55
+ check=False,
56
+ capture_output=True,
57
+ text=True,
58
+ timeout=10,
59
+ )
60
+ except (FileNotFoundError, subprocess.SubprocessError, OSError) as exc:
61
+ return ToolStatus(label, False, f"Unable to execute: {exc}")
62
+ if completed.returncode != 0:
63
+ detail = completed.stderr.strip() or completed.stdout.strip() or "Unknown error"
64
+ return ToolStatus(label, False, detail)
65
+ match = re.search(r"deno\s+([0-9.]+)", completed.stdout)
66
+ if not match:
67
+ return ToolStatus(label, False, "Could not parse version output")
68
+ version = match.group(1)
69
+ if not _version_at_least(version, MIN_DENO_VERSION):
70
+ return ToolStatus(label, False, f"Found v{version}; need ≥ {'.'.join(map(str, MIN_DENO_VERSION))}")
71
+ return ToolStatus(label, True, f"v{version} ready for yt-dlp")
72
+
73
+
74
+ def _check_yt_dlp_python() -> ToolStatus:
75
+ label = "📼 yt-dlp Python"
76
+ try:
77
+ import yt_dlp
78
+ from yt_dlp import YoutubeDL
79
+ from yt_dlp.version import __version__ as version_str
80
+ except Exception as exc:
81
+ return ToolStatus(label, False, f"Import failed: {exc}")
82
+
83
+ try:
84
+ with YoutubeDL(params={"skip_download": True, "quiet": True}) as ydl:
85
+ _ = ydl.params.get("skip_download")
86
+ except Exception as exc:
87
+ return ToolStatus(label, False, f"YoutubeDL bootstrap failed: {exc}")
88
+
89
+ if not _version_at_least(version_str, MIN_YTDLP_VERSION):
90
+ minimum = ".".join(map(str, MIN_YTDLP_VERSION))
91
+ return ToolStatus(label, False, f"Detected {version_str}; require ≥ {minimum}")
92
+
93
+ has_ejs = importlib.util.find_spec("yt_dlp_ejs") is not None
94
+ addon_note = "yt_dlp_ejs ready" if has_ejs else "yt_dlp_ejs missing (JS sites will fail)"
95
+ return ToolStatus(label, has_ejs, f"v{version_str} import OK — {addon_note}")
96
+
97
+
98
+ def run_health_report() -> HealthReport:
99
+ tool_statuses = [_check_deno(), _check_yt_dlp_python()]
100
+ ok = all(status.ok for status in tool_statuses)
101
+ if ok:
102
+ summary = "All systems look good—ready for notebook-style experimentation."
103
+ else:
104
+ blocking = ", ".join(status.label for status in tool_statuses if not status.ok)
105
+ summary = f"Needs attention: {blocking}."
106
+ return HealthReport(ok=ok, summary=summary, tools=tool_statuses)
107
+
108
+
109
+ def _sanitize(text: str) -> str:
110
+ return html.escape(text, quote=False)
111
+
112
+
113
+ def render_health_notice() -> str:
114
+ report = run_health_report()
115
+ if report.ok:
116
+ bg = "#dcfce7"
117
+ border = "#86efac"
118
+ text_color = "#14532d"
119
+ icon = "✅"
120
+ else:
121
+ bg = "#fee2e2"
122
+ border = "#fecaca"
123
+ text_color = "#7f1d1d"
124
+ icon = "🧯"
125
+ bullet_rows = "".join(
126
+ f"<li>{_sanitize(status.label)}: "
127
+ f"<strong>{'Ready' if status.ok else 'Needs attention'}</strong> — "
128
+ f"{_sanitize(status.detail)}</li>"
129
+ for status in report.tools
130
+ )
131
+ return (
132
+ f"<div class='health-box' style='background:{bg};border-color:{border};color:{text_color};'>"
133
+ f"<div class='health-head'>{icon} {_sanitize(report.summary)}</div>"
134
+ f"<ul>{bullet_rows}</ul>"
135
+ "</div>"
136
+ )
137
+
138
+
139
+ def render_placeholder_notice() -> str:
140
+ return (
141
+ "<div class='health-box health-placeholder'>"
142
+ "<div class='health-head'>⏳ Running automated health check…</div>"
143
+ "<p>Step 0 runs implicitly to validate Deno and yt-dlp before any other cell.</p>"
144
+ "</div>"
145
+ )
demo/layout.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ CELL_CSS = """
4
+ .cell-wrapper {
5
+ border: 1px solid rgba(0, 0, 0, 0.9);
6
+ border-radius: 14px;
7
+ padding: 10px;
8
+ margin-bottom: 10px;
9
+ background: transparent;
10
+ }
11
+ .cell-title {
12
+ font-size: 1.15rem;
13
+ font-weight: 600;
14
+ margin: 0 0 10px;
15
+ }
16
+ .health-box {
17
+ border-radius: 14px;
18
+ padding: 18px;
19
+ border: 1px solid transparent;
20
+ }
21
+ .health-box ul {
22
+ margin: 12px 0 0;
23
+ padding-left: 22px;
24
+ }
25
+ .health-box li {
26
+ margin-bottom: 6px;
27
+ }
28
+ .health-placeholder {
29
+ background: #f5f5f5 !important;
30
+ border-color: #d4d4d4 !important;
31
+ color: #1f2937 !important;
32
+ }
33
+ @media (prefers-color-scheme: dark) {
34
+ .cell-wrapper {
35
+ border-color: rgba(255, 255, 255, 0.45);
36
+ }
37
+ .health-placeholder {
38
+ background: rgba(148, 163, 184, 0.15) !important;
39
+ border-color: rgba(148, 163, 184, 0.4) !important;
40
+ color: #e2e8f0 !important;
41
+ }
42
+ }
43
+ """
44
+
45
+
46
+ def wrap_cell(body_html: str) -> str:
47
+ return (
48
+ "<section class='cell-wrapper'>"
49
+ "<div class='cell-title'>🧪 Health check</div>"
50
+ f"{body_html}"
51
+ "</section>"
52
+ )