qqyule commited on
Commit
13e50c5
·
verified ·
1 Parent(s): 50169fb

feat(ui): polish gradio archive workbench

Browse files
Files changed (2) hide show
  1. src/ui/layout.py +92 -20
  2. src/ui/styles.css +182 -121
src/ui/layout.py CHANGED
@@ -21,31 +21,46 @@ from src.utils.zero_gpu import zero_gpu
21
 
22
  CHAT_EMPTY_MESSAGE = "Wake an object first."
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  OBJECT_FILE_EMPTY = """
25
  <div class="archive-empty">
26
  <span class="archive-label">Object File <span class="lang-zh">物品档案</span></span>
27
- <h3>No object awake yet.</h3>
28
- <p>Upload or describe an everyday object to open its secret archive.</p>
29
- <p class="lang-zh block">上传或描述一个日常物品后打开秘密档案。</p>
30
  </div>
31
  """
32
 
33
  DIARY_EMPTY = """
34
  ### Secret Diary
35
 
36
- Wake an object to open its diary.
37
 
38
  <div class="lang-zh block zh-helper">
39
- 唤醒物品后阅读它的记。
40
  </div>
41
  """
42
 
43
  SHARE_CARD_EMPTY = """
44
  <div class="objectverse-placeholder">
45
  <span>Share Card <span class="lang-zh">分享卡片</span></span>
46
- <strong>Waiting for an object file.</strong>
47
- <p>A screenshot-friendly archive card will appear here.</p>
48
- <p class="lang-zh block">可截图分享的档案卡片会显示在这里。</p>
49
  </div>
50
  """
51
 
@@ -218,6 +233,7 @@ GenerationUiResult = tuple[
218
  str,
219
  dict[str, Any] | None,
220
  list[dict[str, str]],
 
221
  ]
222
 
223
 
@@ -249,17 +265,17 @@ def build_app() -> gr.Blocks:
249
  link_text_color_active_dark="#F5D061",
250
  link_text_color_visited="#D4AF37",
251
  link_text_color_visited_dark="#D4AF37",
252
- block_background_fill="transparent",
253
- block_background_fill_dark="transparent",
254
- block_border_width="0px",
255
  block_info_text_color="#A89B84",
256
  block_info_text_color_dark="#A89B84",
257
  block_label_text_color="#A89B84",
258
  block_label_text_color_dark="#A89B84",
259
  block_title_text_color="#E6E1D3",
260
  block_title_text_color_dark="#E6E1D3",
261
- panel_background_fill="transparent",
262
- panel_background_fill_dark="transparent",
263
  accordion_text_color="#E6E1D3",
264
  accordion_text_color_dark="#E6E1D3",
265
  table_text_color="#E6E1D3",
@@ -310,12 +326,17 @@ def build_app() -> gr.Blocks:
310
  f"""
311
  <header id="objectverse-hero">
312
  <div class="hero-copy">
313
- <span class="archive-label">Small Model Object Archive</span>
314
  <h1>{APP_TITLE}</h1>
315
  <p class="hero-kicker">Every object has a secret life.</p>
316
- <p class="hero-feature">Build Small Hackathon entry: upload an object, wake its secret persona, read the diary, chat, and share the evidence. Tiny models, weird lives.</p>
317
  <p class="hero-kicker lang-zh block">万物日记:每个物品都有秘密人生。</p>
318
- <p class="hero-feature lang-zh block">Build Small 黑客松作品:上传物品,唤醒隐藏人格,读日记、追问它,再分享证据。小模型,怪人生。</p>
 
 
 
 
 
319
  </div>
320
  <div class="top-controls" aria-label="Display controls">
321
  <span>Language</span>
@@ -334,6 +355,7 @@ def build_app() -> gr.Blocks:
334
  zero_gpu_probe_output = gr.JSON(visible=False)
335
  vision_runtime_probe_button = gr.Button(visible=False)
336
  vision_runtime_probe_output = gr.JSON(visible=False)
 
337
 
338
  with gr.Row(elem_id="intake", elem_classes=["content-section", "top-grid"]):
339
  with gr.Column(scale=7, elem_classes=["archive-panel", "intake-panel"]):
@@ -346,7 +368,6 @@ def build_app() -> gr.Blocks:
346
  placeholder="Drop an object photo here or click to upload.",
347
  elem_id="object-upload",
348
  )
349
- gr.HTML("""<div class="or-divider"><span>OR</span></div>""", padding=False)
350
  description_input = gr.Textbox(
351
  label=copy.DESCRIPTION_LABEL,
352
  placeholder=copy.DESCRIPTION_PLACEHOLDER,
@@ -364,7 +385,7 @@ def build_app() -> gr.Blocks:
364
  elem_id="personality-mode",
365
  elem_classes=["mode-switch"],
366
  )
367
- generate_button = gr.Button("Wake the Object", variant="primary", elem_id="wake-button")
368
 
369
  with gr.Column(scale=4, elem_classes=["archive-panel", "examples-panel"]):
370
  gr.HTML(
@@ -374,6 +395,7 @@ def build_app() -> gr.Blocks:
374
  <strong>Example Objects</strong>
375
  <span class="lang-zh block">示例物品</span>
376
  </div>
 
377
  </div>
378
  """,
379
  padding=False,
@@ -390,7 +412,7 @@ def build_app() -> gr.Blocks:
390
 
391
  with gr.Row(elem_id="results", elem_classes=["content-section", "results-grid"]):
392
  with gr.Column(scale=5, elem_classes=["archive-panel", "file-panel"]):
393
- gr.HTML(_panel_header("02", "Object File", "Structured understanding and persona.", "物品档案"), padding=False)
394
  object_file_summary = gr.HTML(value=OBJECT_FILE_EMPTY, elem_id="object-file-summary", padding=False)
395
 
396
  with gr.Column(scale=6, elem_classes=["archive-panel", "diary-panel"]):
@@ -403,7 +425,7 @@ def build_app() -> gr.Blocks:
403
 
404
  with gr.Row(elem_id="share-chat", elem_classes=["content-section", "split-section"]):
