CoolFace
Apppublic

Dar066/Resturant_Ordering_Agent

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py428 linesDownload Raw Back to root
1import os2import sqlite33import threading4import asyncio5import time6import uvicorn7import gradio as gr8from datetime import datetime9from fastapi import FastAPI, Request, Form10from fastapi.responses import PlainTextResponse11from twilio.twiml.messaging_response import MessagingResponse12from twilio.rest import Client13from langchain_groq import ChatGroq14from langchain_core.tools import Tool15from langchain_core.messages import ToolMessage16 17# ─────────────────────────────────────────18# 1. CONFIGURATION19# ─────────────────────────────────────────20GROQ_API_KEY       = os.environ.get("GROQ_API_KEY")21TWILIO_ACCOUNT_SID = os.environ.get("TWILIO_ACCOUNT_SID")22TWILIO_AUTH_TOKEN  = os.environ.get("TWILIO_AUTH_TOKEN")23TWILIO_WA_NUMBER   = os.environ.get("TWILIO_WA_NUMBER")  # whatsapp:+1415523888624DB_PATH            = "restaurant.db"25 26# ─────────────────────────────────────────27# 2. DATABASE28# ─────────────────────────────────────────29def get_db():30    return sqlite3.connect(DB_PATH, check_same_thread=False)31 32def init_db():33    conn = get_db()34    cur  = conn.cursor()35    cur.execute("""36        CREATE TABLE IF NOT EXISTS menu (37            id    INTEGER PRIMARY KEY AUTOINCREMENT,38            item  TEXT    NOT NULL UNIQUE,39            price REAL    NOT NULL40        )41    """)42    cur.execute("""43        CREATE TABLE IF NOT EXISTS orders (44            id         INTEGER PRIMARY KEY AUTOINCREMENT,45            timestamp  TEXT    NOT NULL,46            customer   TEXT    NOT NULL,47            phone      TEXT,48            item       TEXT    NOT NULL,49            quantity   INTEGER NOT NULL,50            total      REAL    NOT NULL,51            status     TEXT    NOT NULL DEFAULT 'Awaited'52        )53    """)54    # Seed menu only if empty55    if not cur.execute("SELECT 1 FROM menu LIMIT 1").fetchone():56        cur.executemany(57            "INSERT OR IGNORE INTO menu (item, price) VALUES (?, ?)",58            [("Pizza", 800), ("Burger", 500), ("Pasta", 650),59             ("Fries", 200), ("Cola", 100), ("Salad", 350)]60        )61    conn.commit()62    conn.close()63 64init_db()65 66# ─────────────────────────────────────────67# 3. TOOL FUNCTIONS68# ─────────────────────────────────────────69def get_menu(_input=""):70    conn = get_db()71    rows = conn.execute("SELECT item, price FROM menu ORDER BY item").fetchall()72    conn.close()73    if not rows:74        return "Menu is empty."75    return "\n".join([item + ": Rs. " + str(int(price)) for item, price in rows])76 77 78def place_order(order_str):79    try:80        parts    = [p.strip() for p in str(order_str).split(",")]81        customer = parts[0]82        item     = parts[1]83        qty      = int(parts[2])84        phone    = parts[3] if len(parts) > 3 else ""85    except Exception:86        return "Please provide order as: CustomerName, ItemName, Quantity"87    conn = get_db()88    cur  = conn.cursor()89    cur.execute(90        "SELECT item, price FROM menu WHERE LOWER(item)=LOWER(?)", (item,)91    )92    row = cur.fetchone()93    if row is None:94        conn.close()95        return item + " is not on the menu. Type 'menu' to see options."96    canonical = row[0]97    price     = row[1]98    total     = price * qty99    ts        = datetime.now().strftime("%Y-%m-%d %H:%M:%S")100    cur.execute(101        "INSERT INTO orders (timestamp,customer,phone,item,quantity,total,status) "102        "VALUES (?,?,?,?,?,?,'Awaited')",103        (ts, customer, phone, canonical, qty, total)104    )105    conn.commit()106    conn.close()107    return ("Order confirmed! " + customer + " - " +108            canonical + " x" + str(qty) + " = Rs. " + str(int(total)) +109            " [Status: Awaited]")110 111 112def view_orders(_input=""):113    conn = get_db()114    rows = conn.execute(115        "SELECT timestamp, customer, item, quantity, total, status "116        "FROM orders ORDER BY id DESC LIMIT 10"117    ).fetchall()118    conn.close()119    if not rows:120        return "No orders yet."121    lines = ["Last 10 orders:"]122    for ts, cust, item, qty, total, status in rows:123        lines.append(124            "[" + ts + "] " + cust + " - " +125            item + " x" + str(qty) +126            " = Rs." + str(int(total)) +127            " [" + status + "]"128        )129    return "\n".join(lines)130 131 132# ─────────────────────────────────────────133# 4. LANGCHAIN AGENT134# ─────────────────────────────────────────135llm = ChatGroq(136    model="llama-3.3-70b-versatile",137    api_key=GROQ_API_KEY,138    temperature=0139)140 141tools = [142    Tool(name="GetMenu",    func=get_menu,143         description="Shows restaurant menu and prices."),144    Tool(name="PlaceOrder", func=place_order,145         description="Places an order. Input: CustomerName, ItemName, Quantity, PhoneNumber"),146    Tool(name="ViewOrders", func=view_orders,147         description="Shows last 10 orders with status."),148]149 150tool_map        = {t.name: t for t in tools}151llm_with_tools  = llm.bind_tools(tools)152 153def run_agent(user_input, phone=""):154    messages = [155        ("system",156         "You are a helpful restaurant assistant. "157         "Use tools to check the menu or place orders. "158         "When placing an order always include the customer phone as 4th argument: "159         "CustomerName, ItemName, Quantity, " + phone),160        ("user", user_input)161    ]162    response = None163    for _ in range(5):164        response = llm_with_tools.invoke(messages)165        messages.append(response)166        if not response.tool_calls:167            break168        for tc in response.tool_calls:169            args      = tc["args"]170            arg_input = list(args.values())[0] if args else ""171            obs       = tool_map[tc["name"]].func(arg_input)172            messages.append(ToolMessage(content=str(obs), tool_call_id=tc["id"]))173    return response.content if response else "Sorry, I could not process that."174 175 176# ─────────────────────────────────────────177# 5. FASTAPI WEBHOOK178# ─────────────────────────────────────────179import concurrent.futures180executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)181 182wa_app = FastAPI()183 184@wa_app.post("/webhook/whatsapp")185async def whatsapp_webhook(186    request: Request,187    Body: str = Form(default=""),188    From: str = Form(default="")189):190    # Handle Twilio verification ping (empty body)191    if not Body and not From:192        resp = MessagingResponse()193        return PlainTextResponse(str(resp), media_type="text/xml")194 195    sender  = From196    message = Body.strip()197    print("[WA IN]  " + sender + ": " + message)198 199    try:200        loop  = asyncio.get_event_loop()201        phone = sender.replace("whatsapp:", "")202        reply = await loop.run_in_executor(203            executor,204            lambda: run_agent(message, phone)205        )206        if not reply:207            reply = "Sorry, could not process that."208    except Exception as e:209        reply = "Sorry, something went wrong."210        print("[ERROR] " + str(e))211 212    print("[WA OUT] " + sender + ": " + reply)213    resp = MessagingResponse()214    resp.message(reply)215    return PlainTextResponse(str(resp), media_type="text/xml")216 217# ─────────────────────────────────────────218# 6. ORDER STATUS UPDATER219# ─────────────────────────────────────────220def get_all_orders():221    conn = get_db()222    rows = conn.execute(223        "SELECT id, timestamp, customer, phone, item, quantity, total, status "224        "FROM orders ORDER BY id DESC"225    ).fetchall()226    conn.close()227    return rows228 229def update_order_status(order_id, new_status):230    conn = get_db()231    232    # Get customer details BEFORE updating233    row = conn.execute(234        "SELECT customer, phone, item, quantity FROM orders WHERE id=?",235        (order_id,)236    ).fetchone()237    238    if not row:239        conn.close()240        print("[NOTIFY] Order " + str(order_id) + " not found.")241        return242    243    customer, phone, item, qty = row244    245    # Now update status246    conn.execute(247        "UPDATE orders SET status=? WHERE id=?",248        (new_status, order_id)249    )250    conn.commit()251    conn.close()252    253    print("[STATUS UPDATE] Order " + str(order_id) + " -> " + new_status)254 255    # Send WhatsApp notification256    if phone and TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN:257        try:258            client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)259            msg = (260                "Hi " + customer + "! Update on your order:\n"261                + item + " x" + str(qty) + "\n"262                + "New status: *" + new_status + "*"263            )264            result = client.messages.create(265                body=msg,266                from_=TWILIO_WA_NUMBER,267                to="whatsapp:+" + phone.lstrip("+")268            )269            print("[NOTIFY SENT] SID: " + result.sid + " to " + phone)270        except Exception as e:271            print("[NOTIFY ERROR] " + str(e))272    else:273        print("[NOTIFY SKIP] Missing phone or Twilio credentials.")274 275# ─────────────────────────────────────────276# 7. GRADIO DASHBOARD277# ─────────────────────────────────────────278def get_orders_df():279    rows = get_all_orders()280    if not rows:281        return []282    return [list(r) for r in rows]283 284def refresh_orders():285    return get_orders_df()286 287def update_status(order_id, new_status):288    if not order_id:289        return "Please enter an order ID.", get_orders_df()290    try:291        update_order_status(int(order_id), new_status)292        return (293            "Order " + str(order_id) + " updated to: " + new_status +294            " — WhatsApp notification sent.",295            get_orders_df()296        )297    except Exception as e:298        return "Error: " + str(e), get_orders_df()299 300def add_menu_item(item_name, price):301    if not item_name or not price:302        return "Please enter item name and price.", get_menu_df()303    try:304        conn = get_db()305        conn.execute(306            "INSERT OR IGNORE INTO menu (item, price) VALUES (?, ?)",307            (item_name.strip(), float(price))308        )309        conn.commit()310        conn.close()311        return item_name + " added at Rs. " + str(price), get_menu_df()312    except Exception as e:313        return "Error: " + str(e), get_menu_df()314 315def remove_menu_item(item_name):316    if not item_name:317        return "Please enter item name.", get_menu_df()318    try:319        conn = get_db()320        conn.execute("DELETE FROM menu WHERE LOWER(item)=LOWER(?)", (item_name.strip(),))321        conn.commit()322        conn.close()323        return item_name + " removed.", get_menu_df()324    except Exception as e:325        return "Error: " + str(e), get_menu_df()326 327def get_menu_df():328    conn = get_db()329    rows = conn.execute("SELECT id, item, price FROM menu ORDER BY item").fetchall()330    conn.close()331    return [list(r) for r in rows]332 333 334with gr.Blocks(title="RestaurantBot Dashboard") as dashboard:335 336    gr.Markdown("# RestaurantBot Staff Dashboard")337 338    with gr.Tab("Live Orders"):339        gr.Markdown("### All Orders")340        orders_table = gr.Dataframe(341            headers=["ID", "Time", "Customer", "Phone", "Item", "Qty", "Total", "Status"],342            value=get_orders_df(),343            interactive=False,344            wrap=True345        )346        refresh_btn = gr.Button("Refresh Orders")347        refresh_btn.click(fn=refresh_orders, outputs=orders_table)348 349        gr.Markdown("### Update Order Status")350        with gr.Row():351            order_id_input = gr.Textbox(label="Order ID", placeholder="e.g. 3")352            status_dropdown = gr.Dropdown(353                choices=["Awaited", "Preparing", "Ready", "Delivered", "Cancelled"],354                label="New Status",355                value="Ready"356            )357        update_btn    = gr.Button("Update Status", variant="primary")358        update_result = gr.Textbox(label="Result", interactive=False)359        update_btn.click(360            fn=update_status,361            inputs=[order_id_input, status_dropdown],362            outputs=[update_result, orders_table]363        )364 365    with gr.Tab("Menu Management"):366        gr.Markdown("### Current Menu")367        menu_table = gr.Dataframe(368            headers=["ID", "Item", "Price (Rs.)"],369            value=get_menu_df(),370            interactive=False371        )372 373        gr.Markdown("### Add Item")374        with gr.Row():375            new_item_name  = gr.Textbox(label="Item Name", placeholder="e.g. Shawarma")376            new_item_price = gr.Textbox(label="Price (Rs.)", placeholder="e.g. 450")377        add_btn    = gr.Button("Add Item", variant="primary")378        add_result = gr.Textbox(label="Result", interactive=False)379        add_btn.click(380            fn=add_menu_item,381            inputs=[new_item_name, new_item_price],382            outputs=[add_result, menu_table]383        )384 385        gr.Markdown("### Remove Item")386        remove_item_name = gr.Textbox(label="Item Name to Remove", placeholder="e.g. Salad")387        remove_btn       = gr.Button("Remove Item", variant="stop")388        remove_result    = gr.Textbox(label="Result", interactive=False)389        remove_btn.click(390            fn=remove_menu_item,391            inputs=[remove_item_name],392            outputs=[remove_result, menu_table]393        )394 395    with gr.Tab("Test Agent"):396        gr.Markdown("### Test the bot directly")397        test_input  = gr.Textbox(label="Your message", placeholder="What is on the menu?")398        test_output = gr.Textbox(label="Bot reply", interactive=False)399        test_btn    = gr.Button("Send", variant="primary")400        test_btn.click(401            fn=lambda msg: run_agent(msg),402            inputs=test_input,403            outputs=test_output404        )405 406# ─────────────────────────────────────────407# 8. MOUNT GRADIO ONTO FASTAPI + RUN408# ─────────────────────────────────────────409from fastapi.middleware.cors import CORSMiddleware410from gradio.routes import mount_gradio_app411 412# Add middleware BEFORE app starts413wa_app.add_middleware(414    CORSMiddleware,415    allow_origins=["*"],416    allow_methods=["*"],417    allow_headers=["*"]418)419 420# Mount Gradio dashboard at root "/"421# WhatsApp webhook stays at "/webhook/whatsapp"422app = mount_gradio_app(wa_app, dashboard, path="/")423 424# Run everything with uvicorn directly — no threading needed425if __name__ == "__main__":426    import uvicorn427    uvicorn.run(app, host="0.0.0.0", port=7860)428