Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -149,12 +149,6 @@ def load_orders():
|
|
| 149 |
finally:
|
| 150 |
release_db_connection(conn)
|
| 151 |
|
| 152 |
-
def save_orders(orders_data):
|
| 153 |
-
"""Append a new order to database (used by confirm_order)."""
|
| 154 |
-
# This function is now only called to add one order at a time (the new_order dict)
|
| 155 |
-
# But we keep it for backward compatibility. We'll modify confirm_order to use insert directly.
|
| 156 |
-
pass # not used anymore, will insert directly in confirm_order endpoint
|
| 157 |
-
|
| 158 |
# --- Tool functions (will be called by Gemini) ---
|
| 159 |
def get_menu() -> str:
|
| 160 |
"""
|
|
@@ -171,6 +165,7 @@ def get_menu() -> str:
|
|
| 171 |
def prepare_order_draft(table_number: str, items: list[OrderItem]) -> str:
|
| 172 |
"""
|
| 173 |
Prepares a draft order list for the customer to confirm on their screen.
|
|
|
|
| 174 |
"""
|
| 175 |
table_number_str = str(table_number)
|
| 176 |
items_list = []
|
|
@@ -310,7 +305,17 @@ def delete_menu_item(item_id: int) -> str:
|
|
| 310 |
|
| 311 |
# --- System Instructions ---
|
| 312 |
CUSTOMER_SYSTEM_INSTRUCTION = """Your name is Nila, a classic, warm, and highly professional AI cafe assistant for "Cafe AI". You were developed by "Nastaran Data Algorithm".
|
| 313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
|
| 315 |
ADMIN_SYSTEM_INSTRUCTION = """Your name is Nila, the smart cafe administrator assistant for "Cafe AI". You manage the cafe menu in real time.
|
| 316 |
You can view, add, update, delete menu items, and change availability using the following tools:
|
|
@@ -340,7 +345,7 @@ def customer_chat_page(table_number):
|
|
| 340 |
def admin_dashboard_page():
|
| 341 |
return render_template("cafe.html")
|
| 342 |
|
| 343 |
-
# --- Customer API ---
|
| 344 |
@app.route('/api/customer/chat_stream/<table_number>', methods=['POST'])
|
| 345 |
def api_customer_chat_stream(table_number):
|
| 346 |
api_key = os.environ.get("NILLA_CUSTOMER") or os.environ.get("GEMINI_API_KEY")
|
|
@@ -361,6 +366,7 @@ def api_customer_chat_stream(table_number):
|
|
| 361 |
|
| 362 |
def generate_chunks():
|
| 363 |
try:
|
|
|
|
| 364 |
contents = []
|
| 365 |
for msg in table_chat_histories[table_number][:-1]:
|
| 366 |
role = "user" if msg["role"] == "user" else "model"
|
|
@@ -382,27 +388,70 @@ def api_customer_chat_stream(table_number):
|
|
| 382 |
thinking_config=types.ThinkingConfig(thinking_budget=0)
|
| 383 |
)
|
| 384 |
|
|
|
|
| 385 |
chat = client.chats.create(
|
| 386 |
model="gemini-3.1-flash-lite",
|
| 387 |
history=contents,
|
| 388 |
config=config
|
| 389 |
)
|
| 390 |
|
|
|
|
| 391 |
response = chat.send_message(user_message)
|
| 392 |
|
| 393 |
-
#
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
if
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
if not full_generated_text:
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
|
| 405 |
-
#
|
| 406 |
chunk_size = 4
|
| 407 |
for i in range(0, len(full_generated_text), chunk_size):
|
| 408 |
if not active_tasks.get(table_number, True):
|
|
@@ -411,10 +460,7 @@ def api_customer_chat_stream(table_number):
|
|
| 411 |
yield f"data: {json.dumps({'type': 'text', 'content': chunk}, ensure_ascii=False)}\n\n"
|
| 412 |
time.sleep(0.015)
|
| 413 |
|
| 414 |
-
if
|
| 415 |
-
table_chat_histories[table_number].append({"role": "model", "content": full_generated_text})
|
| 416 |
-
|
| 417 |
-
# Check draft
|
| 418 |
draft = global_drafts.get(table_number)
|
| 419 |
if draft:
|
| 420 |
yield f"data: {json.dumps({'type': 'draft', 'items': draft['items']}, ensure_ascii=False)}\n\n"
|
|
@@ -537,7 +583,7 @@ def api_admin_chat():
|
|
| 537 |
response = chat.send_message(last_user_message)
|
| 538 |
|
| 539 |
# Handle function calls loop
|
| 540 |
-
max_iterations = 10
|
| 541 |
while max_iterations > 0:
|
| 542 |
function_calls = []
|
| 543 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
|
@@ -547,7 +593,6 @@ def api_admin_chat():
|
|
| 547 |
if not function_calls:
|
| 548 |
break
|
| 549 |
|
| 550 |
-
# Execute functions and build response
|
| 551 |
function_response_parts = []
|
| 552 |
for fc in function_calls:
|
| 553 |
func_name = fc.name
|
|
@@ -571,7 +616,6 @@ def api_admin_chat():
|
|
| 571 |
function_response_parts.append(
|
| 572 |
types.Part.from_function_response(name=func_name, response={"result": result})
|
| 573 |
)
|
| 574 |
-
# Send function responses back to the model
|
| 575 |
response = chat.send_message(
|
| 576 |
types.Content(role="user", parts=function_response_parts)
|
| 577 |
)
|
|
@@ -594,7 +638,7 @@ def api_admin_chat():
|
|
| 594 |
except Exception as e:
|
| 595 |
return jsonify({"error": str(e)}), 500
|
| 596 |
|
| 597 |
-
# --- Admin Menu CRUD (manual REST endpoints
|
| 598 |
@app.route('/api/admin/menu', methods=['GET'])
|
| 599 |
def api_admin_get_menu():
|
| 600 |
menu = load_menu()
|
|
|
|
| 149 |
finally:
|
| 150 |
release_db_connection(conn)
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
# --- Tool functions (will be called by Gemini) ---
|
| 153 |
def get_menu() -> str:
|
| 154 |
"""
|
|
|
|
| 165 |
def prepare_order_draft(table_number: str, items: list[OrderItem]) -> str:
|
| 166 |
"""
|
| 167 |
Prepares a draft order list for the customer to confirm on their screen.
|
| 168 |
+
Only call this after verifying that every item exists in the menu (using get_menu).
|
| 169 |
"""
|
| 170 |
table_number_str = str(table_number)
|
| 171 |
items_list = []
|
|
|
|
| 305 |
|
| 306 |
# --- System Instructions ---
|
| 307 |
CUSTOMER_SYSTEM_INSTRUCTION = """Your name is Nila, a classic, warm, and highly professional AI cafe assistant for "Cafe AI". You were developed by "Nastaran Data Algorithm".
|
| 308 |
+
|
| 309 |
+
You are currently talking to a customer at a specific table. Your responsibilities:
|
| 310 |
+
1. Greet warmly and take orders in Persian (you can understand English but always reply in Persian unless the customer insists).
|
| 311 |
+
2. **Before preparing any order draft**, you MUST check the current menu by calling the tool **get_menu()** to see what items are available.
|
| 312 |
+
3. If the customer asks for an item that is NOT in the menu (exact or similar name), politely say: "متأسفانه این آیتم در منوی امروز ما موجود نیست. میتوانم پیشنهادهای دیگری از منو به شما بدهم؟" and then suggest 2-3 similar or popular items from the current menu. Never prepare a draft with items not in the menu.
|
| 313 |
+
4. Once you have verified all requested items exist, use **prepare_order_draft** to create the order.
|
| 314 |
+
5. After preparing the draft, tell the customer they can review and confirm on their screen.
|
| 315 |
+
6. If the menu is empty (no items returned), tell the customer that the cafe is currently not taking orders and to wait for the staff.
|
| 316 |
+
|
| 317 |
+
Always be friendly, speak in Persian, and use the tools to provide accurate information.
|
| 318 |
+
"""
|
| 319 |
|
| 320 |
ADMIN_SYSTEM_INSTRUCTION = """Your name is Nila, the smart cafe administrator assistant for "Cafe AI". You manage the cafe menu in real time.
|
| 321 |
You can view, add, update, delete menu items, and change availability using the following tools:
|
|
|
|
| 345 |
def admin_dashboard_page():
|
| 346 |
return render_template("cafe.html")
|
| 347 |
|
| 348 |
+
# --- Customer API (with function calling loop) ---
|
| 349 |
@app.route('/api/customer/chat_stream/<table_number>', methods=['POST'])
|
| 350 |
def api_customer_chat_stream(table_number):
|
| 351 |
api_key = os.environ.get("NILLA_CUSTOMER") or os.environ.get("GEMINI_API_KEY")
|
|
|
|
| 366 |
|
| 367 |
def generate_chunks():
|
| 368 |
try:
|
| 369 |
+
# Build conversation history for the model
|
| 370 |
contents = []
|
| 371 |
for msg in table_chat_histories[table_number][:-1]:
|
| 372 |
role = "user" if msg["role"] == "user" else "model"
|
|
|
|
| 388 |
thinking_config=types.ThinkingConfig(thinking_budget=0)
|
| 389 |
)
|
| 390 |
|
| 391 |
+
# Create a chat session
|
| 392 |
chat = client.chats.create(
|
| 393 |
model="gemini-3.1-flash-lite",
|
| 394 |
history=contents,
|
| 395 |
config=config
|
| 396 |
)
|
| 397 |
|
| 398 |
+
# Send user message and handle any function calls
|
| 399 |
response = chat.send_message(user_message)
|
| 400 |
|
| 401 |
+
# Loop to process function calls
|
| 402 |
+
max_iterations = 10
|
| 403 |
+
while max_iterations > 0 and active_tasks.get(table_number, True):
|
| 404 |
+
function_calls = []
|
| 405 |
+
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 406 |
+
for part in response.candidates[0].content.parts:
|
| 407 |
+
if hasattr(part, "function_call") and part.function_call:
|
| 408 |
+
function_calls.append(part.function_call)
|
| 409 |
+
|
| 410 |
+
if not function_calls:
|
| 411 |
+
# No more function calls, extract the text
|
| 412 |
+
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 413 |
+
text_parts = [part.text for part in response.candidates[0].content.parts if hasattr(part, "text") and part.text]
|
| 414 |
+
full_generated_text = "".join(text_parts)
|
| 415 |
+
if not full_generated_text:
|
| 416 |
+
try:
|
| 417 |
+
full_generated_text = response.text or ""
|
| 418 |
+
except (ValueError, AttributeError):
|
| 419 |
+
full_generated_text = "متأسفانه مشکلی پیش آمده، لطفاً دوباره تلاش کنید."
|
| 420 |
+
break
|
| 421 |
+
|
| 422 |
+
# Execute function calls
|
| 423 |
+
function_response_parts = []
|
| 424 |
+
for fc in function_calls:
|
| 425 |
+
func_name = fc.name
|
| 426 |
+
args = dict(fc.args) if hasattr(fc, "args") else {}
|
| 427 |
+
print(f"Customer function call: {func_name}({args})")
|
| 428 |
+
try:
|
| 429 |
+
if func_name == "get_menu":
|
| 430 |
+
result = get_menu()
|
| 431 |
+
elif func_name == "prepare_order_draft":
|
| 432 |
+
result = prepare_order_draft(**args)
|
| 433 |
+
else:
|
| 434 |
+
result = f"Unknown function: {func_name}"
|
| 435 |
+
except Exception as e:
|
| 436 |
+
result = f"Error executing {func_name}: {str(e)}"
|
| 437 |
+
function_response_parts.append(
|
| 438 |
+
types.Part.from_function_response(name=func_name, response={"result": result})
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
# Send the results back to the model
|
| 442 |
+
response = chat.send_message(
|
| 443 |
+
types.Content(role="user", parts=function_response_parts)
|
| 444 |
+
)
|
| 445 |
+
max_iterations -= 1
|
| 446 |
+
|
| 447 |
+
# If no text was generated after all iterations
|
| 448 |
if not full_generated_text:
|
| 449 |
+
full_generated_text = "پاسخی دریافت نشد، لطفاً دوباره تلاش کنید."
|
| 450 |
+
|
| 451 |
+
# Save the model's final text to history
|
| 452 |
+
table_chat_histories[table_number].append({"role": "model", "content": full_generated_text})
|
| 453 |
|
| 454 |
+
# Stream the text chunk by chunk (simulate streaming)
|
| 455 |
chunk_size = 4
|
| 456 |
for i in range(0, len(full_generated_text), chunk_size):
|
| 457 |
if not active_tasks.get(table_number, True):
|
|
|
|
| 460 |
yield f"data: {json.dumps({'type': 'text', 'content': chunk}, ensure_ascii=False)}\n\n"
|
| 461 |
time.sleep(0.015)
|
| 462 |
|
| 463 |
+
# After streaming text, check if there's a draft for this table
|
|
|
|
|
|
|
|
|
|
| 464 |
draft = global_drafts.get(table_number)
|
| 465 |
if draft:
|
| 466 |
yield f"data: {json.dumps({'type': 'draft', 'items': draft['items']}, ensure_ascii=False)}\n\n"
|
|
|
|
| 583 |
response = chat.send_message(last_user_message)
|
| 584 |
|
| 585 |
# Handle function calls loop
|
| 586 |
+
max_iterations = 10
|
| 587 |
while max_iterations > 0:
|
| 588 |
function_calls = []
|
| 589 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
|
|
|
| 593 |
if not function_calls:
|
| 594 |
break
|
| 595 |
|
|
|
|
| 596 |
function_response_parts = []
|
| 597 |
for fc in function_calls:
|
| 598 |
func_name = fc.name
|
|
|
|
| 616 |
function_response_parts.append(
|
| 617 |
types.Part.from_function_response(name=func_name, response={"result": result})
|
| 618 |
)
|
|
|
|
| 619 |
response = chat.send_message(
|
| 620 |
types.Content(role="user", parts=function_response_parts)
|
| 621 |
)
|
|
|
|
| 638 |
except Exception as e:
|
| 639 |
return jsonify({"error": str(e)}), 500
|
| 640 |
|
| 641 |
+
# --- Admin Menu CRUD (manual REST endpoints) ---
|
| 642 |
@app.route('/api/admin/menu', methods=['GET'])
|
| 643 |
def api_admin_get_menu():
|
| 644 |
menu = load_menu()
|