405
  with gr.Column(scale=5, elem_classes=["archive-panel", "share-panel"], elem_id="share-panel"):
406
- gr.HTML(_panel_header("04", "Share Card", "Fixed-width card for screenshots.", "分享卡片"), padding=False)
407
  share_card = gr.HTML(value=SHARE_CARD_EMPTY, label=copy.SHARE_CARD_LABEL, padding=False)
408
 
409
  with gr.Column(scale=4, elem_classes=["archive-panel", "chat-panel"], elem_id="chat-panel"):
@@ -437,6 +459,7 @@ def build_app() -> gr.Blocks:
437
  trace_path,
438
  result_state,
439
  chatbot,
 
440
  ]
441
 
442
  generate_button.click(
@@ -532,6 +555,7 @@ def _format_generation_result(result: GenerationResult) -> GenerationUiResult:
532
  result.trace_path,
533
  result.model_dump(mode="json"),
534
  _awake_chat_history(result),
 
535
  )
536
 
537
 
@@ -541,11 +565,20 @@ def _render_object_file(result: GenerationResult) -> str:
541
  features = "".join(f"<li>{escape(feature)}</li>" for feature in obj.visible_features)
542
  tags = "".join(f"<span>{escape(tag)}</span>" for tag in persona.tags)
543
  confidence = f"{obj.confidence:.0%}"
 
 
 
544
  return f"""
545
  <article class="object-file-card">
 
 
 
 
 
546
  <div class="file-meta">
547
  <span>Confidence {escape(confidence)}</span>
548
  <span>{escape(result.trace.mode)}</span>
 
549
  </div>
550
  <h3>{escape(persona.character_name)}</h3>
551
  <p class="object-name">{escape(obj.name)} / {escape(persona.object_name)}</p>
@@ -573,6 +606,27 @@ def _render_object_file(result: GenerationResult) -> str:
573
  """
574
 
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  def _render_trace_summary(result: GenerationResult) -> str:
577
  return f"""
578
  <div class="trace-card">
@@ -615,9 +669,27 @@ def _generation_error(exc: Exception, description: str, mode: str) -> Generation
615
  "",
616
  None,
617
  [{"role": "assistant", "content": f"Generation failed: {error_type}"}],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  )
619
 
620
 
 
 
 
 
621
  def _empty_chat_history() -> list[dict[str, str]]:
622
  return [{"role": "assistant", "content": CHAT_EMPTY_MESSAGE}]
623
 
 
21
 
22
  CHAT_EMPTY_MESSAGE = "Wake an object first."
23
 
24
+ ARCHIVE_STATUS_EMPTY = """
25
+ <div class="archive-status asleep">
26
+ <div>
27
+ <span class="archive-label">Archive Status <span class="lang-zh">档案状态</span></span>
28
+ <strong>Object asleep</strong>
29
+ <p>Drop in a photo or use a sample object to open a case file.</p>
30
+ <p class="lang-zh block">上传照片或使用示例物品来开启档案。</p>
31
+ </div>
32
+ <div class="status-pills">
33
+ <span>Case pending</span>
34
+ <span>Tiny models ready</span>
35
+ </div>
36
+ </div>
37
+ """
38
+
39
  OBJECT_FILE_EMPTY = """
40
  <div class="archive-empty">
41
  <span class="archive-label">Object File <span class="lang-zh">物品档案</span></span>
42
+ <h3>No case opened yet.</h3>
43
+ <p>The shelf is quiet. Wake an everyday object to see its file.</p>
44
+ <p class="lang-zh block">档案架仍然安静。唤醒一个日常物品后查看档案。</p>
45
  </div>
46
  """
47
 
48
  DIARY_EMPTY = """
49
  ### Secret Diary
50
 
51
+ Wake an object to open its private field notes.
52
 
53
  <div class="lang-zh block zh-helper">
54
+ 唤醒物品后阅读它的秘密观察
55
  </div>
56
  """
57
 
58
  SHARE_CARD_EMPTY = """
59
  <div class="objectverse-placeholder">
60
  <span>Share Card <span class="lang-zh">分享卡片</span></span>
61
+ <strong>Evidence slot empty.</strong>
62
+ <p>A screenshot-friendly archive card appears after the object wakes.</p>
63
+ <p class="lang-zh block">物品醒来后,这里会出现可截图分享的档案卡片。</p>
64
  </div>
65
  """
66
 
 
233
  str,
234
  dict[str, Any] | None,
235
  list[dict[str, str]],
236
+ str,
237
  ]
238
 
239
 
 
265
  link_text_color_active_dark="#F5D061",
266
  link_text_color_visited="#D4AF37",
267
  link_text_color_visited_dark="#D4AF37",
268
+ block_background_fill="rgba(30, 28, 25, 0.72)",
269
+ block_background_fill_dark="rgba(30, 28, 25, 0.72)",
270
+ block_border_width="1px",
271
  block_info_text_color="#A89B84",
272
  block_info_text_color_dark="#A89B84",
273
  block_label_text_color="#A89B84",
274
  block_label_text_color_dark="#A89B84",
275
  block_title_text_color="#E6E1D3",
276
  block_title_text_color_dark="#E6E1D3",
277
+ panel_background_fill="rgba(30, 28, 25, 0.88)",
278
+ panel_background_fill_dark="rgba(30, 28, 25, 0.88)",
279
  accordion_text_color="#E6E1D3",
280
  accordion_text_color_dark="#E6E1D3",
281
  table_text_color="#E6E1D3",
 
326
  f"""
327
  <header id="objectverse-hero">
328
  <div class="hero-copy">
329
+ <span class="archive-label">Gradio Small Model Lab</span>
330
  <h1>{APP_TITLE}</h1>
331
  <p class="hero-kicker">Every object has a secret life.</p>
332
+ <p class="hero-feature">Upload a photo, wake a tiny object persona, read its diary, chat, and export the evidence.</p>
333
  <p class="hero-kicker lang-zh block">万物日记:每个物品都有秘密人生。</p>
