rahul7star commited on
Commit
78e102b
·
verified ·
1 Parent(s): 99f33f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +295 -181
app.py CHANGED
@@ -10,7 +10,7 @@ from huggingface_hub import HfApi, hf_hub_download
10
  # =========================================================
11
  REPO_ID = os.getenv("REPO_ID")
12
  REPO_TYPE = "dataset"
13
- HF_TOKEN = os.getenv("HF_TOKEN") # required for private repos
14
 
15
  api = HfApi(token=HF_TOKEN)
16
 
@@ -19,8 +19,6 @@ if not REPO_ID:
19
  if not HF_TOKEN:
20
  raise RuntimeError("HF_TOKEN environment variable is not set")
21
 
22
- # IMPORTANT:
23
- # Gradio accepts files from /tmp, so force HF downloads there
24
  LOCAL_CACHE_DIR = "/tmp/story_showcase"
25
  os.makedirs(LOCAL_CACHE_DIR, exist_ok=True)
26
 
@@ -36,12 +34,10 @@ def format_ts(ts: int) -> str:
36
 
37
 
38
  def natural_story_title(folder_name: str) -> str:
39
- # job_1775798146_Sunbeam_s_Secret -> Sunbeam s Secret
40
  m = re.match(r"job_(\d+)_(.+)$", folder_name)
41
  if not m:
42
  return folder_name.replace("_", " ")
43
- raw = m.group(2).replace("_", " ").strip()
44
- return raw
45
 
46
 
47
  def extract_job_ts(folder_name: str) -> int:
@@ -50,23 +46,17 @@ def extract_job_ts(folder_name: str) -> int:
50
 
51
 
52
  def list_repo_files():
53
- """
54
- Return all file paths from the dataset repo.
55
- """
56
  files = []
57
-
58
  tree = api.list_repo_tree(
59
  repo_id=REPO_ID,
60
  repo_type=REPO_TYPE,
61
  recursive=True,
62
  expand=True,
63
  )
64
-
65
  for item in tree:
66
  path = getattr(item, "path", None)
67
  if path and not path.endswith("/"):
68
  files.append(path)
69
-
70
  return files
71
 
72
 
@@ -105,7 +95,7 @@ def collect_stories():
105
  elif "/images/" in lower and lower.endswith((".png", ".jpg", ".jpeg", ".webp")):
106
  stories[folder]["image_paths"].append(path)
107
 
108
- for _, story in stories.items():
109
  story["image_paths"] = sorted(
110
  story["image_paths"],
111
  key=lambda p: [
@@ -114,36 +104,7 @@ def collect_stories():
114
  ]
115
  )
116
 
117
- sorted_stories = sorted(
118
- stories.values(),
119
- key=lambda x: x["timestamp"],
120
- reverse=True,
121
- )
122
-
123
- return sorted_stories
124
-
125
-
126
- def get_story_by_folder(folder_name: str):
127
- stories = collect_stories()
128
- return next((s for s in stories if s["folder"] == folder_name), None)
129
-
130
-
131
- def download_markdown(path: str) -> str:
132
- if not path:
133
- return "_No story markdown found._"
134
-
135
- try:
136
- local_path = hf_hub_download(
137
- repo_id=REPO_ID,
138
- filename=path,
139
- repo_type=REPO_TYPE,
140
- token=HF_TOKEN,
141
- local_dir=LOCAL_CACHE_DIR,
142
- )
143
- with open(local_path, "r", encoding="utf-8") as f:
144
- return f.read()
145
- except Exception as e:
146
- return f"_Failed to load markdown: {e}_"
147
 
148
 
149
  def download_repo_file(path: str):
@@ -162,84 +123,88 @@ def download_repo_file(path: str):
162
  return None
163
 
164
 
165
- def download_repo_images(paths):
166
- out = []
167
- for path in paths:
 
168
  local_path = download_repo_file(path)
169
- if local_path:
170
- out.append(local_path)
171
- return out
172
-
173
-
174
- def load_story(folder_name: str):
175
- story = get_story_by_folder(folder_name)
176
-
177
- if not story:
178
- return (
179
- "Story not found.",
180
- None,
181
- [],
182
- "Unknown",
183
- "Unknown date",
184
- "0 images • No Theme",
185
- )
186
-
187
- story_md = download_markdown(story["markdown_path"])
188
- video_value = download_repo_file(story["video_path"]) if story["video_path"] else None
189
- gallery_value = download_repo_images(story["image_paths"])
190
- theme_text = "Theme" if story["csv_path"] else "No Theme"
191
-
192
- return (
193
- story_md,
194
- video_value,
195
- gallery_value,
196
- story["title"],
197
- format_ts(story["timestamp"]),
198
- f"{len(story['image_paths'])} images • {theme_text}",
199
- )
200
 
