ComparEdge commited on
Commit
ed1d92d
Β·
verified Β·
1 Parent(s): 456404d

Add SaaS Feature Comparator app

Browse files
Files changed (3) hide show
  1. README.md +59 -5
  2. app.py +412 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,12 +1,66 @@
1
  ---
2
- title: Saas Feature Comparator
3
- emoji: 🌍
4
- colorFrom: pink
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.13.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SaaS Feature Comparator
3
+ emoji: πŸ”
4
+ colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
+ license: cc-by-4.0
11
  ---
12
 
13
+ # πŸ” SaaS Feature Comparator
14
+
15
+ **Compare any 2–4 SaaS products side-by-side β€” powered by [ComparEdge.com](https://comparedge.com)**
16
+
17
+ Instantly see which tools support which features, how pricing stacks up, and what makes each product unique.
18
+
19
+ ---
20
+
21
+ ## ✨ Features
22
+
23
+ - πŸ“‹ **Feature-by-feature comparison** with βœ…/❌ β€” across 300+ SaaS products
24
+ - πŸ” **Smart filtering** β€” hide identical rows, surface only what differs
25
+ - πŸ—‚ **Category filter** β€” narrow products by category (CRM, AI Tools, Project Management, etc.)
26
+ - πŸ“Š **Summary stats** β€” "Product A has X unique features vs Product B"
27
+ - πŸ“‹ **Export to Markdown** β€” copy the comparison for docs, blog posts, or reports
28
+ - ⚑ **Fast** β€” feature index is pre-built at startup for instant comparisons
29
+
30
+ ---
31
+
32
+ ## πŸš€ How to Use
33
+
34
+ 1. **(Optional)** Filter by **Category** to narrow the dropdown list
35
+ 2. Pick **2–4 SaaS products** from the dropdowns
36
+ 3. Click **⚑ Compare**
37
+ 4. Browse the feature table β€” by default only **differing features** are shown
38
+ 5. Toggle **"Show all features"** to see the complete list
39
+ 6. Check **πŸ“Š Feature Summary** for unique-feature stats
40
+ 7. Expand **πŸ“‹ Export** to copy a Markdown version
41
+
42
+ ---
43
+
44
+ ## πŸ“¦ Data
45
+
46
+ - **331 SaaS products** across 28 categories
47
+ - Data curated and maintained by [ComparEdge.com](https://comparedge.com)
48
+ - Features normalized to a common taxonomy for fair comparison
49
+ - Pricing, ratings, pros & cons included
50
+
51
+ ---
52
+
53
+ ## πŸ—‚ Covered Categories
54
+
55
+ `project-management` Β· `crm` Β· `ai-writing` Β· `ai-assistants` Β· `ai-coding` Β· `ai-image` Β· `ai-video` Β· `ai-voice` Β· `ai-productivity` Β· `ai-agents` Β· `llm` Β· `design-tools` Β· `email-marketing` Β· `video-conferencing` Β· `accounting` Β· `website-builders` Β· `cloud-hosting` Β· `vpn` Β· `password-managers` Β· `crypto-exchanges` Β· `crypto-wallets` Β· `crypto-trading-bots` Β· `crypto-analytics` Β· `crypto-tax` Β· `crypto-portfolio-trackers` Β· `crypto-telegram-bots` Β· `defi-tools` Β· `dex`
56
+
57
+ ---
58
+
59
+ ## πŸ”— Links
60
+
61
+ - 🌐 **Full comparison site:** [comparedge.com](https://comparedge.com)
62
+ - πŸ“Š **Detailed reviews & pricing:** [comparedge.com](https://comparedge.com)
63
+
64
+ ---
65
+
66
+ *Powered by [ComparEdge](https://comparedge.com) β€” the SaaS Comparison Platform*
app.py ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SaaS Feature Comparator β€” Powered by ComparEdge.com
3
+ A Gradio app to compare SaaS products side-by-side.
4
+ """
5
+
6
+ import json
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import gradio as gr
12
+ import pandas as pd
13
+
14
+ # ─── Data Loading ──────────────────────────────────────────────────────────────
15
+
16
+ DATA_PATH = Path(__file__).parent / "products.json"
17
+
18
+ with open(DATA_PATH, "r", encoding="utf-8") as f:
19
+ _raw = json.load(f)
20
+
21
+ PRODUCTS: list[dict] = _raw["products"]
22
+
23
+ # Build lookup maps at startup
24
+ PRODUCT_BY_SLUG: dict[str, dict] = {p["slug"]: p for p in PRODUCTS}
25
+ PRODUCT_BY_NAME: dict[str, dict] = {p["name"]: p for p in PRODUCTS}
26
+
27
+ # Collect all categories (pretty-printed)
28
+ CATEGORY_LABELS: dict[str, str] = {}
29
+ for p in PRODUCTS:
30
+ cat = p.get("category", "other")
31
+ CATEGORY_LABELS[cat] = cat.replace("-", " ").title()
32
+
33
+ ALL_CATEGORIES = ["All Categories"] + sorted(CATEGORY_LABELS.values())
34
+
35
+ # Pre-compute feature set per product
36
+ FEATURE_INDEX: dict[str, dict[str, bool]] = {}
37
+ for p in PRODUCTS:
38
+ FEATURE_INDEX[p["name"]] = p.get("normalizedFeatures") or {}
39
+
40
+ # All unique feature names across the whole dataset
41
+ ALL_FEATURES_GLOBAL: list[str] = sorted(
42
+ {feat for feats in FEATURE_INDEX.values() for feat in feats}
43
+ )
44
+
45
+
46
+ def get_price_range(product: dict) -> str:
47
+ """Return a compact price-range string like 'Free – $18/mo'."""
48
+ pricing = product.get("pricing") or {}
49
+ plans = pricing.get("plans") or []
50
+ prices = [pl["price"] for pl in plans if isinstance(pl.get("price"), (int, float))]
51
+ if not prices:
52
+ return "N/A"
53
+ lo, hi = min(prices), max(prices)
54
+ has_free = pricing.get("free", False) or lo == 0
55
+ if lo == hi:
56
+ label = "Free" if lo == 0 else f"${lo}/mo"
57
+ else:
58
+ label = f"{'Free' if has_free else f'${lo}'} – ${hi}/mo"
59
+ return label
60
+
61
+
62
+ def get_rating(product: dict) -> str:
63
+ rating = product.get("rating") or {}
64
+ scores = [v for v in rating.values() if isinstance(v, (int, float))]
65
+ if not scores:
66
+ return "N/A"
67
+ avg = round(sum(scores) / len(scores), 1)
68
+ return f"⭐ {avg}"
69
+
70
+
71
+ def filter_products_by_category(category_label: str) -> list[str]:
72
+ """Return product names matching the chosen category label."""
73
+ if category_label == "All Categories" or not category_label:
74
+ return sorted(PRODUCT_BY_NAME.keys())
75
+ # reverse-map label β†’ category slug
76
+ target_slug = None
77
+ for slug, label in CATEGORY_LABELS.items():
78
+ if label == category_label:
79
+ target_slug = slug
80
+ break
81
+ if not target_slug:
82
+ return sorted(PRODUCT_BY_NAME.keys())
83
+ return sorted(
84
+ p["name"] for p in PRODUCTS if p.get("category") == target_slug
85
+ )
86
+
87
+
88
+ # ─── Comparison Logic ──────────────────────────────────────────────────────────
89
+
90
+ def build_comparison(
91
+ selected_names: list[str],
92
+ show_all: bool,
93
+ ) -> tuple[str, str, str]:
94
+ """
95
+ Returns (html_table, summary_md, export_md).
96
+ """
97
+ selected_names = [n for n in selected_names if n]
98
+ if len(selected_names) < 2:
99
+ msg = "<p style='color:#888;text-align:center;padding:2rem'>Select at least 2 products to compare.</p>"
100
+ return msg, "", ""
101
+
102
+ products = [PRODUCT_BY_NAME[n] for n in selected_names if n in PRODUCT_BY_NAME]
103
+ if len(products) < 2:
104
+ return "<p style='color:#888'>Products not found.</p>", "", ""
105
+
106
+ # Gather features present in at least one selected product
107
+ feature_sets: list[set[str]] = [set(FEATURE_INDEX[p["name"]].keys()) for p in products]
108
+ union_features: list[str] = sorted(set().union(*feature_sets))
109
+
110
+ # Determine which features differ across selected products
111
+ def has_value(p: dict, feat: str) -> bool:
112
+ return bool(FEATURE_INDEX[p["name"]].get(feat, False))
113
+
114
+ def is_uniform(feat: str) -> bool:
115
+ vals = [has_value(p, feat) for p in products]
116
+ return len(set(vals)) == 1
117
+
118
+ if show_all:
119
+ display_features = union_features
120
+ else:
121
+ display_features = [f for f in union_features if not is_uniform(f)]
122
+
123
+ # ── HTML Table ───────────────────────────────────────────────────────────
124
+
125
+ # Header row β€” product cards
126
+ header_cells = "<th style='min-width:160px;background:#1e293b;color:#94a3b8;font-size:12px;text-transform:uppercase;letter-spacing:.05em;padding:10px 14px;text-align:left'>Feature</th>"
127
+ for p in products:
128
+ price = get_price_range(p)
129
+ rating = get_rating(p)
130
+ cat = CATEGORY_LABELS.get(p.get("category", ""), p.get("category", ""))
131
+ header_cells += f"""
132
+ <th style='min-width:180px;background:#1e293b;padding:12px 14px;text-align:center;border-left:1px solid #334155'>
133
+ <div style='font-size:16px;font-weight:700;color:#f1f5f9'>{p['name']}</div>
134
+ <div style='font-size:11px;color:#64748b;margin-top:2px'>{cat}</div>
135
+ <div style='font-size:13px;color:#38bdf8;margin-top:4px'>{price}</div>
136
+ <div style='font-size:12px;color:#fbbf24;margin-top:2px'>{rating}</div>
137
+ </th>"""
138
+
139
+ rows_html = ""
140
+ for i, feat in enumerate(display_features):
141
+ bg = "#0f172a" if i % 2 == 0 else "#111827"
142
+ row = f"<tr style='background:{bg}'>"
143
+ row += f"<td style='padding:8px 14px;color:#cbd5e1;font-size:13px;border-bottom:1px solid #1e293b'>{feat}</td>"
144
+ for p in products:
145
+ val = has_value(p, feat)
146
+ icon = "βœ…" if val else "❌"
147
+ cell_bg = "rgba(34,197,94,.08)" if val else "rgba(239,68,68,.06)"
148
+ row += f"<td style='text-align:center;padding:8px 14px;border-left:1px solid #1e293b;border-bottom:1px solid #1e293b;background:{cell_bg};font-size:16px'>{icon}</td>"
149
+ row += "</tr>"
150
+ rows_html += row
151
+
152
+ if not display_features:
153
+ rows_html = f"<tr><td colspan='{len(products)+1}' style='text-align:center;padding:2rem;color:#64748b'>All selected products share identical features. Enable \"Show all features\" to see the full list.</td></tr>"
154
+
155
+ table_html = f"""
156
+ <div style='overflow-x:auto;border-radius:12px;border:1px solid #1e293b;font-family:Inter,system-ui,sans-serif'>
157
+ <table style='width:100%;border-collapse:collapse'>
158
+ <thead><tr>{header_cells}</tr></thead>
159
+ <tbody>{rows_html}</tbody>
160
+ </table>
161
+ </div>
162
+ <div style='text-align:right;margin-top:6px;font-size:11px;color:#475569'>
163
+ Data by <a href='https://comparedge.com' target='_blank' style='color:#38bdf8;text-decoration:none'>ComparEdge.com</a>
164
+ </div>
165
+ """
166
+
167
+ # ── Summary ──────────────────────────────────────────────────────────────
168
+ summary_lines = ["### πŸ“Š Feature Summary\n"]
169
+ for p in products:
170
+ feats = FEATURE_INDEX[p["name"]]
171
+ total = len(feats)
172
+ supported = sum(1 for v in feats.values() if v)
173
+ summary_lines.append(f"- **{p['name']}**: {supported}/{total} features supported")
174
+
175
+ # Unique features per product
176
+ if len(products) == 2:
177
+ a, b = products
178
+ a_feats = {f for f, v in FEATURE_INDEX[a["name"]].items() if v}
179
+ b_feats = {f for f, v in FEATURE_INDEX[b["name"]].items() if v}
180
+ unique_a = a_feats - b_feats
181
+ unique_b = b_feats - a_feats
182
+ summary_lines.append(f"\n**{a['name']}** has **{len(unique_a)} unique features** not in {b['name']}")
183
+ summary_lines.append(f"**{b['name']}** has **{len(unique_b)} unique features** not in {a['name']}")
184
+
185
+ summary_md = "\n".join(summary_lines)
186
+
187
+ # ── Export Markdown ───────────────────────────────────────────────────────
188
+ export_lines = [f"# SaaS Comparison: {' vs '.join(p['name'] for p in products)}\n"]
189
+ export_lines.append("Generated by [ComparEdge.com](https://comparedge.com)\n")
190
+ export_lines.append("## Product Overview\n")
191
+ for p in products:
192
+ export_lines.append(f"### {p['name']}")
193
+ export_lines.append(f"- Category: {CATEGORY_LABELS.get(p.get('category',''), '')}")
194
+ export_lines.append(f"- Price: {get_price_range(p)}")
195
+ export_lines.append(f"- Rating: {get_rating(p)}")
196
+ pros = p.get("pros") or []
197
+ cons = p.get("cons") or []
198
+ if pros:
199
+ export_lines.append(f"- Pros: {', '.join(pros[:3])}")
200
+ if cons:
201
+ export_lines.append(f"- Cons: {', '.join(cons[:3])}")
202
+ export_lines.append("")
203
+
204
+ export_lines.append("## Feature Comparison\n")
205
+ # Build markdown table
206
+ header_row = "| Feature | " + " | ".join(p["name"] for p in products) + " |"
207
+ sep_row = "|---|" + "---|" * len(products)
208
+ export_lines.append(header_row)
209
+ export_lines.append(sep_row)
210
+ for feat in union_features:
211
+ cells = " | ".join("βœ…" if has_value(p, feat) else "❌" for p in products)
212
+ export_lines.append(f"| {feat} | {cells} |")
213
+
214
+ export_md = "\n".join(export_lines)
215
+
216
+ return table_html, summary_md, export_md
217
+
218
+
219
+ # ─── Gradio UI ─────────────────────────────────────────────────────────────────
220
+
221
+ CSS = """
222
+ body, .gradio-container {
223
+ background: #0a0f1e !important;
224
+ font-family: Inter, system-ui, -apple-system, sans-serif;
225
+ }
226
+ .main-header {
227
+ background: linear-gradient(135deg, #1e40af 0%, #7c3aed 100%);
228
+ border-radius: 16px;
229
+ padding: 24px 32px;
230
+ margin-bottom: 20px;
231
+ text-align: center;
232
+ }
233
+ .main-header h1 {
234
+ color: #f8fafc;
235
+ font-size: 2rem;
236
+ font-weight: 800;
237
+ margin: 0 0 6px 0;
238
+ letter-spacing: -0.02em;
239
+ }
240
+ .main-header p {
241
+ color: #bfdbfe;
242
+ margin: 0;
243
+ font-size: 1rem;
244
+ }
245
+ .controls-panel {
246
+ background: #0f172a;
247
+ border: 1px solid #1e293b;
248
+ border-radius: 12px;
249
+ padding: 20px;
250
+ }
251
+ .section-label {
252
+ color: #94a3b8;
253
+ font-size: 12px;
254
+ font-weight: 600;
255
+ text-transform: uppercase;
256
+ letter-spacing: .08em;
257
+ margin-bottom: 8px;
258
+ }
259
+ footer { display: none !important; }
260
+ .gr-button-primary {
261
+ background: linear-gradient(135deg, #2563eb, #7c3aed) !important;
262
+ border: none !important;
263
+ color: white !important;
264
+ font-weight: 600 !important;
265
+ }
266
+ .gr-button-secondary {
267
+ background: #1e293b !important;
268
+ border: 1px solid #334155 !important;
269
+ color: #94a3b8 !important;
270
+ }
271
+ """
272
+
273
+
274
+ def update_product_list(category_label: str):
275
+ names = filter_products_by_category(category_label)
276
+ return gr.update(choices=names)
277
+
278
+
279
+ def do_compare(p1, p2, p3, p4, show_all):
280
+ selected = [p for p in [p1, p2, p3, p4] if p]
281
+ table, summary, export = build_comparison(selected, show_all)
282
+ return table, summary, export
283
+
284
+
285
+ def clear_all():
286
+ return None, None, None, None, False, "", "", ""
287
+
288
+
289
+ with gr.Blocks(css=CSS, title="SaaS Feature Comparator β€” ComparEdge") as demo:
290
+
291
+ # ── Header ────────────────────────────────────────────────────────────────
292
+ gr.HTML("""
293
+ <div class='main-header'>
294
+ <h1>πŸ” SaaS Feature Comparator</h1>
295
+ <p>Compare any 2–4 SaaS products side-by-side. Instantly see what's different.</p>
296
+ <p style='margin-top:8px;font-size:13px;opacity:.7'>
297
+ Data curated by <a href='https://comparedge.com' target='_blank'
298
+ style='color:#93c5fd;text-decoration:none;font-weight:600'>ComparEdge.com</a>
299
+ β€” the SaaS Comparison Platform
300
+ </p>
301
+ </div>
302
+ """)
303
+
304
+ # ── Controls ──────────────────────────────────────────────────────────────
305
+ with gr.Group(elem_classes="controls-panel"):
306
+ gr.Markdown("### βš™οΈ Select Products to Compare")
307
+
308
+ with gr.Row():
309
+ category_filter = gr.Dropdown(
310
+ choices=ALL_CATEGORIES,
311
+ value="All Categories",
312
+ label="πŸ—‚ Filter by Category",
313
+ interactive=True,
314
+ scale=1,
315
+ )
316
+
317
+ all_names = sorted(PRODUCT_BY_NAME.keys())
318
+
319
+ with gr.Row():
320
+ p1 = gr.Dropdown(choices=all_names, value="Notion",
321
+ label="Product 1", interactive=True, scale=1)
322
+ p2 = gr.Dropdown(choices=all_names, value="ClickUp",
323
+ label="Product 2", interactive=True, scale=1)
324
+
325
+ with gr.Row():
326
+ p3 = gr.Dropdown(choices=all_names, value=None,
327
+ label="Product 3 (optional)", interactive=True, scale=1)
328
+ p4 = gr.Dropdown(choices=all_names, value=None,
329
+ label="Product 4 (optional)", interactive=True, scale=1)
330
+
331
+ with gr.Row():
332
+ show_all_toggle = gr.Checkbox(
333
+ value=False,
334
+ label="Show all features (including identical ones)",
335
+ scale=2,
336
+ )
337
+ compare_btn = gr.Button("⚑ Compare", variant="primary", scale=1)
338
+ clear_btn = gr.Button("πŸ—‘ Clear", variant="secondary", scale=1)
339
+
340
+ # ── Comparison Output ─────────────────────────────────────────────────────
341
+ gr.Markdown("---")
342
+ comparison_html = gr.HTML(
343
+ value="<p style='text-align:center;color:#475569;padding:3rem;font-family:Inter,sans-serif'>Select products above and click <strong>Compare</strong> to see the comparison.</p>"
344
+ )
345
+
346
+ # ── Summary ───────────────────────────────────────────────────────────────
347
+ with gr.Accordion("πŸ“Š Feature Summary", open=True):
348
+ summary_md = gr.Markdown("")
349
+
350
+ # ── Export ────────────────────────────────────────────────────────────────
351
+ with gr.Accordion("πŸ“‹ Export as Markdown", open=False):
352
+ export_box = gr.Textbox(
353
+ label="Copy the markdown below",
354
+ lines=20,
355
+ interactive=False,
356
+ show_copy_button=True,
357
+ )
358
+
359
+ # ── Footer ────────────────────────────────────────────────────────────────
360
+ gr.HTML("""
361
+ <div style='text-align:center;padding:24px 0 8px;color:#475569;font-size:13px;
362
+ font-family:Inter,sans-serif;border-top:1px solid #1e293b;margin-top:20px'>
363
+ Powered by
364
+ <a href='https://comparedge.com' target='_blank'
365
+ style='color:#38bdf8;text-decoration:none;font-weight:600'>ComparEdge</a>
366
+ β€” SaaS Comparison Platform &nbsp;Β·&nbsp;
367
+ <a href='https://comparedge.com' target='_blank'
368
+ style='color:#64748b;text-decoration:none'>Explore 300+ SaaS tools β†’</a>
369
+ </div>
370
+ """)
371
+
372
+ # ── Events ────────────────────────────────────────────────────────────────
373
+ category_filter.change(
374
+ fn=update_product_list,
375
+ inputs=category_filter,
376
+ outputs=[p1],
377
+ )
378
+ category_filter.change(
379
+ fn=update_product_list,
380
+ inputs=category_filter,
381
+ outputs=[p2],
382
+ )
383
+
384
+ compare_btn.click(
385
+ fn=do_compare,
386
+ inputs=[p1, p2, p3, p4, show_all_toggle],
387
+ outputs=[comparison_html, summary_md, export_box],
388
+ )
389
+
390
+ # Also trigger on toggle change
391
+ show_all_toggle.change(
392
+ fn=do_compare,
393
+ inputs=[p1, p2, p3, p4, show_all_toggle],
394
+ outputs=[comparison_html, summary_md, export_box],
395
+ )
396
+
397
+ clear_btn.click(
398
+ fn=clear_all,
399
+ inputs=[],
400
+ outputs=[p1, p2, p3, p4, show_all_toggle, comparison_html, summary_md, export_box],
401
+ )
402
+
403
+ # Auto-compare on load with defaults
404
+ demo.load(
405
+ fn=do_compare,
406
+ inputs=[p1, p2, p3, p4, show_all_toggle],
407
+ outputs=[comparison_html, summary_md, export_box],
408
+ )
409
+
410
+
411
+ if __name__ == "__main__":
412
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio==4.44.0
2
+ pandas>=2.0.0