334
+ <p class="hero-feature lang-zh block">上传照片,唤醒小模型生成的物品人格,读日记、对话并保存证据。</p>
335
+ <div class="hero-badges" aria-label="Project badges">
336
+ <span>Gradio Blocks</span>
337
+ <span>Mock-safe MVP</span>
338
+ <span>&lt; 32B params</span>
339
+ </div>
340
  </div>
341
  <div class="top-controls" aria-label="Display controls">
342
  <span>Language</span>
 
355
  zero_gpu_probe_output = gr.JSON(visible=False)
356
  vision_runtime_probe_button = gr.Button(visible=False)
357
  vision_runtime_probe_output = gr.JSON(visible=False)
358
+ archive_status = gr.HTML(value=ARCHIVE_STATUS_EMPTY, elem_id="archive-status", padding=False)
359
 
360
  with gr.Row(elem_id="intake", elem_classes=["content-section", "top-grid"]):
361
  with gr.Column(scale=7, elem_classes=["archive-panel", "intake-panel"]):
 
368
  placeholder="Drop an object photo here or click to upload.",
369
  elem_id="object-upload",
370
  )
 
371
  description_input = gr.Textbox(
372
  label=copy.DESCRIPTION_LABEL,
373
  placeholder=copy.DESCRIPTION_PLACEHOLDER,
 
385
  elem_id="personality-mode",
386
  elem_classes=["mode-switch"],
387
  )
388
+ generate_button = gr.Button(copy.GENERATE_LABEL, variant="primary", elem_id="wake-button")
389
 
390
  with gr.Column(scale=4, elem_classes=["archive-panel", "examples-panel"]):
391
  gr.HTML(
 
395
  <strong>Example Objects</strong>
396
  <span class="lang-zh block">示例物品</span>
397
  </div>
398
+ <span class="example-badge">6 filed samples</span>
399
  </div>
400
  """,
401
  padding=False,
 
412
 
413
  with gr.Row(elem_id="results", elem_classes=["content-section", "results-grid"]):
414
  with gr.Column(scale=5, elem_classes=["archive-panel", "file-panel"]):
415
+ gr.HTML(_panel_header("02", "Object File", "Structured understanding, persona, and evidence tags.", "物品档案"), padding=False)
416
  object_file_summary = gr.HTML(value=OBJECT_FILE_EMPTY, elem_id="object-file-summary", padding=False)
417
 
418
  with gr.Column(scale=6, elem_classes=["archive-panel", "diary-panel"]):
 
425
 
426
  with gr.Row(elem_id="share-chat", elem_classes=["content-section", "split-section"]):
427
  with gr.Column(scale=5, elem_classes=["archive-panel", "share-panel"], elem_id="share-panel"):
428
+ gr.HTML(_panel_header("04", "Share Card", "Screenshot-friendly field evidence.", "分享卡片"), padding=False)
429
  share_card = gr.HTML(value=SHARE_CARD_EMPTY, label=copy.SHARE_CARD_LABEL, padding=False)
430
 
431
  with gr.Column(scale=4, elem_classes=["archive-panel", "chat-panel"], elem_id="chat-panel"):
 
459
  trace_path,
460
  result_state,
461
  chatbot,
462
+ archive_status,
463
  ]
464
 
465
  generate_button.click(
 
555
  result.trace_path,
556
  result.model_dump(mode="json"),
557
  _awake_chat_history(result),
558
+ _render_archive_status(result),
559
  )
560
 
561
 
 
565
  features = "".join(f"<li>{escape(feature)}</li>" for feature in obj.visible_features)
566
  tags = "".join(f"<span>{escape(tag)}</span>" for tag in persona.tags)
567
  confidence = f"{obj.confidence:.0%}"
568
+ case_id = _case_id(result)
569
+ runtime_badge = "mock-safe" if "mock" in result.trace.model_runtime["text"] else "llama.cpp"
570
+ evidence_tag = f"{result.trace.mode.lower()} witness"
571
  return f"""
572
  <article class="object-file-card">
573
+ <div class="case-strip">
574
+ <span>Case ID {escape(case_id)}</span>
575
+ <span>Awake</span>
576
+ <span>{escape(runtime_badge)}</span>
577
+ </div>
578
  <div class="file-meta">
579
  <span>Confidence {escape(confidence)}</span>
580
  <span>{escape(result.trace.mode)}</span>
581
+ <span>Evidence: {escape(evidence_tag)}</span>
582
  </div>
583
  <h3>{escape(persona.character_name)}</h3>
584
  <p class="object-name">{escape(obj.name)} / {escape(persona.object_name)}</p>
 
606
  """
607
 
608
 
609
+ def _render_archive_status(result: GenerationResult) -> str:
610
+ obj = result.object_understanding.object
611
+ persona = result.persona.persona
612
+ case_id = _case_id(result)
613
+ return f"""
614
+ <div class="archive-status awake">
615
+ <div>
616
+ <span class="archive-label">Archive Status <span class="lang-zh">档案状态</span></span>
617
+ <strong>{escape(persona.character_name)} is awake</strong>
618
+ <p>Case {escape(case_id)} opened for {escape(obj.name)} with {obj.confidence:.0%} object confidence.</p>
619
+ <p class="lang-zh block">档案 {escape(case_id)} 已开启,物品识别置信度 {obj.confidence:.0%}。</p>
620
+ </div>
621
+ <div class="status-pills">
622
+ <span>Object awake</span>
623
+ <span>Diary unlocked</span>
624
+ <span>{escape(result.trace.mode)} mode</span>
625
+ </div>
626
+ </div>
627
+ """
628
+
629
+
630
  def _render_trace_summary(result: GenerationResult) -> str:
631
  return f"""
632
  <div class="trace-card">
 
669
  "",
670
  None,
671
  [{"role": "assistant", "content": f"Generation failed: {error_type}"}],
672
+ f"""
673
+ <div class="archive-status error">
674
+ <div>
675
+ <span class="archive-label">Archive Status <span class="lang-zh">档案状态</span></span>
676
+ <strong>Case jammed</strong>
677
+ <p>{escape(error_type)}: {escape(error_message)}</p>
678
+ <p class="lang-zh block">生成失败,请换一个描述或示例物品再试。</p>
679
+ </div>
680
+ <div class="status-pills">
681
+ <span>Needs retry</span>
682
+ <span>{escape(mode)}</span>
683
+ </div>
684
+ </div>
685
+ """,
686
  )