201
 
202
- def initialize():
 
 
 
203
  stories = collect_stories()
204
 
205
  if not stories:
206
- return (
207
- gr.update(choices=[], value=None),
208
- "No stories found",
209
- f"Last refreshed: {datetime.now().strftime('%d %b %Y, %I:%M:%S %p')}",
210
- "No story found",
211
- "Unknown",
212
- "No content",
213
- None,
214
- [],
215
- )
216
-
217
- choices = [(story["title"], story["folder"]) for story in stories]
218
- first_folder = stories[0]["folder"]
219
-
220
- story_md, video_value, gallery_value, title, created, meta = load_story(first_folder)
221
-
222
- return (
223
- gr.update(choices=choices, value=first_folder),
224
- f"Showing {len(stories)} stories",
225
- f"Last refreshed: {datetime.now().strftime('%d %b %Y, %I:%M:%S %p')}",
226
- title,
227
- f"{created} • {meta}",
228
- story_md,
229
- video_value,
230
- gallery_value,
231
- )
232
-
233
 
234
- def on_story_change(folder_name: str):
235
- story_md, video_value, gallery_value, title, created, meta = load_story(folder_name)
236
- return (
237
- title,
238
- f"{created} {meta}",
239
- story_md,
240
- video_value,
241
- gallery_value,
242
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
 
245
  # =========================================================
@@ -257,32 +222,188 @@ CUSTOM_CSS = """
257
  --accent-2: #0ea5e9;
258
  --shadow: 0 8px 24px rgba(0,0,0,0.06);
259
  }
260
-
261
  .gradio-container {
262
  max-width: 1500px !important;
263
  }
264
-
265
  body, .gradio-container {
266
  background:
267
  radial-gradient(circle at top left, rgba(124,156,255,0.16), transparent 25%),
268
  radial-gradient(circle at top right, rgba(144,224,239,0.10), transparent 20%),
269
  linear-gradient(180deg, #0a0f1d, #0e1422 45%, #0b1020);
270
  }
271
-
272
- .story-shell {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  background: white;
274
  border: 1px solid var(--border);
275
  border-radius: 24px;
276
  padding: 22px;
277
  box-shadow: var(--shadow);
 
 
278
  }
279
-
280
- .top-note {
281
- color: white;
 
 
 
282
  }
283
-
284
- .meta-text {
285
- color: #6b7280;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  }
287
  """
288
 
@@ -295,70 +416,63 @@ with gr.Blocks(
295
  theme=gr.themes.Soft(),
296
  css=CUSTOM_CSS,
297
  ) as demo:
298
- with gr.Row():
299
- refresh_btn = gr.Button("Refresh Stories", variant="primary")
300
- refreshed_at = gr.Markdown("Loading...", elem_classes=["top-note"])
301
-
302
- status_md = gr.Markdown()
303
-
304
- story_selector = gr.Dropdown(
305
- label="Stories",
306
- choices=[],
307
- value=None,
308
- interactive=True,
309
- )
310
-
311
- with gr.Column(elem_classes=["story-shell"]):
312
- title_md = gr.Markdown("## Loading...")
313
- meta_md = gr.Markdown("", elem_classes=["meta-text"])
314
-
315
- with gr.Row():
316
- with gr.Column(scale=6):
317
- story_md = gr.Markdown(label="Story")
318
-
319
- with gr.Column(scale=4):
320
- video_comp = gr.Video(label="Video")
321
- gallery_comp = gr.Gallery(
322
- label="Images",
323
- columns=3,
324
- height="auto",
325
- preview=True,
326
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  demo.load(
329
- fn=initialize,
330
  inputs=[],
331
- outputs=[
332
- story_selector,
333
- status_md,
334
- refreshed_at,
335
- title_md,
336
- meta_md,
337
- story_md,
338
- video_comp,
339
- gallery_comp,
340
- ],
341
  )
342
 
343
  refresh_btn.click(
344
- fn=initialize,
345
  inputs=[],
346
- outputs=[
347
- story_selector,
348
- status_md,
349
- refreshed_at,
350
- title_md,
351
- meta_md,
352
- story_md,
353
- video_comp,
354
- gallery_comp,
355
- ],
356
- )
357
-
358
- story_selector.change(
359
- fn=on_story_change,
360
- inputs=[story_selector],
361
- outputs=[title_md, meta_md, story_md, video_comp, gallery_comp],
362
  )
363
 
364
  demo.launch()
 
10
  # =========================================================
11
  REPO_ID = os.getenv("REPO_ID")
12
  REPO_TYPE = "dataset"
13
+ HF_TOKEN = os.getenv("HF_TOKEN")
14
 
15
  api = HfApi(token=HF_TOKEN)
16
 
 
19
  if not HF_TOKEN:
20
  raise RuntimeError("HF_TOKEN environment variable is not set")
21
 
 
 
22
  LOCAL_CACHE_DIR = "/tmp/story_showcase"
23
  os.makedirs(LOCAL_CACHE_DIR, exist_ok=True)
24
 
 
34
 
35
 
36
  def natural_story_title(folder_name: str) -> str:
 
37
  m = re.match(r"job_(\d+)_(.+)$", folder_name)
38
  if not m:
39
  return folder_name.replace("_", " ")
40
+ return m.group(2).replace("_", " ").strip()
 
41
 
42
 
43
  def extract_job_ts(folder_name: str) -> int:
 
46
 
47
 
48
  def list_repo_files():
 
 
 
49
  files = []
 
50
  tree = api.list_repo_tree(
51
  repo_id=REPO_ID,
52
  repo_type=REPO_TYPE,
53
  recursive=True,
54
  expand=True,
55
  )
 
56
  for item in tree:
57
  path = getattr(item, "path", None)
58
  if path and not path.endswith("/"):
59
  files.append(path)
 
60
  return files
61
 
62
 
 
95
  elif "/images/" in lower and lower.endswith((".png", ".jpg", ".jpeg", ".webp")):
96
  stories[folder]["image_paths"].append(path)
97
 
98
+ for story in stories.values():
99
  story["image_paths"] = sorted(
100
  story["image_paths"],
101
  key=lambda p: [
 
104
  ]
105
  )
106
 
107
+ return sorted(stories.values(), key=lambda x: x["timestamp"], reverse=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
 
110
  def download_repo_file(path: str):
 
123
  return None
124
 
125
 
126
+ def download_markdown(path: str) -> str:
127
+ if not path:
128
+ return "_No story markdown found._"
129
+ try:
130
  local_path = download_repo_file(path)
131
+ if not local_path:
132
+ return "_Failed to load markdown._"
133
+ with open(local_path, "r", encoding="utf-8") as f:
134
+ return f.read()
135
+ except Exception as e:
136
+ return f"_Failed to load markdown: {e}_"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
 
139
+ # =========================================================
140
+ # BUILD UI DATA
141
+ # =========================================================
142
+ def build_story_outputs():
143
  stories = collect_stories()
144
 
145
  if not stories:
146
+ return [
147
+ gr.update(value="<div class='page-wrap'><div class='empty-state'><h2>No stories found</h2><p>Could not find any <code>job_*</code> folders in the repo.</p></div></div>")
148
+ ]
149
+
150
+ hero_html = f"""
151
+ <div class="page-wrap">
152
+ <div class="hero">
153
+ <div>
154
+ <div class="eyebrow">OhamLab Story Showcase</div>
155
+ <h1>OhamLab Enabled</h1>
156
+ <p>Latest generated stories, images, and videos from the dataset repo.</p>
157
+ </div>
158
+ <div class="hero-actions">
159
+ <div class="hero-stat">
160
+ <div class="stat-num">{len(stories)}</div>
161
+ <div class="stat-label">Stories</div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ """
 
 
 
 
 
 
167
 
168
+ outputs = [gr.update(value=hero_html)]
169
+
170
+ max_cards = 20
171
+ for idx in range(max_cards):
172
+ if idx < len(stories):
173
+ story = stories[idx]
174
+ story_md = download_markdown(story["markdown_path"])
175
+ video_path = download_repo_file(story["video_path"]) if story["video_path"] else None
176
+ image_paths = [download_repo_file(p) for p in story["image_paths"]]
177
+ image_paths = [p for p in image_paths if p]
178
+
179
+ title_md = f"## {story['title']}"
180
+ sub_md = story["folder"]
181
+ meta_html = (
182
+ f"<span class='meta-pill'>{format_ts(story['timestamp'])}</span>"
183
+ f"<span class='meta-pill'>{len(story['image_paths'])} images</span>"
184
+ f"<span class='meta-pill'>{'Theme' if story['csv_path'] else 'No Theme'}</span>"
185
+ )
186
+
187
+ outputs.extend([
188
+ gr.update(visible=True), # card container
189
+ gr.update(value=title_md), # title
190
+ gr.update(value=sub_md), # subtitle
191
+ gr.update(value=meta_html), # meta
192
+ gr.update(value=video_path, visible=bool(video_path)), # video
193
+ gr.update(value=story_md), # story markdown
194
+ gr.update(value=image_paths, visible=bool(image_paths)), # gallery
195
+ ])
196
+ else:
197
+ outputs.extend([
198
+ gr.update(visible=False),
199
+ gr.update(value=""),
200
+ gr.update(value=""),
201
+ gr.update(value=""),
202
+ gr.update(value=None, visible=False),
203
+ gr.update(value=""),
204
+ gr.update(value=[], visible=False),
205
+ ])
206
+
207
+ return outputs
208
 
209
 
210
  # =========================================================
 
222
  --accent-2: #0ea5e9;
223
  --shadow: 0 8px 24px rgba(0,0,0,0.06);
224
  }
 
225
  .gradio-container {
226
  max-width: 1500px !important;
227
  }
 
228
  body, .gradio-container {
229
  background:
230
  radial-gradient(circle at top left, rgba(124,156,255,0.16), transparent 25%),
231
  radial-gradient(circle at top right, rgba(144,224,239,0.10), transparent 20%),
232
  linear-gradient(180deg, #0a0f1d, #0e1422 45%, #0b1020);
233
  }
234
+ .page-wrap {
235
+ padding: 6px 4px 30px 4px;
236
+ }
237
+ .hero {
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: space-between;
241
+ gap: 24px;
242
+ background: white;
243
+ border: 1px solid var(--border);
244
+ border-radius: 24px;
245
+ padding: 28px;
246
+ margin-bottom: 24px;
247
+ box-shadow: var(--shadow);
248
+ }
249
+ .eyebrow {
250
+ color: var(--accent-2);
251
+ font-size: 12px;
252
+ font-weight: 700;
253
+ letter-spacing: 0.12em;
254
+ text-transform: uppercase;
255
+ margin-bottom: 8px;
256
+ }
257
+ .hero h1 {
258
+ margin: 0;
259
+ color: var(--text);
260
+ font-size: 36px;
261
+ line-height: 1.1;
262
+ }
263
+ .hero p {
264
+ margin: 10px 0 0 0;
265
+ color: var(--muted);
266
+ font-size: 16px;
267
+ }
268
+ .hero-actions {
269
+ display:flex;
270
+ align-items:center;
271
+ gap:14px;
272
+ }
273
+ .hero-stat {
274
+ min-width: 130px;
275
+ text-align: center;
276
+ background: rgba(255,255,255,0.04);
277
+ border: 1px solid var(--border);
278
+ border-radius: 20px;
279
+ padding: 18px;
280
+ }
281
+ .stat-num {
282
+ color: var(--text);
283
+ font-size: 34px;
284
+ font-weight: 800;
285
+ }
286
+ .stat-label {
287
+ color: var(--muted);
288
+ font-size: 13px;
289
+ }
290
+ .refresh-row button {
291
+ height: 54px !important;
292
+ border-radius: 18px !important;
293
+ }
294
+ .stories-list {
295
+ display: flex;
296
+ flex-direction: column;
297
+ gap: 22px;
298
+ }
299
+ .story-card {
300
  background: white;
301
  border: 1px solid var(--border);
302
  border-radius: 24px;
303
  padding: 22px;
304
  box-shadow: var(--shadow);
305
+ backdrop-filter: blur(8px);
306
+ margin-bottom: 22px;
307
  }
308
+ .story-header {
309
+ display: flex;
310
+ justify-content: space-between;
311
+ align-items: flex-start;
312
+ gap: 18px;
313
+ margin-bottom: 18px;
314
  }
315
+ .story-sub .prose, .story-sub p {
316
+ margin: 0 !important;
317
+ color: var(--muted) !important;
318
+ font-size: 13px !important;
319
+ word-break: break-word;
320
+ }
321
+ .story-meta {
322
+ display: flex;
323
+ flex-wrap: wrap;
324
+ gap: 8px;
325
+ justify-content: flex-end;
326
+ }
327
+ .meta-pill {
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ text-decoration: none;
332
+ color: var(--text);
333
+ background: rgba(124,156,255,0.12);
334
+ border: 1px solid rgba(124,156,255,0.25);
335
+ border-radius: 999px;
336
+ padding: 8px 12px;
337
+ font-size: 12px;
338
+ font-weight: 600;
339
+ margin-right: 8px;
340
+ }
341
+ .story-content {
342
+ display: grid;
343
+ grid-template-columns: 1.15fr 0.85fr;
344
+ gap: 20px;
345
+ }
346
+ .story-text,
347
+ .story-visuals {
348
+ background: rgba(255,255,255,0.025);
349
+ border: 1px solid var(--border);
350
+ border-radius: 20px;
351
+ padding: 18px;
352
+ }
353
+ .story-text h3,
354
+ .story-visuals h3 {
355
+ margin: 0 0 14px 0;
356
+ color: var(--text);
357
+ font-size: 18px;
358
+ }
359
+ .story-markdown .prose,
360
+ .story-markdown .prose p,
361
+ .story-markdown .prose li,
362
+ .story-markdown .prose h1,
363
+ .story-markdown .prose h2,
364
+ .story-markdown .prose h3,
365
+ .story-markdown .prose h4,
366
+ .story-markdown .prose h5,
367
+ .story-markdown .prose h6,
368
+ .story-markdown .prose blockquote {
369
+ color: var(--text) !important;
370
+ }
371
+ .story-video-wrap video {
372
+ width: 100% !important;
373
+ max-height: 580px !important;
374
+ border-radius: 18px !important;
375
+ background: #000 !important;
376
+ border: 1px solid var(--border) !important;
377
+ }
378
+ .story-gallery {
379
+ min-height: 80px;
380
+ }
381
+ .story-gallery .grid-wrap,
382
+ .story-gallery .grid-container,
383
+ .story-gallery .gallery {
384
+ border-radius: 16px !important;
385
+ }
386
+ .empty-state {
387
+ background: rgba(255,255,255,0.025);
388
+ border: 1px dashed var(--border);
389
+ border-radius: 18px;
390
+ padding: 22px;
391
+ color: var(--muted);
392
+ }
393
+ @media (max-width: 1100px) {
394
+ .story-content {
395
+ grid-template-columns: 1fr;
396
+ }
397
+ .story-header {
398
+ flex-direction: column;
399
+ }
400
+ .story-meta {
401
+ justify-content: flex-start;
402
+ }
403
+ .hero {
404
+ flex-direction: column;
405
+ align-items: flex-start;
406
+ }
407
  }
408
  """
409
 
 
416
  theme=gr.themes.Soft(),
417
  css=CUSTOM_CSS,
418
  ) as demo:
419
+ with gr.Column(elem_classes=["page-wrap"]):
420
+ with gr.Row(elem_classes=["refresh-row"]):
421
+ hero_html = gr.HTML()
422
+ refresh_btn = gr.Button("Refresh Stories", variant="primary", scale=0)
423
+
424
+ with gr.Column(elem_classes=["stories-list"]):
425
+ card_components = []
426
+
427
+ for _ in range(20):
428
+ with gr.Column(visible=False, elem_classes=["story-card"]) as card:
429
+ with gr.Row(elem_classes=["story-header"]):
430
+ with gr.Column(scale=6):
431
+ title_md = gr.Markdown()
432
+ sub_md = gr.Markdown(elem_classes=["story-sub"])
433
+ with gr.Column(scale=4):
434
+ meta_html = gr.HTML(elem_classes=["story-meta"])
435
+
436
+ video_comp = gr.Video(label=None, visible=False, elem_classes=["story-video-wrap"])
437
+
438
+ with gr.Row(elem_classes=["story-content"]):
439
+ with gr.Column(elem_classes=["story-text"]):
440
+ gr.Markdown("### Story")
441
+ story_md = gr.Markdown(elem_classes=["story-markdown"])
442
+
443
+ with gr.Column(elem_classes=["story-visuals"]):
444
+ gr.Markdown("### Images")
445
+ gallery_comp = gr.Gallery(
446
+ label=None,
447
+ columns=4,
448
+ height="auto",
449
+ preview=True,
450
+ visible=False,
451
+ elem_classes=["story-gallery"],
452
+ )
453
+
454
+ card_components.extend([
455
+ card,
456
+ title_md,
457
+ sub_md,
458
+ meta_html,
459
+ video_comp,
460
+ story_md,
461
+ gallery_comp,
462
+ ])
463
+
464
+ all_outputs = [hero_html] + card_components
465
 
466
  demo.load(
467
+ fn=build_story_outputs,
468
  inputs=[],
469
+ outputs=all_outputs,
 
 
 
 
 
 
 
 
 
470
  )
471
 
472
  refresh_btn.click(
473
+ fn=build_story_outputs,
474
  inputs=[],
475
+ outputs=all_outputs,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  )
477
 
478
  demo.launch()