SebastianAndreu commited on
Commit
22d6eab
Β·
verified Β·
1 Parent(s): 49a62d7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +533 -169
app.py CHANGED
@@ -1222,183 +1222,547 @@ def visualize_reorganization(df_items, df_loc, df_pairs, df_recs, coords, id_to_
1222
  return img
1223
 
1224
  # =============================================================================
1225
- # GRADIO INTERFACE LAYOUT
1226
  # =============================================================================
1227
 
1228
- # --- Navigation Handler ---
1229
- def change_page(page_name):
1230
- """
1231
- Returns visibility updates (True/False) for the four main containers.
1232
- The order must match the 'outputs' list in the .click() methods.
1233
- """
1234
- is_main = page_name == 'main'
1235
- is_checkout = page_name == 'checkout'
1236
- is_add_items = page_name == 'add_items'
1237
- is_analysis = page_name == 'analysis'
1238
-
1239
- return gr.update(visible=is_main), gr.update(visible=is_checkout), gr.update(visible=is_add_items), gr.update(visible=is_analysis)
1240
-
1241
-
1242
- # --- Gradio Blocks UI Definition ---
1243
- with gr.Blocks(css=CUSTOM_CSS, title="Makerspace Inventory Management System") as demo:
1244
-
1245
- # 1. Define Page Containers (Visibility controlled via the .click() method)
1246
- # The initial page (main menu) is visible=True, all others are visible=False.
1247
- main_menu_container = gr.Box(elem_id="main-menu-container")
1248
- checkout_page = gr.Box(visible=False, elem_classes="module-page")
1249
- add_items_page = gr.Box(visible=False, elem_classes="module-page")
1250
- analysis_page = gr.Box(visible=False, elem_classes="module-page")
1251
-
1252
- # The list of outputs used by all navigation buttons
1253
- page_outputs = [main_menu_container, checkout_page, add_items_page, analysis_page]
1254
-
1255
- # --- MAIN MENU (Always Visible Initially) ---
1256
- with main_menu_container:
1257
- gr.Markdown("# <span id='main-menu-title'>Integrated Makerspace Inventory</span>")
1258
- gr.Markdown("## <span id='main-menu-subtitle'>Smart inventory management powered by AI</span>")
1259
  with gr.Row():
1260
- checkout_btn = gr.Button("πŸ›’ Check Out Items", elem_classes="module-button primary-button")
1261
- add_items_btn = gr.Button("βž• Add Items to Inventory", elem_classes="module-button primary-button")
1262
- analysis_btn = gr.Button("πŸ“Š Inventory Analysis", elem_classes="module-button primary-button")
1263
-
1264
- # --- CHECK OUT MODULE ---
1265
- with checkout_page:
1266
- back_to_main_btn_c = gr.Button("← Back to Main Menu")
1267
- gr.Markdown("<div class='section-header'>πŸ›’ Check Out Items</div>")
1268
-
1269
- # Your existing CHECK OUT UI components go here:
1270
- with gr.Row(variant="panel", elem_classes="gap"):
1271
- with gr.Column(min_width=400):
1272
- gr.Markdown("### πŸ“Έ Scan Item(s) with Image or Webcam")
1273
- image_input = gr.Image(type="filepath", label="Upload Item Image", sources=["upload", "webcam"], elem_classes="image-upload-container")
1274
- manual_text_input = gr.Textbox(label="Manual Item Entry (Optional)", placeholder="e.g., drill bit, digital multimeter, 1/4 inch wrench", info="Enter comma-separated items if image detection fails.")
1275
-
1276
- with gr.Column():
1277
- scan_btn = gr.Button("πŸ” Scan & Match Items", variant="primary", elem_classes="primary-button")
1278
- match_status = gr.Markdown("Ready to scan.", elem_classes="instructions-box")
1279
- matched_items_state = gr.State(value=[]) # To hold the matched items data
1280
-
1281
- gr.Markdown("### πŸ“ Review & Confirm Checkout")
1282
- checkout_preview = gr.Markdown("No items scanned yet.")
1283
- user_id_input = gr.Textbox(label="Your ID (Optional)", placeholder="Enter your student/staff ID for tracking.")
1284
- confirm_checkout_btn = gr.Button("βœ… Confirm Checkout", variant="primary", elem_classes="primary-button")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1285
  checkout_result = gr.Markdown("")
1286
-
1287
- # Link CHECKOUT button actions to functions
1288
- scan_btn.click(
1289
- scan_items_checkout,
1290
- inputs=[image_input, manual_text_input],
1291
- outputs=[matched_items_state, match_status]
1292
- ).then(
1293
- confirm_checkout_preview,
1294
- inputs=[matched_items_state],
1295
- outputs=[checkout_preview]
1296
- )
1297
-
1298
- confirm_checkout_btn.click(
1299
- process_checkout,
1300
- inputs=[matched_items_state, user_id_input],
1301
- outputs=[checkout_result]
1302
- ).then(
1303
- lambda: (gr.update(value=None), gr.update(value="Ready to scan.", visible=True)),
1304
- inputs=None,
1305
- outputs=[image_input, match_status]
1306
- )
1307
-
1308
-
1309
- # --- ADD ITEMS MODULE ---
1310
- with add_items_page:
1311
- back_to_main_btn_a = gr.Button("← Back to Main Menu")
1312
- gr.Markdown("<div class='section-header'>βž• Add Items to Inventory</div>")
1313
-
1314
- # Your existing ADD ITEMS UI components go here:
1315
- with gr.Tabs():
1316
- with gr.TabItem("Receipt/Invoice Upload"):
1317
- gr.Markdown("Upload a PDF or image of a receipt/invoice to automatically add new inventory items.")
1318
- receipt_file = gr.File(label="Upload Receipt (PDF or Image)")
1319
- process_receipt_btn = gr.Button("Extract & Propose Updates", variant="primary", elem_classes="primary-button")
1320
- receipt_updates_table = gr.Dataframe(
1321
- headers=["Include", "Item Name", "Quantity", "Match Type"],
1322
- datatype=["checkbox", "str", "number", "str"],
1323
- row_count=0,
1324
- col_count=4,
1325
- interactive=True,
1326
- label="Proposed Inventory Updates"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1327
  )
1328
- apply_updates_btn = gr.Button("Apply Selected Updates", variant="primary", elem_classes="primary-button")
1329
- receipt_summary = gr.Markdown("")
1330
-
1331
- process_receipt_btn.click(
1332
- extract_text_from_receipt,
1333
- inputs=[receipt_file],
1334
- outputs=[receipt_updates_table, gr.State(value=[])] # Note: The proposals state is not fully used here but kept for completeness
1335
  )
1336
- apply_updates_btn.click(
1337
- apply_updates_from_table,
1338
- inputs=[receipt_updates_table],
1339
- outputs=[receipt_summary, gr.State(value=None)] # Outputting to a dummy State to avoid confusion
 
 
 
 
 
 
 
 
 
1340
  )
1341
-
1342
- with gr.TabItem("Manual Single Update"):
1343
- gr.Markdown("Manually update the quantity of a single item. Use a positive number to add, or a negative number to remove.")
1344
- manual_item_name = gr.Textbox(label="Item Name", placeholder="e.g., Arduino Uno, 3D Printer Filament (White)")
1345
- manual_quantity = gr.Number(label="Quantity Change (+ or -)", value=1)
1346
- manual_update_btn = gr.Button("Apply Manual Update", variant="primary", elem_classes="primary-button")
1347
- manual_update_summary = gr.Markdown("")
1348
-
1349
- manual_update_btn.click(
1350
- manual_update,
1351
- inputs=[manual_item_name, manual_quantity],
1352
- outputs=[manual_update_summary, gr.State(value=None)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1353
  )
1354
-
1355
-
1356
- # --- INVENTORY ANALYSIS MODULE ---
1357
- with analysis_page:
1358
- back_to_main_btn_n = gr.Button("← Back to Main Menu")
1359
- gr.Markdown("<div class='section-header'>πŸ“Š Inventory Analysis</div>")
1360
-
1361
- # Your existing ANALYSIS UI components go here:
1362
- with gr.Accordion("Analysis Configuration", open=False):
1363
- min_support_slider = gr.Slider(minimum=0.01, maximum=0.1, step=0.005, value=0.02, label="Min Support for Item Pairs")
1364
- top_k_pairs_slider = gr.Slider(minimum=10, maximum=100, step=5, value=30, label="Top K Pairs for Relocation")
1365
- max_moves_slider = gr.Slider(minimum=5, maximum=50, step=5, value=15, label="Max Relocation Moves")
1366
- min_gain_slider = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, value=0.05, label="Minimum Gain for a Move")
1367
-
1368
- run_analysis_btn = gr.Button("πŸš€ Run Inventory Optimization Analysis", variant="primary", elem_classes="primary-button")
1369
- analysis_status = gr.Markdown("Press 'Run Inventory Optimization Analysis' to start.")
1370
-
1371
- with gr.Tabs():
1372
- with gr.TabItem("Frequent Item Pairs (Recommendations)"):
1373
- item_pairs_df = gr.Dataframe(interactive=False, label="Frequent Item Pairs", height=300)
1374
-
1375
- with gr.TabItem("Location Relocation Recommendations"):
1376
- relocation_df = gr.Dataframe(interactive=False, label="Optimized Relocation Moves", height=300)
1377
-
1378
- with gr.TabItem("Current Inventory"):
1379
- category_dropdown = gr.Dropdown(choices=["All"] + get_categories(), value="All", label="Filter by Category")
1380
- inventory_df = gr.Dataframe(view_inventory_table(), interactive=False, label="Current Inventory Status", height=500)
1381
- category_dropdown.change(view_inventory_table, inputs=category_dropdown, outputs=inventory_df)
1382
-
1383
- with gr.TabItem("Update History"):
1384
- update_history_text = gr.Textbox(view_update_history(), label="Inventory Update Log", interactive=False)
1385
-
1386
- run_analysis_btn.click(
1387
- run_analysis,
1388
- inputs=[min_support_slider, top_k_pairs_slider, max_moves_slider, min_gain_slider],
1389
- outputs=[item_pairs_df, relocation_df, analysis_status, inventory_df]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1390
  )
1391
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1392
 
1393
- # 3. Add the click events for general navigation (The fix for "No API Found")
1394
- checkout_btn.click(lambda: change_page('checkout'), outputs=page_outputs)
1395
- add_items_btn.click(lambda: change_page('add_items'), outputs=page_outputs)
1396
- analysis_btn.click(lambda: change_page('analysis'), outputs=page_outputs)
1397
-
1398
- # Back button logic
1399
- for btn in [back_to_main_btn_c, back_to_main_btn_a, back_to_main_btn_n]:
1400
- btn.click(lambda: change_page('main'), outputs=page_outputs)
1401
-
1402
-
1403
- # Launch the Gradio App
1404
- demo.launch()
 
1222
  return img
1223
 
1224
  # =============================================================================
1225
+ # GRADIO INTERFACE
1226
  # =============================================================================
1227
 
1228
+ with gr.Blocks(title="Makerspace Inventory System", css=CUSTOM_CSS) as demo:
1229
+
1230
+ # State variables
1231
+ matched_items_state = gr.State(None)
1232
+ proposals_state = gr.State([])
1233
+
1234
+ # Main menu
1235
+ with gr.Group(visible=True, elem_id="main-menu-container") as main_menu:
1236
+ gr.Markdown("# πŸ”§ Makerspace Inventory System", elem_id="main-menu-title")
1237
+ gr.Markdown("*Smart inventory management powered by AI*", elem_id="main-menu-subtitle")
1238
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1239
  with gr.Row():
1240
+ checkout_btn = gr.Button("πŸ›’ Check Out Items", size="lg", elem_classes=["module-button"], scale=1)
1241
+ add_items_btn = gr.Button("πŸ“¦ Add Items", size="lg", elem_classes=["module-button"], scale=1)
1242
+ analysis_btn = gr.Button("πŸ“Š Inventory Analysis", size="lg", elem_classes=["module-button"], scale=1)
1243
+
1244
+ # CHECK OUT MODULE
1245
+ with gr.Group(visible=False, elem_classes=["module-page"]) as checkout_module:
1246
+ gr.Markdown("# πŸ›’ Check Out Items", elem_classes=["section-header"])
1247
+
1248
+ with gr.Accordion("πŸ“– How to Use", open=False):
1249
+ gr.Markdown("""
1250
+ **Step 1:** Upload a photo of items or type item names manually (comma-separated)
1251
+
1252
+ **Step 2:** Review detected items and adjust quantities
1253
+
1254
+ **Step 3:** Confirm checkout with optional user ID
1255
+
1256
+ πŸ’‘ **Tip:** The AI automatically matches items to inventory using smart search!
1257
+ """)
1258
+
1259
+ with gr.Group(visible=True) as checkout_screen1:
1260
+ gr.Markdown("### πŸ“Έ Scan Items", elem_classes=["subsection-header"])
1261
+
1262
+ with gr.Row():
1263
+ with gr.Column(scale=2):
1264
+ checkout_image = gr.Image(
1265
+ type="pil",
1266
+ label="Upload Image of Items",
1267
+ sources=["upload", "webcam"],
1268
+ height=400
1269
+ )
1270
+
1271
+ if EXAMPLE_CHECKOUT_IMAGE:
1272
+ load_example_checkout_btn = gr.Button("πŸ“Έ Load Example", size="sm", variant="secondary", scale=0)
1273
+
1274
+ with gr.Column(scale=1):
1275
+ gr.Markdown("""
1276
+ ### Quick Guide
1277
+
1278
+ **Upload:** Click the upload icon to select an image from your device
1279
+
1280
+ **Webcam:** Click the camera icon to take a photo with your webcam
1281
+
1282
+ πŸ’‘ Make sure items are clearly visible and well-lit
1283
+ """)
1284
+
1285
+ checkout_manual = gr.Textbox(
1286
+ label="Or Enter Item Names Manually",
1287
+ placeholder="e.g., drill, safety glasses, Arduino",
1288
+ info="Separate multiple items with commas"
1289
+ )
1290
+
1291
+ checkout_status = gr.Markdown("")
1292
+ checkout_scan_btn = gr.Button("πŸ” Scan & Match Items", variant="primary", size="lg")
1293
+
1294
+ with gr.Group(visible=False) as checkout_screen2:
1295
+ gr.Markdown("### βœ… Review Items", elem_classes=["subsection-header"])
1296
+ gr.Markdown("*Adjust quantities or remove items before checkout*")
1297
+
1298
+ checkout_item_controls = []
1299
+ for i in range(10):
1300
+ with gr.Row(visible=False) as item_row:
1301
+ with gr.Column(scale=3):
1302
+ item_info = gr.Markdown("")
1303
+ with gr.Column(scale=1):
1304
+ item_qty = gr.Number(value=1, minimum=1, label="Qty")
1305
+ with gr.Column(scale=1):
1306
+ item_remove = gr.Button("πŸ—‘οΈ Remove", size="sm", variant="stop")
1307
+
1308
+ checkout_item_controls.append({
1309
+ 'row': item_row,
1310
+ 'info': item_info,
1311
+ 'qty': item_qty,
1312
+ 'remove': item_remove
1313
+ })
1314
+
1315
+ with gr.Row():
1316
+ checkout_rescan_btn = gr.Button("↩️ Rescan", variant="secondary")
1317
+ checkout_confirm_btn = gr.Button("βœ… Proceed to Checkout", variant="primary", size="lg")
1318
+
1319
+ with gr.Group(visible=False) as checkout_screen3:
1320
+ gr.Markdown("### 🎫 Confirm Checkout", elem_classes=["subsection-header"])
1321
+
1322
+ checkout_preview = gr.Markdown("")
1323
+
1324
+ checkout_user_id = gr.Textbox(
1325
+ label="User ID (Optional)",
1326
+ placeholder="Enter your ID or leave blank",
1327
+ info="Leave blank for auto-generated ID"
1328
+ )
1329
+
1330
+ checkout_processing = gr.Markdown("")
1331
+
1332
+ with gr.Row():
1333
+ checkout_cancel_btn = gr.Button("❌ Cancel", variant="secondary")
1334
+ checkout_final_btn = gr.Button("βœ… Complete Checkout", variant="primary", size="lg")
1335
+
1336
  checkout_result = gr.Markdown("")
1337
+
1338
+ with gr.Row():
1339
+ checkout_return_btn = gr.Button("🏠 Return to Main Menu", variant="secondary")
1340
+ checkout_another_btn = gr.Button("πŸ›’ Check Out More Items", variant="primary", visible=False)
1341
+
1342
+ # ADD ITEMS MODULE
1343
+ with gr.Group(visible=False, elem_classes=["module-page"]) as add_items_module:
1344
+ gr.Markdown("# πŸ“¦ Add Items to Inventory", elem_classes=["section-header"])
1345
+
1346
+ with gr.Accordion("πŸ“– How to Use", open=False):
1347
+ gr.Markdown("""
1348
+ **Receipt Upload:**
1349
+ 1. Upload receipt image or PDF β†’ AI extracts items
1350
+ 2. Review and edit detected items (check/uncheck, edit quantities)
1351
+ 3. Click "Apply Updates" to add to inventory
1352
+
1353
+ **Manual Entry:**
1354
+ - Enter item name and quantity for quick updates
1355
+ - System uses fuzzy matching to find items
1356
+
1357
+ **View & History:**
1358
+ - Browse inventory with category filters
1359
+ - Track all changes with timestamps
1360
+ """)
1361
+
1362
+ with gr.Tab("πŸ“„ Receipt Upload"):
1363
+ gr.Markdown("### Upload Receipt", elem_classes=["subsection-header"])
1364
+
1365
+ receipt_file = gr.File(label="Upload Receipt (PDF or Image)", file_types=[".pdf", ".png", ".jpg", ".jpeg"])
1366
+
1367
+ if EXAMPLE_RECEIPT_PDF:
1368
+ load_example_receipt_btn = gr.Button("πŸ“„ Load Example", size="sm", variant="secondary", scale=0)
1369
+
1370
+ receipt_status = gr.Markdown("")
1371
+
1372
+ gr.Markdown("### Review Detected Items", elem_classes=["subsection-header"])
1373
+ gr.Markdown("*Check items to include, edit quantities, then apply*")
1374
+
1375
+ add_items_table = gr.Dataframe(
1376
+ headers=["Include", "Item Name", "Quantity", "Match Confidence"],
1377
+ label="Detected Items",
1378
+ datatype=["bool", "str", "number", "str"],
1379
+ interactive=True,
1380
+ col_count=(4, "fixed")
1381
+ )
1382
+
1383
+ add_items_status = gr.Markdown("")
1384
+
1385
+ with gr.Row():
1386
+ add_items_reject_btn = gr.Button("❌ Clear All", variant="secondary")
1387
+ add_items_confirm_btn = gr.Button("βœ… Apply Updates", variant="primary", interactive=False)
1388
+
1389
+ with gr.Tab("✍️ Manual Entry"):
1390
+ gr.Markdown("### Manually Add Items", elem_classes=["subsection-header"])
1391
+
1392
+ with gr.Row():
1393
+ manual_item = gr.Textbox(
1394
+ label="Item Name",
1395
+ placeholder="e.g., Arduino Uno",
1396
+ info="Fuzzy matching will find similar items"
1397
  )
1398
+ manual_qty = gr.Number(
1399
+ label="Quantity to Add",
1400
+ value=1,
1401
+ minimum=1
 
 
 
1402
  )
1403
+
1404
+ manual_apply_btn = gr.Button("βž• Add to Inventory", variant="primary")
1405
+ manual_status = gr.Markdown("")
1406
+ manual_inventory_display = gr.Dataframe(label="Updated Inventory", visible=False)
1407
+
1408
+ with gr.Tab("πŸ“Š View & History"):
1409
+ gr.Markdown("### Current Inventory", elem_classes=["subsection-header"])
1410
+
1411
+ with gr.Row():
1412
+ inventory_category_filter = gr.Dropdown(
1413
+ choices=["All"] + get_categories(),
1414
+ value="All",
1415
+ label="Filter by Category"
1416
  )
1417
+ view_inventory_btn = gr.Button("πŸ”„ Refresh Inventory")
1418
+
1419
+ inventory_display = gr.Dataframe(label="Current Inventory", visible=False)
1420
+
1421
+ gr.Markdown("### Update History", elem_classes=["subsection-header"])
1422
+
1423
+ view_history_btn = gr.Button("πŸ“œ View Update Log")
1424
+ history_display = gr.Textbox(label="Update History", lines=10, visible=False)
1425
+
1426
+ add_items_return_btn = gr.Button("🏠 Return to Main Menu", variant="secondary")
1427
+
1428
+ # INVENTORY ANALYSIS MODULE
1429
+ with gr.Group(visible=False, elem_classes=["module-page"]) as analysis_module:
1430
+ gr.Markdown("# πŸ“Š Inventory Layout Analysis", elem_classes=["section-header"])
1431
+
1432
+ with gr.Accordion("πŸ“– Understanding the Analysis", open=True):
1433
+ gr.Markdown("""
1434
+ This module analyzes checkout patterns to optimize item placement and minimize walking distance.
1435
+
1436
+ ### πŸ“ˆ Key Metrics:
1437
+
1438
+ **Support** - Frequency of co-occurrence (e.g., 0.10 = 10% of checkouts)
1439
+
1440
+ **Lift** - Correlation strength (Lift > 1 = items checked out together more than random)
1441
+
1442
+ **Confidence** - Conditional probability (e.g., 0.80 = 80% chance of B when checking A)
1443
+
1444
+ **Distance Saved** - Walking distance reduction in grid units
1445
+
1446
+ ### 🎯 Goal:
1447
+ Items frequently checked together should be placed closer to reduce travel time!
1448
+ """)
1449
+
1450
+ with gr.Row():
1451
+ with gr.Column(scale=1):
1452
+ gr.Markdown("### βš™οΈ Parameters", elem_classes=["subsection-header"])
1453
+
1454
+ min_support = gr.Slider(
1455
+ minimum=0.01,
1456
+ maximum=0.2,
1457
+ value=0.02,
1458
+ step=0.01,
1459
+ label="Minimum Support",
1460
+ info="Lower = more patterns (less significant)"
1461
  )
1462
+
1463
+ top_k_pairs = gr.Slider(
1464
+ minimum=5,
1465
+ maximum=50,
1466
+ value=25,
1467
+ step=5,
1468
+ label="Top K Pairs",
1469
+ info="Number of frequent pairs to optimize"
1470
+ )
1471
+
1472
+ max_moves = gr.Slider(
1473
+ minimum=5,
1474
+ maximum=30,
1475
+ value=10,
1476
+ step=1,
1477
+ label="Maximum Moves",
1478
+ info="Limit on relocations"
1479
+ )
1480
+
1481
+ min_gain = gr.Slider(
1482
+ minimum=0.0,
1483
+ maximum=1.0,
1484
+ value=0.05,
1485
+ step=0.01,
1486
+ label="Minimum Distance Gain",
1487
+ info="Threshold for suggestions"
1488
+ )
1489
+
1490
+ run_analysis_btn = gr.Button("πŸ” Run Analysis", variant="primary", size="lg")
1491
+
1492
+ with gr.Column(scale=2):
1493
+ gr.Markdown("### πŸ“Š Summary", elem_classes=["subsection-header"])
1494
+ analysis_summary = gr.Textbox(label="", lines=14, show_label=False)
1495
+
1496
+ gr.Markdown("### πŸ—ΊοΈ Visual Layout Comparison", elem_classes=["subsection-header"])
1497
+ analysis_viz = gr.Image(label="", type="numpy", show_label=False)
1498
+
1499
+ with gr.Row():
1500
+ with gr.Column():
1501
+ gr.Markdown("### πŸ”— Frequent Pairs", elem_classes=["subsection-header"])
1502
+ pairs_table = gr.Dataframe(label="", show_label=False)
1503
+
1504
+ with gr.Column():
1505
+ gr.Markdown("### πŸ“ Recommendations", elem_classes=["subsection-header"])
1506
+ recs_table = gr.Dataframe(label="", show_label=False)
1507
+
1508
+ analysis_return_btn = gr.Button("🏠 Return to Main Menu", variant="secondary")
1509
+
1510
+ # EVENT HANDLERS - Navigation
1511
+ def show_checkout():
1512
+ return (gr.update(visible=False), gr.update(visible=True),
1513
+ gr.update(visible=False), gr.update(visible=False))
1514
+
1515
+ def show_add_items():
1516
+ return (gr.update(visible=False), gr.update(visible=False),
1517
+ gr.update(visible=True), gr.update(visible=False))
1518
+
1519
+ def show_analysis():
1520
+ return (gr.update(visible=False), gr.update(visible=False),
1521
+ gr.update(visible=False), gr.update(visible=True))
1522
+
1523
+ def return_to_menu():
1524
+ return (gr.update(visible=True), gr.update(visible=False),
1525
+ gr.update(visible=False), gr.update(visible=False))
1526
+
1527
+ checkout_btn.click(fn=show_checkout, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1528
+ add_items_btn.click(fn=show_add_items, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1529
+ analysis_btn.click(fn=show_analysis, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1530
+
1531
+ checkout_return_btn.click(fn=return_to_menu, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1532
+ add_items_return_btn.click(fn=return_to_menu, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1533
+ analysis_return_btn.click(fn=return_to_menu, outputs=[main_menu, checkout_module, add_items_module, analysis_module])
1534
+
1535
+ # EVENT HANDLERS - Checkout
1536
+ def update_checkout_screen2(matched_items):
1537
+ updates = []
1538
+
1539
+ if not matched_items:
1540
+ for i in range(10):
1541
+ updates.extend([gr.update(visible=False), gr.update(value=""), gr.update(value=1)])
1542
+ return updates
1543
+
1544
+ for i in range(10):
1545
+ if i < len(matched_items):
1546
+ item_data = matched_items[i]
1547
+ updates.extend([
1548
+ gr.update(visible=True),
1549
+ gr.update(value=create_checkout_item_display(item_data, i)),
1550
+ gr.update(value=item_data['quantity'], maximum=item_data['item']['Quantity']),
1551
+ ])
1552
+ else:
1553
+ updates.extend([gr.update(visible=False), gr.update(value=""), gr.update(value=1)])
1554
+
1555
+ return updates
1556
+
1557
+ def remove_checkout_item(matched_items, item_idx):
1558
+ if matched_items and 0 <= item_idx < len(matched_items):
1559
+ matched_items.pop(item_idx)
1560
+ return matched_items
1561
+
1562
+ def set_checkout_quantity(matched_items, item_idx, new_qty):
1563
+ if matched_items and 0 <= item_idx < len(matched_items):
1564
+ max_qty = matched_items[item_idx]['item']['Quantity']
1565
+ matched_items[item_idx]['quantity'] = max(1, min(int(new_qty), max_qty))
1566
+ return matched_items
1567
+
1568
+ def reset_checkout():
1569
+ return (
1570
+ gr.update(visible=True), gr.update(visible=False), gr.update(visible=False),
1571
+ None, "", None, "", "", gr.update(visible=False)
1572
+ )
1573
+
1574
+ def show_checkout_complete_options():
1575
+ return gr.update(visible=False), gr.update(visible=True)
1576
+
1577
+ checkout_scan_btn.click(
1578
+ fn=lambda: (gr.update(value="⏳ Scanning...", interactive=False), "πŸ” **Processing...** AI is analyzing your image..."),
1579
+ outputs=[checkout_scan_btn, checkout_status]
1580
+ ).then(
1581
+ fn=scan_items_checkout,
1582
+ inputs=[checkout_image, checkout_manual],
1583
+ outputs=[matched_items_state, checkout_status]
1584
+ ).then(
1585
+ fn=lambda: gr.update(value="πŸ” Scan & Match Items", interactive=True),
1586
+ outputs=[checkout_scan_btn]
1587
+ ).then(
1588
+ fn=lambda items: (
1589
+ gr.update(visible=False) if items else gr.update(),
1590
+ gr.update(visible=True) if items else gr.update(),
1591
+ gr.update(visible=False)
1592
+ ),
1593
+ inputs=[matched_items_state],
1594
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3]
1595
+ ).then(
1596
+ fn=update_checkout_screen2,
1597
+ inputs=[matched_items_state],
1598
+ outputs=[checkout_item_controls[i][key] for i in range(10) for key in ['row', 'info', 'qty']]
1599
+ )
1600
+
1601
+ for i in range(10):
1602
+ checkout_item_controls[i]['qty'].change(
1603
+ fn=lambda items, new_qty, idx=i: set_checkout_quantity(items, idx, new_qty),
1604
+ inputs=[matched_items_state, checkout_item_controls[i]['qty']],
1605
+ outputs=[matched_items_state]
1606
  )
1607
 
1608
+ checkout_item_controls[i]['remove'].click(
1609
+ fn=lambda items, idx=i: remove_checkout_item(items, idx),
1610
+ inputs=[matched_items_state],
1611
+ outputs=[matched_items_state]
1612
+ ).then(
1613
+ fn=update_checkout_screen2,
1614
+ inputs=[matched_items_state],
1615
+ outputs=[checkout_item_controls[j][key] for j in range(10) for key in ['row', 'info', 'qty']]
1616
+ )
1617
+
1618
+ checkout_confirm_btn.click(
1619
+ fn=confirm_checkout_preview,
1620
+ inputs=[matched_items_state],
1621
+ outputs=[checkout_preview]
1622
+ ).then(
1623
+ fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)),
1624
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3]
1625
+ )
1626
+
1627
+ checkout_rescan_btn.click(
1628
+ fn=reset_checkout,
1629
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3, matched_items_state,
1630
+ checkout_status, checkout_image, checkout_manual, checkout_result, checkout_another_btn]
1631
+ )
1632
+
1633
+ checkout_cancel_btn.click(
1634
+ fn=reset_checkout,
1635
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3, matched_items_state,
1636
+ checkout_status, checkout_image, checkout_manual, checkout_result, checkout_another_btn]
1637
+ )
1638
+
1639
+ checkout_final_btn.click(
1640
+ fn=lambda: (gr.update(value="⏳ Processing...", interactive=False), "⏳ **Processing checkout...** Updating inventory..."),
1641
+ outputs=[checkout_final_btn, checkout_processing]
1642
+ ).then(
1643
+ fn=process_checkout,
1644
+ inputs=[matched_items_state, checkout_user_id],
1645
+ outputs=[checkout_result]
1646
+ ).then(
1647
+ fn=lambda: (gr.update(value="βœ… Complete Checkout", interactive=True), ""),
1648
+ outputs=[checkout_final_btn, checkout_processing]
1649
+ ).then(
1650
+ fn=lambda: (gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)),
1651
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3]
1652
+ ).then(
1653
+ fn=show_checkout_complete_options,
1654
+ outputs=[checkout_return_btn, checkout_another_btn]
1655
+ )
1656
+
1657
+ checkout_another_btn.click(
1658
+ fn=reset_checkout,
1659
+ outputs=[checkout_screen1, checkout_screen2, checkout_screen3, matched_items_state,
1660
+ checkout_status, checkout_image, checkout_manual, checkout_result, checkout_another_btn]
1661
+ ).then(
1662
+ fn=lambda: (gr.update(visible=True), gr.update(visible=False)),
1663
+ outputs=[checkout_return_btn, checkout_another_btn]
1664
+ )
1665
+
1666
+ # EVENT HANDLERS - Add Items
1667
+ receipt_file.change(
1668
+ fn=lambda: "πŸ“„ **Processing receipt...** Extracting text with OCR...",
1669
+ outputs=[receipt_status]
1670
+ ).then(
1671
+ fn=extract_text_from_receipt,
1672
+ inputs=[receipt_file],
1673
+ outputs=[add_items_table, proposals_state]
1674
+ ).then(
1675
+ fn=lambda proposals: (gr.update(interactive=len(proposals) > 0), "βœ… Items detected! Review and edit the table below."),
1676
+ inputs=[proposals_state],
1677
+ outputs=[add_items_confirm_btn, receipt_status]
1678
+ )
1679
+
1680
+ add_items_confirm_btn.click(
1681
+ fn=lambda: (gr.update(value="⏳ Applying...", interactive=False), "⏳ **Updating inventory...**"),
1682
+ outputs=[add_items_confirm_btn, add_items_status]
1683
+ ).then(
1684
+ fn=apply_updates_from_table,
1685
+ inputs=[add_items_table],
1686
+ outputs=[add_items_status, inventory_display]
1687
+ ).then(
1688
+ fn=lambda: (gr.update(value="βœ… Apply Updates", interactive=False), gr.update(visible=True)),
1689
+ outputs=[add_items_confirm_btn, inventory_display]
1690
+ )
1691
+
1692
+ add_items_reject_btn.click(
1693
+ fn=lambda: ([["No items", "", "", ""]], "❌ Cleared all items.", []),
1694
+ outputs=[add_items_table, add_items_status, proposals_state]
1695
+ ).then(
1696
+ fn=lambda: gr.update(interactive=False),
1697
+ outputs=[add_items_confirm_btn]
1698
+ )
1699
+
1700
+ manual_apply_btn.click(
1701
+ fn=lambda: gr.update(value="⏳ Adding...", interactive=False),
1702
+ outputs=[manual_apply_btn]
1703
+ ).then(
1704
+ fn=manual_update,
1705
+ inputs=[manual_item, manual_qty],
1706
+ outputs=[manual_status, manual_inventory_display]
1707
+ ).then(
1708
+ fn=lambda: (gr.update(value="βž• Add to Inventory", interactive=True), gr.update(visible=True)),
1709
+ outputs=[manual_apply_btn, manual_inventory_display]
1710
+ )
1711
+
1712
+ view_inventory_btn.click(
1713
+ fn=view_inventory_table,
1714
+ inputs=[inventory_category_filter],
1715
+ outputs=[inventory_display]
1716
+ ).then(
1717
+ fn=lambda: gr.update(visible=True),
1718
+ outputs=[inventory_display]
1719
+ )
1720
+
1721
+ inventory_category_filter.change(
1722
+ fn=view_inventory_table,
1723
+ inputs=[inventory_category_filter],
1724
+ outputs=[inventory_display]
1725
+ )
1726
+
1727
+ view_history_btn.click(
1728
+ fn=view_update_history,
1729
+ outputs=[history_display]
1730
+ ).then(
1731
+ fn=lambda: gr.update(visible=True),
1732
+ outputs=[history_display]
1733
+ )
1734
+
1735
+ # EVENT HANDLERS - Analysis
1736
+ run_analysis_btn.click(
1737
+ fn=run_analysis,
1738
+ inputs=[min_support, top_k_pairs, max_moves, min_gain],
1739
+ outputs=[pairs_table, recs_table, analysis_summary, analysis_viz]
1740
+ )
1741
+
1742
+ # EXAMPLE INPUTS
1743
+ if EXAMPLE_CHECKOUT_IMAGE:
1744
+ def load_checkout_example():
1745
+ try:
1746
+ return Image.open(EXAMPLE_CHECKOUT_IMAGE)
1747
+ except:
1748
+ return None
1749
+
1750
+ load_example_checkout_btn.click(
1751
+ fn=load_checkout_example,
1752
+ outputs=[checkout_image]
1753
+ )
1754
+
1755
+ if EXAMPLE_RECEIPT_PDF:
1756
+ def load_receipt_example():
1757
+ try:
1758
+ return EXAMPLE_RECEIPT_PDF
1759
+ except:
1760
+ return None
1761
+
1762
+ load_example_receipt_btn.click(
1763
+ fn=load_receipt_example,
1764
+ outputs=[receipt_file]
1765
+ )
1766
 
1767
+ if __name__ == "__main__":
1768
+ demo.launch()