687
 
688
 
689
+ def _case_id(result: GenerationResult) -> str:
690
+ return result.trace.trace_id.replace("_", "-").upper()
691
+
692
+
693
  def _empty_chat_history() -> list[dict[str, str]]:
694
  return [{"role": "assistant", "content": CHAT_EMPTY_MESSAGE}]
695
 
src/ui/styles.css CHANGED
@@ -1,21 +1,23 @@
1
  /*
2
- * Objectverse Diary - compact archive UI.
3
  */
4
 
5
- @import url('https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Courier+Prime:ital,wght@0,400;0,700;1,400&display=swap');
6
 
7
  :root {
8
  --ov-bg: #161513;
9
- --ov-bg-panel: rgba(30, 28, 25, 0.72);
 
10
  --ov-bg-input: #1b1a18;
11
- --ov-border-faint: rgba(212, 175, 55, 0.15);
12
  --ov-border-light: rgba(212, 175, 55, 0.34);
13
  --ov-text-main: #e6e1d3;
14
  --ov-text-soft: #d6d1c4;
15
- --ov-text-muted: #8b8678;
16
  --ov-text-dark: #2a261f;
17
  --ov-gold: #d4af37;
18
  --ov-gold-bright: #f5d061;
 
19
  --font-typewriter: 'Courier Prime', 'Space Mono', 'Courier New', monospace;
20
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
21
  --font-serif: Georgia, serif;
@@ -38,20 +40,20 @@ html[data-ov-lang="zh"] .lang-zh.block {
38
  html,
39
  body,
40
  gradio-app {
41
- background: var(--ov-bg);
42
- color: var(--ov-text-main);
43
  min-height: 100%;
44
  margin: 0;
45
  overflow-x: hidden;
 
 
46
  }
47
 
48
  body::before {
49
  content: "";
50
  position: fixed;
51
  inset: 0;
52
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.03'/%3E%3C/svg%3E");
53
- pointer-events: none;
54
  z-index: 9999;
 
 
55
  }
56
 
57
  .gradio-container {
@@ -79,8 +81,8 @@ footer,
79
  #app-container {
80
  max-width: 1180px;
81
  margin: 0 auto !important;
82
- padding: 36px 24px 56px;
83
- gap: 24px !important;
84
  }
85
 
86
  #objectverse-hero {
@@ -88,7 +90,7 @@ footer,
88
  justify-content: space-between;
89
  align-items: flex-start;
90
  gap: 24px;
91
- padding-bottom: 22px;
92
  border-bottom: 1px solid var(--ov-border-faint);
93
  }
94
 
@@ -96,8 +98,8 @@ footer,
96
  margin: 6px 0 8px;
97
  color: var(--ov-text-main) !important;
98
  font-family: var(--font-typewriter);
99
- font-size: clamp(32px, 5vw, 48px);
100
- line-height: 1.05;
101
  letter-spacing: 0;
102
  }
103
 
@@ -105,16 +107,52 @@ footer,
105
  margin: 0;
106
  color: var(--ov-gold) !important;
107
  font-family: var(--font-serif);
108
- font-size: 18px;
109
  font-style: italic;
110
  }
111
 
112
  .hero-feature {
113
- max-width: 680px;
114
- margin: 12px 0 0;
115
  color: var(--ov-text-soft) !important;
116
  font-size: 15px;
117
- line-height: 1.65;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
  .archive-label {
@@ -139,17 +177,18 @@ footer,
139
  display: grid;
140
  grid-template-columns: 1fr 1fr;
141
  min-width: 92px;
142
- border: 1px solid var(--ov-border-light);
143
- border-radius: 4px;
144
  overflow: hidden;
 
 
 
145
  }
146
 
147
  .segmented-control button {
148
  min-height: 34px;
149
  padding: 0 12px;
150
- background: transparent;
151
  border: 0;
152
  border-right: 1px solid var(--ov-border-faint);
 
153
  color: var(--ov-text-muted);
154
  font-family: var(--font-typewriter);
155
  font-size: 12px;
@@ -163,33 +202,73 @@ footer,
163
  .segmented-control button:hover,
164
  .segmented-control button:focus,
165
  .segmented-control button.active {
166
- color: var(--ov-gold);
167
- background: rgba(212, 175, 55, 0.08);
168
  outline: none;
169
  }
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  .content-section {
172
  display: flex !important;
173
- gap: 24px !important;
174
- margin: 0 0 2px;
175
  }
176
 
177
  .archive-panel {
178
  position: relative;
179
- padding: 22px;
180
- background: var(--ov-bg-panel) !important;
181
  border: 1px solid var(--ov-border-faint) !important;
182
  border-radius: 8px;
 
183
  }
184
 
185
  .gradio-container .block,
186
  .gradio-container .form,
187
  .gradio-container .box {
188
- background: transparent !important;
189
- border: none !important;
 
190
  box-shadow: none !important;
191
  }
192
 
 
 
 
 
193
  .gradio-container label,
194
  .gradio-container span.svelte-1gfknul {
195
  color: var(--ov-text-muted) !important;
@@ -198,9 +277,9 @@ footer,
198
 
199
  .gradio-container input,
200
  .gradio-container textarea {
 
 
201
  background: var(--ov-bg-input) !important;
202
- border: 1px solid var(--ov-border-light) !important;
203
- border-radius: 4px !important;
204
  color: var(--ov-text-main) !important;
205
  font-family: var(--font-sans) !important;
206
  }
@@ -208,18 +287,14 @@ footer,
208
  .gradio-container input:focus,
209
  .gradio-container textarea:focus {
210
  border-color: var(--ov-gold) !important;
211
- box-shadow: none !important;
212
  }
213
 
214
  #object-upload {
215
- display: flex;
216
- align-items: center;
217
- justify-content: center;
218
  min-height: 176px;
219
- padding: 34px 20px;
220
- border: 2px dashed var(--ov-border-light) !important;
221
  border-radius: 8px;
222
- background: transparent !important;
223
  color: var(--ov-text-muted) !important;
224
  text-align: center;
225
  }
@@ -228,37 +303,11 @@ footer,
228
  color: var(--ov-text-muted) !important;
229
  }
230
 
231
- .or-divider {
232
- position: relative;
233
- margin: 18px 0;
234
- text-align: center;
235
- }
236
-
237
- .or-divider::before {
238
- content: "";
239
- position: absolute;
240
- top: 50%;
241
- right: 0;
242
- left: 0;
243
- height: 1px;
244
- background: var(--ov-border-faint);
245
- }
246
-
247
- .or-divider span {
248
- position: relative;
249
- z-index: 1;
250
- padding: 0 14px;
251
- background: var(--ov-bg-panel);
252
- color: var(--ov-text-muted);
253
- font-family: var(--font-typewriter);
254
- font-size: 13px;
255
- }
256
-
257
  .mode-header {
258
  display: flex;
259
  align-items: center;
260
  gap: 8px;
261
- margin: 18px 0 12px;
262
  color: var(--ov-text-main) !important;
263
  font-family: var(--font-typewriter);
264
  }
@@ -272,16 +321,16 @@ footer,
272
  #personality-mode .wrap {
273
  display: flex !important;
274
  flex-wrap: wrap !important;
275
- gap: 10px !important;
276
  }
