simikkk commited on
Commit
048bea9
·
verified ·
1 Parent(s): 2ffe341

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -51
app.py CHANGED
@@ -13,7 +13,7 @@ import time
13
  import sqlite3
14
  import hashlib
15
 
16
- print("Initializing OmniParse AI Enterprise Terminal v5.0 with User Accounts...")
17
 
18
  # ─── DATABASE INITIALIZATION ────────────────────────────────────────────────
19
  DB_FILE = "omniparse.db"
@@ -59,6 +59,9 @@ def db_register(username, password):
59
  return json.dumps({"status": "ERROR", "message": "Zadej jméno i heslo, nesmí to bejt prázdný!"})
60
 
61
  username = username.strip().lower()
 
 
 
62
  try:
63
  conn = sqlite3.connect(DB_FILE)
64
  cursor = conn.cursor()
@@ -92,7 +95,7 @@ def db_login(username, password):
92
 
93
  user_id, stored_hash, salt = row
94
 
95
- # Podpora pro session refresh (pokud uživatel reloadne stránku a má token v sessionStorage)
96
  if password == "SESSION_REFRESH":
97
  cursor.execute("SELECT COUNT(*), SUM(fields_count) FROM invoices WHERE user_id = ?", (user_id,))
98
  stats = cursor.fetchone()
@@ -215,9 +218,11 @@ def parse_invoice(image_b64: str, username: str):
215
 
216
  invoice_fp = extracted_fields.get("INVOICE_NUMBER", extracted_fields.get("TOTAL", "UNKNOWN"))
217
 
218
- # Kontrola duplicity v rámci přihlášeného uživatele
219
  is_duplicate = False
220
- if username and invoice_fp != "UNKNOWN":
 
 
221
  try:
222
  conn = sqlite3.connect(DB_FILE)
223
  cursor = conn.cursor()
@@ -253,10 +258,9 @@ def parse_invoice(image_b64: str, username: str):
253
  "extracted_data": extracted_fields,
254
  }
255
 
256
- # Zápis do DB pro konkrétního přihlášeného usera
257
- fields_count = len(extracted_fields)
258
- user_docs, user_fields = 1, fields_count
259
- if username:
260
  try:
261
  conn = sqlite3.connect(DB_FILE)
262
  cursor = conn.cursor()
@@ -270,19 +274,21 @@ def parse_invoice(image_b64: str, username: str):
270
  )
271
  conn.commit()
272
 
273
- # Vytáhnout aktualizované kumulativní statistiky
274
  cursor.execute("SELECT COUNT(*), SUM(fields_count) FROM invoices WHERE user_id = ?", (user_id,))
275
  count_row = cursor.fetchone()
276
- user_docs = count_row[0] or 0
277
- user_fields = count_row[1] or 0
 
 
278
  conn.close()
279
  except Exception as db_err:
280
  print(f"DB Insert failure: {db_err}")
281
-
282
- result["user_stats"] = {
283
- "docs_parsed": user_docs,
284
- "fields_extracted": user_fields
285
- }
 
286
 
287
  # Generování CSV exportu
288
  rows = [["Field", "Value"]]
@@ -425,8 +431,9 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
425
 
426
  <div id="toast-root" style="position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;"></div>
427
 
 
428
  <div id="auth-screen" class="fixed inset-0 z-50 flex items-center justify-center bg-[#09090b] p-4">
429
- <div class="card w-full max-w-md p-8 space-y-6">
430
  <div class="text-center space-y-2">
431
  <div class="mx-auto w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-blue-700 flex items-center justify-center shadow-lg shadow-blue-500/25">
432
  <svg width="22" height="22" viewBox="0 0 16 16" fill="none"><path d="M2 3h12M2 6h8M2 9h10M2 12h6" stroke="white" stroke-width="1.5" stroke-linecap="round"/><circle cx="13" cy="11" r="2.5" stroke="white" stroke-width="1.2"/><path d="M15 13l1.5 1.5" stroke="white" stroke-width="1.2" stroke-linecap="round"/></svg>
