Upload 7 files
Browse files- app.py +1485 -0
- categories.json +318 -0
- floor_0_grid.npy +3 -0
- mall.db +0 -0
- navigation_memory.json +0 -0
- requirements.txt +9 -0
- sort_data.json +0 -0
app.py
ADDED
|
@@ -0,0 +1,1485 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""app.ipynb
|
| 3 |
+
|
| 4 |
+
Automatically generated by Colab.
|
| 5 |
+
|
| 6 |
+
Original file is located at
|
| 7 |
+
https://colab.research.google.com/drive/1e0kcqimC-jHT2w8MwJMSXxzS-wJh1jYS
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
import os
|
| 13 |
+
import random
|
| 14 |
+
import heapq
|
| 15 |
+
import re
|
| 16 |
+
import sqlite3
|
| 17 |
+
import gradio as gr
|
| 18 |
+
import numpy as np
|
| 19 |
+
import matplotlib.pyplot as plt
|
| 20 |
+
from scipy.ndimage import label, distance_transform_edt
|
| 21 |
+
from gtts import gTTS
|
| 22 |
+
from pydub import AudioSegment
|
| 23 |
+
import speech_recognition as sr
|
| 24 |
+
import openai
|
| 25 |
+
|
| 26 |
+
CATEGORIES_FILE = "categories.json"
|
| 27 |
+
PRODUCTS_FILE = "sort_data.json"
|
| 28 |
+
DB_PATH = "mall.db"
|
| 29 |
+
# Smart Mall Assistant
|
| 30 |
+
# Navigation + chatbot + recommender system merged into one Colab-ready script.
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ============================================================
|
| 34 |
+
# CONFIG
|
| 35 |
+
# ============================================================
|
| 36 |
+
CELL_SIZE = 0.2
|
| 37 |
+
MEMORY_FILE = "navigation_memory.json"
|
| 38 |
+
GRID_0_FILE = "floor_0_grid.npy"
|
| 39 |
+
GRID_1_FILE = "floor_0_grid.npy"
|
| 40 |
+
|
| 41 |
+
OPENROUTER_KEY =os.getenv("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")
|
| 42 |
+
OPENROUTER_MODEL = "arcee-ai/trinity-large-preview:free"
|
| 43 |
+
|
| 44 |
+
# ============================================================
|
| 45 |
+
# DATA LOADING
|
| 46 |
+
# ============================================================
|
| 47 |
+
def load_json_file(path, default):
|
| 48 |
+
try:
|
| 49 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 50 |
+
return json.load(f)
|
| 51 |
+
except Exception:
|
| 52 |
+
return default
|
| 53 |
+
|
| 54 |
+
categories_data = load_json_file(CATEGORIES_FILE, [])
|
| 55 |
+
products_data = load_json_file(PRODUCTS_FILE, [])
|
| 56 |
+
|
| 57 |
+
# ============================================================
|
| 58 |
+
# STORE / ROOM MAPPING
|
| 59 |
+
# ============================================================
|
| 60 |
+
ROOM_INFO = {
|
| 61 |
+
350: "Smart Devices Hub",
|
| 62 |
+
351: "Gaming & Accessories Hub",
|
| 63 |
+
352: "Computer Systems Hub",
|
| 64 |
+
353: "Mobile & Tablets Hub",
|
| 65 |
+
354: "Health & Personal Care",
|
| 66 |
+
355: "Bedroom",
|
| 67 |
+
356: "Living Room",
|
| 68 |
+
357: "Study/Office",
|
| 69 |
+
358: "Kitchen",
|
| 70 |
+
450: "Dining Room",
|
| 71 |
+
451: "Bathroom",
|
| 72 |
+
452: "Furnishings",
|
| 73 |
+
453: "Kitchen and Dining",
|
| 74 |
+
454: "Home Decor",
|
| 75 |
+
455: "Tools and utility",
|
| 76 |
+
456: "Lighting and Electricals",
|
| 77 |
+
457: "Cleaning and Bath",
|
| 78 |
+
458: "Pet and Gardening",
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
STORE_CLUSTER_ROOM = {
|
| 82 |
+
"Smart Devices Hub": 350,
|
| 83 |
+
"Gaming & Accessories Hub": 351,
|
| 84 |
+
"Computer Systems Hub": 352,
|
| 85 |
+
"Mobile & Tablets Hub": 353,
|
| 86 |
+
"Health & Personal Care": 354,
|
| 87 |
+
"Bedroom": 355,
|
| 88 |
+
"Living Room": 356,
|
| 89 |
+
"Study/Office": 357,
|
| 90 |
+
"Kitchen": 358,
|
| 91 |
+
"Dining Room": 450,
|
| 92 |
+
"Bathroom": 451,
|
| 93 |
+
"Furnishings": 452,
|
| 94 |
+
"Kitchen and Dining": 453,
|
| 95 |
+
"Home Decor": 454,
|
| 96 |
+
"Tools and utility": 455,
|
| 97 |
+
"Lighting and Electricals": 456,
|
| 98 |
+
"Cleaning and Bath": 457,
|
| 99 |
+
"Pet and Gardening": 458,
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
STORE_CLUSTER = {
|
| 103 |
+
"Audio": "Smart Devices Hub",
|
| 104 |
+
"Cameras": "Smart Devices Hub",
|
| 105 |
+
"Smart Home Automation": "Smart Devices Hub",
|
| 106 |
+
"Smart Wearables": "Smart Devices Hub",
|
| 107 |
+
"Gaming": "Gaming & Accessories Hub",
|
| 108 |
+
"Laptop Accessories": "Gaming & Accessories Hub",
|
| 109 |
+
"Computer Peripherals": "Computer Systems Hub",
|
| 110 |
+
"Laptop & Desktop": "Computer Systems Hub",
|
| 111 |
+
"Storage": "Computer Systems Hub",
|
| 112 |
+
"Mobiles": "Mobile & Tablets Hub",
|
| 113 |
+
"Tablets": "Mobile & Tablets Hub",
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
SUB_TO_CATEGORY = {}
|
| 117 |
+
for type_block in categories_data:
|
| 118 |
+
for cat in type_block.get("categories", []):
|
| 119 |
+
for sub in cat.get("subCategories", []):
|
| 120 |
+
SUB_TO_CATEGORY[sub] = cat["category"]
|
| 121 |
+
|
| 122 |
+
NAME_TO_ROOM = {name.lower(): room for room, name in ROOM_INFO.items()}
|
| 123 |
+
|
| 124 |
+
# ============================================================
|
| 125 |
+
# GRID / CENTROIDS
|
| 126 |
+
# ============================================================
|
| 127 |
+
grid_0 = np.load(GRID_0_FILE)
|
| 128 |
+
grid_1 = np.load(GRID_1_FILE)
|
| 129 |
+
GRID_SHAPE = grid_0.shape
|
| 130 |
+
|
| 131 |
+
room_centroids_f3_grid = {
|
| 132 |
+
350: (70, 465),
|
| 133 |
+
351: (70, 435),
|
| 134 |
+
352: (70, 395),
|
| 135 |
+
353: (70, 360),
|
| 136 |
+
354: (70, 320),
|
| 137 |
+
355: (70, 285),
|
| 138 |
+
356: (70, 215),
|
| 139 |
+
357: (70, 160),
|
| 140 |
+
358: (70, 85),
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
room_numbers_f4 = list(range(450, 459))
|
| 144 |
+
room_centroids_f4_grid = {}
|
| 145 |
+
for old, new in zip(room_centroids_f3_grid.keys(), room_numbers_f4):
|
| 146 |
+
room_centroids_f4_grid[new] = room_centroids_f3_grid[old]
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def apply_room_clearance():
|
| 150 |
+
for r, c in room_centroids_f3_grid.values():
|
| 151 |
+
for dr in [-1, 0, 1]:
|
| 152 |
+
for dc in [-1, 0, 1]:
|
| 153 |
+
rr, cc = r + dr, c + dc
|
| 154 |
+
if 0 <= rr < GRID_SHAPE[0] and 0 <= cc < GRID_SHAPE[1]:
|
| 155 |
+
grid_0[rr, cc] = 0
|
| 156 |
+
grid_1[rr, cc] = 0
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
apply_room_clearance()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def reset_grids():
|
| 163 |
+
global grid_0, grid_1, GRID_SHAPE
|
| 164 |
+
grid_0 = np.load(GRID_0_FILE)
|
| 165 |
+
grid_1 = np.load(GRID_1_FILE)
|
| 166 |
+
GRID_SHAPE = grid_0.shape
|
| 167 |
+
apply_room_clearance()
|
| 168 |
+
|
| 169 |
+
# ============================================================
|
| 170 |
+
# MEMORY LOG
|
| 171 |
+
# ============================================================
|
| 172 |
+
def load_memory():
|
| 173 |
+
try:
|
| 174 |
+
with open(MEMORY_FILE, "r", encoding="utf-8") as f:
|
| 175 |
+
return json.load(f)
|
| 176 |
+
except Exception:
|
| 177 |
+
return []
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def save_memory(memory):
|
| 181 |
+
with open(MEMORY_FILE, "w", encoding="utf-8") as f:
|
| 182 |
+
json.dump(memory, f, indent=4)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
user_memory = load_memory()
|
| 186 |
+
navigation_counter = len(user_memory) + 1
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def save_navigation_mem(start_room, dest_room):
|
| 190 |
+
global navigation_counter
|
| 191 |
+
entry = {
|
| 192 |
+
"nav_id": f"N{navigation_counter}",
|
| 193 |
+
"start_room": start_room,
|
| 194 |
+
"dest_room": dest_room,
|
| 195 |
+
}
|
| 196 |
+
user_memory.append(entry)
|
| 197 |
+
save_memory(user_memory)
|
| 198 |
+
navigation_counter += 1
|
| 199 |
+
return entry
|
| 200 |
+
|
| 201 |
+
# ============================================================
|
| 202 |
+
# OPENROUTER CLIENT
|
| 203 |
+
# ============================================================
|
| 204 |
+
client = openai.OpenAI(
|
| 205 |
+
base_url="https://openrouter.ai/api/v1",
|
| 206 |
+
api_key=OPENROUTER_KEY,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
SYSTEM_PROMPT = """
|
| 210 |
+
You are an AI assistant for indoor navigation inside a shopping mall.
|
| 211 |
+
|
| 212 |
+
Mall stores:
|
| 213 |
+
350 Smart Devices Hub
|
| 214 |
+
351 Gaming & Accessories Hub
|
| 215 |
+
352 Computer Systems Hub
|
| 216 |
+
353 Mobile & Tablets Hub
|
| 217 |
+
354 Health & Personal Care
|
| 218 |
+
355 Bedroom
|
| 219 |
+
356 Living Room
|
| 220 |
+
357 Study/Office
|
| 221 |
+
358 Kitchen
|
| 222 |
+
450 Dining Room
|
| 223 |
+
451 Bathroom
|
| 224 |
+
452 Furnishings
|
| 225 |
+
453 Kitchen and Dining
|
| 226 |
+
454 Home Decor
|
| 227 |
+
455 Tools and utility
|
| 228 |
+
456 Lighting and Electricals
|
| 229 |
+
457 Cleaning and Bath
|
| 230 |
+
458 Pet and Gardening
|
| 231 |
+
|
| 232 |
+
Rules:
|
| 233 |
+
- If the user asks about a store, answer with store information.
|
| 234 |
+
- If the user asks for navigation, help them navigate.
|
| 235 |
+
- If the user says they are hungry, suggest kitchen / dining / food-related stores.
|
| 236 |
+
- Keep answers concise and helpful.
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
# ============================================================
|
| 240 |
+
# HELPERS / SESSION STATE
|
| 241 |
+
# ============================================================
|
| 242 |
+
def set_session_defaults(session):
|
| 243 |
+
session = session or {}
|
| 244 |
+
defaults = {
|
| 245 |
+
"username": None,
|
| 246 |
+
"gender": "other",
|
| 247 |
+
"low": 500,
|
| 248 |
+
"high": 5000,
|
| 249 |
+
"is_new": True,
|
| 250 |
+
"start_floor": None,
|
| 251 |
+
"start_xy": None,
|
| 252 |
+
"nav_mode": "Normal",
|
| 253 |
+
"recommended_room": None,
|
| 254 |
+
"nav_target_room": None,
|
| 255 |
+
"last_dest_room": None,
|
| 256 |
+
"last_referenced_room": None,
|
| 257 |
+
"selected_subcategory": None,
|
| 258 |
+
"accepted_store": False,
|
| 259 |
+
}
|
| 260 |
+
for k, v in defaults.items():
|
| 261 |
+
session.setdefault(k, v)
|
| 262 |
+
return session
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def get_floor(room):
|
| 266 |
+
if 350 <= room <= 358:
|
| 267 |
+
return 0
|
| 268 |
+
if 450 <= room <= 458:
|
| 269 |
+
return 1
|
| 270 |
+
return 0
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def get_centroid(room):
|
| 274 |
+
if room in room_centroids_f3_grid:
|
| 275 |
+
return room_centroids_f3_grid[room]
|
| 276 |
+
if room in room_centroids_f4_grid:
|
| 277 |
+
return room_centroids_f4_grid[room]
|
| 278 |
+
return None
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def clamp_point(x, y, grid):
|
| 282 |
+
r = int(max(0, min(grid.shape[0] - 1, round(float(x)))))
|
| 283 |
+
c = int(max(0, min(grid.shape[1] - 1, round(float(y)))))
|
| 284 |
+
return r, c
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def get_store_info(room):
|
| 288 |
+
info = {
|
| 289 |
+
350: "Smart devices, wearables, smart home products.",
|
| 290 |
+
351: "Gaming items, accessories, and related hardware.",
|
| 291 |
+
352: "Computers, laptops, storage, and peripherals.",
|
| 292 |
+
353: "Mobile phones, tablets, and accessories.",
|
| 293 |
+
354: "Health products and personal care items.",
|
| 294 |
+
355: "Bedroom furniture and decor.",
|
| 295 |
+
356: "Living room furniture and decor.",
|
| 296 |
+
357: "Office and study furniture and tools.",
|
| 297 |
+
358: "Kitchen furniture and kitchen essentials.",
|
| 298 |
+
450: "Dining room furniture and dining essentials.",
|
| 299 |
+
451: "Bathroom items and accessories.",
|
| 300 |
+
452: "Furnishings and home textiles.",
|
| 301 |
+
453: "Kitchen and dining accessories.",
|
| 302 |
+
454: "Home decor products.",
|
| 303 |
+
455: "Tools and utility items.",
|
| 304 |
+
456: "Lighting and electrical products.",
|
| 305 |
+
457: "Cleaning and bath products.",
|
| 306 |
+
458: "Pet and gardening products.",
|
| 307 |
+
}
|
| 308 |
+
name = ROOM_INFO.get(room)
|
| 309 |
+
if not name:
|
| 310 |
+
return None
|
| 311 |
+
return f"🏪 {name} (Room {room})\n📝 {info.get(room, 'Mall store information is available for this store.')}"
|
| 312 |
+
|
| 313 |
+
def find_room_in_text(text):
|
| 314 |
+
text_l = (text or "").lower()
|
| 315 |
+
room_match = re.search(r"\b(3\d{2}|45[0-8])\b", text_l)
|
| 316 |
+
if room_match:
|
| 317 |
+
room = int(room_match.group(1))
|
| 318 |
+
if room in ROOM_INFO:
|
| 319 |
+
return room
|
| 320 |
+
|
| 321 |
+
for name, room in sorted(NAME_TO_ROOM.items(), key=lambda x: len(x[0]), reverse=True):
|
| 322 |
+
if name in text_l:
|
| 323 |
+
return room
|
| 324 |
+
return None
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def get_intro_message():
|
| 328 |
+
return (
|
| 329 |
+
"Hello 👋 Welcome to the Mall Navigation Assistant.\n"
|
| 330 |
+
"You can navigate to:\n"
|
| 331 |
+
+ "\n".join([f"- {name} ({room})" for room, name in ROOM_INFO.items()])
|
| 332 |
+
+ "\nIf you're hungry, you can go to the kitchen/dining or cafeteria-related stores."
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def local_chat_reply(user_text):
|
| 337 |
+
low = (user_text or "").lower().strip()
|
| 338 |
+
if not low:
|
| 339 |
+
return "Please type a message or ask about a store."
|
| 340 |
+
if any(word in low for word in ["hi", "hello", "hey", "good morning", "good evening", "how are you"]):
|
| 341 |
+
return "Hello 👋 Welcome to SmartMall. Tell me a store name, a room number, or where you want to go."
|
| 342 |
+
if any(word in low for word in ["thanks", "thank you"]):
|
| 343 |
+
return "You are welcome."
|
| 344 |
+
|
| 345 |
+
room = find_room_in_text(user_text)
|
| 346 |
+
if room is not None and any(k in low for k in ["what", "tell", "about", "info", "describe", "where"]):
|
| 347 |
+
info = get_store_info(room)
|
| 348 |
+
if info:
|
| 349 |
+
return info
|
| 350 |
+
|
| 351 |
+
return None
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
# ============================================================
|
| 355 |
+
# AUTH / DATABASE OPERATIONS
|
| 356 |
+
# ============================================================
|
| 357 |
+
def save_preference(username, preference):
|
| 358 |
+
if not preference or len(preference) != 3:
|
| 359 |
+
return
|
| 360 |
+
main, cat, sub = preference
|
| 361 |
+
cursor.execute(
|
| 362 |
+
"""INSERT INTO user_preferences (username, main_category, category, subcategory)
|
| 363 |
+
VALUES (?, ?, ?, ?)""",
|
| 364 |
+
(username, main, cat, sub),
|
| 365 |
+
)
|
| 366 |
+
conn.commit()
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def entry_point():
|
| 370 |
+
print("\n=== Smart Mall System ===")
|
| 371 |
+
while True:
|
| 372 |
+
print("\n1) Signup")
|
| 373 |
+
print("2) Login")
|
| 374 |
+
choice = input("Enter choice: ").strip()
|
| 375 |
+
if choice == "1":
|
| 376 |
+
return "signup"
|
| 377 |
+
if choice == "2":
|
| 378 |
+
return "login"
|
| 379 |
+
print("Invalid input ❌")
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def signup():
|
| 383 |
+
print("\n=== SIGNUP ===")
|
| 384 |
+
while True:
|
| 385 |
+
username = input("Choose username: ").strip()
|
| 386 |
+
cursor.execute("SELECT username FROM users WHERE username=?", (username,))
|
| 387 |
+
if cursor.fetchone():
|
| 388 |
+
print("❌ Username exists, try again\n")
|
| 389 |
+
else:
|
| 390 |
+
break
|
| 391 |
+
|
| 392 |
+
password = input("Choose password: ").strip()
|
| 393 |
+
name = input("Enter name: ").strip()
|
| 394 |
+
age = int(input("Enter age: "))
|
| 395 |
+
gender = input("Enter gender (male/female): ").strip().lower()
|
| 396 |
+
|
| 397 |
+
auto = input("Use auto budget? (yes/no): ").strip().lower()
|
| 398 |
+
if auto == "no":
|
| 399 |
+
lower = float(input("Lower budget: "))
|
| 400 |
+
upper = float(input("Upper budget: "))
|
| 401 |
+
else:
|
| 402 |
+
lower, upper = 500, 5000
|
| 403 |
+
|
| 404 |
+
cursor.execute(
|
| 405 |
+
"INSERT INTO users VALUES (?, ?, ?, ?, ?, ?, ?)",
|
| 406 |
+
(username, password, name, age, gender, lower, upper),
|
| 407 |
+
)
|
| 408 |
+
conn.commit()
|
| 409 |
+
|
| 410 |
+
print("\n✅ Signup successful")
|
| 411 |
+
return username, gender, lower, upper, None
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def login():
|
| 415 |
+
print("\n=== LOGIN ===")
|
| 416 |
+
while True:
|
| 417 |
+
username = input("Username: ").strip()
|
| 418 |
+
password = input("Password: ").strip()
|
| 419 |
+
|
| 420 |
+
cursor.execute(
|
| 421 |
+
"SELECT * FROM users WHERE username=? AND password=?",
|
| 422 |
+
(username, password),
|
| 423 |
+
)
|
| 424 |
+
user = cursor.fetchone()
|
| 425 |
+
|
| 426 |
+
if user:
|
| 427 |
+
print("✅ Login successful")
|
| 428 |
+
break
|
| 429 |
+
print("❌ Invalid credentials, try again\n")
|
| 430 |
+
|
| 431 |
+
cursor.execute(
|
| 432 |
+
"""SELECT main_category, category, subcategory
|
| 433 |
+
FROM user_preferences
|
| 434 |
+
WHERE username=?
|
| 435 |
+
ORDER BY id DESC LIMIT 1""",
|
| 436 |
+
(username,),
|
| 437 |
+
)
|
| 438 |
+
pref = cursor.fetchone()
|
| 439 |
+
preference = pref if pref else None
|
| 440 |
+
|
| 441 |
+
return username, user[4], user[5], user[6], preference
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def auth_system():
|
| 445 |
+
while True:
|
| 446 |
+
mode = entry_point()
|
| 447 |
+
if mode == "signup":
|
| 448 |
+
user_data = signup()
|
| 449 |
+
if user_data:
|
| 450 |
+
return user_data, True
|
| 451 |
+
elif mode == "login":
|
| 452 |
+
user_data = login()
|
| 453 |
+
if user_data:
|
| 454 |
+
return user_data, False
|
| 455 |
+
print("❌ Authentication failed, try again...\n")
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
def get_ble_location():
|
| 459 |
+
print("\n📍 Enter your current location")
|
| 460 |
+
x = float(input("X coordinate: "))
|
| 461 |
+
y = float(input("Y coordinate: "))
|
| 462 |
+
return (x, y)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def save_navigation(username, start_room, dest_room):
|
| 466 |
+
if isinstance(start_room, tuple):
|
| 467 |
+
start_room = f"{start_room[0]},{start_room[1]}"
|
| 468 |
+
cursor.execute(
|
| 469 |
+
"INSERT INTO history (username, start_point, end_store) VALUES (?, ?, ?)",
|
| 470 |
+
(username, start_room, str(dest_room)),
|
| 471 |
+
)
|
| 472 |
+
conn.commit()
|
| 473 |
+
|
| 474 |
+
# ============================================================
|
| 475 |
+
# STORE RECOMMENDATION
|
| 476 |
+
# ============================================================
|
| 477 |
+
def nearest_store(user_pos):
|
| 478 |
+
best_room = None
|
| 479 |
+
best_dist = float("inf")
|
| 480 |
+
for _, room in STORE_CLUSTER_ROOM.items():
|
| 481 |
+
centroid = get_centroid(room)
|
| 482 |
+
if centroid is None:
|
| 483 |
+
continue
|
| 484 |
+
dist = np.sqrt((user_pos[0] - centroid[0]) ** 2 + (user_pos[1] - centroid[1]) ** 2)
|
| 485 |
+
if dist < best_dist:
|
| 486 |
+
best_dist = dist
|
| 487 |
+
best_room = room
|
| 488 |
+
return best_room
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def most_visited_store(username):
|
| 492 |
+
cursor.execute(
|
| 493 |
+
"""SELECT end_store, COUNT(*) as cnt
|
| 494 |
+
FROM history
|
| 495 |
+
WHERE username=?
|
| 496 |
+
GROUP BY end_store
|
| 497 |
+
ORDER BY cnt DESC""",
|
| 498 |
+
(username,),
|
| 499 |
+
)
|
| 500 |
+
result = cursor.fetchone()
|
| 501 |
+
return int(result[0]) if result else None
|
| 502 |
+
|
| 503 |
+
|
| 504 |
+
# ============================================================
|
| 505 |
+
# PRODUCT RECOMMENDER
|
| 506 |
+
# ============================================================
|
| 507 |
+
def clean_price(price_str):
|
| 508 |
+
digits = "".join(filter(str.isdigit, str(price_str)))
|
| 509 |
+
return int(digits) if digits else 0
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
def get_categories_for_type(type_name):
|
| 513 |
+
for t in categories_data:
|
| 514 |
+
if t.get("type") == type_name:
|
| 515 |
+
return [c.get("category") for c in t.get("categories", [])]
|
| 516 |
+
return []
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def get_subcategories(type_name, category_name):
|
| 520 |
+
for t in categories_data:
|
| 521 |
+
if t.get("type") == type_name:
|
| 522 |
+
for c in t.get("categories", []):
|
| 523 |
+
if c.get("category") == category_name:
|
| 524 |
+
return c.get("subCategories", [])
|
| 525 |
+
return []
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def resolve_store_from_category(parent_category):
|
| 529 |
+
store_name = STORE_CLUSTER.get(parent_category, parent_category)
|
| 530 |
+
room = STORE_CLUSTER_ROOM.get(store_name)
|
| 531 |
+
return store_name, room
|
| 532 |
+
|
| 533 |
+
|
| 534 |
+
def category_flow():
|
| 535 |
+
print("\n🧭 Category Selection")
|
| 536 |
+
|
| 537 |
+
print("\nMain categories:")
|
| 538 |
+
for i, c in enumerate(categories_data):
|
| 539 |
+
print(i, c["type"])
|
| 540 |
+
main = int(input("Select main category: "))
|
| 541 |
+
main_cat = categories_data[main]
|
| 542 |
+
|
| 543 |
+
print("\nSub categories:")
|
| 544 |
+
for i, c in enumerate(main_cat["categories"]):
|
| 545 |
+
print(i, c["category"])
|
| 546 |
+
sub = int(input("Select category: "))
|
| 547 |
+
sub_cat = main_cat["categories"][sub]
|
| 548 |
+
|
| 549 |
+
print("\nSub-sub categories:")
|
| 550 |
+
for i, c in enumerate(sub_cat["subCategories"]):
|
| 551 |
+
print(i, c)
|
| 552 |
+
sub_sub = int(input("Select subcategory: "))
|
| 553 |
+
|
| 554 |
+
selected_subcategory = sub_cat["subCategories"][sub_sub]
|
| 555 |
+
if selected_subcategory is None:
|
| 556 |
+
print("❌ No subcategory selected")
|
| 557 |
+
return None, None, None, None
|
| 558 |
+
|
| 559 |
+
main_category = main_cat["type"]
|
| 560 |
+
parent_category = sub_cat["category"]
|
| 561 |
+
_, end_store = resolve_store_from_category(parent_category)
|
| 562 |
+
return selected_subcategory, end_store, parent_category, main_category
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
def get_budget(default_low, default_high):
|
| 566 |
+
print("\n💰 Budget Selection")
|
| 567 |
+
choice = input("Use auto budget? (yes/no): ").strip().lower()
|
| 568 |
+
if choice == "no":
|
| 569 |
+
lower = float(input("Enter lower budget: "))
|
| 570 |
+
upper = float(input("Enter upper budget: "))
|
| 571 |
+
else:
|
| 572 |
+
lower, upper = default_low, default_high
|
| 573 |
+
return lower, upper
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
def get_products(subcategory):
|
| 577 |
+
if not subcategory:
|
| 578 |
+
return []
|
| 579 |
+
|
| 580 |
+
result = []
|
| 581 |
+
subcategory = str(subcategory).strip().lower()
|
| 582 |
+
|
| 583 |
+
for type_block in products_data:
|
| 584 |
+
for cat in type_block.get("items", []):
|
| 585 |
+
cat_name = cat.get("category")
|
| 586 |
+
for sub in cat.get("items", []):
|
| 587 |
+
sub_name = sub.get("subCategory")
|
| 588 |
+
if cat_name and cat_name.lower() == subcategory:
|
| 589 |
+
result.extend(sub.get("items", []))
|
| 590 |
+
elif sub_name and sub_name.lower() == subcategory:
|
| 591 |
+
result.extend(sub.get("items", []))
|
| 592 |
+
return result
|
| 593 |
+
|
| 594 |
+
|
| 595 |
+
def filter_by_budget(products, low, high):
|
| 596 |
+
filtered = []
|
| 597 |
+
for p in products:
|
| 598 |
+
price = clean_price(p.get("Price", 0))
|
| 599 |
+
if low <= price <= high:
|
| 600 |
+
item = dict(p)
|
| 601 |
+
item["price_int"] = price
|
| 602 |
+
filtered.append(item)
|
| 603 |
+
return filtered
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
def gender_filter(products, gender):
|
| 607 |
+
if gender not in ["male", "female"]:
|
| 608 |
+
return products
|
| 609 |
+
|
| 610 |
+
keywords = {
|
| 611 |
+
"male": ["gaming", "tool", "power", "sports", "fitness"],
|
| 612 |
+
"female": ["beauty", "hair", "skin", "cosmetic", "makeup", "fashion"],
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
preferred, others = [], []
|
| 616 |
+
for p in products:
|
| 617 |
+
name = str(p.get("Name", "")).lower()
|
| 618 |
+
if any(k in name for k in keywords[gender]):
|
| 619 |
+
preferred.append(p)
|
| 620 |
+
else:
|
| 621 |
+
others.append(p)
|
| 622 |
+
return preferred + others
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def add_promotions(products):
|
| 626 |
+
result = []
|
| 627 |
+
for p in products:
|
| 628 |
+
discount = random.randint(10, 50)
|
| 629 |
+
original = clean_price(p.get("Price", 0))
|
| 630 |
+
discounted = int(original * (1 - discount / 100))
|
| 631 |
+
result.append({
|
| 632 |
+
"Name": p.get("Name", "Unknown Product"),
|
| 633 |
+
"Brand": p.get("Brand", "N/A"),
|
| 634 |
+
"Original": f"{original}",
|
| 635 |
+
"Discounted": f"{discounted}",
|
| 636 |
+
"Discount": discount,
|
| 637 |
+
"Rating": p.get("Rating", 0),
|
| 638 |
+
"Reviews": p.get("No of Reviews", 0),
|
| 639 |
+
})
|
| 640 |
+
return result
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
def recommend_for_user(selected_subcategory, gender, low_budget, high_budget):
|
| 644 |
+
products = get_products(selected_subcategory)
|
| 645 |
+
if not products:
|
| 646 |
+
return {"store": "Unknown", "room": None, "products": []}
|
| 647 |
+
|
| 648 |
+
products = filter_by_budget(products, low_budget, high_budget)
|
| 649 |
+
if not products:
|
| 650 |
+
products = get_products(selected_subcategory)
|
| 651 |
+
|
| 652 |
+
products = gender_filter(products, gender)
|
| 653 |
+
products = sorted(products, key=lambda x: x.get("Rating", 0), reverse=True)
|
| 654 |
+
top_products = add_promotions(products[:5])
|
| 655 |
+
|
| 656 |
+
parent_category = SUB_TO_CATEGORY.get(selected_subcategory)
|
| 657 |
+
store_name = STORE_CLUSTER.get(parent_category, parent_category) if parent_category else None
|
| 658 |
+
room = STORE_CLUSTER_ROOM.get(store_name) if store_name else None
|
| 659 |
+
|
| 660 |
+
return {"store": store_name or "Unknown", "room": room, "products": top_products}
|
| 661 |
+
|
| 662 |
+
# ============================================================
|
| 663 |
+
# NAVIGATION / A*
|
| 664 |
+
# ============================================================
|
| 665 |
+
def find_nearest_free(grid, r, c):
|
| 666 |
+
free = (grid == 0)
|
| 667 |
+
_, ind = distance_transform_edt(~free, return_indices=True)
|
| 668 |
+
nr, nc = ind[:, r, c]
|
| 669 |
+
return int(nr), int(nc)
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
def extract_stairs(grid):
|
| 673 |
+
binary = (grid == 2).astype(int)
|
| 674 |
+
labeled, num = label(binary)
|
| 675 |
+
return labeled, num
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
stairs_0, n0 = extract_stairs(grid_0)
|
| 679 |
+
stairs_1, n1 = extract_stairs(grid_1)
|
| 680 |
+
stairs = {}
|
| 681 |
+
stair_cells = {0: {}, 1: {}}
|
| 682 |
+
for i in range(1, min(n0, n1) + 1):
|
| 683 |
+
sid = f"S{i}"
|
| 684 |
+
stairs[sid] = {"floors": [0, 1]}
|
| 685 |
+
for r, c in np.argwhere(stairs_0 == i):
|
| 686 |
+
stair_cells[0][(int(r), int(c))] = sid
|
| 687 |
+
for r, c in np.argwhere(stairs_1 == i):
|
| 688 |
+
stair_cells[1][(int(r), int(c))] = sid
|
| 689 |
+
|
| 690 |
+
elevator_cells = {0: set(), 1: set()}
|
| 691 |
+
for f in [0, 1]:
|
| 692 |
+
grid = grid_0 if f == 0 else grid_1
|
| 693 |
+
coords = np.argwhere(grid == 3)
|
| 694 |
+
for r, c in coords:
|
| 695 |
+
elevator_cells[f].add((int(r), int(c)))
|
| 696 |
+
|
| 697 |
+
fire_active = False
|
| 698 |
+
fire_room = None
|
| 699 |
+
fire_step = 0
|
| 700 |
+
fire_center = None
|
| 701 |
+
crowded_active = False
|
| 702 |
+
crowded_room = None
|
| 703 |
+
crowded_center = None
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def mark_danger(grid, centroid, radius=20):
|
| 707 |
+
r0, c0 = centroid
|
| 708 |
+
for i in range(grid.shape[0]):
|
| 709 |
+
for j in range(grid.shape[1]):
|
| 710 |
+
dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2)
|
| 711 |
+
if dist < radius:
|
| 712 |
+
grid[i, j] = 1
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
def mark_crowd(grid, centroid, room_radius=15, corridor_radius=40):
|
| 716 |
+
r0, c0 = centroid
|
| 717 |
+
for i in range(grid.shape[0]):
|
| 718 |
+
for j in range(grid.shape[1]):
|
| 719 |
+
dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2)
|
| 720 |
+
if dist < room_radius:
|
| 721 |
+
grid[i, j] = 5
|
| 722 |
+
elif dist < corridor_radius and grid[i, j] == 0:
|
| 723 |
+
grid[i, j] = 6
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
def heuristic(a, b):
|
| 727 |
+
return abs(a[1] - b[1]) + abs(a[2] - b[2]) + abs(a[0] - b[0]) * 10
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
def neighbors(node, mode):
|
| 731 |
+
floor, r, c = node
|
| 732 |
+
grid = grid_0 if floor == 0 else grid_1
|
| 733 |
+
moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
| 734 |
+
result = []
|
| 735 |
+
|
| 736 |
+
for dr, dc in moves:
|
| 737 |
+
nr = r + dr
|
| 738 |
+
nc = c + dc
|
| 739 |
+
if 0 <= nr < grid.shape[0] and 0 <= nc < grid.shape[1]:
|
| 740 |
+
cell = grid[nr, nc]
|
| 741 |
+
if cell == 1:
|
| 742 |
+
continue
|
| 743 |
+
|
| 744 |
+
cost = 1
|
| 745 |
+
|
| 746 |
+
if fire_active and fire_center is not None:
|
| 747 |
+
fr, fc = fire_center
|
| 748 |
+
dist = np.sqrt((nr - fr) ** 2 + (nc - fc) ** 2)
|
| 749 |
+
if dist < 120:
|
| 750 |
+
cost += (120 - dist) * 0.3
|
| 751 |
+
|
| 752 |
+
if cell == 4:
|
| 753 |
+
cost += 8
|
| 754 |
+
|
| 755 |
+
if mode == "Crowded":
|
| 756 |
+
if cell == 5:
|
| 757 |
+
cost += 200
|
| 758 |
+
elif cell == 6:
|
| 759 |
+
cost += 40
|
| 760 |
+
|
| 761 |
+
if mode == "Special Needs" and cell == 4:
|
| 762 |
+
cost += 100
|
| 763 |
+
|
| 764 |
+
result.append(((floor, int(nr), int(nc)), cost))
|
| 765 |
+
|
| 766 |
+
is_stair = (r, c) in stair_cells[floor]
|
| 767 |
+
is_elevator = (r, c) in elevator_cells[floor]
|
| 768 |
+
|
| 769 |
+
if mode == "Special Needs":
|
| 770 |
+
if is_elevator:
|
| 771 |
+
for f in [0, 1]:
|
| 772 |
+
if f != floor:
|
| 773 |
+
result.append(((f, r, c), 10))
|
| 774 |
+
else:
|
| 775 |
+
if is_stair:
|
| 776 |
+
sid = stair_cells[floor][(r, c)]
|
| 777 |
+
cost = 15 if mode != "Crowded" else 40
|
| 778 |
+
if fire_active and fire_center is not None:
|
| 779 |
+
fr, fc = fire_center
|
| 780 |
+
dist = np.sqrt((r - fr) ** 2 + (c - fc) ** 2)
|
| 781 |
+
if dist < 150:
|
| 782 |
+
cost += 200
|
| 783 |
+
for f in stairs[sid]["floors"]:
|
| 784 |
+
if f != floor:
|
| 785 |
+
result.append(((f, r, c), cost))
|
| 786 |
+
|
| 787 |
+
return result
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
def astar(start, goal, mode="Normal"):
|
| 791 |
+
open_set = []
|
| 792 |
+
heapq.heappush(open_set, (0, start))
|
| 793 |
+
came = {}
|
| 794 |
+
g = {start: 0}
|
| 795 |
+
|
| 796 |
+
while open_set:
|
| 797 |
+
_, cur = heapq.heappop(open_set)
|
| 798 |
+
if cur == goal:
|
| 799 |
+
path = []
|
| 800 |
+
while cur in came:
|
| 801 |
+
path.append(cur)
|
| 802 |
+
cur = came[cur]
|
| 803 |
+
path.append(start)
|
| 804 |
+
return path[::-1]
|
| 805 |
+
|
| 806 |
+
for nxt, cost in neighbors(cur, mode):
|
| 807 |
+
ng = g[cur] + cost
|
| 808 |
+
if nxt not in g or ng < g[nxt]:
|
| 809 |
+
g[nxt] = ng
|
| 810 |
+
heapq.heappush(open_set, (ng + heuristic(nxt, goal), nxt))
|
| 811 |
+
came[nxt] = cur
|
| 812 |
+
return None
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
def path_to_instructions(path):
|
| 816 |
+
if not path:
|
| 817 |
+
return "No path found."
|
| 818 |
+
|
| 819 |
+
instructions = []
|
| 820 |
+
last_floor, last_r, last_c = path[0]
|
| 821 |
+
current_dir = None
|
| 822 |
+
dist = 0
|
| 823 |
+
|
| 824 |
+
def add_instruction(direction, distance):
|
| 825 |
+
if distance > 0:
|
| 826 |
+
instructions.append(f"Move {direction} {distance * CELL_SIZE:.1f}m.")
|
| 827 |
+
|
| 828 |
+
for i in range(1, len(path)):
|
| 829 |
+
f, r, c = path[i]
|
| 830 |
+
if f != last_floor:
|
| 831 |
+
add_instruction(current_dir, dist)
|
| 832 |
+
instructions.append(f"Take stairs from floor {last_floor + 3} to floor {f + 3}.")
|
| 833 |
+
current_dir = None
|
| 834 |
+
dist = 0
|
| 835 |
+
last_floor = f
|
| 836 |
+
|
| 837 |
+
dr = r - last_r
|
| 838 |
+
dc = c - last_c
|
| 839 |
+
if abs(dr) > abs(dc):
|
| 840 |
+
direction = "down" if dr > 0 else "up"
|
| 841 |
+
step = abs(dr)
|
| 842 |
+
else:
|
| 843 |
+
direction = "right" if dc > 0 else "left"
|
| 844 |
+
step = abs(dc)
|
| 845 |
+
|
| 846 |
+
if direction == current_dir:
|
| 847 |
+
dist += step
|
| 848 |
+
else:
|
| 849 |
+
add_instruction(current_dir, dist)
|
| 850 |
+
current_dir = direction
|
| 851 |
+
dist = step
|
| 852 |
+
|
| 853 |
+
last_r, last_c = r, c
|
| 854 |
+
|
| 855 |
+
add_instruction(current_dir, dist)
|
| 856 |
+
instructions.append("You have arrived at your destination room.")
|
| 857 |
+
return ". ".join(instructions)
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
def plot_static_path(path):
|
| 861 |
+
fig, axs = plt.subplots(1, 2, figsize=(18, 6))
|
| 862 |
+
grids = [grid_0, grid_1]
|
| 863 |
+
|
| 864 |
+
for i, grid in enumerate(grids):
|
| 865 |
+
axs[i].imshow(grid, cmap="gray_r", origin="upper")
|
| 866 |
+
stairs_pos = np.where(grid == 2)
|
| 867 |
+
axs[i].scatter(stairs_pos[1], stairs_pos[0], s=40, label="Stairs")
|
| 868 |
+
|
| 869 |
+
xs = [p[2] for p in path if p[0] == i]
|
| 870 |
+
ys = [p[1] for p in path if p[0] == i]
|
| 871 |
+
if xs and ys:
|
| 872 |
+
axs[i].plot(xs, ys, linewidth=3, label="Path")
|
| 873 |
+
axs[i].scatter(xs[0], ys[0], s=80, label="Start")
|
| 874 |
+
axs[i].scatter(xs[-1], ys[-1], s=80, label="End")
|
| 875 |
+
|
| 876 |
+
axs[i].set_title(f"Floor {i + 3}")
|
| 877 |
+
axs[i].legend()
|
| 878 |
+
|
| 879 |
+
img_path = "/content/static_path.png"
|
| 880 |
+
plt.savefig(img_path)
|
| 881 |
+
plt.close()
|
| 882 |
+
return img_path
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
def navigate(start_floor, start_xy, dest_room, mode="Normal"):
|
| 886 |
+
if start_xy is None or dest_room is None:
|
| 887 |
+
return None, "❌ Missing start or destination."
|
| 888 |
+
|
| 889 |
+
sf = int(start_floor)
|
| 890 |
+
sr, sc = clamp_point(start_xy[0], start_xy[1], grid_0 if sf == 0 else grid_1)
|
| 891 |
+
sr, sc = find_nearest_free(grid_0 if sf == 0 else grid_1, sr, sc)
|
| 892 |
+
|
| 893 |
+
df = get_floor(dest_room)
|
| 894 |
+
dc = get_centroid(dest_room)
|
| 895 |
+
if dc is None:
|
| 896 |
+
return None, "❌ Could not find room centroid."
|
| 897 |
+
|
| 898 |
+
gr, gc = clamp_point(dc[0], dc[1], grid_0 if df == 0 else grid_1)
|
| 899 |
+
gr, gc = find_nearest_free(grid_0 if df == 0 else grid_1, gr, gc)
|
| 900 |
+
|
| 901 |
+
path = astar((sf, sr, sc), (df, gr, gc), mode)
|
| 902 |
+
instructions = path_to_instructions(path)
|
| 903 |
+
img_path = plot_static_path(path) if path else None
|
| 904 |
+
return img_path, instructions
|
| 905 |
+
|
| 906 |
+
# ============================================================
|
| 907 |
+
# CHATBOT LOGIC
|
| 908 |
+
# ============================================================
|
| 909 |
+
def chatbot_response_fallback(text):
|
| 910 |
+
local = local_chat_reply(text)
|
| 911 |
+
if local is not None:
|
| 912 |
+
return local
|
| 913 |
+
try:
|
| 914 |
+
response = client.chat.completions.create(
|
| 915 |
+
model=OPENROUTER_MODEL,
|
| 916 |
+
messages=[
|
| 917 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 918 |
+
{"role": "user", "content": text},
|
| 919 |
+
],
|
| 920 |
+
)
|
| 921 |
+
content = response.choices[0].message.content
|
| 922 |
+
if content:
|
| 923 |
+
return content
|
| 924 |
+
except Exception:
|
| 925 |
+
pass
|
| 926 |
+
return "I am here to help with mall navigation and store information. Try a store name like Electronics Store or room 351."
|
| 927 |
+
|
| 928 |
+
|
| 929 |
+
def extract_rooms(user_text, history):
|
| 930 |
+
lower = (user_text or "").lower().strip()
|
| 931 |
+
room = find_room_in_text(user_text)
|
| 932 |
+
|
| 933 |
+
if room is not None:
|
| 934 |
+
if any(k in lower for k in ["what is", "tell me", "about", "info", "describe", "where is"]):
|
| 935 |
+
return "chat", None, room, False
|
| 936 |
+
if any(k in lower for k in ["take me", "guide me", "go there", "navigate", "go to", "show me"]):
|
| 937 |
+
return "navigation", None, room, True
|
| 938 |
+
return "navigation", None, room, False
|
| 939 |
+
|
| 940 |
+
if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]):
|
| 941 |
+
return "navigation", None, None, True
|
| 942 |
+
|
| 943 |
+
conversation = ""
|
| 944 |
+
for u, b in history[-6:]:
|
| 945 |
+
if u:
|
| 946 |
+
conversation += f"User: {u}\n"
|
| 947 |
+
if b:
|
| 948 |
+
conversation += f"Assistant: {b}\n"
|
| 949 |
+
|
| 950 |
+
prompt = f"""
|
| 951 |
+
You are an AI assistant for indoor navigation inside a shopping mall.
|
| 952 |
+
|
| 953 |
+
Extract navigation intent from the conversation.
|
| 954 |
+
|
| 955 |
+
Return JSON only with:
|
| 956 |
+
{{
|
| 957 |
+
"intent": "navigation" or "chat",
|
| 958 |
+
"start_room": null,
|
| 959 |
+
"dest_room": null,
|
| 960 |
+
"confirmed": true or false
|
| 961 |
+
}}
|
| 962 |
+
|
| 963 |
+
Conversation:
|
| 964 |
+
{conversation}
|
| 965 |
+
|
| 966 |
+
User message:
|
| 967 |
+
{user_text}
|
| 968 |
+
"""
|
| 969 |
+
|
| 970 |
+
try:
|
| 971 |
+
response = client.chat.completions.create(
|
| 972 |
+
model=OPENROUTER_MODEL,
|
| 973 |
+
messages=[{"role": "user", "content": prompt}],
|
| 974 |
+
response_format={"type": "json_object"},
|
| 975 |
+
)
|
| 976 |
+
data = json.loads(response.choices[0].message.content)
|
| 977 |
+
intent = data.get("intent", "chat")
|
| 978 |
+
start_room = data.get("start_room")
|
| 979 |
+
dest_room = data.get("dest_room")
|
| 980 |
+
confirmed = data.get("confirmed", False)
|
| 981 |
+
if start_room:
|
| 982 |
+
start_room = int(start_room)
|
| 983 |
+
if dest_room:
|
| 984 |
+
dest_room = int(dest_room)
|
| 985 |
+
return intent, start_room, dest_room, confirmed
|
| 986 |
+
except Exception:
|
| 987 |
+
return "chat", None, None, False
|
| 988 |
+
|
| 989 |
+
|
| 990 |
+
def chatbot_respond(user_text, history, session):
|
| 991 |
+
session = set_session_defaults(session)
|
| 992 |
+
history = history or []
|
| 993 |
+
if not history:
|
| 994 |
+
history.append(("", get_intro_message()))
|
| 995 |
+
|
| 996 |
+
lower = (user_text or "").lower().strip()
|
| 997 |
+
local = local_chat_reply(user_text)
|
| 998 |
+
if local is not None and not any(k in lower for k in ["take me", "guide me", "go there", "navigate", "go to", "show me"]):
|
| 999 |
+
history.append((user_text, local))
|
| 1000 |
+
try:
|
| 1001 |
+
gTTS(local).save("/content/voice.mp3")
|
| 1002 |
+
except Exception:
|
| 1003 |
+
pass
|
| 1004 |
+
return history, None, "/content/voice.mp3", session
|
| 1005 |
+
|
| 1006 |
+
intent, _, dest_room, confirmed = extract_rooms(user_text, history)
|
| 1007 |
+
|
| 1008 |
+
info_room = find_room_in_text(user_text)
|
| 1009 |
+
if info_room is not None and any(k in lower for k in ["what", "tell", "about", "info", "describe", "where"]):
|
| 1010 |
+
info = get_store_info(info_room)
|
| 1011 |
+
if info:
|
| 1012 |
+
bot = f"{info}\n\nWould you like navigation to {ROOM_INFO[info_room]} (Room {info_room})?"
|
| 1013 |
+
session["last_referenced_room"] = info_room
|
| 1014 |
+
session["last_dest_room"] = info_room
|
| 1015 |
+
history.append((user_text, bot))
|
| 1016 |
+
try:
|
| 1017 |
+
gTTS(bot).save("/content/voice.mp3")
|
| 1018 |
+
except Exception:
|
| 1019 |
+
pass
|
| 1020 |
+
return history, None, "/content/voice.mp3", session
|
| 1021 |
+
|
| 1022 |
+
if intent == "navigation" and dest_room is not None:
|
| 1023 |
+
store_name = ROOM_INFO.get(dest_room, f"Room {dest_room}")
|
| 1024 |
+
if not confirmed:
|
| 1025 |
+
bot = f"I can guide you to {store_name} (Room {dest_room}). Do you want navigation?"
|
| 1026 |
+
history.append((user_text, bot))
|
| 1027 |
+
session["last_referenced_room"] = dest_room
|
| 1028 |
+
session["last_dest_room"] = dest_room
|
| 1029 |
+
session["nav_target_room"] = dest_room
|
| 1030 |
+
try:
|
| 1031 |
+
gTTS(bot).save("/content/voice.mp3")
|
| 1032 |
+
except Exception:
|
| 1033 |
+
pass
|
| 1034 |
+
return history, None, "/content/voice.mp3", session
|
| 1035 |
+
|
| 1036 |
+
if session.get("start_floor") is None or session.get("start_xy") is None:
|
| 1037 |
+
bot = "Please set your starting location first in the Shop & Navigate tab."
|
| 1038 |
+
history.append((user_text, bot))
|
| 1039 |
+
try:
|
| 1040 |
+
gTTS(bot).save("/content/voice.mp3")
|
| 1041 |
+
except Exception:
|
| 1042 |
+
pass
|
| 1043 |
+
return history, None, "/content/voice.mp3", session
|
| 1044 |
+
|
| 1045 |
+
img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest_room, session.get("nav_mode", "Normal"))
|
| 1046 |
+
save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest_room)
|
| 1047 |
+
save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest_room)
|
| 1048 |
+
|
| 1049 |
+
bot = f"🗺️ Navigating to {store_name} (Room {dest_room})\n\n{instructions}"
|
| 1050 |
+
history.append((user_text, bot))
|
| 1051 |
+
session["last_dest_room"] = dest_room
|
| 1052 |
+
session["nav_target_room"] = dest_room
|
| 1053 |
+
try:
|
| 1054 |
+
gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3")
|
| 1055 |
+
except Exception:
|
| 1056 |
+
pass
|
| 1057 |
+
return history, img_path, "/content/voice.mp3", session
|
| 1058 |
+
|
| 1059 |
+
if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]):
|
| 1060 |
+
dest = (
|
| 1061 |
+
session.get("last_dest_room")
|
| 1062 |
+
or session.get("nav_target_room")
|
| 1063 |
+
or session.get("last_referenced_room")
|
| 1064 |
+
or session.get("recommended_room")
|
| 1065 |
+
)
|
| 1066 |
+
if dest is not None:
|
| 1067 |
+
store_name = ROOM_INFO.get(dest, f"Room {dest}")
|
| 1068 |
+
if session.get("start_floor") is None or session.get("start_xy") is None:
|
| 1069 |
+
bot = f"I can take you to {store_name}, but I need your starting location first in the Shop & Navigate tab."
|
| 1070 |
+
history.append((user_text, bot))
|
| 1071 |
+
try:
|
| 1072 |
+
gTTS(bot).save("/content/voice.mp3")
|
| 1073 |
+
except Exception:
|
| 1074 |
+
pass
|
| 1075 |
+
return history, None, "/content/voice.mp3", session
|
| 1076 |
+
|
| 1077 |
+
img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest, session.get("nav_mode", "Normal"))
|
| 1078 |
+
save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest)
|
| 1079 |
+
save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest)
|
| 1080 |
+
|
| 1081 |
+
bot = f"🗺️ Navigating to {store_name} (Room {dest})\n\n{instructions}"
|
| 1082 |
+
history.append((user_text, bot))
|
| 1083 |
+
session["last_dest_room"] = dest
|
| 1084 |
+
session["nav_target_room"] = dest
|
| 1085 |
+
try:
|
| 1086 |
+
gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3")
|
| 1087 |
+
except Exception:
|
| 1088 |
+
pass
|
| 1089 |
+
return history, img_path, "/content/voice.mp3", session
|
| 1090 |
+
|
| 1091 |
+
bot = chatbot_response_fallback(user_text)
|
| 1092 |
+
history.append((user_text, bot))
|
| 1093 |
+
try:
|
| 1094 |
+
gTTS(bot).save("/content/voice.mp3")
|
| 1095 |
+
except Exception:
|
| 1096 |
+
pass
|
| 1097 |
+
return history, None, "/content/voice.mp3", session
|
| 1098 |
+
|
| 1099 |
+
|
| 1100 |
+
def speech_to_text(audio):
|
| 1101 |
+
if audio is None:
|
| 1102 |
+
return ""
|
| 1103 |
+
try:
|
| 1104 |
+
sound = AudioSegment.from_file(audio)
|
| 1105 |
+
wav = "/content/temp.wav"
|
| 1106 |
+
sound.export(wav, format="wav")
|
| 1107 |
+
r = sr.Recognizer()
|
| 1108 |
+
with sr.AudioFile(wav) as src:
|
| 1109 |
+
data = r.record(src)
|
| 1110 |
+
return r.recognize_google(data)
|
| 1111 |
+
except Exception:
|
| 1112 |
+
return ""
|
| 1113 |
+
|
| 1114 |
+
# ============================================================
|
| 1115 |
+
# UI HELPERS
|
| 1116 |
+
# ============================================================
|
| 1117 |
+
def format_products_html(promos, store_name, room):
|
| 1118 |
+
if not promos:
|
| 1119 |
+
return "<div style='color:#ff6b6b;padding:20px;text-align:center;font-family:monospace'>❌ No products found in your budget range.</div>"
|
| 1120 |
+
|
| 1121 |
+
cards = ""
|
| 1122 |
+
for p in promos:
|
| 1123 |
+
stars = "⭐" * int(round(float(p.get("Rating", 0))))
|
| 1124 |
+
cards += f"""
|
| 1125 |
+
<div style="background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid #00d4ff33;
|
| 1126 |
+
border-radius:12px;padding:16px;margin-bottom:12px;">
|
| 1127 |
+
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;">
|
| 1128 |
+
<div style="flex:1;">
|
| 1129 |
+
<div style="font-size:11px;color:#888;font-family:monospace;margin-bottom:4px">{p.get('Brand','N/A')}</div>
|
| 1130 |
+
<div style="color:#e0e0e0;font-size:13px;font-family:monospace;line-height:1.4">{p.get('Name','Unknown')}</div>
|
| 1131 |
+
<div style="margin-top:8px;font-size:12px;color:#ffd700">{stars} {p.get('Rating',0)} ({int(p.get('Reviews',0)):,} reviews)</div>
|
| 1132 |
+
</div>
|
| 1133 |
+
<div style="text-align:right;flex-shrink:0;">
|
| 1134 |
+
<div style="color:#ff6b6b;font-size:12px;text-decoration:line-through;font-family:monospace">{p.get('Original','0')} EGP</div>
|
| 1135 |
+
<div style="color:#00ff88;font-size:16px;font-weight:bold;font-family:monospace">{p.get('Discounted','0')} EGP</div>
|
| 1136 |
+
<div style="background:#ff4500;color:white;border-radius:20px;padding:3px 10px;
|
| 1137 |
+
font-size:11px;font-weight:bold;margin-top:4px;font-family:monospace">{p.get('Discount',0)}% OFF 🔥</div>
|
| 1138 |
+
</div>
|
| 1139 |
+
</div>
|
| 1140 |
+
</div>"""
|
| 1141 |
+
|
| 1142 |
+
return f"""
|
| 1143 |
+
<div style="font-family:monospace;max-height:500px;overflow-y:auto;padding:4px;">
|
| 1144 |
+
<div style="color:#00d4ff;font-size:14px;font-weight:bold;margin-bottom:12px;
|
| 1145 |
+
padding-bottom:8px;border-bottom:1px solid #00d4ff33;">
|
| 1146 |
+
🏪 {store_name} · Room {room}
|
| 1147 |
+
</div>
|
| 1148 |
+
{cards}
|
| 1149 |
+
</div>"""
|
| 1150 |
+
|
| 1151 |
+
|
| 1152 |
+
def set_location_ui(x, y, floor_choice, session):
|
| 1153 |
+
session = set_session_defaults(session)
|
| 1154 |
+
if x is None or y is None:
|
| 1155 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Please enter coordinates.</div>", session
|
| 1156 |
+
session["start_floor"] = 0 if str(floor_choice) == "3" else 1
|
| 1157 |
+
session["start_xy"] = (float(x), float(y))
|
| 1158 |
+
session["start_room"] = session["start_xy"]
|
| 1159 |
+
return f"<div style='color:#00ff88;font-family:monospace'>✅ Location saved: floor {int(floor_choice)}, x={x}, y={y}</div>", session
|
| 1160 |
+
|
| 1161 |
+
|
| 1162 |
+
def recommend_store_for_session(session):
|
| 1163 |
+
session = set_session_defaults(session)
|
| 1164 |
+
if not session.get("username"):
|
| 1165 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Please login first.</div>", session
|
| 1166 |
+
if session.get("start_xy") is None or session.get("start_floor") is None:
|
| 1167 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Please enter your current location first.</div>", session
|
| 1168 |
+
|
| 1169 |
+
if session.get("is_new", True):
|
| 1170 |
+
room = nearest_store(session["start_xy"])
|
| 1171 |
+
msg = "🆕 New user → Nearest store recommended"
|
| 1172 |
+
else:
|
| 1173 |
+
room = most_visited_store(session["username"])
|
| 1174 |
+
if room is None:
|
| 1175 |
+
room = nearest_store(session["start_xy"])
|
| 1176 |
+
msg = "🔁 Returning user → Most visited store recommended"
|
| 1177 |
+
|
| 1178 |
+
store_name = ROOM_INFO.get(room, f"Room {room}")
|
| 1179 |
+
session["recommended_room"] = room
|
| 1180 |
+
session["nav_target_room"] = room
|
| 1181 |
+
session["last_referenced_room"] = room
|
| 1182 |
+
session["last_dest_room"] = room
|
| 1183 |
+
|
| 1184 |
+
html = f"""
|
| 1185 |
+
<div style="background:linear-gradient(135deg,#1a1a2e,#0d2137);border:1px solid #00d4ff55;
|
| 1186 |
+
border-radius:12px;padding:20px;font-family:monospace;">
|
| 1187 |
+
<div style="color:#888;font-size:11px;letter-spacing:2px">{msg}</div>
|
| 1188 |
+
<div style="color:#00d4ff;font-size:22px;font-weight:bold;margin:8px 0">{store_name}</div>
|
| 1189 |
+
<div style="color:#555;font-size:12px">Room {room}</div>
|
| 1190 |
+
</div>"""
|
| 1191 |
+
return html, session
|
| 1192 |
+
|
| 1193 |
+
|
| 1194 |
+
def update_cat(type_name):
|
| 1195 |
+
return gr.update(choices=get_categories_for_type(type_name), value=None)
|
| 1196 |
+
|
| 1197 |
+
|
| 1198 |
+
def update_sub(type_name, cat_name):
|
| 1199 |
+
return gr.update(choices=get_subcategories(type_name, cat_name), value=None)
|
| 1200 |
+
|
| 1201 |
+
|
| 1202 |
+
def handle_agreement(choice, session):
|
| 1203 |
+
session = set_session_defaults(session)
|
| 1204 |
+
session["accepted_store"] = (choice or "").startswith("✅")
|
| 1205 |
+
types = [t["type"] for t in categories_data]
|
| 1206 |
+
if session["accepted_store"]:
|
| 1207 |
+
return (
|
| 1208 |
+
gr.update(visible=False),
|
| 1209 |
+
gr.update(visible=False),
|
| 1210 |
+
gr.update(visible=False),
|
| 1211 |
+
gr.update(choices=types, visible=True),
|
| 1212 |
+
)
|
| 1213 |
+
return (
|
| 1214 |
+
gr.update(choices=types, visible=True),
|
| 1215 |
+
gr.update(visible=True),
|
| 1216 |
+
gr.update(visible=True),
|
| 1217 |
+
gr.update(choices=types, visible=True),
|
| 1218 |
+
)
|
| 1219 |
+
|
| 1220 |
+
|
| 1221 |
+
def do_navigate(choice, type_name, cat_name, sub_name, nav_mode_val, danger_room, session):
|
| 1222 |
+
session = set_session_defaults(session)
|
| 1223 |
+
if not session.get("username"):
|
| 1224 |
+
return "❌ Please login first.", None
|
| 1225 |
+
if session.get("start_xy") is None or session.get("start_floor") is None:
|
| 1226 |
+
return "❌ Please set your location first.", None
|
| 1227 |
+
|
| 1228 |
+
reset_grids()
|
| 1229 |
+
|
| 1230 |
+
if "Yes" in (choice or ""):
|
| 1231 |
+
dest_room = session.get("recommended_room")
|
| 1232 |
+
if dest_room is None:
|
| 1233 |
+
return "❌ Please get a recommendation first.", None
|
| 1234 |
+
else:
|
| 1235 |
+
if not sub_name:
|
| 1236 |
+
return "❌ Please select a sub-category.", None
|
| 1237 |
+
parent = SUB_TO_CATEGORY.get(sub_name, cat_name)
|
| 1238 |
+
store = STORE_CLUSTER.get(parent, parent)
|
| 1239 |
+
dest_room = STORE_CLUSTER_ROOM.get(store)
|
| 1240 |
+
if dest_room is None:
|
| 1241 |
+
return "❌ Could not map the selected category to a store.", None
|
| 1242 |
+
|
| 1243 |
+
session["dest_room"] = dest_room
|
| 1244 |
+
session["nav_mode"] = nav_mode_val
|
| 1245 |
+
|
| 1246 |
+
room_val = None
|
| 1247 |
+
try:
|
| 1248 |
+
if danger_room is not None and str(danger_room).strip() != "":
|
| 1249 |
+
room_val = int(float(danger_room))
|
| 1250 |
+
except Exception:
|
| 1251 |
+
room_val = None
|
| 1252 |
+
|
| 1253 |
+
global fire_active, fire_room, fire_step, fire_center
|
| 1254 |
+
global crowded_active, crowded_room, crowded_center
|
| 1255 |
+
|
| 1256 |
+
fire_active = False
|
| 1257 |
+
crowded_active = False
|
| 1258 |
+
fire_room = None
|
| 1259 |
+
crowded_room = None
|
| 1260 |
+
fire_center = None
|
| 1261 |
+
crowded_center = None
|
| 1262 |
+
|
| 1263 |
+
if nav_mode_val == "Fire" and room_val is not None and room_val in ROOM_INFO:
|
| 1264 |
+
fire_active = True
|
| 1265 |
+
fire_room = room_val
|
| 1266 |
+
fire_center = get_centroid(room_val)
|
| 1267 |
+
if fire_center is not None:
|
| 1268 |
+
fire_step += 1
|
| 1269 |
+
radius = 20 + fire_step * 6
|
| 1270 |
+
if get_floor(room_val) == 0:
|
| 1271 |
+
mark_danger(grid_0, fire_center, radius)
|
| 1272 |
+
else:
|
| 1273 |
+
mark_danger(grid_1, fire_center, radius)
|
| 1274 |
+
|
| 1275 |
+
if nav_mode_val == "Crowded" and room_val is not None and room_val in ROOM_INFO:
|
| 1276 |
+
crowded_active = True
|
| 1277 |
+
crowded_room = room_val
|
| 1278 |
+
crowded_center = get_centroid(room_val)
|
| 1279 |
+
if crowded_center is not None:
|
| 1280 |
+
if get_floor(room_val) == 0:
|
| 1281 |
+
mark_crowd(grid_0, crowded_center)
|
| 1282 |
+
else:
|
| 1283 |
+
mark_crowd(grid_1, crowded_center)
|
| 1284 |
+
|
| 1285 |
+
save_navigation(session["username"], (session["start_floor"], session["start_xy"]), dest_room)
|
| 1286 |
+
save_navigation_mem((session["start_floor"], session["start_xy"]), dest_room)
|
| 1287 |
+
|
| 1288 |
+
img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest_room, nav_mode_val)
|
| 1289 |
+
return instructions, img_path
|
| 1290 |
+
|
| 1291 |
+
|
| 1292 |
+
def do_get_products(type_name, cat_name, sub_name, session):
|
| 1293 |
+
session = set_session_defaults(session)
|
| 1294 |
+
if not session.get("username"):
|
| 1295 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Please login first.</div>"
|
| 1296 |
+
if not sub_name:
|
| 1297 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Please select a sub-category.</div>"
|
| 1298 |
+
|
| 1299 |
+
if type_name and cat_name and sub_name:
|
| 1300 |
+
save_preference(session["username"], (type_name, cat_name, sub_name))
|
| 1301 |
+
|
| 1302 |
+
gender = session.get("gender", "other")
|
| 1303 |
+
low = session.get("low", 500)
|
| 1304 |
+
high = session.get("high", 5000)
|
| 1305 |
+
result = recommend_for_user(sub_name, gender, low, high)
|
| 1306 |
+
session["selected_subcategory"] = sub_name
|
| 1307 |
+
return format_products_html(result["products"], result["store"], result["room"])
|
| 1308 |
+
|
| 1309 |
+
# ============================================================
|
| 1310 |
+
# MAIN GRADIO APP
|
| 1311 |
+
# ============================================================
|
| 1312 |
+
with gr.Blocks(
|
| 1313 |
+
theme=gr.themes.Base(primary_hue="cyan", secondary_hue="blue", neutral_hue="slate"),
|
| 1314 |
+
css="""
|
| 1315 |
+
body { background: #0a0a14 !important; }
|
| 1316 |
+
.gradio-container { background: #0a0a14 !important; max-width: 1200px !important; }
|
| 1317 |
+
.tab-nav button { font-family: monospace; letter-spacing: 2px; font-size: 12px; }
|
| 1318 |
+
.panel { background: #111827; border: 1px solid #1e3a5f; border-radius: 12px; padding: 20px; }
|
| 1319 |
+
h1, h2, h3 { font-family: monospace !important; letter-spacing: 3px !important; }
|
| 1320 |
+
footer { display: none !important; }
|
| 1321 |
+
""",
|
| 1322 |
+
title="SmartMall AI System",
|
| 1323 |
+
) as demo:
|
| 1324 |
+
|
| 1325 |
+
session_state = gr.State({})
|
| 1326 |
+
|
| 1327 |
+
gr.HTML("""
|
| 1328 |
+
<div style="text-align:center;padding:24px 0 8px;font-family:monospace;">
|
| 1329 |
+
<div style="font-size:28px;font-weight:900;letter-spacing:6px;
|
| 1330 |
+
background:linear-gradient(90deg,#00d4ff,#00ff88,#ff4466);
|
| 1331 |
+
-webkit-background-clip:text;-webkit-text-fill-color:transparent">
|
| 1332 |
+
SMART MALL AI
|
| 1333 |
+
</div>
|
| 1334 |
+
<div style="color:#555;font-size:11px;letter-spacing:4px;margin-top:6px">
|
| 1335 |
+
NAVIGATION · RECOMMENDATIONS · ASSISTANT
|
| 1336 |
+
</div>
|
| 1337 |
+
</div>
|
| 1338 |
+
""")
|
| 1339 |
+
|
| 1340 |
+
with gr.Tabs():
|
| 1341 |
+
# ----------------------------------------------------
|
| 1342 |
+
# TAB 1 — AUTH
|
| 1343 |
+
# ----------------------------------------------------
|
| 1344 |
+
with gr.Tab("🔐 LOGIN / SIGNUP"):
|
| 1345 |
+
gr.HTML("<div style='color:#00d4ff;font-family:monospace;font-size:13px;margin-bottom:16px'>Enter your credentials to begin</div>")
|
| 1346 |
+
with gr.Row():
|
| 1347 |
+
with gr.Column(scale=1):
|
| 1348 |
+
auth_username = gr.Textbox(label="Username", placeholder="your_username")
|
| 1349 |
+
auth_password = gr.Textbox(label="Password", type="password", placeholder="••••••••")
|
| 1350 |
+
auth_name = gr.Textbox(label="Full Name (signup only)", placeholder="John Doe")
|
| 1351 |
+
auth_age = gr.Number(label="Age (signup only)", value=25)
|
| 1352 |
+
auth_gender = gr.Dropdown(["male", "female", "other"], label="Gender (signup only)", value="male")
|
| 1353 |
+
auth_budget_l = gr.Number(label="Min Budget EGP (signup only)", value=500)
|
| 1354 |
+
auth_budget_h = gr.Number(label="Max Budget EGP (signup only)", value=5000)
|
| 1355 |
+
|
| 1356 |
+
with gr.Row():
|
| 1357 |
+
signup_btn = gr.Button("SIGNUP", variant="primary")
|
| 1358 |
+
login_btn = gr.Button("LOGIN", variant="secondary")
|
| 1359 |
+
auth_status = gr.HTML()
|
| 1360 |
+
|
| 1361 |
+
def do_signup(username, password, name, age, gender, low, high, session):
|
| 1362 |
+
cursor.execute("SELECT username FROM users WHERE username=?", (username,))
|
| 1363 |
+
if cursor.fetchone():
|
| 1364 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Username already exists.</div>", session
|
| 1365 |
+
cursor.execute(
|
| 1366 |
+
"INSERT INTO users VALUES (?,?,?,?,?,?,?)",
|
| 1367 |
+
(username, password, name, int(age), gender, low, high),
|
| 1368 |
+
)
|
| 1369 |
+
conn.commit()
|
| 1370 |
+
session = set_session_defaults(session)
|
| 1371 |
+
session.update({"username": username, "gender": gender, "low": low, "high": high, "is_new": True})
|
| 1372 |
+
return f"<div style='color:#00ff88;font-family:monospace'>✅ Welcome, {name}! Account created.</div>", session
|
| 1373 |
+
|
| 1374 |
+
def do_login(username, password, session):
|
| 1375 |
+
cursor.execute("SELECT * FROM users WHERE username=? AND password=?", (username, password))
|
| 1376 |
+
user = cursor.fetchone()
|
| 1377 |
+
if not user:
|
| 1378 |
+
return "<div style='color:#ff6b6b;font-family:monospace'>❌ Invalid credentials.</div>", session
|
| 1379 |
+
session = set_session_defaults(session)
|
| 1380 |
+
session.update({"username": user[0], "gender": user[4], "low": user[5], "high": user[6], "is_new": False})
|
| 1381 |
+
return f"<div style='color:#00ff88;font-family:monospace'>✅ Welcome back, {user[2]}!</div>", session
|
| 1382 |
+
|
| 1383 |
+
signup_btn.click(do_signup, [auth_username, auth_password, auth_name, auth_age, auth_gender, auth_budget_l, auth_budget_h, session_state], [auth_status, session_state])
|
| 1384 |
+
login_btn.click(do_login, [auth_username, auth_password, session_state], [auth_status, session_state])
|
| 1385 |
+
|
| 1386 |
+
# ----------------------------------------------------
|
| 1387 |
+
# TAB 2 — RECOMMENDATION + NAVIGATION
|
| 1388 |
+
# ----------------------------------------------------
|
| 1389 |
+
with gr.Tab("🏪 SHOP & NAVIGATE"):
|
| 1390 |
+
gr.HTML("<div style='color:#00d4ff;font-family:monospace;font-size:13px;margin-bottom:16px'>Get store recommendations and navigate</div>")
|
| 1391 |
+
|
| 1392 |
+
gr.HTML("<div style='color:#888;font-family:monospace;font-size:11px;margin-bottom:8px'>STEP 0 — SET YOUR LOCATION</div>")
|
| 1393 |
+
with gr.Row():
|
| 1394 |
+
loc_x = gr.Number(label="X Coordinate")
|
| 1395 |
+
loc_y = gr.Number(label="Y Coordinate")
|
| 1396 |
+
loc_floor = gr.Dropdown(["3", "4"], label="Floor", value="3")
|
| 1397 |
+
set_loc_btn = gr.Button("📍 Set Location")
|
| 1398 |
+
loc_status = gr.HTML()
|
| 1399 |
+
|
| 1400 |
+
with gr.Row():
|
| 1401 |
+
with gr.Column(scale=1):
|
| 1402 |
+
gr.HTML("<div style='color:#888;font-family:monospace;font-size:11px;margin-bottom:8px'>STEP 1 — GET RECOMMENDATION</div>")
|
| 1403 |
+
recommend_btn = gr.Button("🎯 Get Store Recommendation", variant="primary")
|
| 1404 |
+
rec_result_html = gr.HTML()
|
| 1405 |
+
rec_agree = gr.Radio(["✅ Yes, navigate me there", "❌ No, I'll choose my own"], label="Accept recommendation?")
|
| 1406 |
+
|
| 1407 |
+
gr.HTML("<div style='color:#888;font-family:monospace;font-size:11px;margin-top:20px;margin-bottom:8px'>STEP 2 — CHOOSE CATEGORY (for navigation if rejected, and for products)</div>")
|
| 1408 |
+
cat_type = gr.Dropdown([t["type"] for t in categories_data], label="Main Category")
|
| 1409 |
+
cat_cat = gr.Dropdown([], label="Category")
|
| 1410 |
+
cat_sub = gr.Dropdown([], label="Sub-Category")
|
| 1411 |
+
nav_mode = gr.Dropdown(["Normal", "Fire", "Crowded", "Special Needs"], label="Navigation Mode", value="Normal")
|
| 1412 |
+
danger_room = gr.Textbox(label="Fire/Crowded Room Number", placeholder="Example: 350")
|
| 1413 |
+
navigate_btn = gr.Button("🗺️ Start Navigation", variant="primary")
|
| 1414 |
+
|
| 1415 |
+
with gr.Column(scale=1):
|
| 1416 |
+
nav_instructions = gr.Textbox(label="Navigation Instructions", lines=10, interactive=False)
|
| 1417 |
+
nav_image = gr.Image(label="Mall Map", type="filepath")
|
| 1418 |
+
|
| 1419 |
+
gr.HTML("<div style='color:#888;font-family:monospace;font-size:11px;margin-top:20px;margin-bottom:8px'>STEP 3 — PRODUCT RECOMMENDATIONS</div>")
|
| 1420 |
+
with gr.Row():
|
| 1421 |
+
prod_type = gr.Dropdown([], label="Category Type")
|
| 1422 |
+
prod_cat = gr.Dropdown([], label="Category")
|
| 1423 |
+
prod_sub = gr.Dropdown([], label="Sub-Category")
|
| 1424 |
+
get_products_btn = gr.Button("🛍️ Get Product Recommendations", variant="primary")
|
| 1425 |
+
products_html = gr.HTML()
|
| 1426 |
+
|
| 1427 |
+
set_loc_btn.click(set_location_ui, [loc_x, loc_y, loc_floor, session_state], [loc_status, session_state])
|
| 1428 |
+
recommend_btn.click(recommend_store_for_session, [session_state], [rec_result_html, session_state])
|
| 1429 |
+
rec_agree.change(handle_agreement, [rec_agree, session_state], [cat_type, cat_cat, cat_sub, prod_type])
|
| 1430 |
+
cat_type.change(update_cat, [cat_type], [cat_cat])
|
| 1431 |
+
cat_cat.change(update_sub, [cat_type, cat_cat], [cat_sub])
|
| 1432 |
+
prod_type.change(update_cat, [prod_type], [prod_cat])
|
| 1433 |
+
prod_cat.change(update_sub, [prod_type, prod_cat], [prod_sub])
|
| 1434 |
+
|
| 1435 |
+
navigate_btn.click(
|
| 1436 |
+
do_navigate,
|
| 1437 |
+
[rec_agree, cat_type, cat_cat, cat_sub, nav_mode, danger_room, session_state],
|
| 1438 |
+
[nav_instructions, nav_image],
|
| 1439 |
+
)
|
| 1440 |
+
|
| 1441 |
+
get_products_btn.click(do_get_products, [prod_type, prod_cat, prod_sub, session_state], [products_html])
|
| 1442 |
+
|
| 1443 |
+
def init_dropdowns():
|
| 1444 |
+
types = [t["type"] for t in categories_data]
|
| 1445 |
+
return gr.update(choices=types), gr.update(choices=types)
|
| 1446 |
+
|
| 1447 |
+
demo.load(init_dropdowns, [], [prod_type, cat_type])
|
| 1448 |
+
|
| 1449 |
+
# ----------------------------------------------------
|
| 1450 |
+
# TAB 3 — CHATBOT
|
| 1451 |
+
# ----------------------------------------------------
|
| 1452 |
+
with gr.Tab("🤖 AI ASSISTANT"):
|
| 1453 |
+
gr.HTML("<div style='color:#00d4ff;font-family:monospace;font-size:13px;margin-bottom:16px'>Chat with the mall AI assistant</div>")
|
| 1454 |
+
|
| 1455 |
+
chatbot_widget = gr.Chatbot(
|
| 1456 |
+
label="SmartMall Assistant",
|
| 1457 |
+
height=480,
|
| 1458 |
+
value=[("", "👋 Welcome to SmartMall! I can help you navigate, find products, or answer any questions. How can I help?")],
|
| 1459 |
+
)
|
| 1460 |
+
|
| 1461 |
+
with gr.Row():
|
| 1462 |
+
chat_msg = gr.Textbox(placeholder="Ask me anything about the mall...", label="Message", scale=4)
|
| 1463 |
+
chat_mic = gr.Audio(source="microphone", type="filepath", label="🎤", scale=1)
|
| 1464 |
+
|
| 1465 |
+
chat_audio_out = gr.Audio(label="Voice Response")
|
| 1466 |
+
chat_img_out = gr.Image(label="Navigation Map", type="filepath")
|
| 1467 |
+
|
| 1468 |
+
def chat_respond(msg, history, session):
|
| 1469 |
+
if not msg.strip():
|
| 1470 |
+
return history, None, None, session
|
| 1471 |
+
history, img, audio, session = chatbot_respond(msg, history, session)
|
| 1472 |
+
return history, img, audio, session
|
| 1473 |
+
|
| 1474 |
+
def voice_chat(audio, history, session):
|
| 1475 |
+
text = speech_to_text(audio)
|
| 1476 |
+
if not text:
|
| 1477 |
+
history.append(("🎤 [voice]", "Sorry, I couldn't understand the audio."))
|
| 1478 |
+
return history, None, None, session
|
| 1479 |
+
return chat_respond(text, history, session)
|
| 1480 |
+
|
| 1481 |
+
chat_msg.submit(chat_respond, [chat_msg, chatbot_widget, session_state], [chatbot_widget, chat_img_out, chat_audio_out, session_state])
|
| 1482 |
+
chat_mic.change(voice_chat, [chat_mic, chatbot_widget, session_state], [chatbot_widget, chat_img_out, chat_audio_out, session_state])
|
| 1483 |
+
|
| 1484 |
+
if __name__ == "__main__":
|
| 1485 |
+
demo.launch()
|
categories.json
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"type": "Electronics",
|
| 4 |
+
"categories": [
|
| 5 |
+
{
|
| 6 |
+
"category": "Audio",
|
| 7 |
+
"subCategories": [
|
| 8 |
+
"Bluetooth Headphones",
|
| 9 |
+
"Wired Headphones",
|
| 10 |
+
"True Wireless Earbuds",
|
| 11 |
+
"Bluetooth Speakers",
|
| 12 |
+
"Soundbars",
|
| 13 |
+
"Home Theatres",
|
| 14 |
+
"TV Streaming Device"
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"category": "Cameras",
|
| 19 |
+
"subCategories": [
|
| 20 |
+
"DSLR & Mirrorless",
|
| 21 |
+
"Sports & action",
|
| 22 |
+
"Point & Shoot",
|
| 23 |
+
"Instant Cameras",
|
| 24 |
+
"Camcorders",
|
| 25 |
+
"Camera tripods",
|
| 26 |
+
"Camera Lenses",
|
| 27 |
+
"Drone",
|
| 28 |
+
"Flashes",
|
| 29 |
+
"Gimbals",
|
| 30 |
+
"Binoculars"
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"category": "Computer Peripherals",
|
| 35 |
+
"subCategories": [
|
| 36 |
+
"Printers",
|
| 37 |
+
"Monitors",
|
| 38 |
+
"Projectors",
|
| 39 |
+
"Portable projectors",
|
| 40 |
+
"Ink Catridges",
|
| 41 |
+
"Ink Bottles",
|
| 42 |
+
"Receipt Printers",
|
| 43 |
+
"Lamination Machines",
|
| 44 |
+
"Note Counting Machines",
|
| 45 |
+
"Barcode scanners",
|
| 46 |
+
"Currency Detectors"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"category": "Gaming",
|
| 51 |
+
"subCategories": [
|
| 52 |
+
"Gaming Consoles",
|
| 53 |
+
"Gaming Mouse",
|
| 54 |
+
"Gaming Keyboards",
|
| 55 |
+
"Gamepads",
|
| 56 |
+
"Games",
|
| 57 |
+
"Gaming Mousepads",
|
| 58 |
+
"Controllers",
|
| 59 |
+
"Gaming Components"
|
| 60 |
+
]
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"category": "Health & Personal Care",
|
| 64 |
+
"subCategories": [
|
| 65 |
+
"Trimmers",
|
| 66 |
+
"Hair Straighteners",
|
| 67 |
+
"Hair Dryers",
|
| 68 |
+
"Epilators",
|
| 69 |
+
"Glucometers And Accessories",
|
| 70 |
+
"Blood Pressure Monitors",
|
| 71 |
+
"Digital Thermometers",
|
| 72 |
+
"Weighing Scales",
|
| 73 |
+
"Massagers",
|
| 74 |
+
"Nebulizers",
|
| 75 |
+
"Vaporizers"
|
| 76 |
+
]
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"category": "Laptop Accessories",
|
| 80 |
+
"subCategories": [
|
| 81 |
+
"Mouse",
|
| 82 |
+
"Laptop Keyboards",
|
| 83 |
+
"Router",
|
| 84 |
+
"Data Cards",
|
| 85 |
+
"UPS",
|
| 86 |
+
"USB Gadgets",
|
| 87 |
+
"Security Software",
|
| 88 |
+
"Laptop Battery",
|
| 89 |
+
"Laptop Adapter",
|
| 90 |
+
"Wireless USB Adapter",
|
| 91 |
+
"Processor"
|
| 92 |
+
]
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"category": "Laptop & Desktop",
|
| 96 |
+
"subCategories": [
|
| 97 |
+
"Laptops",
|
| 98 |
+
"Gaming Laptops",
|
| 99 |
+
"Desktop PCs",
|
| 100 |
+
"All in One PCs",
|
| 101 |
+
"Mini PCs",
|
| 102 |
+
"Tower PCs",
|
| 103 |
+
"PC Finder"
|
| 104 |
+
]
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"category": "Smart Home Automation",
|
| 108 |
+
"subCategories": [
|
| 109 |
+
"Smart Assistants",
|
| 110 |
+
"Smart Lights",
|
| 111 |
+
"Smart Cameras",
|
| 112 |
+
"Smart Switches",
|
| 113 |
+
"Smart Door Locks",
|
| 114 |
+
"Sensors & Alarms"
|
| 115 |
+
]
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"category": "Smart Wearables",
|
| 119 |
+
"subCategories": [
|
| 120 |
+
"Smart Watches",
|
| 121 |
+
"Smart Bands",
|
| 122 |
+
"Smart Glasses"
|
| 123 |
+
]
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"category": "Storage",
|
| 127 |
+
"subCategories": [
|
| 128 |
+
"Mobile Memory Card",
|
| 129 |
+
"Computer Storage Pendrive",
|
| 130 |
+
"Mobile Storage Pendrive",
|
| 131 |
+
"External Hard Drive",
|
| 132 |
+
"Internal Hard Drive"
|
| 133 |
+
]
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"category": "Tabelts",
|
| 137 |
+
"subCategories": ["Wifi", "Wifi + Cellular"]
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"category": "Mobiles",
|
| 141 |
+
"subCategories": [
|
| 142 |
+
"Mi mobiles",
|
| 143 |
+
"Realme mobiles",
|
| 144 |
+
"Samsung mobiles",
|
| 145 |
+
"Infinix mobiles",
|
| 146 |
+
"OPPO mobiles",
|
| 147 |
+
"Apple mobiles",
|
| 148 |
+
"Vivo mobiles",
|
| 149 |
+
"Honor mobiles",
|
| 150 |
+
"Asus mobiles"
|
| 151 |
+
]
|
| 152 |
+
}
|
| 153 |
+
]
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"type": "Furniture",
|
| 157 |
+
"categories": [
|
| 158 |
+
{
|
| 159 |
+
"category": "Bedroom",
|
| 160 |
+
"subCategories": [
|
| 161 |
+
"Beds",
|
| 162 |
+
"Mattresses",
|
| 163 |
+
"Wardrobes",
|
| 164 |
+
"Bedding",
|
| 165 |
+
"Side tables",
|
| 166 |
+
"Mirrors",
|
| 167 |
+
"Pillows"
|
| 168 |
+
]
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"category": "Living Room",
|
| 172 |
+
"subCategories": [
|
| 173 |
+
"Sofa",
|
| 174 |
+
"Coffee table",
|
| 175 |
+
"TV Stand",
|
| 176 |
+
"cabinets",
|
| 177 |
+
"Curtains",
|
| 178 |
+
"Lighting"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"category": "Study/Office",
|
| 183 |
+
"subCategories": [
|
| 184 |
+
"Desk",
|
| 185 |
+
"Laptop tables",
|
| 186 |
+
"Office chair",
|
| 187 |
+
"Drawer units",
|
| 188 |
+
"Shelves"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"category": "Kitchen",
|
| 193 |
+
"subCategories": [
|
| 194 |
+
"Kitchen chairs",
|
| 195 |
+
"Kitchen trolleys",
|
| 196 |
+
"Kitchen appliances",
|
| 197 |
+
"Kitchen sinks",
|
| 198 |
+
"Kitchen taps"
|
| 199 |
+
]
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"category": "Dining Room",
|
| 203 |
+
"subCategories": [
|
| 204 |
+
"Dining tables",
|
| 205 |
+
"Dining chairs",
|
| 206 |
+
"Dining sets",
|
| 207 |
+
"Cutlery units"
|
| 208 |
+
]
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"category": "Bathroom",
|
| 212 |
+
"subCategories": [
|
| 213 |
+
"Vanities",
|
| 214 |
+
"Showers",
|
| 215 |
+
"Bathroom storage",
|
| 216 |
+
"Taps",
|
| 217 |
+
"Sinks"
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
]
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"type": "Household",
|
| 224 |
+
"categories": [
|
| 225 |
+
{
|
| 226 |
+
"category": "Furnishings",
|
| 227 |
+
"subCategories": [
|
| 228 |
+
"Bed Linens",
|
| 229 |
+
"Bedsheets",
|
| 230 |
+
"Blankets",
|
| 231 |
+
"Curtains",
|
| 232 |
+
"Bath linen",
|
| 233 |
+
"Pillows",
|
| 234 |
+
"Kitchen linen"
|
| 235 |
+
]
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"category": "Kitchen and Dining",
|
| 239 |
+
"subCategories": [
|
| 240 |
+
"Cookware",
|
| 241 |
+
"Lunchboxes, bottles",
|
| 242 |
+
"Knives,Choppers",
|
| 243 |
+
"Gas stove",
|
| 244 |
+
"Dinnerware",
|
| 245 |
+
"Kitchen storage",
|
| 246 |
+
"Bakeware"
|
| 247 |
+
]
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"category": "Home Decor",
|
| 251 |
+
"subCategories": [
|
| 252 |
+
"Lighting",
|
| 253 |
+
"Wallpapers",
|
| 254 |
+
"Paintings and posters",
|
| 255 |
+
"Clocks",
|
| 256 |
+
"Decoratives",
|
| 257 |
+
"Vases",
|
| 258 |
+
"Home fragrances",
|
| 259 |
+
"Photo frames",
|
| 260 |
+
"Wall decor"
|
| 261 |
+
]
|
| 262 |
+
},
|
| 263 |
+
{
|
| 264 |
+
"category": "Tools and utility",
|
| 265 |
+
"subCategories": [
|
| 266 |
+
"Hand tools",
|
| 267 |
+
"Power tools",
|
| 268 |
+
"Measuring tools",
|
| 269 |
+
"Home storage",
|
| 270 |
+
"Umbrellas",
|
| 271 |
+
"Lock and security",
|
| 272 |
+
"Paint equipment"
|
| 273 |
+
]
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"category": "Lighting and Electricals",
|
| 277 |
+
"subCategories": [
|
| 278 |
+
"Bulbs",
|
| 279 |
+
"Emergency lights",
|
| 280 |
+
"Torches",
|
| 281 |
+
"Tube lights",
|
| 282 |
+
"Extension cords",
|
| 283 |
+
"Batteries",
|
| 284 |
+
"Solar lights"
|
| 285 |
+
]
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"category": "Cleaning and Bath",
|
| 289 |
+
"subCategories": [
|
| 290 |
+
"Mops",
|
| 291 |
+
"Cleaning Supplies",
|
| 292 |
+
"Air freshners",
|
| 293 |
+
"Household supplies",
|
| 294 |
+
"Liquid Detergents",
|
| 295 |
+
"Taps and Faucets",
|
| 296 |
+
"Bathroom shelves",
|
| 297 |
+
"Shower heads",
|
| 298 |
+
"Bathroom Accesories"
|
| 299 |
+
]
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"category": "Pet and Gardening",
|
| 303 |
+
"subCategories": [
|
| 304 |
+
"Plant seeds",
|
| 305 |
+
"Manure",
|
| 306 |
+
"Pots",
|
| 307 |
+
"Garden tools set",
|
| 308 |
+
"Watering equipment",
|
| 309 |
+
"Dog essentials",
|
| 310 |
+
"Cat essentials",
|
| 311 |
+
"Fish and aquatic",
|
| 312 |
+
"Pet hygiene",
|
| 313 |
+
"Pet toys"
|
| 314 |
+
]
|
| 315 |
+
}
|
| 316 |
+
]
|
| 317 |
+
}
|
| 318 |
+
]
|
floor_0_grid.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c5371819d353037135d8ec5f47937c8857c7e02c17e3c4eda3e0bdb12dc8876d
|
| 3 |
+
size 42581
|
mall.db
ADDED
|
Binary file (24.6 kB). View file
|
|
|
navigation_memory.json
ADDED
|
File without changes
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==3.39.0
|
| 2 |
+
gradio-client==0.3.0
|
| 3 |
+
numpy
|
| 4 |
+
scipy
|
| 5 |
+
matplotlib
|
| 6 |
+
openai
|
| 7 |
+
gTTS
|
| 8 |
+
SpeechRecognition
|
| 9 |
+
pydub
|
sort_data.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|