277
 
278
  #personality-mode label {
279
  flex: 1 1 120px;
280
- min-height: 48px;
281
- padding: 13px 10px !important;
282
- border: 1px solid var(--ov-border-light) !important;
283
  border-radius: 6px !important;
284
- background: transparent !important;
285
  text-align: center;
286
  cursor: pointer;
287
  }
@@ -290,13 +339,13 @@ footer,
290
  display: block;
291
  color: var(--ov-text-main) !important;
292
  font-family: var(--font-typewriter);
293
- font-size: 14px;
294
  }
295
 
296
  #personality-mode label:has(input:checked) {
297
  border-color: var(--ov-gold) !important;
298
- background: rgba(212, 175, 55, 0.06) !important;
299
- box-shadow: 0 0 0 1px var(--ov-gold) inset;
300
  }
301
 
302
  #personality-mode label:has(input:checked) span {
@@ -305,26 +354,29 @@ footer,
305
 
306
  #wake-button {
307
  width: 100%;
308
- margin-top: 22px;
309
- padding: 18px !important;
310
- border: 0 !important;
311
- border-radius: 4px !important;
312
  background: linear-gradient(180deg, #d8ac54 0%, #a67c2d 100%) !important;
313
  color: var(--ov-text-dark) !important;
314
  font-family: var(--font-typewriter);
315
- font-size: 18px !important;
316
  font-weight: 700;
317
- box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.28), 0 4px 14px rgba(0, 0, 0, 0.34) !important;
318
  }
319
 
320
  #wake-button:hover {
321
- filter: brightness(1.08);
322
- transform: translateY(-1px);
323
  }
324
 
325
  .example-header {
326
- margin-bottom: 18px;
327
- padding-bottom: 14px;
 
 
 
 
328
  border-bottom: 1px solid var(--ov-border-faint);
329
  }
330
 
@@ -336,7 +388,7 @@ footer,
336
  font-weight: 400;
337
  }
338
 
339
- .example-header span {
340
  color: var(--ov-text-muted) !important;
341
  font-size: 13px;
342
  }