@@ -435,29 +442,40 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
435
  <p class="text-xs text-zinc-400">Multi-User Document Data-Mining Terminal</p>
436
  </div>
437
 
438
- <div class="space-y-4">
439
  <div>
440
- <label class="block text-xs font-semibold uppercase tracking-wider text-zinc-500 mb-1">Uživatelské jméno</label>
441
- <input type="text" id="auth-username" class="chat-input" placeholder="Zadej login...">
442
  </div>
443
  <div>
444
- <label class="block text-xs font-semibold uppercase tracking-wider text-zinc-500 mb-1">Heslo</label>
445
- <input type="password" id="auth-password" class="chat-input" placeholder="••••••••">
446
  </div>
447
  </div>
448
 
449
- <div class="flex flex-col gap-2 pt-2">
450
- <button onclick="handleAuthAction('login')" class="btn-primary w-full py-3 font-semibold text-sm">
451
  Přihlásit se do systému ⚡
452
  </button>
453
- <button onclick="handleAuthAction('register')" class="btn-secondary w-full py-3 text-sm">
454
- Registrovat nový účet
 
 
 
 
 
 
 
 
 
 
455
  </button>
456
  </div>
457
  </div>
458
  </div>
459
 
460
 
 
461
  <div id="app-screen" style="display: none;">
462
  <header style="border-bottom:1px solid var(--border);background:rgba(9,9,11,.92);backdrop-filter:blur(16px); position:sticky; top:0; z-index:40;">
463
  <div style="max-width:1280px;margin:0 auto;padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between;">
@@ -473,17 +491,18 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
473
  <span id="status-dot" style="width:8px;height:8px;border-radius:50%;background:#34d399;"></span>
474
  <span id="status-text" style="font-size:12px;color:var(--text2);font-weight:500;">System Ready</span>
475
  </div>
476
- <span id="user-display" class="text-sm font-medium text-blue-400"></span>
477
- <button onclick="handleLogout()" class="text-xs text-red-400 hover:underline">Odhlásit se</button>
478
  </div>
479
  </div>
480
  </header>
481
 
482
  <main style="max-width:1280px;margin:0 auto;padding:24px;">
 
483
  <div class="grid grid-cols-4 gap-3 mb-5">
484
  <div class="card p-4 flex items-center gap-3">
485
  <div class="text-2xl">📄</div>
486
- <div><p class="text-[10px] text-zinc-500 uppercase font-bold">Moje Dokumenty</p><p id="st-docs" class="text-lg font-bold text-white">0</p></div>
487
  </div>
488
  <div class="card p-4 flex items-center gap-3">
489
  <div class="text-2xl">⚡</div>
@@ -499,7 +518,9 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
499
  </div>
500
  </div>
501
 
 
502
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
 
503
  <div class="space-y-4">
504
  <div class="card p-5 space-y-4">
505
  <div class="flex justify-between items-center">
@@ -544,6 +565,7 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
544
  </div>
545
  </div>
546
 
 
547
  <div class="space-y-4">
548
  <div class="card p-5 space-y-3">
549
  <div class="flex justify-between items-center"><span class="text-sm font-semibold">Live Extraction Terminal</span><span class="section-label">Output JSON</span></div>
@@ -580,6 +602,7 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);}
580
  </main>
581
  </div>
582
 
 
583
  <script>
584
  let currentBase64 = "";
585
  let latestExtractedJson = "";
@@ -588,6 +611,10 @@ let authPolling = null;
588
  let parsePolling = null;
589
  let chatPolling = null;
590
 
 
 
 
 
