evalstate HF Staff commited on
Commit
7a2b591
·
verified ·
1 Parent(s): 2e22b48

Deploy Hub Classic research agent UI

Browse files

Use the selected Hub Classic light/dark Prefab design, official Hugging Face logo, Source Sans 3 and IBM Plex Mono, with MCP App CSP resource domains for Google Fonts.

Dockerfile CHANGED
@@ -6,7 +6,7 @@ RUN apt-get update && \
6
 
7
  WORKDIR /app
8
  RUN uv pip install --system --no-cache \
9
- fast-agent-mcp==0.9.17 \
10
  'fastmcp[apps]==3.4.4' \
11
  prefab-ui==0.20.2 \
12
  huggingface_hub
 
6
 
7
  WORKDIR /app
8
  RUN uv pip install --system --no-cache \
9
+ fast-agent-mcp==0.9.21 \
10
  'fastmcp[apps]==3.4.4' \
11
  prefab-ui==0.20.2 \
12
  huggingface_hub
__pycache__/fastmcp_research_app.cpython-314.pyc DELETED
Binary file (35.1 kB)
 
research/__pycache__/__init__.cpython-314.pyc DELETED
Binary file (245 Bytes)
 
research/__pycache__/activity_narrator.cpython-314.pyc DELETED
Binary file (18.6 kB)
 
research/__pycache__/app_artifacts.cpython-314.pyc DELETED
Binary file (7.45 kB)
 
research/__pycache__/app_auth.cpython-314.pyc DELETED
Binary file (3.39 kB)
 
research/__pycache__/app_jobs.cpython-314.pyc DELETED
Binary file (23.7 kB)
 
research/__pycache__/app_observability.cpython-314.pyc DELETED
Binary file (21.4 kB)
 
research/__pycache__/app_renderer.cpython-314.pyc DELETED
Binary file (5.41 kB)
 
research/__pycache__/app_ui.cpython-314.pyc DELETED
Binary file (24.5 kB)
 
research/__pycache__/fastmcp_server.cpython-314.pyc DELETED
Binary file (9.54 kB)
 
research/__pycache__/research_runner.cpython-314.pyc DELETED
Binary file (8.5 kB)
 
research/__pycache__/research_workspace.cpython-314.pyc DELETED
Binary file (8.31 kB)
 
