waroca commited on
Commit
4e2c730
·
verified ·
1 Parent(s): 34b644f

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +109 -79
  2. components/catalog.py +154 -36
app.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
  import gradio as gr
4
  from dotenv import load_dotenv
5
  from components import subscriptions
 
6
  import utils
7
  import theme
8
  import os
@@ -383,85 +384,18 @@ def main():
383
 
384
  # Catalog Tab
385
  with gr.Tab("Catalog", id="catalog"):
386
- # Use gr.render to dynamically create cards with buttons
387
- @gr.render(triggers=[demo.load])
388
- def render_catalog(profile: gr.OAuthProfile | None = None, token: gr.OAuthToken | None = None):
389
- datasets = utils.get_catalog() or []
390
-
391
- if not datasets:
392
- gr.HTML("""
393
- <div class="empty-state">
394
- <div class="empty-state-icon">📦</div>
395
- <div class="empty-state-title">No datasets available</div>
396
- <div class="empty-state-text">Check back soon for new data products.</div>
397
- </div>
398
- """)
399
- return
400
-
401
- for dataset in datasets:
402
- dataset_id = dataset.get('dataset_id', '')
403
- display_name = dataset.get('display_name', dataset_id)
404
- description = dataset.get('description', 'No description available.')
405
-
406
- plans = dataset.get("plans", [])
407
- is_free = False
408
- price = "10"
409
- if plans:
410
- plan = plans[0]
411
- price_id = plan.get("stripe_price_id", "")
412
- if price_id in ["free", "0", 0]:
413
- is_free = True
414
- else:
415
- price = plan.get("price", "10")
416
-
417
- price_class = "free" if is_free else ""
418
- price_text = "Free Trial" if is_free else f"${price}/mo"
419
- button_text = "Start 24h Trial" if is_free else f"Subscribe - ${price}/mo"
420
-
421
- with gr.Group(elem_classes="card-wrapper"):
422
- gr.HTML(f"""
423
- <h3 class="dataset-title">{display_name}</h3>
424
- <p class="dataset-id">{dataset_id}</p>
425
- <p class="dataset-desc">{description}</p>
426
- <p class="dataset-price {price_class}">{price_text}</p>
427
- """)
428
-
429
- if profile:
430
- # Create button with closure to capture dataset info
431
- btn = gr.Button(button_text, variant="primary", size="sm")
432
-
433
- # Capture variables in closure
434
- ds_id = dataset_id
435
- ds_name = display_name
436
- ds_is_free = is_free
437
-
438
- def make_handler(did, dname, dfree):
439
- def handler(p: gr.OAuthProfile | None, t: gr.OAuthToken | None):
440
- if not p:
441
- return "⚠️ Please sign in first to subscribe."
442
-
443
- hf_token = t.token if t else None
444
-
445
- if dfree:
446
- result = utils.subscribe_free(did, p.username, hf_token)
447
- if "error" in result:
448
- return f"❌ Error: {result['error']}"
449
- return f"✅ Your 24-hour DataPass for **{dname}** is active! Go to My Subscriptions to get your access token."
450
- else:
451
- result = utils.create_checkout_session(did, p.username, hf_token)
452
- if "error" in result:
453
- return f"❌ Error: {result['error']}"
454
- if "checkout_url" in result:
455
- return f"🔗 [Click here to complete payment]({result['checkout_url']})"
456
- return "Error creating checkout session."
457
- return handler
458
-
459
- btn.click(
460
- fn=make_handler(ds_id, ds_name, ds_is_free),
461
- outputs=[subscribe_status]
462
- )
463
- else:
464
- gr.HTML('<p class="login-hint">Sign in to subscribe</p>')
465
 
466
  # Subscriptions Tab
467
  with gr.Tab("My Subscriptions", id="subscriptions"):
@@ -479,12 +413,71 @@ def main():
479
  </div>
