simikkk commited on
Commit
445ecb4
·
verified ·
1 Parent(s): bd9111a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -109
app.py CHANGED
@@ -110,86 +110,105 @@ def process_invoice_file(file_path: str, user_id: int) -> dict:
110
  if lines: data['vendor'] = lines[0]
111
 
112
  data['filename'] = os.path.basename(file_path)
 
113
  if supabase:
114
- supabase.table("invoices").insert({"user_id": user_id, "filename": data.get('filename'), "vendor": data.get('vendor'), "inv_number": data.get('inv_number'), "total": data.get('total'), "status": "done"}).execute()
115
  return data
116
  except Exception as e: return {"filename": os.path.basename(file_path), "status": "error"}
117
 
118
  # ==========================================
119
- # 3. CYBERpunk / COMMAND CENTER DESIGN
120
  # ==========================================
121
  CUSTOM_CSS = """
122
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
123
  :root {
124
- --bg: #050505;
125
- --panel: rgba(15, 15, 15, 0.6);
126
- --border: rgba(255, 255, 255, 0.08);
127
- --text: #ffffff;
128
- --muted: #6b7280;
129
- --accent: #00ff9d;
130
- --accent-glow: rgba(0, 255, 157, 0.15);
131
- --danger: #ff3366;
 
132
  }
133
  * { box-sizing: border-box; font-family: 'Inter', sans-serif; }
134
- body { background: var(--bg); color: var(--text); margin: 0; background-image: radial-gradient(circle at 50% 0%, rgba(0, 255, 157, 0.05) 0%, transparent 50%); }
135
  .gradio-container { max-width: 100% !important; padding: 0 !important; background: transparent !important; min-height: 100vh; }
136
  .hide { display: none !important; }
137
 
 
 
 
 
 
 
 
 
138
  /* Layout */
139
- .app-shell { display: flex; min-height: 100vh; }
140
- .sidebar { width: 260px; border-right: 1px solid var(--border); padding: 24px; display: flex; flex-direction: column; gap: 8px; background: rgba(0,0,0,0.3); backdrop-filter: blur(20px); position: sticky; top: 0; height: 100vh; }
141
- .main-area { flex: 1; padding: 40px 60px; overflow-y: auto; }
142
- .navbar { display: flex; justify-content: space-between; align-items: center; padding: 20px 40px; border-bottom: 1px solid var(--border); backdrop-filter: blur(10px); position: sticky; top: 0; background: rgba(5,5,5,0.8); z-index: 100; }
143
- .hero { text-align: center; padding: 120px 20px; }
144
- .hero h1 { font-size: 64px; font-weight: 700; margin-bottom: 24px; background: linear-gradient(180deg, #fff 0%, #888 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.1; letter-spacing: -2px; }
145
  .hero p { font-size: 20px; color: var(--muted); max-width: 600px; margin: 0 auto 40px; }
 
 
 
 
 
 
 
 
 
 
 
146
 
147
- /* Gradio Overrides - Making it look like a hacker SaaS */
148
  .gr-button {
149
- background: transparent !important;
150
  border: 1px solid var(--border) !important;
151
- color: var(--muted) !important;
152
  border-radius: 8px !important;
153
- padding: 10px 16px !important;
154
  font-weight: 500 !important;
155
- transition: all 0.2s !important;
156
  box-shadow: none !important;
157
- text-align: left !important;
158
- justify-content: flex-start !important;
159
  font-size: 14px !important;
160
  }
161
- .gr-button:hover { border-color: var(--accent) !important; color: var(--accent) !important; background: var(--accent-glow) !important; }
162
- .btn-primary { background: var(--accent) !important; color: #000 !important; border: none !important; font-weight: 700 !important; justify-content: center !important; text-align: center !important; }
163
- .btn-primary:hover { box-shadow: 0 0 30px var(--accent-glow) !important; transform: translateY(-1px) !important; background: var(--accent) !important; color: #000 !important; }
164
- .btn-danger { background: transparent !important; border: 1px solid var(--danger) !important; color: var(--danger) !important; justify-content: center !important; text-align: center !important; }
165
- .btn-danger:hover { background: var(--danger) !important; color: #fff !important; }
166
 
167
- .gr-textbox { background: rgba(0,0,0,0.3) !important; border: 1px solid var(--border) !important; border-radius: 8px !important; color: var(--text) !important; }
168
  .gr-textbox input { color: var(--text) !important; }
169
- .gr-textbox:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 2px var(--accent-glow) !important; }
170
  .gr-form { background: transparent !important; border: none !important; }
171
  .gr-box { border: none !important; background: transparent !important; }
172
 
173
- /* Dataframe - Command Center Table */
174
- .gr-dataframe { border: 1px solid var(--border) !important; border-radius: 12px !important; overflow: hidden; background: rgba(0,0,0,0.2) !important; }
175
  .gr-dataframe table { background: transparent !important; color: var(--text) !important; border-collapse: collapse; width: 100%; }
176
- .gr-dataframe th { background: rgba(0,0,0,0.5) !important; border-bottom: 1px solid var(--border) !important; color: var(--accent) !important; text-align: left; padding: 16px !important; font-family: 'JetBrains Mono', monospace !important; font-size: 12px !important; text-transform: uppercase; letter-spacing: 1px; }
177
- .gr-dataframe td { border-bottom: 1px solid var(--border) !important; padding: 16px !important; font-family: 'Inter', sans-serif !important; }
178
- .gr-dataframe tr:hover { background: var(--accent-glow) !important; }
179
 
180
  .gr-markdown { color: var(--text) !important; background: transparent !important; }
181
- .gr-markdown a { color: var(--accent) !important; text-decoration: none !important; border-bottom: 1px solid var(--accent) !important; }
182
  footer { display: none !important; }
183
-
184
- /* Logo */
185
- .logo-text { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 18px; display: flex; align-items: center; gap: 10px; color: var(--text); }
186
- .logo-dot { width: 10px; height: 10px; background: var(--accent); border-radius: 50%; box-shadow: 0 0 10px var(--accent); animation: pulse 2s infinite; }
187
- @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
188
  """
