# -*- coding: utf-8 -*- """app.ipynb Automatically generated by Colab. Original file is located at https://colab.research.google.com/drive/1e0kcqimC-jHT2w8MwJMSXxzS-wJh1jYS """ import json import math import os import random import heapq import re import sqlite3 import gradio as gr import numpy as np import matplotlib.pyplot as plt from scipy.ndimage import label, distance_transform_edt from gtts import gTTS from pydub import AudioSegment import speech_recognition as sr import openai CATEGORIES_FILE = "categories.json" PRODUCTS_FILE = "sort_data.json" DB_PATH = "mall.db" # Smart Mall Assistant # Navigation + chatbot + recommender system merged into one Colab-ready script. # ============================================================ # CONFIG # ============================================================ CELL_SIZE = 0.2 MEMORY_FILE = "navigation_memory.json" GRID_0_FILE = "floor_0_grid.npy" GRID_1_FILE = "floor_0_grid.npy" OPENROUTER_KEY = "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" OPENROUTER_MODEL = "tencent/hy3-preview:free" def load_json_file(path, default): try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception: return default categories_data = load_json_file(CATEGORIES_FILE, []) products_data = load_json_file(PRODUCTS_FILE, []) # ============================================================ # STORE / ROOM MAPPING # ============================================================ ROOM_INFO = { 350: "Smart Devices Hub", 351: "Gaming & Accessories Hub", 352: "Computer Systems Hub", 353: "Mobile & Tablets Hub", 354: "Health & Personal Care", 355: "Bedroom", 356: "Living Room", 357: "Study/Office", 358: "Kitchen", 450: "Dining Room", 451: "Bathroom", 452: "Furnishings", 453: "Kitchen and Dining", 454: "Home Decor", 455: "Tools and utility", 456: "Lighting and Electricals", 457: "Cleaning and Bath", 458: "Pet and Gardening", } STORE_CLUSTER_ROOM = { "Smart Devices Hub": 350, "Gaming & Accessories Hub": 351, "Computer Systems Hub": 352, "Mobile & Tablets Hub": 353, "Health & Personal Care": 354, "Bedroom": 355, "Living Room": 356, "Study/Office": 357, "Kitchen": 358, "Dining Room": 450, "Bathroom": 451, "Furnishings": 452, "Kitchen and Dining": 453, "Home Decor": 454, "Tools and utility": 455, "Lighting and Electricals": 456, "Cleaning and Bath": 457, "Pet and Gardening": 458, } STORE_CLUSTER = { "Audio": "Smart Devices Hub", "Cameras": "Smart Devices Hub", "Smart Home Automation": "Smart Devices Hub", "Smart Wearables": "Smart Devices Hub", "Gaming": "Gaming & Accessories Hub", "Laptop Accessories": "Gaming & Accessories Hub", "Computer Peripherals": "Computer Systems Hub", "Laptop & Desktop": "Computer Systems Hub", "Storage": "Computer Systems Hub", "Mobiles": "Mobile & Tablets Hub", "Tablets": "Mobile & Tablets Hub", } SUB_TO_CATEGORY = {} for type_block in categories_data: for cat in type_block.get("categories", []): for sub in cat.get("subCategories", []): SUB_TO_CATEGORY[sub] = cat["category"] NAME_TO_ROOM = {name.lower(): room for room, name in ROOM_INFO.items()} # ============================================================ # GRID / CENTROIDS # ============================================================ grid_0 = np.load(GRID_0_FILE) grid_1 = np.load(GRID_1_FILE) GRID_SHAPE = grid_0.shape room_centroids_f3_grid = { 350: (70, 465), 351: (70, 435), 352: (70, 395), 353: (70, 360), 354: (70, 320), 355: (70, 285), 356: (70, 215), 357: (70, 160), 358: (70, 85), } room_numbers_f4 = list(range(450, 459)) room_centroids_f4_grid = {} for old, new in zip(room_centroids_f3_grid.keys(), room_numbers_f4): room_centroids_f4_grid[new] = room_centroids_f3_grid[old] def apply_room_clearance(): for r, c in room_centroids_f3_grid.values(): for dr in [-1, 0, 1]: for dc in [-1, 0, 1]: rr, cc = r + dr, c + dc if 0 <= rr < GRID_SHAPE[0] and 0 <= cc < GRID_SHAPE[1]: grid_0[rr, cc] = 0 grid_1[rr, cc] = 0 apply_room_clearance() def reset_grids(): global grid_0, grid_1, GRID_SHAPE grid_0 = np.load(GRID_0_FILE) grid_1 = np.load(GRID_1_FILE) GRID_SHAPE = grid_0.shape apply_room_clearance() # ============================================================ # MEMORY LOG # ============================================================ def load_memory(): try: with open(MEMORY_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: return [] def save_memory(memory): with open(MEMORY_FILE, "w", encoding="utf-8") as f: json.dump(memory, f, indent=4) user_memory = load_memory() navigation_counter = len(user_memory) + 1 def save_navigation_mem(start_room, dest_room): global navigation_counter entry = { "nav_id": f"N{navigation_counter}", "start_room": start_room, "dest_room": dest_room, } user_memory.append(entry) save_memory(user_memory) navigation_counter += 1 return entry # ============================================================ # OPENROUTER CLIENT # ============================================================ client = openai.OpenAI( base_url="https://openrouter.ai/api/v1", api_key=OPENROUTER_KEY, ) SYSTEM_PROMPT = """ You are an AI assistant for indoor navigation inside a shopping mall. Mall stores: 350 Smart Devices Hub 351 Gaming & Accessories Hub 352 Computer Systems Hub 353 Mobile & Tablets Hub 354 Health & Personal Care 355 Bedroom 356 Living Room 357 Study/Office 358 Kitchen 450 Dining Room 451 Bathroom 452 Furnishings 453 Kitchen and Dining 454 Home Decor 455 Tools and utility 456 Lighting and Electricals 457 Cleaning and Bath 458 Pet and Gardening Rules: - If the user asks about a store, answer with store information. - If the user asks for navigation, help them navigate. - 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. - Keep answers concise and helpful. """ # ============================================================ # HELPERS / SESSION STATE # ============================================================ def set_session_defaults(session): session = session or {} defaults = { "username": None, "gender": "other", "low": 500, "high": 5000, "is_new": True, "start_floor": None, "start_xy": None, "nav_mode": "Normal", "recommended_room": None, "nav_target_room": None, "last_dest_room": None, "last_referenced_room": None, "selected_subcategory": None, "accepted_store": False, } for k, v in defaults.items(): session.setdefault(k, v) return session def get_floor(room): if 350 <= room <= 358: return 0 if 450 <= room <= 458: return 1 return 0 def get_centroid(room): if room in room_centroids_f3_grid: return room_centroids_f3_grid[room] if room in room_centroids_f4_grid: return room_centroids_f4_grid[room] return None def clamp_point(x, y, grid): r = int(max(0, min(grid.shape[0] - 1, round(float(x))))) c = int(max(0, min(grid.shape[1] - 1, round(float(y))))) return r, c def get_store_info(room): info = { 350: "Smart devices, wearables, smart home products.", 351: "Gaming items, accessories, and related hardware.", 352: "Computers, laptops, storage, and peripherals.", 353: "Mobile phones, tablets, and accessories.", 354: "Health products and personal care items.", 355: "Bedroom furniture and decor.", 356: "Living room furniture and decor.", 357: "Office and study furniture and tools.", 358: "Kitchen furniture and kitchen essentials.", 450: "Dining room furniture and dining essentials.", 451: "Bathroom items and accessories.", 452: "Furnishings and home textiles.", 453: "Kitchen and dining accessories.", 454: "Home decor products.", 455: "Tools and utility items.", 456: "Lighting and electrical products.", 457: "Cleaning and bath products.", 458: "Pet and gardening products.", } name = ROOM_INFO.get(room) if not name: return None return f"πͺ {name} (Room {room})\nπ {info.get(room, 'Mall store information is available for this store.')}" def find_room_in_text(text): text_l = (text or "").lower() room_match = re.search(r"\b(3\d{2}|45[0-8])\b", text_l) if room_match: room = int(room_match.group(1)) if room in ROOM_INFO: return room for name, room in sorted(NAME_TO_ROOM.items(), key=lambda x: len(x[0]), reverse=True): if name in text_l: return room return None def get_intro_message(): return ( "Hello π Welcome to the Mall Navigation Assistant.\n" "You can navigate to:\n" + "\n".join([f"- {name} ({room})" for room, name in ROOM_INFO.items()]) + "\nIf you're hungry, you can go to the kitchen/dining or cafeteria-related stores." ) def local_chat_reply(user_text): low = (user_text or "").lower().strip() if not low: return "Please type a message or ask about a store." if any(word in low for word in ["hi", "hello", "hey", "good morning", "good evening", "how are you"]): return "Hello π Welcome to SmartMall. Tell me a store name, a room number, or where you want to go." if any(word in low for word in ["thanks", "thank you"]): return "You are welcome." room = find_room_in_text(user_text) if room is not None and any(k in low for k in ["what", "tell", "about", "info", "describe", "where"]): info = get_store_info(room) if info: return info return None # ============================================================ # AUTH / DATABASE OPERATIONS # ============================================================ def save_preference(username, preference): if not preference or len(preference) != 3: return main, cat, sub = preference cursor.execute( """INSERT INTO user_preferences (username, main_category, category, subcategory) VALUES (?, ?, ?, ?)""", (username, main, cat, sub), ) conn.commit() def entry_point(): print("\n=== Smart Mall System ===") while True: print("\n1) Signup") print("2) Login") choice = input("Enter choice: ").strip() if choice == "1": return "signup" if choice == "2": return "login" print("Invalid input β") def signup(): print("\n=== SIGNUP ===") while True: username = input("Choose username: ").strip() cursor.execute("SELECT username FROM users WHERE username=?", (username,)) if cursor.fetchone(): print("β Username exists, try again\n") else: break password = input("Choose password: ").strip() name = input("Enter name: ").strip() age = int(input("Enter age: ")) gender = input("Enter gender (male/female): ").strip().lower() auto = input("Use auto budget? (yes/no): ").strip().lower() if auto == "no": lower = float(input("Lower budget: ")) upper = float(input("Upper budget: ")) else: lower, upper = 500, 5000 cursor.execute( "INSERT INTO users VALUES (?, ?, ?, ?, ?, ?, ?)", (username, password, name, age, gender, lower, upper), ) conn.commit() print("\nβ Signup successful") return username, gender, lower, upper, None def login(): print("\n=== LOGIN ===") while True: username = input("Username: ").strip() password = input("Password: ").strip() cursor.execute( "SELECT * FROM users WHERE username=? AND password=?", (username, password), ) user = cursor.fetchone() if user: print("β Login successful") break print("β Invalid credentials, try again\n") cursor.execute( """SELECT main_category, category, subcategory FROM user_preferences WHERE username=? ORDER BY id DESC LIMIT 1""", (username,), ) pref = cursor.fetchone() preference = pref if pref else None return username, user[4], user[5], user[6], preference def auth_system(): while True: mode = entry_point() if mode == "signup": user_data = signup() if user_data: return user_data, True elif mode == "login": user_data = login() if user_data: return user_data, False print("β Authentication failed, try again...\n") def get_ble_location(): print("\nπ Enter your current location") x = float(input("X coordinate: ")) y = float(input("Y coordinate: ")) return (x, y) def save_navigation(username, start_room, dest_room): if isinstance(start_room, tuple): start_room = f"{start_room[0]},{start_room[1]}" cursor.execute( "INSERT INTO history (username, start_point, end_store) VALUES (?, ?, ?)", (username, start_room, str(dest_room)), ) conn.commit() # ============================================================ # STORE RECOMMENDATION # ============================================================ def nearest_store(user_pos, user_floor): best_room = None best_dist = float("inf") for _, room in STORE_CLUSTER_ROOM.items(): # β ΩΩΨͺΨ±Ψ© ΨΉΩΩ ΩΩΨ³ Ψ§ΩΨ―ΩΨ± if get_floor(room) != user_floor: continue centroid = get_centroid(room) if centroid is None: continue dist = np.sqrt((user_pos[0] - centroid[0]) ** 2 + (user_pos[1] - centroid[1]) ** 2) if dist < best_dist: best_dist = dist best_room = room return best_room def most_visited_store(username): cursor.execute( """SELECT end_store, COUNT(*) as cnt FROM history WHERE username=? GROUP BY end_store ORDER BY cnt DESC""", (username,), ) result = cursor.fetchone() return int(result[0]) if result else None # ============================================================ # PRODUCT RECOMMENDER # ============================================================ def clean_price(price_str): digits = "".join(filter(str.isdigit, str(price_str))) return int(digits) if digits else 0 def get_categories_for_type(type_name): for t in categories_data: if t.get("type") == type_name: return [c.get("category") for c in t.get("categories", [])] return [] def get_subcategories(type_name, category_name): for t in categories_data: if t.get("type") == type_name: for c in t.get("categories", []): if c.get("category") == category_name: return c.get("subCategories", []) return [] def resolve_store_from_category(parent_category): store_name = STORE_CLUSTER.get(parent_category, parent_category) room = STORE_CLUSTER_ROOM.get(store_name) return store_name, room def category_flow(): print("\nπ§ Category Selection") print("\nMain categories:") for i, c in enumerate(categories_data): print(i, c["type"]) main = int(input("Select main category: ")) main_cat = categories_data[main] print("\nSub categories:") for i, c in enumerate(main_cat["categories"]): print(i, c["category"]) sub = int(input("Select category: ")) sub_cat = main_cat["categories"][sub] print("\nSub-sub categories:") for i, c in enumerate(sub_cat["subCategories"]): print(i, c) sub_sub = int(input("Select subcategory: ")) selected_subcategory = sub_cat["subCategories"][sub_sub] if selected_subcategory is None: print("β No subcategory selected") return None, None, None, None main_category = main_cat["type"] parent_category = sub_cat["category"] _, end_store = resolve_store_from_category(parent_category) return selected_subcategory, end_store, parent_category, main_category def get_budget(default_low, default_high): print("\nπ° Budget Selection") choice = input("Use auto budget? (yes/no): ").strip().lower() if choice == "no": lower = float(input("Enter lower budget: ")) upper = float(input("Enter upper budget: ")) else: lower, upper = default_low, default_high return lower, upper def get_products(subcategory): if not subcategory: return [] result = [] subcategory = str(subcategory).strip().lower() for type_block in products_data: for cat in type_block.get("items", []): cat_name = cat.get("category") for sub in cat.get("items", []): sub_name = sub.get("subCategory") if cat_name and cat_name.lower() == subcategory: result.extend(sub.get("items", [])) elif sub_name and sub_name.lower() == subcategory: result.extend(sub.get("items", [])) return result def filter_by_budget(products, low, high): filtered = [] for p in products: price = clean_price(p.get("Price", 0)) if low <= price <= high: item = dict(p) item["price_int"] = price filtered.append(item) return filtered def gender_filter(products, gender): if gender not in ["male", "female"]: return products keywords = { "male": ["gaming", "tool", "power", "sports", "fitness"], "female": ["beauty", "hair", "skin", "cosmetic", "makeup", "fashion"], } preferred, others = [], [] for p in products: name = str(p.get("Name", "")).lower() if any(k in name for k in keywords[gender]): preferred.append(p) else: others.append(p) return preferred + others def add_promotions(products): result = [] for p in products: discount = random.randint(10, 50) original = clean_price(p.get("Price", 0)) discounted = int(original * (1 - discount / 100)) result.append({ "Name": p.get("Name", "Unknown Product"), "Brand": p.get("Brand", "N/A"), "Original": f"{original}", "Discounted": f"{discounted}", "Discount": discount, "Rating": p.get("Rating", 0), "Reviews": p.get("No of Reviews", 0), }) return result def recommend_for_user(selected_subcategory, gender, low_budget, high_budget): products = get_products(selected_subcategory) if not products: return {"store": "Unknown", "room": None, "products": []} products = filter_by_budget(products, low_budget, high_budget) if not products: products = get_products(selected_subcategory) products = gender_filter(products, gender) products = sorted(products, key=lambda x: x.get("Rating", 0), reverse=True) top_products = add_promotions(products[:5]) parent_category = SUB_TO_CATEGORY.get(selected_subcategory) store_name = STORE_CLUSTER.get(parent_category, parent_category) if parent_category else None room = STORE_CLUSTER_ROOM.get(store_name) if store_name else None return {"store": store_name or "Unknown", "room": room, "products": top_products} # ============================================================ # NAVIGATION / A* # ============================================================ def find_nearest_free(grid, r, c): free = (grid == 0) _, ind = distance_transform_edt(~free, return_indices=True) nr, nc = ind[:, r, c] return int(nr), int(nc) def extract_stairs(grid): binary = (grid == 2).astype(int) labeled, num = label(binary) return labeled, num stairs_0, n0 = extract_stairs(grid_0) stairs_1, n1 = extract_stairs(grid_1) stairs = {} stair_cells = {0: {}, 1: {}} for i in range(1, min(n0, n1) + 1): sid = f"S{i}" stairs[sid] = {"floors": [0, 1]} for r, c in np.argwhere(stairs_0 == i): stair_cells[0][(int(r), int(c))] = sid for r, c in np.argwhere(stairs_1 == i): stair_cells[1][(int(r), int(c))] = sid elevator_cells = {0: set(), 1: set()} for f in [0, 1]: grid = grid_0 if f == 0 else grid_1 coords = np.argwhere(grid == 3) for r, c in coords: elevator_cells[f].add((int(r), int(c))) fire_active = False fire_room = None fire_step = 0 fire_center = None crowded_active = False crowded_room = None crowded_center = None def mark_danger(grid, centroid, radius=20): r0, c0 = centroid for i in range(grid.shape[0]): for j in range(grid.shape[1]): dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2) if dist < radius: grid[i, j] = 1 def mark_crowd(grid, centroid, room_radius=15, corridor_radius=40): r0, c0 = centroid for i in range(grid.shape[0]): for j in range(grid.shape[1]): dist = np.sqrt((i - r0) ** 2 + (j - c0) ** 2) if dist < room_radius: grid[i, j] = 5 elif dist < corridor_radius and grid[i, j] == 0: grid[i, j] = 6 def heuristic(a, b): return abs(a[1] - b[1]) + abs(a[2] - b[2]) + abs(a[0] - b[0]) * 10 def neighbors(node, mode): floor, r, c = node grid = grid_0 if floor == 0 else grid_1 moves = [(-1, 0), (1, 0), (0, -1), (0, 1)] result = [] for dr, dc in moves: nr = r + dr nc = c + dc if 0 <= nr < grid.shape[0] and 0 <= nc < grid.shape[1]: cell = grid[nr, nc] if cell == 1: continue cost = 1 if fire_active and fire_center is not None: fr, fc = fire_center dist = np.sqrt((nr - fr) ** 2 + (nc - fc) ** 2) if dist < 120: cost += (120 - dist) * 0.3 if cell == 4: cost += 8 if mode == "Crowded": if cell == 5: cost += 200 elif cell == 6: cost += 40 if mode == "Special Needs" and cell == 4: cost += 100 result.append(((floor, int(nr), int(nc)), cost)) is_stair = (r, c) in stair_cells[floor] is_elevator = (r, c) in elevator_cells[floor] if mode == "Special Needs": if is_elevator: for f in [0, 1]: if f != floor: result.append(((f, r, c), 10)) else: if is_stair: sid = stair_cells[floor][(r, c)] cost = 15 if mode != "Crowded" else 40 if fire_active and fire_center is not None: fr, fc = fire_center dist = np.sqrt((r - fr) ** 2 + (c - fc) ** 2) if dist < 150: cost += 200 for f in stairs[sid]["floors"]: if f != floor: result.append(((f, r, c), cost)) return result def astar(start, goal, mode="Normal"): open_set = [] heapq.heappush(open_set, (0, start)) came = {} g = {start: 0} while open_set: _, cur = heapq.heappop(open_set) if cur == goal: path = [] while cur in came: path.append(cur) cur = came[cur] path.append(start) return path[::-1] for nxt, cost in neighbors(cur, mode): ng = g[cur] + cost if nxt not in g or ng < g[nxt]: g[nxt] = ng heapq.heappush(open_set, (ng + heuristic(nxt, goal), nxt)) came[nxt] = cur return None def path_to_instructions(path): if not path: return "No path found." instructions = [] last_floor, last_r, last_c = path[0] current_dir = None dist = 0 def add_instruction(direction, distance): if distance > 0: instructions.append(f"Move {direction} {distance * CELL_SIZE:.1f}m.") for i in range(1, len(path)): f, r, c = path[i] if f != last_floor: add_instruction(current_dir, dist) instructions.append(f"Take stairs from floor {last_floor + 3} to floor {f + 3}.") current_dir = None dist = 0 last_floor = f dr = r - last_r dc = c - last_c if abs(dr) > abs(dc): direction = "down" if dr > 0 else "up" step = abs(dr) else: direction = "right" if dc > 0 else "left" step = abs(dc) if direction == current_dir: dist += step else: add_instruction(current_dir, dist) current_dir = direction dist = step last_r, last_c = r, c add_instruction(current_dir, dist) instructions.append("You have arrived at your destination room.") return ". ".join(instructions) def plot_static_path(path): fig, axs = plt.subplots(1, 2, figsize=(18, 6)) grids = [grid_0, grid_1] for i, grid in enumerate(grids): axs[i].imshow(grid, cmap="gray_r", origin="upper") stairs_pos = np.where(grid == 2) axs[i].scatter(stairs_pos[1], stairs_pos[0], s=40, label="Stairs") xs = [p[2] for p in path if p[0] == i] ys = [p[1] for p in path if p[0] == i] if xs and ys: axs[i].plot(xs, ys, linewidth=3, label="Path") axs[i].scatter(xs[0], ys[0], s=80, label="Start") axs[i].scatter(xs[-1], ys[-1], s=80, label="End") axs[i].set_title(f"Floor {i + 3}") axs[i].legend() img_path = "/content/static_path.png" plt.savefig(img_path) plt.close() return img_path def navigate(start_floor, start_xy, dest_room, mode="Normal"): if start_xy is None or dest_room is None: return None, "β Missing start or destination." sf = int(start_floor) sr, sc = clamp_point(start_xy[0], start_xy[1], grid_0 if sf == 0 else grid_1) sr, sc = find_nearest_free(grid_0 if sf == 0 else grid_1, sr, sc) df = get_floor(dest_room) dc = get_centroid(dest_room) if dc is None: return None, "β Could not find room centroid." gr, gc = clamp_point(dc[0], dc[1], grid_0 if df == 0 else grid_1) gr, gc = find_nearest_free(grid_0 if df == 0 else grid_1, gr, gc) path = astar((sf, sr, sc), (df, gr, gc), mode) instructions = path_to_instructions(path) img_path = plot_static_path(path) if path else None return img_path, instructions # ============================================================ # CHATBOT LOGIC # ============================================================ def chatbot_response_fallback(text): local = local_chat_reply(text) if local is not None: return local try: response = client.chat.completions.create( model=OPENROUTER_MODEL, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": text}, ], ) content = response.choices[0].message.content if content: return content except Exception: pass return "I am here to help with mall navigation and store information. Try a store name like Electronics Store or room 351." def extract_rooms(user_text, history): lower = (user_text or "").lower().strip() room = find_room_in_text(user_text) if room is not None: if any(k in lower for k in ["what is", "tell me", "about", "info", "describe", "where is"]): return "chat", None, room, False if any(k in lower for k in ["take me", "guide me", "go there", "navigate", "go to", "show me"]): return "navigation", None, room, True return "navigation", None, room, False if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]): return "navigation", None, None, True conversation = "" for u, b in history[-6:]: if u: conversation += f"User: {u}\n" if b: conversation += f"Assistant: {b}\n" prompt = f""" You are an AI assistant for indoor navigation inside a shopping mall. Extract navigation intent from the conversation. Return JSON only with: {{ "intent": "navigation" or "chat", "start_room": null, "dest_room": null, "confirmed": true or false }} Conversation: {conversation} User message: {user_text} """ try: response = client.chat.completions.create( model=OPENROUTER_MODEL, messages=[{"role": "user", "content": prompt}], response_format={"type": "json_object"}, ) data = json.loads(response.choices[0].message.content) intent = data.get("intent", "chat") start_room = data.get("start_room") dest_room = data.get("dest_room") confirmed = data.get("confirmed", False) if start_room: start_room = int(start_room) if dest_room: dest_room = int(dest_room) return intent, start_room, dest_room, confirmed except Exception: return "chat", None, None, False def chatbot_respond(user_text, history, session): session = set_session_defaults(session) history = history or [] if not history: history.append(("", get_intro_message())) lower = (user_text or "").lower().strip() local = local_chat_reply(user_text) 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"]): history.append((user_text, local)) try: gTTS(local).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session intent, _, dest_room, confirmed = extract_rooms(user_text, history) info_room = find_room_in_text(user_text) if info_room is not None and any(k in lower for k in ["what", "tell", "about", "info", "describe", "where"]): info = get_store_info(info_room) if info: bot = f"{info}\n\nWould you like navigation to {ROOM_INFO[info_room]} (Room {info_room})?" session["last_referenced_room"] = info_room session["last_dest_room"] = info_room history.append((user_text, bot)) try: gTTS(bot).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session if intent == "navigation" and dest_room is not None: store_name = ROOM_INFO.get(dest_room, f"Room {dest_room}") if not confirmed: bot = f"I can guide you to {store_name} (Room {dest_room}). Do you want navigation?" history.append((user_text, bot)) session["last_referenced_room"] = dest_room session["last_dest_room"] = dest_room session["nav_target_room"] = dest_room try: gTTS(bot).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session if session.get("start_floor") is None or session.get("start_xy") is None: bot = "Please set your starting location first in the Shop & Navigate tab." history.append((user_text, bot)) try: gTTS(bot).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest_room, session.get("nav_mode", "Normal")) save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest_room) save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest_room) bot = f"πΊοΈ Navigating to {store_name} (Room {dest_room})\n\n{instructions}" history.append((user_text, bot)) session["last_dest_room"] = dest_room session["nav_target_room"] = dest_room try: gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3") except Exception: pass return history, img_path, "/content/voice.mp3", session if any(phrase in lower for phrase in ["take me there", "guide me there", "go there", "navigate me there"]): dest = ( session.get("last_dest_room") or session.get("nav_target_room") or session.get("last_referenced_room") or session.get("recommended_room") ) if dest is not None: store_name = ROOM_INFO.get(dest, f"Room {dest}") if session.get("start_floor") is None or session.get("start_xy") is None: bot = f"I can take you to {store_name}, but I need your starting location first in the Shop & Navigate tab." history.append((user_text, bot)) try: gTTS(bot).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session img_path, instructions = navigate(session["start_floor"], session["start_xy"], dest, session.get("nav_mode", "Normal")) save_navigation(session.get("username", "guest"), (session["start_floor"], session["start_xy"]), dest) save_navigation_mem(f"floor={session['start_floor']},x={session['start_xy'][0]},y={session['start_xy'][1]}", dest) bot = f"πΊοΈ Navigating to {store_name} (Room {dest})\n\n{instructions}" history.append((user_text, bot)) session["last_dest_room"] = dest session["nav_target_room"] = dest try: gTTS(f"Navigating to {store_name}. {instructions.replace(chr(10), ' ')}").save("/content/voice.mp3") except Exception: pass return history, img_path, "/content/voice.mp3", session bot = chatbot_response_fallback(user_text) history.append((user_text, bot)) try: gTTS(bot).save("/content/voice.mp3") except Exception: pass return history, None, "/content/voice.mp3", session def speech_to_text(audio): if audio is None: return "" try: sound = AudioSegment.from_file(audio) wav = "/content/temp.wav" sound.export(wav, format="wav") r = sr.Recognizer() with sr.AudioFile(wav) as src: data = r.record(src) return r.recognize_google(data) except Exception: return "" # ============================================================ # UI HELPERS # ============================================================ def format_products_html(promos, store_name, room): if not promos: return "