480
  """)
481
 
 
 
 
482
  # Load user status
483
  def load_user_status(profile: gr.OAuthProfile | None):
484
  if profile:
485
  return f"👤 Signed in as **{profile.username}**"
486
  return "Sign in to subscribe to datasets"
487
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  # Load subscriptions
489
  def load_subscriptions(profile: gr.OAuthProfile | None, token: gr.OAuthToken | None):
490
  if not profile:
@@ -500,10 +493,47 @@ def main():
500
  user_subs = utils.get_user_subscriptions(profile.username, hf_token)
501
  return subscriptions.create_subscriptions_html(user_subs)
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  # Load on page load
504
  demo.load(fn=load_user_status, outputs=[user_status])
 
505
  demo.load(fn=load_subscriptions, outputs=[subscriptions_container])
506
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  return demo
508
 
509
 
 
3
  import gradio as gr
4
  from dotenv import load_dotenv
5
  from components import subscriptions
6
+ from components.catalog import create_catalog_html, get_dataset_choices
7
  import utils
8
  import theme
9
  import os
 
384
 
385
  # Catalog Tab
386
  with gr.Tab("Catalog", id="catalog"):
387
+ catalog_container = gr.HTML()
388
+
389
+ # Subscribe section (visible when logged in)
390
+ gr.HTML('<div style="margin-top: 1.5rem;"></div>')
391
+ with gr.Row():
392
+ dataset_dropdown = gr.Dropdown(
393
+ label="Select a dataset to subscribe",
394
+ choices=[],
395
+ interactive=True,
396
+ scale=3
397
+ )
398
+ subscribe_btn = gr.Button("Subscribe", variant="primary", scale=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
  # Subscriptions Tab
401
  with gr.Tab("My Subscriptions", id="subscriptions"):
 
413
  </div>
414
  """)
415
 
416
+ # Store datasets for reference
417
+ datasets_cache = []
418
+
419
  # Load user status
420
  def load_user_status(profile: gr.OAuthProfile | None):
421
  if profile:
422
  return f"👤 Signed in as **{profile.username}**"
423
  return "Sign in to subscribe to datasets"
424
 
425
+ # Load catalog and dropdown
426
+ def load_catalog_and_dropdown(profile: gr.OAuthProfile | None):
427
+ nonlocal datasets_cache
428
+ datasets_cache = utils.get_catalog() or []
429
+ username = profile.username if profile else None
430
+
431
+ # Generate catalog HTML
432
+ catalog_html = create_catalog_html(datasets_cache, username)
433
+
434
+ # Generate dropdown choices
435
+ if username:
436
+ choices = get_dataset_choices(datasets_cache, username)
437
+ else:
438
+ choices = []
439
+
440
+ return catalog_html, gr.update(choices=choices, value=None)
441
+
442
+ # Handle subscription
443
+ def handle_subscribe(dataset_choice, profile: gr.OAuthProfile | None, token: gr.OAuthToken | None):
444
+ if not profile:
445
+ return "Please sign in first to subscribe."
446
+
447
+ if not dataset_choice:
448
+ return "Please select a dataset from the dropdown."
449
+
450
+ # Find the dataset info
451
+ dataset_id = dataset_choice
452
+ dataset_info = next((d for d in datasets_cache if d.get('dataset_id') == dataset_id), None)
453
+
454
+ if not dataset_info:
455
+ return f"Dataset not found: {dataset_id}"
456
+
457
+ hf_token = token.token if token else None
458
+ plans = dataset_info.get("plans", [])
459
+
460
+ if plans:
461
+ plan = plans[0]
462
+ price_id = plan.get("stripe_price_id", "")
463
+ is_free = price_id in ["free", "0", 0]
464
+
465
+ if is_free:
466
+ result = utils.subscribe_free(dataset_id, profile.username, hf_token)
467
+ if "error" in result:
468
+ return f"Error: {result['error']}"
469
+ display_name = dataset_info.get('display_name', dataset_id)
470
+ return f"Your 24-hour DataPass for **{display_name}** is active! Go to My Subscriptions to get your access token."
471
+ else:
472
+ result = utils.create_checkout_session(dataset_id, profile.username, hf_token)
473
+ if "error" in result:
474
+ return f"Error: {result['error']}"
475
+ if "checkout_url" in result:
476
+ return f"[Click here to complete payment]({result['checkout_url']})"
477
+ return "Error creating checkout session."
478
+
479
+ return "No subscription plans available for this dataset."
480
+
481
  # Load subscriptions
