CoolFace
Apppublic

adham-Ashraf/Navigation-System-Recommender-System

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py1550 linesDownload Raw Back to root
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)

Showing the first 1,200 of 1550 lines. Download the file for the rest.