SebastianAndreu commited on
Commit
49a62d7
Β·
verified Β·
1 Parent(s): 76098f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +169 -533
app.py CHANGED
@@ -1222,547 +1222,183 @@ def visualize_reorganization(df_items, df_loc, df_pairs, df_recs, coords, id_to_
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()
 
 
 
 
 
 
 
 
 
 
 
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()