482
  def load_subscriptions(profile: gr.OAuthProfile | None, token: gr.OAuthToken | None):
483
  if not profile:
 
493
  user_subs = utils.get_user_subscriptions(profile.username, hf_token)
494
  return subscriptions.create_subscriptions_html(user_subs)
495
 
496
+ # Update button text based on selection
497
+ def update_button_text(dataset_choice):
498
+ if not dataset_choice:
499
+ return gr.update(value="Subscribe")
500
+
501
+ # Find the dataset info
502
+ dataset_info = next((d for d in datasets_cache if d.get('dataset_id') == dataset_choice), None)
503
+ if not dataset_info:
504
+ return gr.update(value="Subscribe")
505
+
506
+ plans = dataset_info.get("plans", [])
507
+ if plans:
508
+ plan = plans[0]
509
+ price_id = plan.get("stripe_price_id", "")
510
+ if price_id in ["free", "0", 0]:
511
+ return gr.update(value="Start Free Trial")
512
+ else:
513
+ price = plan.get("price", "10")
514
+ return gr.update(value=f"Subscribe - ${price}/mo")
515
+
516
+ return gr.update(value="Subscribe")
517
+
518
  # Load on page load
519
  demo.load(fn=load_user_status, outputs=[user_status])
520
+ demo.load(fn=load_catalog_and_dropdown, outputs=[catalog_container, dataset_dropdown])
521
  demo.load(fn=load_subscriptions, outputs=[subscriptions_container])
522
 
523
+ # Update button on dropdown change
524
+ dataset_dropdown.change(
525
+ fn=update_button_text,
526
+ inputs=[dataset_dropdown],
527
+ outputs=[subscribe_btn]
528
+ )
529
+
530
+ # Subscribe button handler
531
+ subscribe_btn.click(
532
+ fn=handle_subscribe,
533
+ inputs=[dataset_dropdown],
534
+ outputs=[subscribe_status]
535
+ )
536
+
537
  return demo
538
 
539
 
components/catalog.py CHANGED
@@ -1,5 +1,5 @@
1
  def create_catalog_html(datasets, username=None):
2
- """Creates HTML string for the dataset catalog (display only, no buttons)."""
3
  if not datasets:
4
  return """
5
  <div class="empty-state">
@@ -10,69 +10,187 @@ def create_catalog_html(datasets, username=None):
10
  """
11
 
12
  cards_html = ""
13
- for dataset in datasets:
 
 
 
 
14
  # Determine pricing display
15
  plans = dataset.get("plans", [])
16
- price_display = ""
17
- action_text = ""
 
18
 
19
  if plans:
20
  plan = plans[0]
21
  price_id = plan.get("stripe_price_id", "")
22
  if price_id in ["free", "0", 0]:
 
23
  trial_days = plan.get("access_duration_days", 1)
24
- trial_text = "24h" if trial_days == 1 else f"{trial_days}-day"
25
- price_display = f'<span class="price-tag price-free">{trial_text} Free Trial</span>'
26
- if not username:
27
- action_text = '<span class="login-hint">Sign in to start trial</span>'
28
  else:
29
  price = plan.get("price", "10")
30
- price_display = f'''
31
- <span class="price-tag">
32
- <span class="currency">$</span>{price}
33
- <span class="period">/mo</span>
34
- </span>
35
- '''
36
- if not username:
37
- action_text = '<span class="login-hint">Sign in to subscribe</span>'
38
 