189
 
190
  # ==========================================
191
- # 4. BACKEND FUNCTIONS
192
  # ==========================================
 
 
 
 
 
 
 
 
 
 
 
 
193
  def get_user_invoices(user_dict):
194
  if not user_dict or not supabase: return pd.DataFrame()
195
  res = supabase.table("invoices").select("*").eq("user_id", user_dict['id']).order("created_at", desc=True).limit(100).execute()
@@ -241,109 +260,148 @@ def create_stripe_checkout(plan: str, user_dict):
241
  # ==========================================
242
  # 5. GRADIO UI ARCHITECTURE
243
  # ==========================================
244
- with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", neutral_hue="zinc")) as app:
245
  user_state = gr.State(None)
246
 
 
 
247
  # ---------------- VIEW 1: LANDING ----------------
248
  with gr.Group(visible=True) as landing_view:
249
- with gr.Row(elem_id="navbar"):
250
- gr.HTML("<div class='logo-text'><div class='logo-dot'></div>OmniParse_AI</div>")
251
- landing_login_btn = gr.Button("Log In", size="sm")
252
- with gr.Column(elem_id="hero"):
253
- gr.HTML("<h1>Invoice processing in <span style='color: var(--accent);'>seconds</span>, not hours.</h1>")
254
- gr.HTML("<p>AI extracts vendor, dates, amounts and line items from any PDF or image. Export to CSV, JSON or Excel.</p>")
255
- landing_signup_btn = gr.Button("Start Free — 20 invoices", elem_classes="btn-primary", size="lg")
256
- gr.HTML("<div style='margin-top: 40px; color: var(--muted); font-size: 14px; font-family: JetBrains Mono;'>99.2% accuracy · &lt;4s per invoice · 40+ formats</div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
  # ---------------- VIEW 2: AUTH ----------------
259
  with gr.Group(visible=False) as auth_view:
260
- with gr.Column(elem_id="hero"):
261
- gr.HTML("<div class='logo-text' style='justify-content:center; margin-bottom:24px;'><div class='logo-dot'></div>OmniParse_AI</div>")
262
- gr.HTML("<h2 style='margin-bottom:24px; font-size: 32px;'>Access Terminal</h2>")
263
- auth_email = gr.Textbox(label="Work Email", placeholder="you@company.com")
264
  auth_pass = gr.Textbox(label="Password", type="password", placeholder="Min 12 chars, 1 upper, 1 symbol")
265
- auth_submit = gr.Button("Execute", elem_classes="btn-primary", size="lg")
266
  auth_msg = gr.Markdown("")
267
- auth_back = gr.Button("Back to Home", elem_classes="btn-danger", size="sm")
268
 
269
  # ---------------- VIEW 3: DASHBOARD ----------------
270
  with gr.Group(visible=False) as dash_view:
271
- with gr.Row(elem_id="navbar"):
272
- gr.HTML("<div class='logo-text'><div class='logo-dot'></div>OmniParse_AI</div>")
273
- dash_logout = gr.Button("Log Out", elem_classes="btn-danger", size="sm")
 
 
 
274
 
275
- with gr.Row(elem_classes="app-shell"):
276
- # Sidebar
277
- with gr.Column(elem_classes="sidebar"):
278
- gr.HTML("<div style='color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:1px; margin-bottom:10px;'>Menu</div>")
279
- nav_upload = gr.Button("1. Upload Invoice")
280
- nav_invoices = gr.Button("2. My Invoices")
281
- nav_upgrade = gr.Button("3. Upgrade Plan")
282
-
283
- # Main Content Area
284
- with gr.Column(elem_classes="main-area"):
285
- # Upload Panel
286
- with gr.Group(visible=True) as panel_upload:
287
- gr.HTML("<h2 style='margin-bottom:20px;'>Upload Invoices</h2>")
288
- file_input = gr.File(file_count="multiple", file_types=[".pdf", ".png", ".jpg"])
289
- process_btn = gr.Button("Process Invoices", elem_classes="btn-primary", variant="primary")
290
- upload_status = gr.Markdown("")
291
-
292
- # Invoices Panel
293
- with gr.Group(visible=False) as panel_invoices:
294
- gr.HTML("<h2 style='margin-bottom:20px;'>Processed Invoices</h2>")
295
- invoices_table = gr.Dataframe(headers=["Vendor", "Invoice#", "Total", "Status"], wrap=True, interactive=False)
296
  export_btn = gr.Button("Export to CSV", elem_classes="btn-primary")
297
- csv_file = gr.File(label="Download CSV", visible=False)
298
-
299
- # Upgrade Panel
300
- with gr.Group(visible=False) as panel_upgrade:
301
- gr.HTML("<h2 style='margin-bottom:20px;'>Upgrade Plan</h2>")
302
- with gr.Row():
303
- gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Basic</h4><h2 style='color:var(--accent);'>$29/mo</h2></div>")
304
- gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Pro</h4><h2 style='color:var(--accent);'>$129/mo</h2></div>")
305
- gr.HTML("<div style='text-align:center;padding:30px;border:1px solid var(--border);border-radius:12px;'><h4>Enterprise</h4><h2 style='color:var(--accent);'>$499/mo</h2></div>")
306
- with gr.Row():
307
- up_basic = gr.Button("Choose Basic", elem_classes="btn-primary")
308
- up_pro = gr.Button("Choose Pro", elem_classes="btn-primary")
309
- up_ent = gr.Button("Choose Enterprise", elem_classes="btn-primary")
310
- checkout_link = gr.Markdown("")
311
 
312
  # ==========================================
313
- # 6. EVENT BINDINGS (FUNKČNÍ INTERAKCE)
314
  # ==========================================
315
  def show_auth(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
316
  def show_landing(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
317
  def show_dashboard(user):
318
  if not user: return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
319
- return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
 
 
 
 
 
 
320
 
321
  landing_login_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
322
  landing_signup_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
 
323
  auth_back.click(fn=show_landing, outputs=[landing_view, auth_view, dash_view])
324
 
325
  auth_submit.click(
326
  fn=handle_auth,
327
  inputs=[auth_email, auth_pass],
328
  outputs=[user_state, auth_msg, landing_view, auth_view, dash_view]
 
 
329
  ).then(
330
  fn=get_user_invoices, inputs=[user_state], outputs=[invoices_table]
331
  )
332
 
333
- dash_logout.click(fn=lambda: (None, *show_landing()), outputs=[user_state, landing_view, auth_view, dash_view])
 
 
 
334
 
335
  def switch_panel(panel_name):
336
- u, i, p = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
337
  if panel_name == "upload": u = gr.update(visible=True)
338
  elif panel_name == "invoices": i = gr.update(visible=True)
339
- elif panel_name == "upgrade": p = gr.update(visible=True)
340
- return u, i, p
341
-
342
- nav_upload.click(fn=lambda: switch_panel("upload"), outputs=[panel_upload, panel_invoices, panel_upgrade])
343
- nav_invoices.click(fn=lambda: switch_panel("invoices"), outputs=[panel_upload, panel_invoices, panel_upgrade])
344
- nav_upgrade.click(fn=lambda: switch_panel("upgrade"), outputs=[panel_upload, panel_invoices, panel_upgrade])
 
 
 
 
 
 
 
 
345
 
346
- process_btn.click(fn=handle_upload, inputs=[file_input, user_state], outputs=[upload_status, invoices_table])
347
 
348
  def handle_export(user_dict):
349
  df = get_user_invoices(user_dict)
@@ -355,7 +413,6 @@ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="emerald", ne
355
 
356
  up_basic.click(fn=lambda u: create_stripe_checkout("basic", u), inputs=[user_state], outputs=[checkout_link])
357
  up_pro.click(fn=lambda u: create_stripe_checkout("pro", u), inputs=[user_state], outputs=[checkout_link])
358
- up_ent.click(fn=lambda u: create_stripe_checkout("enterprise", u), inputs=[user_state], outputs=[checkout_link])
359
 
360
  if __name__ == "__main__":
361
  app.launch(server_name="0.0.0.0", server_port=7860)
 
110
  if lines: data['vendor'] = lines[0]
111
 
112
  data['filename'] = os.path.basename(file_path)
113
+ data['status'] = 'done'
114
  if supabase:
115
+ supabase.table("invoices").insert({"user_id": user_id, "filename": data.get('filename'), "vendor": data.get('vendor'), "inv_number": data.get('inv_number'), "total": data.get('total'), "status": "done", "confidence": 0.95}).execute()
116
  return data
117
  except Exception as e: return {"filename": os.path.basename(file_path), "status": "error"}
118
 
119
  # ==========================================
120
+ # 3. MODERN SAAS DESIGN SYSTEM (CSS)
121
  # ==========================================
122
  CUSTOM_CSS = """
123
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
124
  :root {
125
+ --bg: #09090b;
126
+ --panel: #0f0f12;
127
+ --border: #1f1f23;
128
+ --text: #f4f4f5;
129
+ --muted: #71717a;
130
+ --accent: #6366f1; /* Indigo */
131
+ --accent-hover: #4f46e5;
132
+ --accent-glow: rgba(99, 102, 241, 0.2);
133
+ --success: #10b981;
134
  }
135
  * { box-sizing: border-box; font-family: 'Inter', sans-serif; }
136
+ body { background: var(--bg); color: var(--text); margin: 0; background-image: radial-gradient(circle at 50% 0%, rgba(99, 102, 241, 0.08) 0%, transparent 40%); }
137
  .gradio-container { max-width: 100% !important; padding: 0 !important; background: transparent !important; min-height: 100vh; }
138
  .hide { display: none !important; }
139
 
140
+ /* Navbar */
141
+ .navbar { display: flex; justify-content: space-between; align-items: center; padding: 20px 60px; border-bottom: 1px solid var(--border); backdrop-filter: blur(10px); position: sticky; top: 0; background: rgba(9,9,11,0.8); z-index: 100; }
142
+ .logo { font-weight: 700; font-size: 20px; display: flex; align-items: center; gap: 8px; color: var(--text); }
143
+ .logo svg { width: 24px; height: 24px; fill: var(--accent); }
144
+ .nav-links { display: flex; gap: 32px; align-items: center; }
145
+ .nav-link { color: var(--muted); font-size: 14px; font-weight: 500; text-decoration: none; transition: 0.2s; }
146
+ .nav-link:hover { color: var(--text); }
147
+
148
  /* Layout */
149
+ .main-container { max-width: 1200px; margin: 0 auto; padding: 60px 40px; }
150
+ .hero { text-align: center; padding: 80px 20px; }
151
+ .hero h1 { font-size: 64px; font-weight: 800; margin-bottom: 24px; background: linear-gradient(180deg, #fff 0%, #a1a1aa 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.1; letter-spacing: -2px; }
 
 
 
152
  .hero p { font-size: 20px; color: var(--muted); max-width: 600px; margin: 0 auto 40px; }
153
+ .badges { display: flex; justify-content: center; gap: 20px; margin-top: 40px; color: var(--muted); font-size: 13px; font-weight: 500; }
154
+ .badge { display: flex; align-items: center; gap: 6px; border: 1px solid var(--border); padding: 6px 12px; border-radius: 20px; background: var(--panel); }
155
+
156
+ /* Dashboard */
157
+ .dash-grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 20px; margin-bottom: 40px; }
158
+ .stat-card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 24px; }
159
+ .stat-label { color: var(--muted); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
160
+ .stat-value { font-size: 28px; font-weight: 700; color: var(--text); }
161
+
162
+ .dropzone { background: var(--panel) !important; border: 2px dashed var(--border) !important; border-radius: 16px !important; padding: 60px !important; text-align: center !important; transition: 0.2s !important; }
163
+ .dropzone:hover { border-color: var(--accent) !important; background: var(--accent-glow) !important; }
164
 
165
+ /* Gradio Overrides */
166
  .gr-button {
167
+ background: var(--panel) !important;
168
  border: 1px solid var(--border) !important;
169
+ color: var(--text) !important;
170
  border-radius: 8px !important;
171
+ padding: 10px 20px !important;
172
  font-weight: 500 !important;
173
+ transition: 0.2s !important;
174
  box-shadow: none !important;
 
 
175
  font-size: 14px !important;
176
  }
177
+ .gr-button:hover { border-color: var(--text) !important; }
178
+ .btn-primary { background: var(--accent) !important; border: none !important; color: #fff !important; font-weight: 600 !important; }
179
+ .btn-primary:hover { background: var(--accent-hover) !important; box-shadow: 0 0 20px var(--accent-glow) !important; }
180
+ .btn-ghost { background: transparent !important; border: 1px solid var(--border) !important; }
 
181
 
182
+ .gr-textbox { background: var(--panel) !important; border: 1px solid var(--border) !important; border-radius: 8px !important; color: var(--text) !important; }
183
  .gr-textbox input { color: var(--text) !important; }
184
+ .gr-textbox:focus { border-color: var(--accent) !important; }
185
  .gr-form { background: transparent !important; border: none !important; }
186
  .gr-box { border: none !important; background: transparent !important; }
187
 
188
+ .gr-dataframe { border: 1px solid var(--border) !important; border-radius: 12px !important; overflow: hidden; background: var(--panel) !important; }
 
189
  .gr-dataframe table { background: transparent !important; color: var(--text) !important; border-collapse: collapse; width: 100%; }
190
+ .gr-dataframe th { background: rgba(0,0,0,0.3) !important; border-bottom: 1px solid var(--border) !important; color: var(--muted) !important; text-align: left; padding: 12px !important; font-size: 12px !important; text-transform: uppercase; letter-spacing: 1px; }
191
+ .gr-dataframe td { border-bottom: 1px solid var(--border) !important; padding: 12px !important; }
 
192
 
193
  .gr-markdown { color: var(--text) !important; background: transparent !important; }
 
194
  footer { display: none !important; }
 
 
 
 
 
195
  """
196
 
197
  # ==========================================
198
+ # 4. BACKEND LOGICS
199
  # ==========================================
200
+ def get_dashboard_stats(user_dict):
201
+ if not user_dict or not supabase:
202
+ return 0, "$0.00", 0, "--"
203
+ res = supabase.table("invoices").select("total, is_duplicate, confidence").eq("user_id", user_dict['id']).execute()
204
+ data = res.data
205
+ total_count = len(data)
206
+ total_amount = sum([d['total'] for d in data if d.get('total')])
207
+ duplicates = sum([1 for d in data if d.get('is_duplicate')])
208
+ avg_conf = sum([d['confidence'] for d in data if d.get('confidence')]) / total_count if total_count > 0 else 0
209
+
210
+ return total_count, f"${total_amount:,.2f}", duplicates, f"{avg_conf*100:.1f}%" if total_count > 0 else "--"
211
+
212
  def get_user_invoices(user_dict):
213
  if not user_dict or not supabase: return pd.DataFrame()
214
  res = supabase.table("invoices").select("*").eq("user_id", user_dict['id']).order("created_at", desc=True).limit(100).execute()
 
260
  # ==========================================
261
  # 5. GRADIO UI ARCHITECTURE
262
  # ==========================================
263
+ with gr.Blocks(css=CUSTOM_CSS, theme=gr.themes.Default(primary_hue="indigo", neutral_hue="zinc")) as app:
264
  user_state = gr.State(None)
265
 
266
+ ICON_BOLT = '<svg viewBox="0 0 24 24"><path d="M13 2L3 14h7l-1 8 10-12h-7l1-8z"/></svg>'
267
+
268
  # ---------------- VIEW 1: LANDING ----------------
269
  with gr.Group(visible=True) as landing_view:
270
+ with gr.Row(elem_classes="navbar"):
271
+ gr.HTML(f"<div class='logo'>{ICON_BOLT} OmniParse</div>")
272
+ with gr.Row(elem_classes="nav-links"):
273
+ gr.HTML("<a href='#features' class='nav-link'>Features</a>")
274
+ gr.HTML("<a href='#pricing' class='nav-link'>Pricing</a>")
275
+ landing_login_btn = gr.Button("Log In", size="sm", elem_classes="btn-ghost")
276
+ landing_signup_btn = gr.Button("Get Started", size="sm", elem_classes="btn-primary")
277
+
278
+ with gr.Column(elem_classes="hero"):
279
+ gr.HTML("<h1>Invoice Processing, Reimagined</h1>")
280
+ gr.HTML("<p>AI extracts vendor, dates, amounts and line items from any PDF or image in seconds. Export to CSV, JSON or Excel.</p>")
281
+ with gr.Row(elem_classes="hero-cta", justify="center"):
282
+ cta_start = gr.Button("Start Free Trial", elem_classes="btn-primary", size="lg")
283
+ cta_how = gr.Button("See How It Works", elem_classes="btn-ghost", size="lg")
284
+ gr.HTML("""
285
+ <div class="badges">
286
+ <div class="badge">⚡ Powered by advanced AI</div>
287
+ <div class="badge">🔒 SOC 2 Compliant</div>
288
+ </div>
289
+ """)
290
+
291
+ with gr.Column(elem_classes="main-container"):
292
+ gr.HTML("<h2 style='text-align:center; margin-bottom:40px;'>Everything You Need</h2>")
293
+ gr.HTML("""
294
+ <div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:20px;">
295
+ <div class="stat-card"><h3>Instant Extraction</h3><p style="color:var(--muted); font-size:14px;">Extract all invoice fields in under 3 seconds with 99% accuracy.</p></div>
296
+ <div class="stat-card"><h3>Duplicate Detection</h3><p style="color:var(--muted); font-size:14px;">Automatically flags duplicate invoices to prevent double payments.</p></div>
297
+ <div class="stat-card"><h3>AI Chat Assistant</h3><p style="color:var(--muted); font-size:14px;">Ask questions about your invoices and get instant insights.</p></div>
298
+ <div class="stat-card"><h3>Multi-Currency</h3><p style="color:var(--muted); font-size:14px;">Process invoices in multiple currencies with automatic conversion.</p></div>
299
+ <div class="stat-card"><h3>Bulk Processing</h3><p style="color:var(--muted); font-size:14px;">Upload hundreds of invoices at once and process them in parallel.</p></div>
300
+ <div class="stat-card"><h3>Enterprise Security</h3><p style="color:var(--muted); font-size:14px;">Your data is encrypted at rest and in transit. SOC 2 compliant.</p></div>
301
+ </div>
302
+ """)
303
 
304
  # ---------------- VIEW 2: AUTH ----------------
305
  with gr.Group(visible=False) as auth_view:
306
+ with gr.Column(elem_classes="hero"):
307
+ gr.HTML(f"<div class='logo' style='justify-content:center; margin-bottom:24px;'>{ICON_BOLT} OmniParse</div>")
308
+ gr.HTML("<h1 style='font-size:48px;'>Welcome back</h1>")
309
+ auth_email = gr.Textbox(label="Email", placeholder="you@company.com")
310
  auth_pass = gr.Textbox(label="Password", type="password", placeholder="Min 12 chars, 1 upper, 1 symbol")
311
+ auth_submit = gr.Button("Sign In / Sign Up", elem_classes="btn-primary", size="lg")
312
  auth_msg = gr.Markdown("")
313
+ auth_back = gr.Button("Back to Home", elem_classes="btn-ghost", size="sm")
314
 
315
  # ---------------- VIEW 3: DASHBOARD ----------------
316
  with gr.Group(visible=False) as dash_view:
317
+ with gr.Row(elem_classes="navbar"):
318
+ gr.HTML(f"<div class='logo'>{ICON_BOLT} OmniParse</div>")
319
+ with gr.Row(elem_classes="nav-links"):
320
+ dash_nav_upload = gr.Button("Upload", elem_classes="btn-ghost", size="sm")
321
+ dash_nav_inv = gr.Button("Invoices", elem_classes="btn-ghost", size="sm")
322
+ dash_logout = gr.Button("Log Out", elem_classes="btn-ghost", size="sm")
323
 
324
+ with gr.Column(elem_classes="main-container"):
325
+ # Stat Cards Row
326
+ with gr.Row(elem_classes="dash-grid"):
327
+ stat_total = gr.HTML("<div class='stat-card'><div class='stat-label'>Total Invoices</div><div class='stat-value'>0</div></div>")
328
+ stat_amount = gr.HTML("<div class='stat-card'><div class='stat-label'>Total Amount</div><div class='stat-value'>$0.00</div></div>")
329
+ stat_dupes = gr.HTML("<div class='stat-card'><div class='stat-label'>Duplicates</div><div class='stat-value'>0</div></div>")
330
+ stat_conf = gr.HTML("<div class='stat-card'><div class='stat-label'>Avg Confidence</div><div class='stat-value'>--</div></div>")
331
+
332
+ # Upload Panel
333
+ with gr.Group(visible=True) as panel_upload:
334
+ gr.HTML("<h2 style='margin-bottom:20px;'>Welcome back! Upload your invoices</h2>")
335
+ file_input = gr.File(file_count="multiple", file_types=[".pdf", ".png", ".jpg"], elem_classes="dropzone")
336
+ process_btn = gr.Button("Process Invoices", elem_classes="btn-primary", variant="primary")
337
+ upload_status = gr.Markdown("")
338
+
339
+ # Invoices Panel
340
+ with gr.Group(visible=False) as panel_invoices:
341
+ gr.HTML("<h2 style='margin-bottom:20px;'>Processed Invoices</h2>")
342
+ invoices_table = gr.Dataframe(headers=["Vendor", "Invoice#", "Total", "Status"], wrap=True, interactive=False)
343
+ with gr.Row():
 
344
  export_btn = gr.Button("Export to CSV", elem_classes="btn-primary")
345
+ up_basic = gr.Button("Upgrade to Basic", elem_classes="btn-ghost")
346
+ up_pro = gr.Button("Upgrade to Pro", elem_classes="btn-ghost")
347
+ csv_file = gr.File(label="Download CSV", visible=False)
348
+ checkout_link = gr.Markdown("")
 
 
 
 
 
 
 
 
 
 
349
 
350
  # ==========================================
351
+ # 6. EVENT BINDINGS
352
  # ==========================================
353
  def show_auth(): return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
354
  def show_landing(): return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
355
  def show_dashboard(user):
356
  if not user: return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
357
+ # Update stats on login
358
+ total, amount, dupes, conf = get_dashboard_stats(user)
359
+ stat_total_html = f"<div class='stat-card'><div class='stat-label'>Total Invoices</div><div class='stat-value'>{total}</div></div>"
360
+ stat_amount_html = f"<div class='stat-card'><div class='stat-label'>Total Amount</div><div class='stat-value'>{amount}</div></div>"
361
+ stat_dupes_html = f"<div class='stat-card'><div class='stat-label'>Duplicates</div><div class='stat-value'>{dupes}</div></div>"
362
+ stat_conf_html = f"<div class='stat-card'><div class='stat-label'>Avg Confidence</div><div class='stat-value'>{conf}</div></div>"
363
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), stat_total_html, stat_amount_html, stat_dupes_html, stat_conf_html
364
 
365
  landing_login_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
366
  landing_signup_btn.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
367
+ cta_start.click(fn=show_auth, outputs=[landing_view, auth_view, dash_view])
368
  auth_back.click(fn=show_landing, outputs=[landing_view, auth_view, dash_view])
369
 
370
  auth_submit.click(
371
  fn=handle_auth,
372
  inputs=[auth_email, auth_pass],
373
  outputs=[user_state, auth_msg, landing_view, auth_view, dash_view]
374
+ ).then(
375
+ fn=show_dashboard, inputs=[user_state], outputs=[landing_view, auth_view, dash_view, stat_total, stat_amount, stat_dupes, stat_conf]
376
  ).then(
377
  fn=get_user_invoices, inputs=[user_state], outputs=[invoices_table]
378
  )
379
 
380
+ def logout():
381
+ return None, *show_landing(), *["<div class='stat-card'><div class='stat-label'>Total Invoices</div><div class='stat-value'>0</div></div>"]*4
382
+
383
+ dash_logout.click(fn=logout, outputs=[user_state, landing_view, auth_view, dash_view, stat_total, stat_amount, stat_dupes, stat_conf])
384
 
385
  def switch_panel(panel_name):
386
+ u, i = gr.update(visible=False), gr.update(visible=False)
387
  if panel_name == "upload": u = gr.update(visible=True)
388
  elif panel_name == "invoices": i = gr.update(visible=True)
389
+ return u, i
390
+
391
+ dash_nav_upload.click(fn=lambda: switch_panel("upload"), outputs=[panel_upload, panel_invoices])
392
+ dash_nav_inv.click(fn=lambda: switch_panel("invoices"), outputs=[panel_upload, panel_invoices])
393
+
394
+ def handle_processing(files, user):
395
+ status, df = handle_upload(files, user)
396
+ total, amount, dupes, conf = get_dashboard_stats(user)
397
+ return status, df, (
398
+ f"<div class='stat-card'><div class='stat-label'>Total Invoices</div><div class='stat-value'>{total}</div></div>",
399
+ f"<div class='stat-card'><div class='stat-label'>Total Amount</div><div class='stat-value'>{amount}</div></div>",
400
+ f"<div class='stat-card'><div class='stat-label'>Duplicates</div><div class='stat-value'>{dupes}</div></div>",
401
+ f"<div class='stat-card'><div class='stat-label'>Avg Confidence</div><div class='stat-value'>{conf}</div></div>"
402
+ )
403
 
404
+ process_btn.click(fn=handle_processing, inputs=[file_input, user_state], outputs=[upload_status, invoices_table, stat_total, stat_amount, stat_dupes, stat_conf])
405
 
406
  def handle_export(user_dict):
407
  df = get_user_invoices(user_dict)
 
413
 
414
  up_basic.click(fn=lambda u: create_stripe_checkout("basic", u), inputs=[user_state], outputs=[checkout_link])
415
  up_pro.click(fn=lambda u: create_stripe_checkout("pro", u), inputs=[user_state], outputs=[checkout_link])
 
416
 
417
  if __name__ == "__main__":
418
  app.launch(server_name="0.0.0.0", server_port=7860)