adham-Ashraf/Navigation-System-Recommender-System
0
1# -*- coding: utf-8 -*-2"""app.ipynb3 4Automatically generated by Colab.5 6Original file is located at7 https://colab.research.google.com/drive/1e0kcqimC-jHT2w8MwJMSXxzS-wJh1jYS8"""9 10import json11import math12import os13import random14import heapq15import re16import sqlite317import gradio as gr18import numpy as np19import matplotlib.pyplot as plt20from scipy.ndimage import label, distance_transform_edt21from gtts import gTTS22from pydub import AudioSegment23import speech_recognition as sr24import openai25 26CATEGORIES_FILE = "categories.json"27PRODUCTS_FILE = "sort_data.json"28DB_PATH = "mall.db"29# Smart Mall Assistant30# Navigation + chatbot + recommender system merged into one Colab-ready script.31 32 33# ============================================================34# CONFIG35# ============================================================36CELL_SIZE = 0.237MEMORY_FILE = "navigation_memory.json"38GRID_0_FILE = "floor_0_grid.npy"39GRID_1_FILE = "floor_0_grid.npy"40 41OPENROUTER_KEY = "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"42OPENROUTER_MODEL = "tencent/hy3-preview:free"43 44def load_json_file(path, default):45 try:46 with open(path, "r", encoding="utf-8") as f:47 return json.load(f)48 except Exception:49 return default50 51categories_data = load_json_file(CATEGORIES_FILE, [])52products_data = load_json_file(PRODUCTS_FILE, [])53 54# ============================================================55# STORE / ROOM MAPPING56# ============================================================57ROOM_INFO = {58 350: "Smart Devices Hub",59 351: "Gaming & Accessories Hub",60 352: "Computer Systems Hub",61 353: "Mobile & Tablets Hub",62 354: "Health & Personal Care",63 355: "Bedroom",64 356: "Living Room",65 357: "Study/Office",66 358: "Kitchen",67 450: "Dining Room",68 451: "Bathroom",69 452: "Furnishings",70 453: "Kitchen and Dining",71 454: "Home Decor",72 455: "Tools and utility",73 456: "Lighting and Electricals",74 457: "Cleaning and Bath",75 458: "Pet and Gardening",76}77 78STORE_CLUSTER_ROOM = {79 "Smart Devices Hub": 350,80 "Gaming & Accessories Hub": 351,81 "Computer Systems Hub": 352,82 "Mobile & Tablets Hub": 353,83 "Health & Personal Care": 354,84 "Bedroom": 355,85 "Living Room": 356,86 "Study/Office": 357,87 "Kitchen": 358,88 "Dining Room": 450,89 "Bathroom": 451,90 "Furnishings": 452,91 "Kitchen and Dining": 453,92 "Home Decor": 454,93 "Tools and utility": 455,94 "Lighting and Electricals": 456,95 "Cleaning and Bath": 457,96 "Pet and Gardening": 458,97}98 99STORE_CLUSTER = {100 "Audio": "Smart Devices Hub",101 "Cameras": "Smart Devices Hub",102 "Smart Home Automation": "Smart Devices Hub",103 "Smart Wearables": "Smart Devices Hub",104 "Gaming": "Gaming & Accessories Hub",105 "Laptop Accessories": "Gaming & Accessories Hub",106 "Computer Peripherals": "Computer Systems Hub",107 "Laptop & Desktop": "Computer Systems Hub",108 "Storage": "Computer Systems Hub",109 "Mobiles": "Mobile & Tablets Hub",110 "Tablets": "Mobile & Tablets Hub",111}112 113SUB_TO_CATEGORY = {}114for type_block in categories_data:115 for cat in type_block.get("categories", []):116 for sub in cat.get("subCategories", []):117 SUB_TO_CATEGORY[sub] = cat["category"]118 119NAME_TO_ROOM = {name.lower(): room for room, name in ROOM_INFO.items()}120 121# ============================================================122# GRID / CENTROIDS123# ============================================================124grid_0 = np.load(GRID_0_FILE)125grid_1 = np.load(GRID_1_FILE)126GRID_SHAPE = grid_0.shape127 128room_centroids_f3_grid = {129 350: (70, 465),130 351: (70, 435),131 352: (70, 395),132 353: (70, 360),133 354: (70, 320),134 355: (70, 285),135 356: (70, 215),136 357: (70, 160),137 358: (70, 85),138}139 140room_numbers_f4 = list(range(450, 459))141room_centroids_f4_grid = {}142for old, new in zip(room_centroids_f3_grid.keys(), room_numbers_f4):143 room_centroids_f4_grid[new] = room_centroids_f3_grid[old]144 145 146def apply_room_clearance():147 for r, c in room_centroids_f3_grid.values():148 for dr in [-1, 0, 1]:149 for dc in [-1, 0, 1]:150 rr, cc = r + dr, c + dc151 if 0 <= rr < GRID_SHAPE[0] and 0 <= cc < GRID_SHAPE[1]:152 grid_0[rr, cc] = 0153 grid_1[rr, cc] = 0154 155 156apply_room_clearance()157 158 159def reset_grids():160 global grid_0, grid_1, GRID_SHAPE161 grid_0 = np.load(GRID_0_FILE)162 grid_1 = np.load(GRID_1_FILE)163 GRID_SHAPE = grid_0.shape164 apply_room_clearance()165 166# ============================================================167# MEMORY LOG168# ============================================================169def load_memory():170 try:171 with open(MEMORY_FILE, "r", encoding="utf-8") as f:172 return json.load(f)173 except Exception:174 return []175 176 177def save_memory(memory):178 with open(MEMORY_FILE, "w", encoding="utf-8") as f:179 json.dump(memory, f, indent=4)180 181 182user_memory = load_memory()183navigation_counter = len(user_memory) + 1184 185 186def save_navigation_mem(start_room, dest_room):187 global navigation_counter188 entry = {189 "nav_id": f"N{navigation_counter}",190 "start_room": start_room,191 "dest_room": dest_room,192 }193 user_memory.append(entry)194 save_memory(user_memory)195 navigation_counter += 1196 return entry197 198# ============================================================199# OPENROUTER CLIENT200# ============================================================201client = openai.OpenAI(202 base_url="https://openrouter.ai/api/v1",203 api_key=OPENROUTER_KEY,204)205 206SYSTEM_PROMPT = """207You are an AI assistant for indoor navigation inside a shopping mall.208 209Mall stores:210350 Smart Devices Hub211351 Gaming & Accessories Hub212352 Computer Systems Hub213353 Mobile & Tablets Hub214354 Health & Personal Care215355 Bedroom216356 Living Room217357 Study/Office218358 Kitchen219450 Dining Room220451 Bathroom221452 Furnishings222453 Kitchen and Dining223454 Home Decor224455 Tools and utility225456 Lighting and Electricals226457 Cleaning and Bath227458 Pet and Gardening228 229Rules:230- If the user asks about a store, answer with store information.231- If the user asks for navigation, help them navigate.232- If the user says he is hungry, suggest kitchen / dining / food-related stores and when he tells you that he agree os suggestion then help him navigate.233- Keep answers concise and helpful.234"""235 236# ============================================================237# HELPERS / SESSION STATE238# ============================================================239def set_session_defaults(session):240 session = session or {}241 defaults = {242 "username": None,243 "gender": "other",244 "low": 500,245 "high": 5000,246 "is_new": True,247 "start_floor": None,248 "start_xy": None,249 "nav_mode": "Normal",250 "recommended_room": None,251 "nav_target_room": None,252 "last_dest_room": None,253 "last_referenced_room": None,254 "selected_subcategory": None,255 "accepted_store": False,256 }257 for k, v in defaults.items():258 session.setdefault(k, v)259 return session260 261 262def get_floor(room):263 if 350 <= room <= 358:264 return 0265 if 450 <= room <= 458:266 return 1267 return 0268 269 270def get_centroid(room):271 if room in room_centroids_f3_grid:272 return room_centroids_f3_grid[room]273 if room in room_centroids_f4_grid:274 return room_centroids_f4_grid[room]275 return None276 277 278def clamp_point(x, y, grid):279 r = int(max(0, min(grid.shape[0] - 1, round(float(x)))))280 c = int(max(0, min(grid.shape[1] - 1, round(float(y)))))281 return r, c282 283 284def get_store_info(room):285 info = {286 350: "Smart devices, wearables, smart home products.",287 351: "Gaming items, accessories, and related hardware.",288 352: "Computers, laptops, storage, and peripherals.",289 353: "Mobile phones, tablets, and accessories.",290 354: "Health products and personal care items.",291 355: "Bedroom furniture and decor.",292 356: "Living room furniture and decor.",293 357: "Office and study furniture and tools.",294 358: "Kitchen furniture and kitchen essentials.",295 450: "Dining room furniture and dining essentials.",296 451: "Bathroom items and accessories.",297 452: "Furnishings and home textiles.",298 453: "Kitchen and dining accessories.",299 454: "Home decor products.",300 455: "Tools and utility items.",301 456: "Lighting and electrical products.",302 457: "Cleaning and bath products.",303 458: "Pet and gardening products.",304 }305 name = ROOM_INFO.get(room)306 if not name:307 return None308 return f"πͺ {name} (Room {room})\nπ {info.get(room, 'Mall store information is available for this store.')}"309 310def find_room_in_text(text):311 text_l = (text or "").lower()312 room_match = re.search(r"\b(3\d{2}|45[0-8])\b", text_l)313 if room_match:314 room = int(room_match.group(1))315 if room in ROOM_INFO:316 return room317 318 for name, room in sorted(NAME_TO_ROOM.items(), key=lambda x: len(x[0]), reverse=True):319 if name in text_l:320 return room321 return None322 323 324def get_intro_message():325 return (326 "Hello π Welcome to the Mall Navigation Assistant.\n"327 "You can navigate to:\n"328 + "\n".join([f"- {name} ({room})" for room, name in ROOM_INFO.items()])329 + "\nIf you're hungry, you can go to the kitchen/dining or cafeteria-related stores."330 )331 332 333def local_chat_reply(user_text):334 low = (user_text or "").lower().strip()335 if not low:336 return "Please type a message or ask about a store."337 if any(word in low for word in ["hi", "hello", "hey", "good morning", "good evening", "how are you"]):338 return "Hello π Welcome to SmartMall. Tell me a store name, a room number, or where you want to go."339 if any(word in low for word in ["thanks", "thank you"]):340 return "You are welcome."341 342 room = find_room_in_text(user_text)343 if room is not None and any(k in low for k in ["what", "tell", "about", "info", "describe", "where"]):344 info = get_store_info(room)345 if info:346 return info347 348 return None349 350 351# ============================================================352# AUTH / DATABASE OPERATIONS353# ============================================================354def save_preference(username, preference):355 if not preference or len(preference) != 3:356 return357 main, cat, sub = preference358 cursor.execute(359 """INSERT INTO user_preferences (username, main_category, category, subcategory)360 VALUES (?, ?, ?, ?)""",361 (username, main, cat, sub),362 )363 conn.commit()364 365 366def entry_point():367 print("\n=== Smart Mall System ===")368 while True:369 print("\n1) Signup")370 print("2) Login")371 choice = input("Enter choice: ").strip()372 if choice == "1":373 return "signup"374 if choice == "2":375 return "login"376 print("Invalid input β")377 378 379def signup():380 print("\n=== SIGNUP ===")381 while True:382 username = input("Choose username: ").strip()383 cursor.execute("SELECT username FROM users WHERE username=?", (username,))384 if cursor.fetchone():385 print("β Username exists, try again\n")386 else:387 break388 389 password = input("Choose password: ").strip()390 name = input("Enter name: ").strip()391 age = int(input("Enter age: "))392 gender = input("Enter gender (male/female): ").strip().lower()393 394 auto = input("Use auto budget? (yes/no): ").strip().lower()395 if auto == "no":396 lower = float(input("Lower budget: "))397 upper = float(input("Upper budget: "))398 else:399 lower, upper = 500, 5000400 401 cursor.execute(402 "INSERT INTO users VALUES (?, ?, ?, ?, ?, ?, ?)",403 (username, password, name, age, gender, lower, upper),404 )405 conn.commit()406 407 print("\nβ
Signup successful")408 return username, gender, lower, upper, None409 410 411def login():412 print("\n=== LOGIN ===")413 while True:414 username = input("Username: ").strip()415 password = input("Password: ").strip()416 417 cursor.execute(418 "SELECT * FROM users WHERE username=? AND password=?",419 (username, password),420 )421 user = cursor.fetchone()422 423 if user:424 print("β
Login successful")425 break426 print("β Invalid credentials, try again\n")427 428 cursor.execute(429 """SELECT main_category, category, subcategory430 FROM user_preferences431 WHERE username=?432 ORDER BY id DESC LIMIT 1""",433 (username,),434 )435 pref = cursor.fetchone()436 preference = pref if pref else None437 438 return username, user[4], user[5], user[6], preference439 440 441def auth_system():442 while True:443 mode = entry_point()444 if mode == "signup":445 user_data = signup()446 if user_data:447 return user_data, True448 elif mode == "login":449 user_data = login()450 if user_data:451 return user_data, False452 print("β Authentication failed, try again...\n")453 454 455def get_ble_location():456 print("\nπ Enter your current location")457 x = float(input("X coordinate: "))458 y = float(input("Y coordinate: "))459 return (x, y)460 461 462def save_navigation(username, start_room, dest_room):463 if isinstance(start_room, tuple):464 start_room = f"{start_room[0]},{start_room[1]}"465 cursor.execute(466 "INSERT INTO history (username, start_point, end_store) VALUES (?, ?, ?)",467 (username, start_room, str(dest_room)),468 )469 conn.commit()470 471# ============================================================472# STORE RECOMMENDATION473# ============================================================474def nearest_store(user_pos, user_floor):475 best_room = None476 best_dist = float("inf")477 478 for _, room in STORE_CLUSTER_ROOM.items():479 # β
ΩΩΨͺΨ±Ψ© ΨΉΩΩ ΩΩΨ³ Ψ§ΩΨ―ΩΨ±480 if get_floor(room) != user_floor:481 continue482 483 centroid = get_centroid(room)484 if centroid is None:485 continue486 487 dist = np.sqrt((user_pos[0] - centroid[0]) ** 2 + (user_pos[1] - centroid[1]) ** 2)488 489 if dist < best_dist:490 best_dist = dist491 best_room = room492 493 return best_room494 495 496def most_visited_store(username):497 cursor.execute(498 """SELECT end_store, COUNT(*) as cnt499 FROM history500 WHERE username=?501 GROUP BY end_store502 ORDER BY cnt DESC""",503 (username,),504 )505 result = cursor.fetchone()506 return int(result[0]) if result else None507 508 509# ============================================================510# PRODUCT RECOMMENDER511# ============================================================512def clean_price(price_str):513 digits = "".join(filter(str.isdigit, str(price_str)))514 return int(digits) if digits else 0515 516 517def get_categories_for_type(type_name):518 for t in categories_data:519 if t.get("type") == type_name:520 return [c.get("category") for c in t.get("categories", [])]521 return []522 523 524def get_subcategories(type_name, category_name):525 for t in categories_data:526 if t.get("type") == type_name:527 for c in t.get("categories", []):528 if c.get("category") == category_name:529 return c.get("subCategories", [])530 return []531 532 533def resolve_store_from_category(parent_category):534 store_name = STORE_CLUSTER.get(parent_category, parent_category)535 room = STORE_CLUSTER_ROOM.get(store_name)536 return store_name, room537 538 539def category_flow():540 print("\nπ§ Category Selection")541 542 print("\nMain categories:")543 for i, c in enumerate(categories_data):544 print(i, c["type"])545 main = int(input("Select main category: "))546 main_cat = categories_data[main]547 548 print("\nSub categories:")549 for i, c in enumerate(main_cat["categories"]):550 print(i, c["category"])551 sub = int(input("Select category: "))552 sub_cat = main_cat["categories"][sub]553 554 print("\nSub-sub categories:")555 for i, c in enumerate(sub_cat["subCategories"]):556 print(i, c)557 sub_sub = int(input("Select subcategory: "))558 559 selected_subcategory = sub_cat["subCategories"][sub_sub]560 if selected_subcategory is None:561 print("β No subcategory selected")562 return None, None, None, None563 564 main_category = main_cat["type"]565 parent_category = sub_cat["category"]566 _, end_store = resolve_store_from_category(parent_category)567 return selected_subcategory, end_store, parent_category, main_category568 569 570def get_budget(default_low, default_high):571 print("\nπ° Budget Selection")572 choice = input("Use auto budget? (yes/no): ").strip().lower()573 if choice == "no":574 lower = float(input("Enter lower budget: "))575 upper = float(input("Enter upper budget: "))576 else:577 lower, upper = default_low, default_high578 return lower, upper579 580 581def get_products(subcategory):582 if not subcategory:583 return []584 585 result = []586 subcategory = str(subcategory).strip().lower()587 588 for type_block in products_data:589 for cat in type_block.get("items", []):590 cat_name = cat.get("category")591 for sub in cat.get("items", []):592 sub_name = sub.get("subCategory")593 if cat_name and cat_name.lower() == subcategory:594 result.extend(sub.get("items", []))595 elif sub_name and sub_name.lower() == subcategory:596 result.extend(sub.get("items", []))597 return result598 599 600def filter_by_budget(products, low, high):601 filtered = []602 for p in products:603 price = clean_price(p.get("Price", 0))604 if low <= price <= high:605 item = dict(p)606 item["price_int"] = price607 filtered.append(item)608 return filtered609 610 611def gender_filter(products, gender):612 if gender not in ["male", "female"]:613 return products614 615 keywords = {616 "male": ["gaming", "tool", "power", "sports", "fitness"],617 "female": ["beauty", "hair", "skin", "cosmetic", "makeup", "fashion"],618 }619 620 preferred, others = [], []621 for p in products:622 name = str(p.get("Name", "")).lower()623 if any(k in name for k in keywords[gender]):624 preferred.append(p)625 else:626 others.append(p)627 return preferred + others628 629 630def add_promotions(products):631 result = []632 for p in products:633 discount = random.randint(10, 50)634 original = clean_price(p.get("Price", 0))635 discounted = int(original * (1 - discount / 100))636 result.append({637 "Name": p.get("Name", "Unknown Product"),638 "Brand": p.get("Brand", "N/A"),639 "Original": f"{original}",640 "Discounted": f"{discounted}",641 "Discount": discount,642 "Rating": p.get("Rating", 0),643 "Reviews": p.get("No of Reviews", 0),644 })645 return result646 647 648def recommend_for_user(selected_subcategory, gender, low_budget, high_budget):649 products = get_products(selected_subcategory)650 if not products:651 return {"store": "Unknown", "room": None, "products": []}652 653 products = filter_by_budget(products, low_budget, high_budget)654 if not products:655 products = get_products(selected_subcategory)656 657 products = gender_filter(products, gender)658 products = sorted(products, key=lambda x: x.get("Rating", 0), reverse=True)659 top_products = add_promotions(products[:5])660 661 parent_category = SUB_TO_CATEGORY.get(selected_subcategory)662 store_name = STORE_CLUSTER.get(parent_category, parent_category) if parent_category else None663 room = STORE_CLUSTER_ROOM.get(store_name) if store_name else None664 665 return {"store": store_name or "Unknown", "room": room, "products": top_products}666 667# ============================================================668# NAVIGATION / A*669# ============================================================670def find_nearest_free(grid, r, c):671 free = (grid == 0)672 _, ind = distance_transform_edt(~free, return_indices=True)673 nr, nc = ind[:, r, c]674 return int(nr), int(nc)675 676 677def extract_stairs(grid):678 binary = (grid == 2).astype(int)679 labeled, num = label(binary)680 return labeled, num681 682 683stairs_0, n0 = extract_stairs(grid_0)684stairs_1, n1 = extract_stairs(grid_1)685stairs = {}686stair_cells = {0: {}, 1: {}}687for i in range(1, min(n0, n1) + 1):688 sid = f"S{i}"689 stairs[sid] = {"floors": [0, 1]}690 for r, c in np.argwhere(stairs_0 == i):691 stair_cells[0][(int(r), int(c))] = sid692 for r, c in np.argwhere(stairs_1 == i):693 stair_cells[1][(int(r), int(c))] = sid694 695elevator_cells = {0: set(), 1: set()}696for f in [0, 1]:697 grid = grid_0 if f == 0 else grid_1698 coords = np.argwhere(grid == 3)699 for r, c in coords:700 elevator_cells[f].add((int(r), int(c)))701 702fire_active = False703fire_room = None704fire_step = 0705fire_center = None706crowded_active = False707crowded_room = None708crowded_center = None709 710 711def mark_danger(grid, centroid, radius=20):712 r0, c0 = centroid713 for i in range(grid.shape[0]):714 for j in range(grid.shape[1]):715 dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2)716 if dist < radius:717 grid[i, j] = 1718 719 720def mark_crowd(grid, centroid, room_radius=15, corridor_radius=40):721 r0, c0 = centroid722 for i in range(grid.shape[0]):723 for j in range(grid.shape[1]):724 dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2)725 if dist < room_radius:726 grid[i, j] = 5727 elif dist < corridor_radius and grid[i, j] == 0:728 grid[i, j] = 6729 730 731def heuristic(a, b):732 return abs(a[1] - b[1]) + abs(a[2] - b[2]) + abs(a[0] - b[0]) * 10733 734 735def neighbors(node, mode):736 floor, r, c = node737 grid = grid_0 if floor == 0 else grid_1738 moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]739 result = []740 741 for dr, dc in moves:742 nr = r + dr743 nc = c + dc744 if 0 <= nr < grid.shape[0] and 0 <= nc < grid.shape[1]:745 cell = grid[nr, nc]746 if cell == 1:747 continue748 749 cost = 1750 751 if fire_active and fire_center is not None:752 fr, fc = fire_center753 dist = np.sqrt((nr - fr) ** 2 + (nc - fc) ** 2)754 if dist < 120:755 cost += (120 - dist) * 0.3756 757 if cell == 4:758 cost += 8759 760 if mode == "Crowded":761 if cell == 5:762 cost += 200763 elif cell == 6:764 cost += 40765 766 if mode == "Special Needs" and cell == 4:767 cost += 100768 769 result.append(((floor, int(nr), int(nc)), cost))770 771 is_stair = (r, c) in stair_cells[floor]772 is_elevator = (r, c) in elevator_cells[floor]773 774 if mode == "Special Needs":775 if is_elevator:776 for f in [0, 1]:777 if f != floor:778 result.append(((f, r, c), 10))779 else:780 if is_stair:781 sid = stair_cells[floor][(r, c)]782 cost = 15 if mode != "Crowded" else 40783 if fire_active and fire_center is not None:784 fr, fc = fire_center785 dist = np.sqrt((r - fr) ** 2 + (c - fc) ** 2)786 if dist < 150:787 cost += 200788 for f in stairs[sid]["floors"]:789 if f != floor:790 result.append(((f, r, c), cost))791 792 return result793 794 795def astar(start, goal, mode="Normal"):796 open_set = []797 heapq.heappush(open_set, (0, start))798 came = {}799 g = {start: 0}800 801 while open_set:802 _, cur = heapq.heappop(open_set)803 if cur == goal:804 path = []805 while cur in came:806 path.append(cur)807 cur = came[cur]808 path.append(start)809 return path[::-1]810 811 for nxt, cost in neighbors(cur, mode):812 ng = g[cur] + cost813 if nxt not in g or ng < g[nxt]:814 g[nxt] = ng815 heapq.heappush(open_set, (ng + heuristic(nxt, goal), nxt))816 came[nxt] = cur817 return None818 819 820def path_to_instructions(path):821 if not path:822 return "No path found."823 824 instructions = []825 last_floor, last_r, last_c = path[0]826 current_dir = None827 dist = 0828 829 def add_instruction(direction, distance):830 if distance > 0:831 instructions.append(f"Move {direction} {distance * CELL_SIZE:.1f}m.")832 833 for i in range(1, len(path)):834 f, r, c = path[i]835 if f != last_floor:836 add_instruction(current_dir, dist)837 instructions.append(f"Take stairs from floor {last_floor + 3} to floor {f + 3}.")838 current_dir = None839 dist = 0840 last_floor = f841 842 dr = r - last_r843 dc = c - last_c844 if abs(dr) > abs(dc):845 direction = "down" if dr > 0 else "up"846 step = abs(dr)847 else:848 direction = "right" if dc > 0 else "left"849 step = abs(dc)850 851 if direction == current_dir:852 dist += step853 else:854 add_instruction(current_dir, dist)855 current_dir = direction856 dist = step857 858 last_r, last_c = r, c859 860 add_instruction(current_dir, dist)861 instructions.append("You have arrived at your destination room.")862 return ". ".join(instructions)863 864 865def plot_static_path(path):866 fig, axs = plt.subplots(1, 2, figsize=(18, 6))867 grids = [grid_0, grid_1]868 869 for i, grid in enumerate(grids):870 axs[i].imshow(grid, cmap="gray_r", origin="upper")871 stairs_pos = np.where(grid == 2)872 axs[i].scatter(stairs_pos[1], stairs_pos[0], s=40, label="Stairs")873 874 xs = [p[2] for p in path if p[0] == i]875 ys = [p[1] for p in path if p[0] == i]876 if xs and ys:877 axs[i].plot(xs, ys, linewidth=3, label="Path")878 axs[i].scatter(xs[0], ys[0], s=80, label="Start")879 axs[i].scatter(xs[-1], ys[-1], s=80, label="End")880 881 axs[i].set_title(f"Floor {i + 3}")882 axs[i].legend()883 884 img_path = "/content/static_path.png"885 plt.savefig(img_path)886 plt.close()887 return img_path888 889 890def navigate(start_floor, start_xy, dest_room, mode="Normal"):891 if start_xy is None or dest_room is None:892 return None, "β Missing start or destination."893 894 sf = int(start_floor)895 sr, sc = clamp_point(start_xy[0], start_xy[1], grid_0 if sf == 0 else grid_1)896 sr, sc = find_nearest_free(grid_0 if sf == 0 else grid_1, sr, sc)897 898 df = get_floor(dest_room)899 dc = get_centroid(dest_room)900 if dc is None:901 return None, "β Could not find room centroid."902 903 gr, gc = clamp_point(dc[0], dc[1], grid_0 if df == 0 else grid_1)904 gr, gc = find_nearest_free(grid_0 if df == 0 else grid_1, gr, gc)905 906 path = astar((sf, sr, sc), (df, gr, gc), mode)907 instructions = path_to_instructions(path)908 img_path = plot_static_path(path) if path else None909 return img_path, instructions910 911# ============================================================912# CHATBOT LOGIC913# ============================================================914def chatbot_response_fallback(text):915 local = local_chat_reply(text)916 if local is not None:917 return local918 try:919 response = client.chat.completions.create(920 model=OPENROUTER_MODEL,921 messages=[922 {"role": "system", "content": SYSTEM_PROMPT},923 {"role": "user", "content": text},924 ],925 )926 content = response.choices[0].message.content927 if content:928 return content929 except Exception:930 pass931 return "I am here to help with mall navigation and store information. Try a store name like Electronics Store or room 351."932 933 934def extract_rooms(user_text, history):935 lower = (user_text or "").lower().strip()936 room = find_room_in_text(user_text)937 938 if room is not None:939 if any(k in lower for k in ["what is", "tell me", "about", "info", "describe", "where is"]):940 return "chat", None, room, False941 if any(k in lower for k in ["take me", "guide me", "go there", "navigate", "go to", "show me"]):942 return "navigation", None, room, True943 return "navigation", None, room, False944 945 if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]):946 return "navigation", None, None, True947 948 conversation = ""949 for u, b in history[-6:]:950 if u:951 conversation += f"User: {u}\n"952 if b:953 conversation += f"Assistant: {b}\n"954 955 prompt = f"""956You are an AI assistant for indoor navigation inside a shopping mall.957 958Extract navigation intent from the conversation.959 960Return JSON only with:961{{962 "intent": "navigation" or "chat",963 "start_room": null,964 "dest_room": null,965 "confirmed": true or false966}}967 968Conversation:969{conversation}970 971User message:972{user_text}973"""974 975 try:976 response = client.chat.completions.create(977 model=OPENROUTER_MODEL,978 messages=[{"role": "user", "content": prompt}],979 response_format={"type": "json_object"},980 )981 data = json.loads(response.choices[0].message.content)982 intent = data.get("intent", "chat")983 start_room = data.get("start_room")984 dest_room = data.get("dest_room")985 confirmed = data.get("confirmed", False)986 if start_room:987 start_room = int(start_room)988 if dest_room:989 dest_room = int(dest_room)990 return intent, start_room, dest_room, confirmed991 except Exception:992 return "chat", None, None, False993 994 995def chatbot_respond(user_text, history, session):996 session = set_session_defaults(session)997 history = history or []998 if not history:999 history.append(("", get_intro_message()))1000 1001 lower = (user_text or "").lower().strip()1002 local = local_chat_reply(user_text)1003 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"]):1004 history.append((user_text, local))1005 try:1006 gTTS(local).save("/content/voice.mp3")1007 except Exception:1008 pass1009 return history, None, "/content/voice.mp3", session1010 1011 intent, _, dest_room, confirmed = extract_rooms(user_text, history)1012 1013 info_room = find_room_in_text(user_text)1014 if info_room is not None and any(k in lower for k in ["what", "tell", "about", "info", "describe", "where"]):1015 info = get_store_info(info_room)1016 if info:1017 bot = f"{info}\n\nWould you like navigation to {ROOM_INFO[info_room]} (Room {info_room})?"1018 session["last_referenced_room"] = info_room1019 session["last_dest_room"] = info_room1020 history.append((user_text, bot))1021 try:1022 gTTS(bot).save("/content/voice.mp3")1023 except Exception:1024 pass1025 return history, None, "/content/voice.mp3", session1026 1027 if intent == "navigation" and dest_room is not None:1028 store_name = ROOM_INFO.get(dest_room, f"Room {dest_room}")1029 if not confirmed:1030 bot = f"I can guide you to {store_name} (Room {dest_room}). Do you want navigation?"1031 history.append((user_text, bot))1032 session["last_referenced_room"] = dest_room1033 session["last_dest_room"] = dest_room1034 session["nav_target_room"] = dest_room1035 try:1036 gTTS(bot).save("/content/voice.mp3")1037 except Exception:1038 pass1039 return history, None, "/content/voice.mp3", session1040 1041 if session.get("start_floor") is None or session.get("start_xy") is None:1042 bot = "Please set your starting location first in the Shop & Navigate tab."1043 history.append((user_text, bot))1044 try:1045 gTTS(bot).save("/content/voice.mp3")1046 except Exception:1047 pass1048 return history, None, "/content/voice.mp3", session1049 1050 img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest_room, session.get("nav_mode", "Normal"))1051 save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest_room)1052 save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest_room)1053 1054 bot = f"πΊοΈ Navigating to {store_name} (Room {dest_room})\n\n{instructions}"1055 history.append((user_text, bot))1056 session["last_dest_room"] = dest_room1057 session["nav_target_room"] = dest_room1058 try:1059 gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3")1060 except Exception:1061 pass1062 return history, img_path, "/content/voice.mp3", session1063 1064 if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]):1065 dest = (1066 session.get("last_dest_room")1067 or session.get("nav_target_room")1068 or session.get("last_referenced_room")1069 or session.get("recommended_room")1070 )1071 if dest is not None:1072 store_name = ROOM_INFO.get(dest, f"Room {dest}")1073 if session.get("start_floor") is None or session.get("start_xy") is None:1074 bot = f"I can take you to {store_name}, but I need your starting location first in the Shop & Navigate tab."1075 history.append((user_text, bot))1076 try:1077 gTTS(bot).save("/content/voice.mp3")1078 except Exception:1079 pass1080 return history, None, "/content/voice.mp3", session1081 1082 img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest, session.get("nav_mode", "Normal"))1083 save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest)1084 save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest)1085 1086 bot = f"πΊοΈ Navigating to {store_name} (Room {dest})\n\n{instructions}"1087 history.append((user_text, bot))1088 session["last_dest_room"] = dest1089 session["nav_target_room"] = dest1090 try:1091 gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3")1092 except Exception:1093 pass1094 return history, img_path, "/content/voice.mp3", session1095 1096 bot = chatbot_response_fallback(user_text)1097 history.append((user_text, bot))1098 try:1099 gTTS(bot).save("/content/voice.mp3")1100 except Exception:1101 pass1102 return history, None, "/content/voice.mp3", session1103 1104 1105def speech_to_text(audio):1106 if audio is None:1107 return ""1108 try:1109 sound = AudioSegment.from_file(audio)1110 wav = "/content/temp.wav"1111 sound.export(wav, format="wav")1112 r = sr.Recognizer()1113 with sr.AudioFile(wav) as src:1114 data = r.record(src)1115 return r.recognize_google(data)1116 except Exception:1117 return ""1118 1119# ============================================================1120# UI HELPERS1121# ============================================================1122def format_products_html(promos, store_name, room):1123 if not promos:1124 return "<div style='color:#ff6b6b;padding:20px;text-align:center;font-family:monospace'>β No products found in your budget range.</div>"1125 1126 cards = ""1127 for p in promos:1128 stars = "β" * int(round(float(p.get("Rating", 0))))1129 cards += f"""1130 <div style="background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid #00d4ff33;1131 border-radius:12px;padding:16px;margin-bottom:12px;">1132 <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;">1133 <div style="flex:1;">1134 <div style="font-size:11px;color:#888;font-family:monospace;margin-bottom:4px">{p.get('Brand','N/A')}</div>1135 <div style="color:#e0e0e0;font-size:13px;font-family:monospace;line-height:1.4">{p.get('Name','Unknown')}</div>1136 <div style="margin-top:8px;font-size:12px;color:#ffd700">{stars} {p.get('Rating',0)} ({int(p.get('Reviews',0)):,} reviews)</div>1137 </div>1138 <div style="text-align:right;flex-shrink:0;">1139 <div style="color:#ff6b6b;font-size:12px;text-decoration:line-through;font-family:monospace">{p.get('Original','0')} EGP</div>1140 <div style="color:#00ff88;font-size:16px;font-weight:bold;font-family:monospace">{p.get('Discounted','0')} EGP</div>1141 <div style="background:#ff4500;color:white;border-radius:20px;padding:3px 10px;1142 font-size:11px;font-weight:bold;margin-top:4px;font-family:monospace">{p.get('Discount',0)}% OFF π₯</div>1143 </div>1144 </div>1145 </div>"""1146 1147 return f"""1148 <div style="font-family:monospace;max-height:500px;overflow-y:auto;padding:4px;">1149 <div style="color:#00d4ff;font-size:14px;font-weight:bold;margin-bottom:12px;1150 padding-bottom:8px;border-bottom:1px solid #00d4ff33;">1151 πͺ {store_name} Β· Room {room}1152 </div>1153 {cards}1154 </div>"""1155 1156 1157def set_location_ui(x, y, floor_choice, session):1158 session = set_session_defaults(session)1159 if x is None or y is None:1160 return "<div style='color:#ff6b6b;font-family:monospace'>β Please enter coordinates.</div>", session1161 session["start_floor"] = 0 if str(floor_choice) == "3" else 11162 session["start_xy"] = (float(x), float(y))1163 session["start_room"] = session["start_xy"]1164 return f"<div style='color:#00ff88;font-family:monospace'>β
Location saved: floor {int(floor_choice)}, x={x}, y={y}</div>", session1165 1166 1167def recommend_store_for_session(session):1168 session = set_session_defaults(session)1169 if not session.get("username"):1170 return "<div style='color:#ff6b6b;font-family:monospace'>β Please login first.</div>", session1171 if session.get("start_xy") is None or session.get("start_floor") is None:1172 return "<div style='color:#ff6b6b;font-family:monospace'>β Please enter your current location first.</div>", session1173 1174 if session.get("is_new", True):1175 room = nearest_store(session["start_xy"], session["start_floor"])1176 msg = "π New user β Nearest store recommended"1177 else:1178 room = most_visited_store(session["username"])1179 if room is None:1180 room = nearest_store(session["start_xy"], session["start_floor"])1181 msg = "π Returning user β Most visited store recommended"1182 1183 store_name = ROOM_INFO.get(room, f"Room {room}")1184 session["recommended_room"] = room1185 session["nav_target_room"] = room1186 session["last_referenced_room"] = room1187 session["last_dest_room"] = room1188 1189 html = f"""1190 <div style="background:linear-gradient(135deg,#1a1a2e,#0d2137);border:1px solid #00d4ff55;1191 border-radius:12px;padding:20px;font-family:monospace;">1192 <div style="color:#888;font-size:11px;letter-spacing:2px">{msg}</div>1193 <div style="color:#00d4ff;font-size:22px;font-weight:bold;margin:8px 0">{store_name}</div>1194 <div style="color:#555;font-size:12px">Room {room}</div>1195 </div>"""1196 return html, session1197 1198 1199def update_cat(type_name):1200 return gr.update(choices=get_categories_for_type(type_name), value=None)