39
- cards_html += f"""
40
- <div class="dataset-card animate-in">
41
- <div class="card-header">
42
- <h3 class="card-title">{dataset.get('display_name', dataset.get('dataset_id', 'Untitled'))}</h3>
43
- <span class="card-badge">{dataset.get('dataset_id', '')}</span>
 
 
 
 
 
 
 
44
  </div>
45
- <p class="card-description">{dataset.get('description', 'No description available.')}</p>
46
- <div class="card-footer">
47
- {price_display}
48
- <div class="card-actions">
49
- {action_text}
 
 
 
 
 
 
50
  </div>
51
  </div>
 
 
 
 
 
52
  </div>
53
  """
54
 
55
- # Wrap in container with grid layout
56
  html = f"""
57
- <div class="catalog-grid">
58
  {cards_html}
59
  </div>
60
  <style>
61
- .catalog-grid {{
62
- display: grid;
63
- grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
64
- gap: 1.5rem;
65
  padding: 0.5rem 0;
66
  }}
67
- .login-hint {{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  font-size: 0.875rem;
69
- color: var(--text-tertiary);
70
- font-style: italic;
 
71
  }}
72
- .card-actions {{
 
73
  display: flex;
74
  align-items: center;
 
 
75
  gap: 0.75rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }}
77
  </style>
78
  """
 
1
  def create_catalog_html(datasets, username=None):
2
+ """Creates HTML string for the dataset catalog with improved card design."""
3
  if not datasets:
4
  return """
5
  <div class="empty-state">
 
10
  """
11
 
12
  cards_html = ""
13
+ for i, dataset in enumerate(datasets):
14
+ dataset_id = dataset.get('dataset_id', '')
15
+ display_name = dataset.get('display_name', dataset_id)
16
+ description = dataset.get('description', 'No description available.')
17
+
18
  # Determine pricing display
19
  plans = dataset.get("plans", [])
20
+ is_free = False
21
+ price = "10"
22
+ trial_days = 1
23
 
24
  if plans:
25
  plan = plans[0]
26
  price_id = plan.get("stripe_price_id", "")
27
  if price_id in ["free", "0", 0]:
28
+ is_free = True
29
  trial_days = plan.get("access_duration_days", 1)
 
 
 
 
30
  else:
31
  price = plan.get("price", "10")
 
 
 
 
 
 
 
 
32
 
33
+ # Price badge
34
+ if is_free:
35
+ trial_text = "24h" if trial_days == 1 else f"{trial_days}-day"
36
+ price_badge = f'<span class="catalog-price-badge free">{trial_text} Free Trial</span>'
37
+ else:
38
+ price_badge = f'<span class="catalog-price-badge paid">${price}<span class="period">/mo</span></span>'
39
+
40
+ # Action area - only show hint when not logged in
41
+ if not username:
42
+ action_html = '''
43
+ <div class="catalog-card-action">
44
+ <span class="action-hint login-required">Sign in to subscribe</span>
45
  </div>
46
+ '''
47
+ else:
48
+ action_html = ''
49
+
50
+ cards_html += f"""
51
+ <div class="catalog-card" data-dataset-id="{dataset_id}">
52
+ <div class="catalog-card-header">
53
+ <div class="catalog-card-icon">📊</div>
54
+ <div class="catalog-card-title-area">
55
+ <h3 class="catalog-card-title">{display_name}</h3>
56
+ <span class="catalog-card-id">{dataset_id}</span>
57
  </div>
58
  </div>
59
+ <p class="catalog-card-description">{description}</p>
60
+ <div class="catalog-card-footer">
61
+ {price_badge}
62
+ {action_html}
63
+ </div>
64
  </div>
65
  """
66
 