591
  function showToast(msg, type='info') {
592
  const root = document.getElementById("toast-root");
593
  const toast = document.createElement("div");
@@ -605,26 +632,41 @@ function checkLoginState() {
605
  if (user) {
606
  document.getElementById("auth-screen").style.display = "none";
607
  document.getElementById("app-screen").style.display = "block";
608
- document.getElementById("user-display").innerText = `👤 ${user}`;
609
 
610
- // Refresh statistik z DB na pozadí
611
- const grUser = document.querySelector("#gr-auth-user textarea");
612
- const grPass = document.querySelector("#gr-auth-pass textarea");
613
- if (grUser && grPass) {
614
- grUser.value = user;
615
- grUser.dispatchEvent(new Event("input", { bubbles: true }));
616
- grPass.value = "SESSION_REFRESH";
617
- grPass.dispatchEvent(new Event("input", { bubbles: true }));
618
- document.querySelector("#gr-login-btn").click();
 
 
 
 
 
 
 
 
 
 
 
 
619
  }
620
- if(authPolling) clearInterval(authPolling);
621
- authPolling = setInterval(checkAuthResponse, 150);
622
  } else {
623
  document.getElementById("auth-screen").style.display = "flex";
624
  document.getElementById("app-screen").style.display = "none";
625
  }
626
  }
627
 
 
 
 
 
 
 
628
  function handleAuthAction(action) {
629
  const userVal = document.getElementById("auth-username").value.trim();
630
  const passVal = document.getElementById("auth-password").value;
@@ -671,6 +713,7 @@ function checkAuthResponse() {
671
  showToast("Přihlášení úspěšné!");
672
  document.getElementById("auth-screen").style.display = "none";
673
  document.getElementById("app-screen").style.display = "block";
 
674
  document.getElementById("user-display").innerText = `👤 ${data.username}`;
675
  if (data.stats) {
676
  document.getElementById("st-docs").innerText = data.stats.docs_parsed;
@@ -719,7 +762,7 @@ function executeExtraction() {
719
  document.getElementById("btn-loading").style.display = "flex";
720
  document.getElementById("status-text").innerText = "Těžím data...";
721
 
722
- const user = sessionStorage.getItem("omniparse_user") || "";
723
  const grImg = document.querySelector("#gr-image-input textarea");
724
  const grUser = document.querySelector("#gr-current-user textarea");
725
 
@@ -756,12 +799,27 @@ function checkParseResponse() {
756
  document.getElementById("st-time").innerText = `${res.parse_time_seconds}s`;
757
  document.getElementById("st-conf").innerText = `${Math.round(res.security_and_compliance.confidence_score * 100)}%`;
758
  document.getElementById("comp-val").innerText = res.security_and_compliance.cross_field_validation;
759
- document.getElementById("comp-dup").innerText = res.security_and_compliance.duplicate_detected ? "⚠️ POTENCIÁLNÍ DUPLICITA" : "✅ OK (Unikátní)";
760
 
761
- if (res.user_stats) {
762
- document.getElementById("st-docs").innerText = res.user_stats.docs_parsed;
763
- document.getElementById("st-fields").innerText = res.user_stats.fields_extracted;
 
 
764
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  if (res._csv_b64) {
766
  csvB64Data = res._csv_b64;
767
  document.getElementById("export-actions").style.display = "block";
@@ -834,7 +892,6 @@ function checkChatResponse() {
834
  }
835
  }
836
 
837
- // Inicializační kontrola session při bootu
838
  setTimeout(checkLoginState, 500);
839
  </script>
840
  </body>
@@ -843,10 +900,9 @@ setTimeout(checkLoginState, 500);
843
 
844
  # ─── GRADIO APPLICATION CONTAINER ───────────────────────────────────────────
845
  with gr.Blocks(css=CUSTOM_CSS, title="OmniParse AI Enterprise Terminal") as demo:
846
- # Vstřikování custom HTML frontendu s kompletní logikou
847
  gr.HTML(HTML_UI)
848
 
849
- # Skryté ovládací prvky (Bridge) — JS komunikuje přímo s těmito objekty
850
  with gr.Row(elem_id="hidden-controls", visible=False):
851
  with gr.Column():
852
  gr_image_input = gr.Textbox(label="image_b64", elem_id="gr-image-input", visible=False)
@@ -859,14 +915,12 @@ with gr.Blocks(css=CUSTOM_CSS, title="OmniParse AI Enterprise Terminal") as demo
859
  gr_chat_btn = gr.Button("chat", elem_id="gr-chat-btn", visible=False)
860
  gr_chat_out = gr.Textbox(label="chat_out", elem_id="gr-chat-out", visible=False)
861
 
862
- # Auth kanály pro přihlašování a registraci
863
  gr_auth_user = gr.Textbox(label="auth_user", elem_id="gr-auth-user", visible=False)
864
  gr_auth_pass = gr.Textbox(label="auth_pass", elem_id="gr-auth-pass", visible=False)
865
  gr_login_btn = gr.Button("login", elem_id="gr-login-btn", visible=False)
866
  gr_register_btn = gr.Button("register", elem_id="gr-register-btn", visible=False)
867
  gr_auth_response = gr.Textbox(label="auth_response", elem_id="gr-auth-response", visible=False)
868
 
869
- # Propojení frontend-backend eventů
870
  gr_parse_btn.click(
871
  fn=gr_parse,
872
  inputs=[gr_image_input, gr_current_user],
 
13
  import sqlite3
14
  import hashlib
15
 
16
+ print("Initializing OmniParse AI Enterprise Terminal v5.5 with Optional Free Plan...")
17
 
18
  # ─── DATABASE INITIALIZATION ────────────────────────────────────────────────
19
  DB_FILE = "omniparse.db"
 
59
  return json.dumps({"status": "ERROR", "message": "Zadej jméno i heslo, nesmí to bejt prázdný!"})
60
 
61
  username = username.strip().lower()
62
+ if username == "guest":
63
+ return json.dumps({"status": "ERROR", "message": "Jméno 'guest' je rezervované pro Free Plan!"})
64
+
65
  try:
66
  conn = sqlite3.connect(DB_FILE)
67
  cursor = conn.cursor()
 
95
 
96
  user_id, stored_hash, salt = row
97
 
98
+ # Podpora pro session refresh
99
  if password == "SESSION_REFRESH":
100
  cursor.execute("SELECT COUNT(*), SUM(fields_count) FROM invoices WHERE user_id = ?", (user_id,))
101
  stats = cursor.fetchone()
 
218
 
219
  invoice_fp = extracted_fields.get("INVOICE_NUMBER", extracted_fields.get("TOTAL", "UNKNOWN"))
220
 
221
+ # Kontrola duplicity v rámci přihlášeného uživatele (pokud to není host)
222
  is_duplicate = False
223
+ is_guest = (not username or username.strip().lower() == "guest")
224
+
225
+ if not is_guest and invoice_fp != "UNKNOWN":
226
  try:
227
  conn = sqlite3.connect(DB_FILE)
228
  cursor = conn.cursor()
 
258
  "extracted_data": extracted_fields,
259
  }
260
 
261
+ # Zápis do DB pouze pokud uživatel NENÍ host
262
+ if not is_guest:
263
+ fields_count = len(extracted_fields)
 
264
  try:
265
  conn = sqlite3.connect(DB_FILE)
266
  cursor = conn.cursor()
 
274
  )
275
  conn.commit()
276
 
 
277
  cursor.execute("SELECT COUNT(*), SUM(fields_count) FROM invoices WHERE user_id = ?", (user_id,))
278
  count_row = cursor.fetchone()
279
+ result["user_stats"] = {
280
+ "docs_parsed": count_row[0] or 0,
281
+ "fields_extracted": count_row[1] or 0
282
+ }
283
  conn.close()
284
  except Exception as db_err:
285
  print(f"DB Insert failure: {db_err}")
286
+ else:
287
+ # Pro hosta vrátíme flag, JS si spočítá lokální session statistiky sám
288
+ result["user_stats"] = {
289
+ "docs_parsed": "GUEST_MODE",
290
+ "fields_extracted": "GUEST_MODE"
291
+ }
292
 
293
  # Generování CSV exportu
294
  rows = [["Field", "Value"]]
 
431
 
432
  <div id="toast-root" style="position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;"></div>
433
 
434
+ <!-- ═══ 1. AUTHENTICATION OVERLAY SCREEN ═══ -->
435
  <div id="auth-screen" class="fixed inset-0 z-50 flex items-center justify-center bg-[#09090b] p-4">
436
+ <div class="card w-full max-w-md p-8 space-y-5">
437
  <div class="text-center space-y-2">
438
  <div class="mx-auto w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-blue-700 flex items-center justify-center shadow-lg shadow-blue-500/25">
439
  <svg width="22" height="22" viewBox="0 0 16 16" fill="none"><path d="M2 3h12M2 6h8M2 9h10M2 12h6" stroke="white" stroke-width="1.5" stroke-linecap="round"/><circle cx="13" cy="11" r="2.5" stroke="white" stroke-width="1.2"/><path d="M15 13l1.5 1.5" stroke="white" stroke-width="1.2" stroke-linecap="round"/></svg>
 
442
  <p class="text-xs text-zinc-400">Multi-User Document Data-Mining Terminal</p>
443
  </div>
444
 
445
+ <div class="space-y-3">
446
  <div>
447
+ <label class="block text-[10px] font-bold uppercase tracking-wider text-zinc-500 mb-1">Uživatelské jméno</label>
448
+ <input type="text" id="auth-username" class="chat-input text-xs" placeholder="Zadej login...">
449
  </div>
450
  <div>
451
+ <label class="block text-[10px] font-bold uppercase tracking-wider text-zinc-500 mb-1">Heslo</label>
452
+ <input type="password" id="auth-password" class="chat-input text-xs" placeholder="••••••••">
453
  </div>
454
  </div>
455
 
456
+ <div class="flex flex-col gap-2 pt-1">
457
+ <button onclick="handleAuthAction('login')" class="btn-primary w-full py-2.5 font-semibold text-xs">
458
  Přihlásit se do systému ⚡
459
  </button>
460
+ <button onclick="handleAuthAction('register')" class="btn-secondary w-full py-2.5 text-xs">
461
+ Registrovat nový cloudový účet
462
+ </button>
463
+
464
+ <div class="relative flex py-2 items-center">
465
+ <div class="flex-grow border-t border-zinc-800"></div>
466
+ <span class="flex-shrink mx-3 text-[10px] text-zinc-600 font-bold uppercase">Nebo</span>
467
+ <div class="flex-grow border-t border-zinc-800"></div>
468
+ </div>
469
+
470
+ <button onclick="bypassAuthAsGuest()" class="w-full py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-300 rounded-xl text-xs font-semibold transition-all">
471
+ Pokračovat bez registrace (Free Plan) →
472
  </button>
473
  </div>
474
  </div>
475
  </div>
476
 
477
 
478
+ <!-- ═══ 2. MAIN APPLICATION WORKSPACE ═══ -->
479
  <div id="app-screen" style="display: none;">
480
  <header style="border-bottom:1px solid var(--border);background:rgba(9,9,11,.92);backdrop-filter:blur(16px); position:sticky; top:0; z-index:40;">
481
  <div style="max-width:1280px;margin:0 auto;padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between;">
 
491
  <span id="status-dot" style="width:8px;height:8px;border-radius:50%;background:#34d399;"></span>
492
  <span id="status-text" style="font-size:12px;color:var(--text2);font-weight:500;">System Ready</span>
493
  </div>
494
+ <span id="user-display" class="text-sm font-medium"></span>
495
+ <button onclick="handleLogout()" class="text-xs text-red-400 hover:underline">Zpět na login / Odhlásit</button>
496
  </div>
497
  </div>
498
  </header>
499
 
500
  <main style="max-width:1280px;margin:0 auto;padding:24px;">
501
+ <!-- Stats Banner -->
502
  <div class="grid grid-cols-4 gap-3 mb-5">
503
  <div class="card p-4 flex items-center gap-3">
504
  <div class="text-2xl">📄</div>
505
+ <div><p class="text-[10px] text-zinc-500 uppercase font-bold">Zpracované dokumenty</p><p id="st-docs" class="text-lg font-bold text-white">0</p></div>
506
  </div>
507
  <div class="card p-4 flex items-center gap-3">
508
  <div class="text-2xl">⚡</div>
 
518
  </div>
519
  </div>
520
 
521
+ <!-- Workspace Grid -->
522
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
523
+ <!-- Left Controls -->
524
  <div class="space-y-4">
525
  <div class="card p-5 space-y-4">
526
  <div class="flex justify-between items-center">
 
565
  </div>
566
  </div>
567
 
568
+ <!-- Right Terminal & Terminal Assist -->
569
  <div class="space-y-4">
570
  <div class="card p-5 space-y-3">
571
  <div class="flex justify-between items-center"><span class="text-sm font-semibold">Live Extraction Terminal</span><span class="section-label">Output JSON</span></div>
 
602
  </main>
603
  </div>
604
 
605
+ <!-- ═══ 3. GRADIO PIPELINE JAVASCRIPT BRIDGE ═══ -->
606
  <script>
607
  let currentBase64 = "";
608
  let latestExtractedJson = "";
 
611
  let parsePolling = null;
612
  let chatPolling = null;
613
 
614
+ // Lokální paměť statistik pro uživatele na Free Planu (neukládá se do DB)
615
+ let guestDocsCount = 0;
616
+ let guestFieldsCount = 0;
617
+
618
  function showToast(msg, type='info') {
619
  const root = document.getElementById("toast-root");
620
  const toast = document.createElement("div");
 
632
  if (user) {
633
  document.getElementById("auth-screen").style.display = "none";
634
  document.getElementById("app-screen").style.display = "block";
 
635
 
636
+ if (user.toLowerCase() === "guest") {
637
+ document.getElementById("user-display").className = "text-sm font-medium text-amber-400";
638
+ document.getElementById("user-display").innerText = "👤 Free Plan (Neregistrován)";
639
+ document.getElementById("st-docs").innerText = guestDocsCount + " (Free)";
640
+ document.getElementById("st-fields").innerText = guestFieldsCount + " (Místní)";
641
+ } else {
642
+ document.getElementById("user-display").className = "text-sm font-medium text-blue-400";
643
+ document.getElementById("user-display").innerText = `👤 ${user}`;
644
+
645
+ // Refresh cloudu z DB pro registrované
646
+ const grUser = document.querySelector("#gr-auth-user textarea");
647
+ const grPass = document.querySelector("#gr-auth-pass textarea");
648
+ if (grUser && grPass) {
649
+ grUser.value = user;
650
+ grUser.dispatchEvent(new Event("input", { bubbles: true }));
651
+ grPass.value = "SESSION_REFRESH";
652
+ grPass.dispatchEvent(new Event("input", { bubbles: true }));
653
+ document.querySelector("#gr-login-btn").click();
654
+ }
655
+ if(authPolling) clearInterval(authPolling);
656
+ authPolling = setInterval(checkAuthResponse, 150);
657
  }
 
 
658
  } else {
659
  document.getElementById("auth-screen").style.display = "flex";
660
  document.getElementById("app-screen").style.display = "none";
661
  }
662
  }
663
 
664
+ function bypassAuthAsGuest() {
665
+ sessionStorage.setItem("omniparse_user", "Guest");
666
+ showToast("Vstupuješ jako host v bezplatném režimu.");
667
+ checkLoginState();
668
+ }
669
+
670
  function handleAuthAction(action) {
671
  const userVal = document.getElementById("auth-username").value.trim();
672
  const passVal = document.getElementById("auth-password").value;
 
713
  showToast("Přihlášení úspěšné!");
714
  document.getElementById("auth-screen").style.display = "none";
715
  document.getElementById("app-screen").style.display = "block";
716
+ document.getElementById("user-display").className = "text-sm font-medium text-blue-400";
717
  document.getElementById("user-display").innerText = `👤 ${data.username}`;
718
  if (data.stats) {
719
  document.getElementById("st-docs").innerText = data.stats.docs_parsed;
 
762
  document.getElementById("btn-loading").style.display = "flex";
763
  document.getElementById("status-text").innerText = "Těžím data...";
764
 
765
+ const user = sessionStorage.getItem("omniparse_user") || "Guest";
766
  const grImg = document.querySelector("#gr-image-input textarea");
767
  const grUser = document.querySelector("#gr-current-user textarea");
768
 
 
799
  document.getElementById("st-time").innerText = `${res.parse_time_seconds}s`;
800
  document.getElementById("st-conf").innerText = `${Math.round(res.security_and_compliance.confidence_score * 100)}%`;
801
  document.getElementById("comp-val").innerText = res.security_and_compliance.cross_field_validation;
 
802
 
803
+ // Zobrazení duplicity
804
+ if (sessionStorage.getItem("omniparse_user").toLowerCase() === "guest") {
805
+ document.getElementById("comp-dup").innerText = "🔒 Vyžaduje registraci";
806
+ } else {
807
+ document.getElementById("comp-dup").innerText = res.security_and_compliance.duplicate_detected ? "⚠️ POTENCIÁLNÍ DUPLICITA" : "✅ OK (Unikátní)";
808
  }
809
+
810
+ // Počítání a update statistik na panelu
811
+ if (sessionStorage.getItem("omniparse_user").toLowerCase() === "guest") {
812
+ guestDocsCount++;
813
+ guestFieldsCount += Object.keys(res.extracted_data).length;
814
+ document.getElementById("st-docs").innerText = guestDocsCount + " (Free)";
815
+ document.getElementById("st-fields").innerText = guestFieldsCount + " (Místní)";
816
+ } else {
817
+ if (res.user_stats) {
818
+ document.getElementById("st-docs").innerText = res.user_stats.docs_parsed;
819
+ document.getElementById("st-fields").innerText = res.user_stats.fields_extracted;
820
+ }
821
+ }
822
+
823
  if (res._csv_b64) {
824
  csvB64Data = res._csv_b64;
825
  document.getElementById("export-actions").style.display = "block";
 
892
  }
893
  }
894
 
 
895
  setTimeout(checkLoginState, 500);
896
  </script>
897
  </body>
 
900
 
901
  # ─── GRADIO APPLICATION CONTAINER ───────────────────────────────────────────
902
  with gr.Blocks(css=CUSTOM_CSS, title="OmniParse AI Enterprise Terminal") as demo:
 
903
  gr.HTML(HTML_UI)
904
 
905
+ # Skryté ovládací prvky (Bridge)
906
  with gr.Row(elem_id="hidden-controls", visible=False):
907
  with gr.Column():
908
  gr_image_input = gr.Textbox(label="image_b64", elem_id="gr-image-input", visible=False)
 
915
  gr_chat_btn = gr.Button("chat", elem_id="gr-chat-btn", visible=False)
916
  gr_chat_out = gr.Textbox(label="chat_out", elem_id="gr-chat-out", visible=False)
917
 
 
918
  gr_auth_user = gr.Textbox(label="auth_user", elem_id="gr-auth-user", visible=False)
919
  gr_auth_pass = gr.Textbox(label="auth_pass", elem_id="gr-auth-pass", visible=False)
920
  gr_login_btn = gr.Button("login", elem_id="gr-login-btn", visible=False)
921
  gr_register_btn = gr.Button("register", elem_id="gr-register-btn", visible=False)
922
  gr_auth_response = gr.Textbox(label="auth_response", elem_id="gr-auth-response", visible=False)
923
 
 
924
  gr_parse_btn.click(
925
  fn=gr_parse,
926
  inputs=[gr_image_input, gr_current_user],