research/app_ui.py CHANGED
@@ -15,6 +15,7 @@ from prefab_ui.components import (
15
  Dialog,
16
  Div,
17
  Heading,
 
18
  If,
19
  Markdown,
20
  Row,
@@ -24,6 +25,15 @@ from prefab_ui.components import (
24
  from prefab_ui.components.control_flow import ForEach
25
  from prefab_ui.rx import RESULT, STATE
26
 
 
 
 
 
 
 
 
 
 
27
  BROADSHEET_CSS = """
28
  .dispatch-app {
29
  --dispatch-accent: color-mix(in oklab, var(--warning) 68%, var(--foreground));
@@ -562,8 +572,11 @@ def build_research_ui(
562
  *,
563
  build_id: str = "dev",
564
  live: bool = True,
 
565
  ) -> PrefabApp:
566
  snapshot = _ui_snapshot(snapshot)
 
 
567
  on_mount = None
568
  if live:
569
  on_mount = [
@@ -635,10 +648,19 @@ def build_research_ui(
635
  if not live:
636
  read_report = SetState("chat_sent", True)
637
 
 
 
 
 
 
 
 
 
638
  with PrefabApp(
639
  title="Research Dispatch",
640
- css_class="dispatch-app",
641
- css=[BROADSHEET_CSS],
 
642
  state={
643
  "job": snapshot,
644
  "topic": topic,
@@ -655,7 +677,21 @@ def build_research_ui(
655
  with Div(css_class="dispatch-sheet", on_mount=on_mount):
656
  with Column(css_class="dispatch-header", gap=0):
657
  with Row(css_class="dispatch-topbar", gap=4):
658
- Text("Research Dispatch", css_class="dispatch-kicker")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  with Row(css_class="dispatch-controls", gap=3):
660
  with Row(css_class="dispatch-stats", gap=2):
661
  Text(STATE.job.elapsed, css_class="dispatch-stat")
 
15
  Dialog,
16
  Div,
17
  Heading,
18
+ Image,
19
  If,
20
  Markdown,
21
  Row,
 
25
  from prefab_ui.components.control_flow import ForEach
26
  from prefab_ui.rx import RESULT, STATE
27
 
28
+ from .hf_design import (
29
+ HF_DESIGN_CSS,
30
+ HF_FONT_STYLESHEET,
31
+ HF_LOGO_DATA_URI,
32
+ HFDesign,
33
+ design_css_class,
34
+ validate_design,
35
+ )
36
+
37
  BROADSHEET_CSS = """
38
  .dispatch-app {
39
  --dispatch-accent: color-mix(in oklab, var(--warning) 68%, var(--foreground));
 
572
  *,
573
  build_id: str = "dev",
574
  live: bool = True,
575
+ design: HFDesign | None = None,
576
  ) -> PrefabApp:
577
  snapshot = _ui_snapshot(snapshot)
578
+ if design is not None:
579
+ design = validate_design(design)
580
  on_mount = None
581
  if live:
582
  on_mount = [
 
648
  if not live:
649
  read_report = SetState("chat_sent", True)
650
 
651
+ css_class = "dispatch-app"
652
+ css = [BROADSHEET_CSS]
653
+ stylesheets = None
654
+ if design is not None:
655
+ css_class = design_css_class(design)
656
+ css.append(HF_DESIGN_CSS)
657
+ stylesheets = [HF_FONT_STYLESHEET]
658
+
659
  with PrefabApp(
660
  title="Research Dispatch",
661
+ css_class=css_class,
662
+ css=css,
663
+ stylesheets=stylesheets,
664
  state={
665
  "job": snapshot,
666
  "topic": topic,
 
677
  with Div(css_class="dispatch-sheet", on_mount=on_mount):
678
  with Column(css_class="dispatch-header", gap=0):
679
  with Row(css_class="dispatch-topbar", gap=4):
680
+ if design is None:
681
+ Text("Research Dispatch", css_class="dispatch-kicker")
682
+ else:
683
+ with Row(css_class="hf-brand", gap=2):
684
+ Image(
685
+ src=HF_LOGO_DATA_URI,
686
+ alt="Hugging Face",
687
+ css_class="hf-brand-logo",
688
+ )
689
+ with Column(css_class="hf-brand-copy", gap=0):
690
+ Text("Hugging Face", css_class="hf-brand-name")
691
+ Text(
692
+ "Research Agent",
693
+ css_class="hf-brand-product",
694
+ )
695
  with Row(css_class="dispatch-controls", gap=3):
696
  with Row(css_class="dispatch-stats", gap=2):
697
  Text(STATE.job.elapsed, css_class="dispatch-stat")
research/assets/huggingface-logo.svg ADDED
research/fastmcp_server.py CHANGED
@@ -10,6 +10,7 @@ from typing import Annotated, Any
10
  from fast_agent import FastAgent
11
  from fastmcp import Context as MCPContext
12
  from fastmcp import FastMCP, FastMCPApp
 
13
  from pydantic import Field
14
  from prefab_ui.app import PrefabApp
15
 
@@ -23,10 +24,25 @@ from .app_jobs import (
23
  )
24
  from .app_renderer import app_build_id, install_versioned_renderer
25
  from .app_ui import build_research_ui
 
26
  from .research_runner import ResearchRunner
27
 
28
  RESEARCH_HOME = Path(__file__).parent
29
  AGENT_CARDS = RESEARCH_HOME / "agent-cards"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
 
32
  def parse_args() -> argparse.Namespace:
@@ -82,7 +98,14 @@ def register_research_app(
82
  else "local development user"
83
  )
84
  job.add_event(f"Workspace access confirmed for {identity}.", kind="Setup")
85
- return build_research_ui(topic, job.snapshot(), build_id=build_id)
 
 
 
 
 
 
 
86
 
87
  @app.tool()
88
  async def start_research(
 
10
  from fast_agent import FastAgent
11
  from fastmcp import Context as MCPContext
12
  from fastmcp import FastMCP, FastMCPApp
13
+ from fastmcp.apps import ResourceCSP
14
  from pydantic import Field
15
  from prefab_ui.app import PrefabApp
16
 
 
24
  )
25
  from .app_renderer import app_build_id, install_versioned_renderer
26
  from .app_ui import build_research_ui
27
+ from .hf_design import HF_RESOURCE_DOMAINS, HFDesign
28
  from .research_runner import ResearchRunner
29
 
30
  RESEARCH_HOME = Path(__file__).parent
31
  AGENT_CARDS = RESEARCH_HOME / "agent-cards"
32
+ PRODUCTION_UI_DESIGN: HFDesign = "hub-classic"
33
+
34
+
35
+ def configure_research_ui_csp(app: FastMCPApp, tool_name: str = "research") -> None:
36
+ """Allow the Google-hosted Hub fonts used by design variants."""
37
+ csp = ResourceCSP(resource_domains=list(HF_RESOURCE_DOMAINS)).model_dump(
38
+ by_alias=True,
39
+ exclude_none=True,
40
+ )
41
+ for tool in app._local._components.values():
42
+ if getattr(tool, "name", None) == tool_name:
43
+ tool.meta.setdefault("ui", {})["csp"] = csp
44
+ return
45
+ raise RuntimeError(f"Prefab UI tool was not registered: {tool_name}")
46
 
47
 
48
  def parse_args() -> argparse.Namespace:
 
98
  else "local development user"
99
  )
100
  job.add_event(f"Workspace access confirmed for {identity}.", kind="Setup")
101
+ return build_research_ui(
102
+ topic,
103
+ job.snapshot(),
104
+ build_id=build_id,
105
+ design=PRODUCTION_UI_DESIGN,
106
+ )
107
+
108
+ configure_research_ui_csp(app)
109
 
110
  @app.tool()
111
  async def start_research(
research/hf_design.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face design options for Prefab review builds."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ from pathlib import Path
7
+ from typing import Literal, get_args
8
+
9
+ HFDesign = Literal["hub-classic", "spaces-gradient", "dataset-studio"]
10
+ HF_DESIGNS = get_args(HFDesign)
11
+
12
+ HF_FONT_STYLESHEET = (
13
+ "https://fonts.googleapis.com/css2?"
14
+ "family=IBM+Plex+Mono:wght@400;600&"
15
+ "family=Source+Sans+3:wght@400;600;700&display=swap"
16
+ )
17
+ HF_RESOURCE_DOMAINS = (
18
+ "https://fonts.googleapis.com",
19
+ "https://fonts.gstatic.com",
20
+ )
21
+
22
+ _LOGO = Path(__file__).with_name("assets") / "huggingface-logo.svg"
23
+ HF_LOGO_DATA_URI = (
24
+ "data:image/svg+xml;base64,"
25
+ + base64.b64encode(_LOGO.read_bytes()).decode("ascii")
26
+ )
27
+
28
+ HF_DESIGN_CSS = """
29
+ .hf-design {
30
+ --hf-yellow: #ffd21e;
31
+ --hf-amber: #f59e0b;
32
+ --hf-orange: #ff9d0b;
33
+ --hf-blue: #3b82f6;
34
+ --hf-violet: #7c3aed;
35
+ --hf-gray-50: #f9fafb;
36
+ --hf-gray-100: #f3f4f6;
37
+ --hf-gray-200: #e5e7eb;
38
+ --hf-gray-300: #d1d5db;
39
+ --hf-gray-400: #9ca3af;
40
+ --hf-gray-500: #6b7280;
41
+ --hf-gray-700: #374151;
42
+ --hf-gray-850: #141c2e;
43
+ --hf-gray-925: #101623;
44
+ --hf-gray-950: #0b0f19;
45
+ --hf-canvas: var(--hf-gray-50);
46
+ --hf-surface: #fff;
47
+ --hf-surface-soft: var(--hf-gray-50);
48
+ --hf-line: var(--hf-gray-200);
49
+ --hf-text: #111827;
50
+ --hf-muted: var(--hf-gray-500);
51
+ --dispatch-accent: #b45309;
52
+ --dispatch-live: #10b981;
53
+ --dispatch-panel: var(--hf-surface-soft);
54
+ min-height: 800px;
55
+ padding: 24px;
56
+ background: var(--hf-canvas);
57
+ color: var(--hf-text);
58
+ font-family: "Source Sans 3", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif;
59
+ }
60
+ .dark .hf-design {
61
+ --hf-canvas: var(--hf-gray-950);
62
+ --hf-surface: var(--hf-gray-925);
63
+ --hf-surface-soft: var(--hf-gray-850);
64
+ --hf-line: #263044;
65
+ --hf-text: #e5e7eb;
66
+ --hf-muted: #9ca3af;
67
+ --dispatch-accent: #fbbf24;
68
+ --dispatch-live: #34d399;
69
+ }
70
+ .hf-design .dispatch-sheet {
71
+ width: min(100%, 920px);
72
+ height: 800px;
73
+ border-color: var(--hf-line);
74
+ border-radius: 14px;
75
+ background: var(--hf-surface);
76
+ box-shadow: 0 1px 2px rgb(15 23 42 / .04), 0 10px 32px rgb(15 23 42 / .08);
77
+ }
78
+ .dark .hf-design .dispatch-sheet {
79
+ box-shadow: 0 18px 50px rgb(0 0 0 / .28);
80
+ }
81
+ .hf-design .dispatch-header {
82
+ padding: 22px 30px 18px;
83
+ }
84
+ .hf-design .dispatch-body {
85
+ padding: 0 30px;
86
+ }
87
+ .hf-design .dispatch-footer-log {
88
+ padding-right: 30px;
89
+ padding-left: 30px;
90
+ }
91
+ .hf-design .dispatch-session {
92
+ padding-right: 30px;
93
+ padding-left: 30px;
94
+ }
95
+ .hf-design .dispatch-kicker,
96
+ .hf-design .dispatch-section-label,
97
+ .hf-design .dispatch-time,
98
+ .hf-design .dispatch-source,
99
+ .hf-design .dispatch-meta,
100
+ .hf-design .dispatch-log-line,
101
+ .hf-design .dispatch-query-toggle,
102
+ .hf-design .dispatch-status,
103
+ .hf-design .dispatch-stat,
104
+ .hf-design .dispatch-trace,
105
+ .hf-design .dispatch-log-toggle-button {
106
+ font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
107
+ }
108
+ .hf-brand {
109
+ min-width: 0;
110
+ flex: none;
111
+ align-items: center;
112
+ }
113
+ .hf-brand-logo {
114
+ width: 32px;
115
+ height: 30px;
116
+ flex: none;
117
+ object-fit: contain;
118
+ }
119
+ .hf-brand-copy {
120
+ min-width: 0;
121
+ }
122
+ .hf-brand-name {
123
+ color: var(--hf-text);
124
+ font-size: 16px;
125
+ font-weight: 700;
126
+ line-height: 1;
127
+ }
128
+ .hf-brand-product {
129
+ margin-top: 4px;
130
+ color: var(--hf-muted);
131
+ font-family: "IBM Plex Mono", ui-monospace, monospace;
132
+ font-size: 9px;
133
+ letter-spacing: .09em;
134
+ line-height: 1;
135
+ text-transform: uppercase;
136
+ }
137
+ .hf-design .dispatch-controls {
138
+ gap: 10px;
139
+ }
140
+ .hf-design .dispatch-stat {
141
+ color: var(--hf-muted);
142
+ font-size: 10px;
143
+ letter-spacing: 0;
144
+ text-transform: none;
145
+ }
146
+ .hf-design .dispatch-control-divider {
147
+ background: var(--hf-line);
148
+ }
149
+ .hf-design .dispatch-status {
150
+ min-height: 26px;
151
+ border: 1px solid transparent;
152
+ border-radius: 7px;
153
+ font-weight: 600;
154
+ letter-spacing: 0;
155
+ }
156
+ .hf-design .dispatch-status-running {
157
+ border-color: #fde68a;
158
+ background: #fffbeb;
159
+ color: #92400e;
160
+ }
161
+ .dark .hf-design .dispatch-status-running {
162
+ border-color: rgb(245 158 11 / .28);
163
+ background: rgb(245 158 11 / .12);
164
+ color: #fbbf24;
165
+ }
166
+ .hf-design .dispatch-status-complete {
167
+ border-color: #bbf7d0;
168
+ background: #f0fdf4;
169
+ color: #047857;
170
+ }
171
+ .dark .hf-design .dispatch-status-complete {
172
+ border-color: rgb(16 185 129 / .28);
173
+ background: rgb(16 185 129 / .12);
174
+ color: #34d399;
175
+ }
176
+ .hf-design .dispatch-status-failed {
177
+ border-color: #fecaca;
178
+ background: #fef2f2;
179
+ color: #b91c1c;
180
+ }
181
+ .dark .hf-design .dispatch-status-failed {
182
+ border-color: rgb(239 68 68 / .28);
183
+ background: rgb(239 68 68 / .12);
184
+ color: #fca5a5;
185
+ }
186
+ .hf-design .dispatch-header-separator,
187
+ .hf-design .dispatch-event,
188
+ .hf-design .dispatch-footer {
189
+ border-color: var(--hf-line);
190
+ }
191
+ .hf-design .dispatch-section-label {
192
+ color: var(--hf-muted);
193
+ font-size: 10px;
194
+ font-weight: 600;
195
+ letter-spacing: .08em;
196
+ }
197
+ .hf-design .dispatch-headline,
198
+ .hf-design .dispatch-query,
199
+ .hf-design .dispatch-report-card-title,
200
+ .hf-design .dispatch-report-card-copy,
201
+ .hf-design .dispatch-current-copy,
202
+ .hf-design .dispatch-event-message {
203
+ font-family: "Source Sans 3", "Source Sans Pro", ui-sans-serif, system-ui, sans-serif;
204
+ }
205
+ .hf-design .dispatch-headline {
206
+ color: var(--hf-text);
207
+ font-size: clamp(26px, 4vw, 34px);
208
+ font-weight: 700;
209
+ letter-spacing: -.025em;
210
+ }
211
+ .hf-design .dispatch-query {
212
+ color: var(--hf-muted);
213
+ font-size: 14px;
214
+ }
215
+ .hf-design .dispatch-report-card,
216
+ .hf-design .dispatch-markdown-report,
217
+ .hf-design .dispatch-log {
218
+ border-color: var(--hf-line);
219
+ background: var(--hf-surface-soft);
220
+ }
221
+ .hf-design .dispatch-report-card {
222
+ border-radius: 11px;
223
+ }
224
+ .hf-design .dispatch-report-card-title {
225
+ color: var(--hf-text);
226
+ font-weight: 600;
227
+ }
228
+ .hf-design .dispatch-report-card-copy,
229
+ .hf-design .dispatch-time,
230
+ .hf-design .dispatch-log-line,
231
+ .hf-design .dispatch-trace,
232
+ .hf-design .dispatch-meta {
233
+ color: var(--hf-muted);
234
+ }
235
+ .hf-design .dispatch-current-copy {
236
+ color: var(--hf-text);
237
+ font-size: 20px;
238
+ font-weight: 400;
239
+ }
240
+ .hf-design .dispatch-event-message {
241
+ color: var(--hf-text);
242
+ }
243
+ .hf-design .dispatch-source {
244
+ font-weight: 600;
245
+ }
246
+ .hf-design .dispatch-rule {
247
+ background: var(--hf-line);
248
+ }
249
+ .hf-design .dispatch-footer {
250
+ background: var(--hf-surface);
251
+ }
252
+ .hf-design .dispatch-log-toggle-button {
253
+ color: var(--hf-muted);
254
+ }
255
+
256
+ /* Option A: closest to the neutral Hub list and profile surfaces. */
257
+ .hf-hub-classic {
258
+ background:
259
+ linear-gradient(180deg, var(--hf-surface) 0 68px, var(--hf-canvas) 68px);
260
+ }
261
+ .hf-hub-classic .dispatch-sheet {
262
+ border-radius: 12px;
263
+ }
264
+ .hf-hub-classic .dispatch-header {
265
+ border-top: 3px solid var(--hf-yellow);
266
+ }
267
+ .hf-hub-classic .dispatch-current-block {
268
+ margin-top: 20px;
269
+ padding: 18px;
270
+ border: 1px solid var(--hf-line);
271
+ border-radius: 11px;
272
+ background: var(--hf-surface);
273
+ box-shadow: 0 1px 3px rgb(15 23 42 / .05);
274
+ }
275
+ .hf-hub-classic .dispatch-history {
276
+ margin-top: 18px;
277
+ }
278
+ .hf-hub-classic .dispatch-report-card {
279
+ background: linear-gradient(135deg, var(--hf-surface), var(--hf-surface-soft));
280
+ box-shadow: 0 1px 3px rgb(15 23 42 / .04);
281
+ }
282
+ .hf-hub-classic .dispatch-report-action {
283
+ border-color: var(--hf-line);
284
+ background: var(--hf-surface);
285
+ color: var(--hf-text);
286
+ }
287
+
288
+ /* Option B: borrows the colorful featured-card language from Spaces. */
289
+ .hf-spaces-gradient {
290
+ --dispatch-accent: #7c3aed;
291
+ background:
292
+ radial-gradient(circle at 15% 15%, rgb(255 210 30 / .14), transparent 28%),
293
+ radial-gradient(circle at 85% 5%, rgb(99 102 241 / .16), transparent 30%),
294
+ var(--hf-canvas);
295
+ }
296
+ .dark .hf-spaces-gradient {
297
+ --dispatch-accent: #c4b5fd;
298
+ }
299
+ .hf-spaces-gradient .dispatch-sheet {
300
+ overflow: hidden;
301
+ border-color: rgb(99 102 241 / .22);
302
+ border-radius: 20px;
303
+ }
304
+ .hf-spaces-gradient .dispatch-header {
305
+ position: relative;
306
+ background:
307
+ linear-gradient(135deg, rgb(255 210 30 / .12), rgb(99 102 241 / .10) 55%, rgb(236 72 153 / .08));
308
+ }
309
+ .dark .hf-spaces-gradient .dispatch-header {
310
+ background:
311
+ linear-gradient(135deg, rgb(245 158 11 / .12), rgb(79 70 229 / .18) 55%, rgb(190 24 93 / .12));
312
+ }
313
+ .hf-spaces-gradient .dispatch-header::after {
314
+ content: "";
315
+ position: absolute;
316
+ right: -50px;
317
+ bottom: -80px;
318
+ width: 180px;
319
+ height: 180px;
320
+ border: 28px solid rgb(255 210 30 / .16);
321
+ border-radius: 999px;
322
+ pointer-events: none;
323
+ }
324
+ .hf-spaces-gradient .dispatch-current-block {
325
+ margin-top: 22px;
326
+ padding: 22px;
327
+ overflow: hidden;
328
+ border-radius: 15px;
329
+ background: linear-gradient(135deg, #34469b, #4f46e5 52%, #7c3aed);
330
+ box-shadow: 0 12px 28px rgb(79 70 229 / .22);
331
+ }
332
+ .hf-spaces-gradient .dispatch-current-block .dispatch-time,
333
+ .hf-spaces-gradient .dispatch-current-block .dispatch-source,
334
+ .hf-spaces-gradient .dispatch-current-block .dispatch-current-copy {
335
+ color: #fff;
336
+ }
337
+ .hf-spaces-gradient .dispatch-current-block .dispatch-time {
338
+ opacity: .7;
339
+ }
340
+ .hf-spaces-gradient .dispatch-current-block .dispatch-live-dot {
341
+ background: var(--hf-yellow);
342
+ }
343
+ .hf-spaces-gradient .dispatch-current-block .dispatch-rule {
344
+ background: rgb(255 255 255 / .25);
345
+ }
346
+ .hf-spaces-gradient .dispatch-current-block .dispatch-rule-running::after,
347
+ .hf-spaces-gradient .dispatch-current-block .dispatch-rule-completed {
348
+ background: var(--hf-yellow);
349
+ }
350
+ .hf-spaces-gradient .dispatch-report-card {
351
+ border: 0;
352
+ background: linear-gradient(145deg, #34469b, #4f46e5);
353
+ color: #fff;
354
+ box-shadow: 0 8px 20px rgb(59 70 155 / .18);
355
+ }
356
+ .hf-spaces-gradient .dispatch-report-card:nth-child(2) {
357
+ background: linear-gradient(145deg, #6634b8, #c02675);
358
+ }
359
+ .hf-spaces-gradient .dispatch-report-card .dispatch-section-label,
360
+ .hf-spaces-gradient .dispatch-report-card-title,
361
+ .hf-spaces-gradient .dispatch-report-card-copy {
362
+ color: #fff;
363
+ }
364
+ .hf-spaces-gradient .dispatch-report-card .dispatch-section-label,
365
+ .hf-spaces-gradient .dispatch-report-card-copy {
366
+ opacity: .78;
367
+ }
368
+ .hf-spaces-gradient .dispatch-report-action {
369
+ border-color: rgb(255 255 255 / .32);
370
+ background: rgb(255 255 255 / .10);
371
+ color: #fff;
372
+ }
373
+
374
+ /* Option C: a compact, structured workspace inspired by dataset browsing. */
375
+ .hf-dataset-studio {
376
+ --dispatch-accent: #2563eb;
377
+ padding: 0;
378
+ background: var(--hf-surface);
379
+ }
380
+ .dark .hf-dataset-studio {
381
+ --dispatch-accent: #60a5fa;
382
+ }
383
+ .hf-dataset-studio .dispatch-sheet {
384
+ width: min(100%, 1040px);
385
+ height: 820px;
386
+ border-radius: 0;
387
+ box-shadow: none;
388
+ }
389
+ .hf-dataset-studio .dispatch-header {
390
+ padding: 20px 28px 16px;
391
+ border-bottom: 1px solid var(--hf-line);
392
+ background: var(--hf-surface);
393
+ }
394
+ .hf-dataset-studio .dispatch-header-separator {
395
+ display: none;
396
+ }
397
+ .hf-dataset-studio .dispatch-query-wrap {
398
+ margin-top: 18px;
399
+ padding: 18px 20px;
400
+ border: 1px solid var(--hf-line);
401
+ border-radius: 10px;
402
+ background: var(--hf-surface-soft);
403
+ }
404
+ .hf-dataset-studio .dispatch-headline {
405
+ font-size: clamp(23px, 3vw, 29px);
406
+ }
407
+ .hf-dataset-studio .dispatch-body {
408
+ padding: 18px 28px;
409
+ background:
410
+ linear-gradient(90deg, var(--hf-surface-soft) 0 4px, transparent 4px);
411
+ }
412
+ .hf-dataset-studio .dispatch-report-grid {
413
+ margin-top: 0;
414
+ }
415
+ .hf-dataset-studio .dispatch-report-card {
416
+ border-radius: 8px;
417
+ background: var(--hf-surface);
418
+ box-shadow: 0 1px 3px rgb(15 23 42 / .05);
419
+ }
420
+ .hf-dataset-studio .dispatch-current-block {
421
+ margin-top: 0;
422
+ padding: 18px 18px 20px;
423
+ border: 1px solid var(--hf-line);
424
+ border-radius: 9px;
425
+ background: var(--hf-surface);
426
+ }
427
+ .hf-dataset-studio .dispatch-current-meta {
428
+ padding-bottom: 10px;
429
+ border-bottom: 1px solid var(--hf-line);
430
+ }
431
+ .hf-dataset-studio .dispatch-current {
432
+ margin-top: 14px;
433
+ }
434
+ .hf-dataset-studio .dispatch-history {
435
+ margin-top: 18px;
436
+ padding: 16px 18px;
437
+ border: 1px solid var(--hf-line);
438
+ border-radius: 9px;
439
+ background: var(--hf-surface);
440
+ }
441
+ .hf-dataset-studio .dispatch-event {
442
+ margin-top: 8px;
443
+ padding: 10px 12px;
444
+ border: 1px solid var(--hf-line);
445
+ border-radius: 7px;
446
+ background: var(--hf-surface-soft);
447
+ opacity: 1;
448
+ }
449
+ .hf-dataset-studio .dispatch-event + .dispatch-event {
450
+ margin-top: 8px;
451
+ }
452
+ .hf-dataset-studio .dispatch-footer {
453
+ background: var(--hf-surface-soft);
454
+ }
455
+
456
+ @media (max-width: 720px) {
457
+ .hf-design {
458
+ min-height: 800px;
459
+ padding: 0;
460
+ }
461
+ .hf-design .dispatch-sheet {
462
+ height: 800px;
463
+ border-radius: 0;
464
+ }
465
+ .hf-design .dispatch-header {
466
+ padding: 18px 18px 16px;
467
+ }
468
+ .hf-design .dispatch-body {
469
+ padding-right: 18px;
470
+ padding-left: 18px;
471
+ }
472
+ .hf-design .dispatch-footer-log,
473
+ .hf-design .dispatch-session {
474
+ padding-right: 18px;
475
+ padding-left: 18px;
476
+ }
477
+ .hf-brand-name {
478
+ font-size: 15px;
479
+ }
480
+ .hf-spaces-gradient .dispatch-current-block,
481
+ .hf-hub-classic .dispatch-current-block {
482
+ padding: 17px;
483
+ }
484
+ .hf-dataset-studio .dispatch-query-wrap {
485
+ padding: 14px;
486
+ }
487
+ }
488
+ """
489
+
490
+
491
+ def design_css_class(design: HFDesign) -> str:
492
+ return f"dispatch-app hf-design hf-{design}"
493
+
494
+
495
+ def validate_design(design: str) -> HFDesign:
496
+ if design not in HF_DESIGNS:
497
+ choices = ", ".join(HF_DESIGNS)
498
+ raise ValueError(f"Unknown design {design!r}; expected one of: {choices}")
499
+ return design # type: ignore[return-value]