67
+ # Wrap in container with improved styles
68
  html = f"""
69
+ <div class="catalog-container">
70
  {cards_html}
71
  </div>
72
  <style>
73
+ .catalog-container {{
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 1rem;
77
  padding: 0.5rem 0;
78
  }}
79
+
80
+ .catalog-card {{
81
+ background: var(--bg-card, #1c1c1e);
82
+ border: 1px solid var(--border-color, rgba(255,255,255,0.08));
83
+ border-radius: 16px;
84
+ padding: 1.25rem 1.5rem;
85
+ transition: all 0.2s ease;
86
+ position: relative;
87
+ }}
88
+
89
+ .catalog-card:hover {{
90
+ border-color: var(--border-color-strong, rgba(255,255,255,0.15));
91
+ transform: translateY(-2px);
92
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
93
+ }}
94
+
95
+ .catalog-card-header {{
96
+ display: flex;
97
+ align-items: flex-start;
98
+ gap: 1rem;
99
+ margin-bottom: 0.75rem;
100
+ }}
101
+
102
+ .catalog-card-icon {{
103
+ font-size: 2rem;
104
+ line-height: 1;
105
+ flex-shrink: 0;
106
+ }}
107
+
108
+ .catalog-card-title-area {{
109
+ flex: 1;
110
+ min-width: 0;
111
+ }}
112
+
113
+ .catalog-card-title {{
114
+ font-size: 1.125rem;
115
+ font-weight: 600;
116
+ color: var(--text-primary, #f5f5f7);
117
+ margin: 0 0 0.25rem 0;
118
+ line-height: 1.3;
119
+ }}
120
+
121
+ .catalog-card-id {{
122
+ font-size: 0.75rem;
123
+ color: var(--text-tertiary, #6e6e73);
124
+ font-family: 'SF Mono', Monaco, monospace;
125
+ background: var(--bg-tertiary, rgba(255,255,255,0.05));
126
+ padding: 0.125rem 0.5rem;
127
+ border-radius: 4px;
128
+ display: inline-block;
129
+ }}
130
+
131
+ .catalog-card-description {{
132
  font-size: 0.875rem;
133
+ color: var(--text-secondary, #a1a1a6);
134
+ line-height: 1.5;
135
+ margin: 0 0 1rem 0;
136
  }}
137
+
138
+ .catalog-card-footer {{
139
  display: flex;
140
  align-items: center;
141
+ justify-content: space-between;
142
+ flex-wrap: wrap;
143
  gap: 0.75rem;
144
+ padding-top: 0.75rem;
145
+ border-top: 1px solid var(--border-color, rgba(255,255,255,0.06));
146
+ }}
147
+
148
+ .catalog-price-badge {{
149
+ font-size: 1rem;
150
+ font-weight: 600;
151
+ padding: 0.375rem 0.875rem;
152
+ border-radius: 20px;
153
+ display: inline-flex;
154
+ align-items: baseline;
155
+ gap: 0.125rem;
156
+ }}
157
+
158
+ .catalog-price-badge.free {{
159
+ background: rgba(52, 199, 89, 0.15);
160
+ color: #34c759;
161
+ }}
162
+
163
+ .catalog-price-badge.paid {{
164
+ background: var(--bg-tertiary, rgba(255,255,255,0.05));
165
+ color: var(--text-primary, #f5f5f7);
166
+ }}
167
+
168
+ .catalog-price-badge .period {{
169
+ font-size: 0.75rem;
170
+ font-weight: 400;
171
+ color: var(--text-tertiary, #6e6e73);
172
+ }}
173
+
174
+ .catalog-card-action {{
175
+ display: flex;
176
+ align-items: center;
177
+ }}
178
+
179
+ .action-hint {{
180
+ font-size: 0.8125rem;
181
+ color: var(--text-tertiary, #6e6e73);
182
+ }}
183
+
184
+ .action-hint.login-required {{
185
+ font-style: italic;
186
+ color: var(--text-tertiary, #6e6e73);
187
+ }}
188
+
189
+ @media (max-width: 640px) {{
190
+ .catalog-card-footer {{
191
+ flex-direction: column;
192
+ align-items: flex-start;
193
+ }}
194
  }}
195
  </style>
196
  """