@@ -344,11 +396,12 @@ footer,
344
  button.example-card {
345
  display: block;
346
  width: 100%;
347
- margin-bottom: 10px !important;
348
- padding: 14px !important;
 
349
  border: 1px solid var(--ov-border-faint) !important;
350
- border-radius: 4px !important;
351
- background: var(--ov-bg-input) !important;
352
  color: var(--ov-text-main) !important;
353
  font-family: var(--font-typewriter) !important;
354
  text-align: left !important;
@@ -358,6 +411,7 @@ button.example-card {
358
  button.example-card:hover,
359
  button.example-card:focus {
360
  border-color: var(--ov-gold) !important;
 
361
  }
362
 
363
  button.example-card * {
@@ -367,24 +421,24 @@ button.example-card * {
367
  .panel-header {
368
  display: grid;
369
  grid-template-columns: auto 1fr;
370
- gap: 14px;
371
- margin-bottom: 18px;
372
- padding-bottom: 14px;
373
  border-bottom: 1px solid var(--ov-border-faint);
374
  }
375
 
376
  .panel-header > span {
377
  color: var(--ov-gold) !important;
378
  font-family: var(--font-typewriter);
379
- font-size: 16px;
380
  }
381
 
382
  .panel-header h2 {
383
  margin: 0 0 4px;
384
  color: var(--ov-text-main) !important;
385
  font-family: var(--font-typewriter);
386
- font-size: 22px;
387
- line-height: 1.2;
388
  }
389
 
390
  .panel-header h2 small,
@@ -402,7 +456,7 @@ button.example-card * {
402
  .diary-entry {
403
  color: var(--ov-text-soft) !important;
404
  font-family: var(--font-serif) !important;
405
- font-size: 17px;
406
  line-height: 1.75;
407
  }
408
 
@@ -412,7 +466,7 @@ button.example-card * {
412
  margin: 0 0 14px;
413
  color: var(--ov-gold) !important;
414
  font-family: var(--font-typewriter);
415
- font-size: 17px;
416
  line-height: 1.3;
417
  text-transform: uppercase;
418
  }
@@ -433,9 +487,11 @@ button.example-card * {
433
 
434
  .archive-empty,
435
  .objectverse-placeholder {
436
- padding: 34px 24px;
437
  border: 1px dashed var(--ov-border-light);
 
438
  color: var(--ov-text-muted) !important;
 
439
  text-align: center;
440
  }
441
 
@@ -467,14 +523,14 @@ button.example-card * {
467
  color: var(--ov-text-main) !important;
468
  }
469
 
 
 
 
 
470
  .file-meta {
471
- display: flex;
472
- flex-wrap: wrap;
473
- gap: 8px;
474
  margin-bottom: 12px;
475
  }
476
 
477
- .file-meta span,
478
  .object-name,
479
  .object-file-card dt,
480
  .object-file-card p,
@@ -527,22 +583,9 @@ button.example-card * {
527
 
528
  .file-tags,
529
  .card-tags {
530
- display: flex;
531
- flex-wrap: wrap;
532
- gap: 8px;
533
  margin-top: 16px;
534
  }
535
 
536
- .file-tags span,
537
- .card-tags span {
538
- padding: 4px 8px;
539
- border: 1px solid var(--ov-border-light) !important;
540
- border-radius: 999px;
541
- color: var(--ov-gold-bright) !important;
542
- font-family: var(--font-typewriter);
543
- font-size: 12px;
544
- }
545
-
546
  .objectverse-card {
547
  max-width: 520px;
548
  padding: 22px;
@@ -565,7 +608,7 @@ button.example-card * {
565
 
566
  .card-quote {
567
  font-family: var(--font-serif);
568
- font-size: 17px;
569
  line-height: 1.65;
570
  }
571
 
@@ -577,8 +620,8 @@ button.example-card * {
577
 
578
  .quiet-button {
579
  border: 1px solid var(--ov-border-light) !important;
580
- border-radius: 4px !important;
581
- background: transparent !important;
582
  color: var(--ov-gold) !important;
583
  font-family: var(--font-typewriter) !important;
584
  }
@@ -586,7 +629,7 @@ button.example-card * {
586
  .developer-details {
587
  border: 1px solid var(--ov-border-faint) !important;
588
  border-radius: 8px !important;
589
- background: rgba(30, 28, 25, 0.42) !important;
590
  }
591
 
592
  .developer-json-grid {
@@ -601,6 +644,10 @@ button.example-card * {
601
  color: var(--ov-text-main) !important;
602
  }
603
 
 
 
 
 
604
  @media (max-width: 980px) {
605
  #app-container {
606
  padding: 28px 18px 44px;
@@ -609,12 +656,17 @@ button.example-card * {
609
  #objectverse-hero,
610
  .content-section,
611
  .split-section,
612
- .developer-json-grid {
 
613
  flex-direction: column !important;
614
  }
615
 
616
- .top-controls {
 
617
  width: 100%;
 
 
 
618
  justify-content: space-between;
619
  }
620
  }
@@ -625,7 +677,7 @@ button.example-card * {
625
  }
626
 
627
  .archive-panel {
628
- padding: 18px;
629
  }
630
 
631
  .hero-copy h1 {
@@ -642,6 +694,15 @@ button.example-card * {
642
  line-height: 1.55;
643
  }
644
 
 
 
 
 
 
 
 
 
 
645
  #personality-mode label {
646
  flex: 1 1 45% !important;
647
  padding: 10px 6px !important;
 
1
  /*
2
+ * Objectverse Diary - Gradio archive workbench UI.
3
  */
4
 
5
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Courier+Prime:ital,wght@0,400;0,700;1,400&display=swap');
6
 
7
  :root {
8
  --ov-bg: #161513;
9
+ --ov-bg-panel: rgba(30, 28, 25, 0.82);
10
+ --ov-bg-panel-solid: #1f1d1a;
11
  --ov-bg-input: #1b1a18;
12
+ --ov-border-faint: rgba(212, 175, 55, 0.16);
13
  --ov-border-light: rgba(212, 175, 55, 0.34);
14
  --ov-text-main: #e6e1d3;
15
  --ov-text-soft: #d6d1c4;
16
+ --ov-text-muted: #9d927d;
17
  --ov-text-dark: #2a261f;
18
  --ov-gold: #d4af37;
19
  --ov-gold-bright: #f5d061;
20
+ --ov-red: #d76b55;
21
  --font-typewriter: 'Courier Prime', 'Space Mono', 'Courier New', monospace;
22
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
23
  --font-serif: Georgia, serif;
 
40
  html,
41
  body,
42
  gradio-app {
 
 
43
  min-height: 100%;
44
  margin: 0;
45
  overflow-x: hidden;
46
+ background: var(--ov-bg);
47
+ color: var(--ov-text-main);
48
  }
49
 
50
  body::before {
51
  content: "";
52
  position: fixed;
53
  inset: 0;
 
 
54
  z-index: 9999;
55
+ pointer-events: none;
56
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.025'/%3E%3C/svg%3E");
57
  }
58
 
59
  .gradio-container {
 
81
  #app-container {
82
  max-width: 1180px;
83
  margin: 0 auto !important;
84
+ padding: 34px 24px 56px;
85
+ gap: 18px !important;
86
  }
87
 
88
  #objectverse-hero {
 
90
  justify-content: space-between;
91
  align-items: flex-start;
92
  gap: 24px;
93
+ padding: 22px 0 18px;
94
  border-bottom: 1px solid var(--ov-border-faint);
95
  }
96
 
 
98
  margin: 6px 0 8px;
99
  color: var(--ov-text-main) !important;
100
  font-family: var(--font-typewriter);
101
+ font-size: 42px;
102
+ line-height: 1.08;
103
  letter-spacing: 0;
104
  }
105
 
 
107
  margin: 0;
108
  color: var(--ov-gold) !important;
109
  font-family: var(--font-serif);
110
+ font-size: 17px;
111
  font-style: italic;
112
  }
113
 
114
  .hero-feature {
115
+ max-width: 660px;
116
+ margin: 10px 0 0;
117
  color: var(--ov-text-soft) !important;
118
  font-size: 15px;
119
+ line-height: 1.6;
120
+ }
121
+
122
+ .hero-badges,
123
+ .status-pills,
124
+ .case-strip,
125
+ .file-meta,
126
+ .file-tags,
127
+ .card-tags {
128
+ display: flex;
129
+ flex-wrap: wrap;
130
+ gap: 8px;
131
+ }
132
+
133
+ .hero-badges {
134
+ margin-top: 14px;
135
+ }
136
+
137
+ .hero-badges span,
138
+ .status-pills span,
139
+ .case-strip span,
140
+ .file-meta span,
141
+ .file-tags span,
142
+ .card-tags span,
143
+ .example-badge {
144
+ display: inline-flex;
145
+ align-items: center;
146
+ min-height: 24px;
147
+ padding: 3px 8px;
148
+ border: 1px solid var(--ov-border-light);
149
+ border-radius: 999px;
150
+ color: var(--ov-gold-bright) !important;
151
+ background: rgba(212, 175, 55, 0.06);
152
+ font-family: var(--font-typewriter);
153
+ font-size: 12px;
154
+ line-height: 1.2;
155
+ white-space: normal;
156
  }
157
 
158
  .archive-label {
 
177
  display: grid;
178
  grid-template-columns: 1fr 1fr;
179
  min-width: 92px;
 
 
180
  overflow: hidden;
181
+ border: 1px solid var(--ov-border-light);
182
+ border-radius: 6px;
183
+ background: rgba(22, 21, 19, 0.65);
184
  }
185
 
186
  .segmented-control button {
187
  min-height: 34px;
188
  padding: 0 12px;
 
189
  border: 0;
190
  border-right: 1px solid var(--ov-border-faint);
191
+ background: transparent;
192
  color: var(--ov-text-muted);
193
  font-family: var(--font-typewriter);
194
  font-size: 12px;
 
202
  .segmented-control button:hover,
203
  .segmented-control button:focus,
204
  .segmented-control button.active {
205
+ color: var(--ov-text-dark);
206
+ background: var(--ov-gold);
207
  outline: none;
208
  }
209
 
210
+ .archive-status {
211
+ display: flex;
212
+ justify-content: space-between;
213
+ align-items: center;
214
+ gap: 18px;
215
+ padding: 14px 16px;
216
+ border: 1px solid var(--ov-border-faint);
217
+ border-left: 4px solid var(--ov-border-light);
218
+ border-radius: 8px;
219
+ background: rgba(30, 28, 25, 0.72);
220
+ }
221
+
222
+ .archive-status strong {
223
+ display: block;
224
+ margin: 4px 0;
225
+ color: var(--ov-text-main) !important;
226
+ font-family: var(--font-typewriter);
227
+ font-size: 16px;
228
+ }
229
+
230
+ .archive-status p {
231
+ margin: 0;
232
+ color: var(--ov-text-muted) !important;
233
+ font-size: 13px;
234
+ line-height: 1.5;
235
+ }
236
+
237
+ .archive-status.awake {
238
+ border-left-color: var(--ov-gold);
239
+ }
240
+
241
+ .archive-status.error {
242
+ border-left-color: var(--ov-red);
243
+ }
244
+
245
  .content-section {
246
  display: flex !important;
247
+ gap: 18px !important;
248
+ margin: 0;
249
  }
250
 
251
  .archive-panel {
252
  position: relative;
253
+ padding: 18px;
 
254
  border: 1px solid var(--ov-border-faint) !important;
255
  border-radius: 8px;
256
+ background: var(--ov-bg-panel) !important;
257
  }
258
 
259
  .gradio-container .block,
260
  .gradio-container .form,
261
  .gradio-container .box {
262
+ border-color: var(--ov-border-faint) !important;
263
+ border-radius: 8px !important;
264
+ background: rgba(22, 21, 19, 0.52) !important;
265
  box-shadow: none !important;
266
  }
267
 
268
+ .gradio-container .form {
269
+ gap: 12px !important;
270
+ }
271
+
272
  .gradio-container label,
273
  .gradio-container span.svelte-1gfknul {
274
  color: var(--ov-text-muted) !important;
 
277
 
278
  .gradio-container input,
279
  .gradio-container textarea {
280
+ border: 1px solid rgba(212, 175, 55, 0.25) !important;
281
+ border-radius: 6px !important;
282
  background: var(--ov-bg-input) !important;
 
 
283
  color: var(--ov-text-main) !important;
284
  font-family: var(--font-sans) !important;
285
  }
 
287
  .gradio-container input:focus,
288
  .gradio-container textarea:focus {
289
  border-color: var(--ov-gold) !important;
290
+ box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.18) !important;
291
  }
292
 
293
  #object-upload {
 
 
 
294
  min-height: 176px;
295
+ border: 1px dashed var(--ov-border-light) !important;
 
296
  border-radius: 8px;
297
+ background: rgba(22, 21, 19, 0.38) !important;
298
  color: var(--ov-text-muted) !important;
299
  text-align: center;
300
  }
 
303
  color: var(--ov-text-muted) !important;
304
  }
305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  .mode-header {
307
  display: flex;
308
  align-items: center;
309
  gap: 8px;
310
+ margin: 16px 0 10px;
311
  color: var(--ov-text-main) !important;
312
  font-family: var(--font-typewriter);
313
  }
 
321
  #personality-mode .wrap {
322
  display: flex !important;
323
  flex-wrap: wrap !important;
324
+ gap: 8px !important;
325
  }
326
 
327
  #personality-mode label {
328
  flex: 1 1 120px;
329
+ min-height: 44px;
330
+ padding: 10px !important;
331
+ border: 1px solid var(--ov-border-faint) !important;
332
  border-radius: 6px !important;
333
+ background: rgba(22, 21, 19, 0.36) !important;
334
  text-align: center;
335
  cursor: pointer;
336
  }
 
339
  display: block;
340
  color: var(--ov-text-main) !important;
341
  font-family: var(--font-typewriter);
342
+ font-size: 13px;
343
  }
344
 
345
  #personality-mode label:has(input:checked) {
346
  border-color: var(--ov-gold) !important;
347
+ background: rgba(212, 175, 55, 0.1) !important;
348
+ box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.35) inset;
349
  }
350
 
351
  #personality-mode label:has(input:checked) span {
 
354
 
355
  #wake-button {
356
  width: 100%;
357
+ margin-top: 18px;
358
+ padding: 15px !important;
359
+ border: 1px solid rgba(245, 208, 97, 0.4) !important;
360
+ border-radius: 6px !important;
361
  background: linear-gradient(180deg, #d8ac54 0%, #a67c2d 100%) !important;
362
  color: var(--ov-text-dark) !important;
363
  font-family: var(--font-typewriter);
364
+ font-size: 16px !important;
365
  font-weight: 700;
366
+ box-shadow: none !important;
367
  }
368
 
369
  #wake-button:hover {
370
+ filter: brightness(1.07);
 
371
  }
372
 
373
  .example-header {
374
+ display: flex;
375
+ justify-content: space-between;
376
+ align-items: flex-start;
377
+ gap: 12px;
378
+ margin-bottom: 14px;
379
+ padding-bottom: 12px;
380
  border-bottom: 1px solid var(--ov-border-faint);
381
  }
382
 
 
388
  font-weight: 400;
389
  }
390
 
391
+ .example-header span:not(.example-badge) {
392
  color: var(--ov-text-muted) !important;
393
  font-size: 13px;
394
  }
 
396
  button.example-card {
397
  display: block;
398
  width: 100%;
399
+ min-height: 42px;
400
+ margin-bottom: 8px !important;
401
+ padding: 11px 12px !important;
402
  border: 1px solid var(--ov-border-faint) !important;
403
+ border-radius: 6px !important;
404
+ background: rgba(22, 21, 19, 0.58) !important;
405
  color: var(--ov-text-main) !important;
406
  font-family: var(--font-typewriter) !important;
407
  text-align: left !important;
 
411
  button.example-card:hover,
412
  button.example-card:focus {
413
  border-color: var(--ov-gold) !important;
414
+ background: rgba(212, 175, 55, 0.08) !important;
415
  }
416
 
417
  button.example-card * {
 
421
  .panel-header {
422
  display: grid;
423
  grid-template-columns: auto 1fr;
424
+ gap: 12px;
425
+ margin-bottom: 14px;
426
+ padding-bottom: 12px;
427
  border-bottom: 1px solid var(--ov-border-faint);
428
  }
429
 
430
  .panel-header > span {
431
  color: var(--ov-gold) !important;
432
  font-family: var(--font-typewriter);
433
+ font-size: 15px;
434
  }
435
 
436
  .panel-header h2 {
437
  margin: 0 0 4px;
438
  color: var(--ov-text-main) !important;
439
  font-family: var(--font-typewriter);
440
+ font-size: 20px;
441
+ line-height: 1.25;
442
  }
443
 
444
  .panel-header h2 small,
 
456
  .diary-entry {
457
  color: var(--ov-text-soft) !important;
458
  font-family: var(--font-serif) !important;
459
+ font-size: 16px;
460
  line-height: 1.75;
461
  }
462
 
 
466
  margin: 0 0 14px;
467
  color: var(--ov-gold) !important;
468
  font-family: var(--font-typewriter);
469
+ font-size: 16px;
470
  line-height: 1.3;
471
  text-transform: uppercase;
472
  }
 
487
 
488
  .archive-empty,
489
  .objectverse-placeholder {
490
+ padding: 28px 22px;
491
  border: 1px dashed var(--ov-border-light);
492
+ border-radius: 8px;
493
  color: var(--ov-text-muted) !important;
494
+ background: rgba(22, 21, 19, 0.32);
495
  text-align: center;
496
  }
497
 
 
523
  color: var(--ov-text-main) !important;
524
  }
525
 
526
+ .case-strip {
527
+ margin-bottom: 12px;
528
+ }
529
+
530
  .file-meta {
 
 
 
531
  margin-bottom: 12px;
532
  }
533
 
 
534
  .object-name,
535
  .object-file-card dt,
536
  .object-file-card p,
 
583
 
584
  .file-tags,
585
  .card-tags {
 
 
 
586
  margin-top: 16px;
587
  }
588
 
 
 
 
 
 
 
 
 
 
 
589
  .objectverse-card {
590
  max-width: 520px;
591
  padding: 22px;
 
608
 
609
  .card-quote {
610
  font-family: var(--font-serif);
611
+ font-size: 16px;
612
  line-height: 1.65;
613
  }
614
 
 
620
 
621
  .quiet-button {
622
  border: 1px solid var(--ov-border-light) !important;
623
+ border-radius: 6px !important;
624
+ background: rgba(22, 21, 19, 0.45) !important;
625
  color: var(--ov-gold) !important;
626
  font-family: var(--font-typewriter) !important;
627
  }
 
629
  .developer-details {
630
  border: 1px solid var(--ov-border-faint) !important;
631
  border-radius: 8px !important;
632
+ background: rgba(30, 28, 25, 0.58) !important;
633
  }
634
 
635
  .developer-json-grid {
 
644
  color: var(--ov-text-main) !important;
645
  }
646
 
647
+ .gradio-container :is(.chatbot, .json-holder, pre) {
648
+ background: rgba(22, 21, 19, 0.5) !important;
649
+ }
650
+
651
  @media (max-width: 980px) {
652
  #app-container {
653
  padding: 28px 18px 44px;
 
656
  #objectverse-hero,
657
  .content-section,
658
  .split-section,
659
+ .developer-json-grid,
660
+ .archive-status {
661
  flex-direction: column !important;
662
  }
663
 
664
+ .top-controls,
665
+ .archive-status {
666
  width: 100%;
667
+ }
668
+
669
+ .top-controls {
670
  justify-content: space-between;
671
  }
672
  }
 
677
  }
678
 
679
  .archive-panel {
680
+ padding: 16px;
681
  }
682
 
683
  .hero-copy h1 {
 
694
  line-height: 1.55;
695
  }
696
 
697
+ .hero-badges span,
698
+ .status-pills span,
699
+ .case-strip span,
700
+ .file-meta span,
701
+ .file-tags span,
702
+ .card-tags span {
703
+ font-size: 11px;
704
+ }
705
+
706
  #personality-mode label {
707
  flex: 1 1 45% !important;
708
  padding: 10px 6px !important;