Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -231,10 +231,101 @@ def set_item_availability(item_name: str, available: bool) -> str:
|
|
| 231 |
finally:
|
| 232 |
release_db_connection(conn)
|
| 233 |
|
| 234 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
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".
|
| 236 |
... (keep existing)"""
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
# --- Routes ---
|
| 240 |
@app.route('/')
|
|
@@ -299,7 +390,7 @@ def api_customer_chat_stream(table_number):
|
|
| 299 |
|
| 300 |
response = chat.send_message(user_message)
|
| 301 |
|
| 302 |
-
# Extract text
|
| 303 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 304 |
parts = response.candidates[0].content.parts
|
| 305 |
text_parts = [part.text for part in parts if hasattr(part, "text") and part.text]
|
|
@@ -431,7 +522,7 @@ def api_admin_chat():
|
|
| 431 |
|
| 432 |
config = types.GenerateContentConfig(
|
| 433 |
system_instruction=ADMIN_SYSTEM_INSTRUCTION,
|
| 434 |
-
tools=[
|
| 435 |
thinking_config=types.ThinkingConfig(thinking_budget=0)
|
| 436 |
)
|
| 437 |
|
|
@@ -445,6 +536,48 @@ def api_admin_chat():
|
|
| 445 |
|
| 446 |
response = chat.send_message(last_user_message)
|
| 447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
admin_response_text = ""
|
| 449 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 450 |
parts = response.candidates[0].content.parts
|
|
@@ -455,22 +588,20 @@ def api_admin_chat():
|
|
| 455 |
try:
|
| 456 |
admin_response_text = response.text or ""
|
| 457 |
except (ValueError, AttributeError):
|
| 458 |
-
admin_response_text = "تغییرات مورد نظر شما با موفقیت
|
| 459 |
|
| 460 |
return jsonify({"success": True, "response": admin_response_text})
|
| 461 |
except Exception as e:
|
| 462 |
return jsonify({"error": str(e)}), 500
|
| 463 |
|
| 464 |
-
# --- Admin Menu CRUD ---
|
| 465 |
@app.route('/api/admin/menu', methods=['GET'])
|
| 466 |
def api_admin_get_menu():
|
| 467 |
-
"""Get full menu for admin panel."""
|
| 468 |
menu = load_menu()
|
| 469 |
return jsonify(menu)
|
| 470 |
|
| 471 |
@app.route('/api/admin/menu/item', methods=['POST'])
|
| 472 |
def api_admin_add_menu_item():
|
| 473 |
-
"""Add a new menu item manually."""
|
| 474 |
data = request.json or {}
|
| 475 |
name = data.get("name", "").strip()
|
| 476 |
if not name:
|
|
@@ -497,12 +628,10 @@ def api_admin_add_menu_item():
|
|
| 497 |
|
| 498 |
@app.route('/api/admin/menu/item/<int:item_id>', methods=['PUT'])
|
| 499 |
def api_admin_update_menu_item(item_id):
|
| 500 |
-
"""Update a menu item's fields."""
|
| 501 |
data = request.json or {}
|
| 502 |
conn = get_db_connection()
|
| 503 |
try:
|
| 504 |
with conn.cursor() as cur:
|
| 505 |
-
# Build dynamic SET clause
|
| 506 |
allowed_fields = ["name", "description", "price", "available"]
|
| 507 |
updates = []
|
| 508 |
values = []
|
|
@@ -527,7 +656,6 @@ def api_admin_update_menu_item(item_id):
|
|
| 527 |
|
| 528 |
@app.route('/api/admin/menu/item/<int:item_id>', methods=['DELETE'])
|
| 529 |
def api_admin_delete_menu_item(item_id):
|
| 530 |
-
"""Delete a menu item."""
|
| 531 |
conn = get_db_connection()
|
| 532 |
try:
|
| 533 |
with conn.cursor() as cur:
|
|
|
|
| 231 |
finally:
|
| 232 |
release_db_connection(conn)
|
| 233 |
|
| 234 |
+
# --- NEW: Full CRUD tools for admin chat ---
|
| 235 |
+
def get_menu_items() -> str:
|
| 236 |
+
"""
|
| 237 |
+
Returns the complete menu with IDs, names, descriptions, prices, and availability statuses.
|
| 238 |
+
"""
|
| 239 |
+
menu = load_menu()
|
| 240 |
+
return json.dumps(menu, ensure_ascii=False)
|
| 241 |
+
|
| 242 |
+
def add_menu_item(name: str, description: str = "", price: str = "", available: bool = True) -> str:
|
| 243 |
+
"""
|
| 244 |
+
Add a new item to the cafe menu. Returns confirmation message.
|
| 245 |
+
"""
|
| 246 |
+
conn = get_db_connection()
|
| 247 |
+
try:
|
| 248 |
+
with conn.cursor() as cur:
|
| 249 |
+
cur.execute(
|
| 250 |
+
"INSERT INTO menu (name, description, price, available) VALUES (%s, %s, %s, %s) RETURNING id;",
|
| 251 |
+
(name.strip(), description.strip(), price.strip(), available)
|
| 252 |
+
)
|
| 253 |
+
new_id = cur.fetchone()[0]
|
| 254 |
+
conn.commit()
|
| 255 |
+
return f"آیتم '{name}' با شناسه {new_id} با موفقیت اضافه شد."
|
| 256 |
+
except Exception as e:
|
| 257 |
+
conn.rollback()
|
| 258 |
+
return f"خطا در افزودن آیتم: {str(e)}"
|
| 259 |
+
finally:
|
| 260 |
+
release_db_connection(conn)
|
| 261 |
+
|
| 262 |
+
def update_menu_item(item_id: int, name: str = None, description: str = None, price: str = None, available: bool = None) -> str:
|
| 263 |
+
"""
|
| 264 |
+
Update an existing menu item by ID. Only provided fields are updated.
|
| 265 |
+
"""
|
| 266 |
+
conn = get_db_connection()
|
| 267 |
+
try:
|
| 268 |
+
updates = {}
|
| 269 |
+
if name is not None:
|
| 270 |
+
updates["name"] = name.strip()
|
| 271 |
+
if description is not None:
|
| 272 |
+
updates["description"] = description.strip()
|
| 273 |
+
if price is not None:
|
| 274 |
+
updates["price"] = price.strip()
|
| 275 |
+
if available is not None:
|
| 276 |
+
updates["available"] = available
|
| 277 |
+
if not updates:
|
| 278 |
+
return "هیچ فیلدی برای بروزرسانی ارسال نشده است."
|
| 279 |
+
with conn.cursor() as cur:
|
| 280 |
+
set_clause = ", ".join([f"{field} = %s" for field in updates])
|
| 281 |
+
values = list(updates.values()) + [item_id]
|
| 282 |
+
cur.execute(f"UPDATE menu SET {set_clause} WHERE id = %s;", values)
|
| 283 |
+
conn.commit()
|
| 284 |
+
if cur.rowcount == 0:
|
| 285 |
+
return f"آیتم با شناسه {item_id} یافت نشد."
|
| 286 |
+
return f"آیتم {item_id} با موفقیت بروزرسانی شد."
|
| 287 |
+
except Exception as e:
|
| 288 |
+
conn.rollback()
|
| 289 |
+
return f"خطا در بروزرسانی آیتم: {str(e)}"
|
| 290 |
+
finally:
|
| 291 |
+
release_db_connection(conn)
|
| 292 |
+
|
| 293 |
+
def delete_menu_item(item_id: int) -> str:
|
| 294 |
+
"""
|
| 295 |
+
Delete a menu item by ID.
|
| 296 |
+
"""
|
| 297 |
+
conn = get_db_connection()
|
| 298 |
+
try:
|
| 299 |
+
with conn.cursor() as cur:
|
| 300 |
+
cur.execute("DELETE FROM menu WHERE id = %s;", (item_id,))
|
| 301 |
+
conn.commit()
|
| 302 |
+
if cur.rowcount == 0:
|
| 303 |
+
return f"آیتم با شناسه {item_id} یافت نشد."
|
| 304 |
+
return f"آیتم {item_id} با موفقیت حذف شد."
|
| 305 |
+
except Exception as e:
|
| 306 |
+
conn.rollback()
|
| 307 |
+
return f"خطا در حذف آیتم: {str(e)}"
|
| 308 |
+
finally:
|
| 309 |
+
release_db_connection(conn)
|
| 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 |
... (keep existing)"""
|
| 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:
|
| 317 |
+
- **get_menu_items()**: fetch the full menu with IDs, names, descriptions, prices, and availability.
|
| 318 |
+
- **add_menu_item(name, description, price, available)**: add a new item (description and price optional, available is True by default).
|
| 319 |
+
- **update_menu_item(item_id, name, description, price, available)**: update any fields of an existing item by its ID (only pass the fields you want to change).
|
| 320 |
+
- **delete_menu_item(item_id)**: permanently remove an item.
|
| 321 |
+
- **set_item_availability(item_name, available)**: quickly toggle availability of an item by its exact name.
|
| 322 |
+
|
| 323 |
+
When the user asks to see the menu, use get_menu_items and show a nicely formatted list.
|
| 324 |
+
When asked to add an item, ask for the necessary details and then call add_menu_item.
|
| 325 |
+
For modifications, first get the menu to find the item ID, then use update_menu_item.
|
| 326 |
+
For removals, use delete_menu_item with the correct ID.
|
| 327 |
+
Always confirm every change clearly. Communicate primarily in Persian (Farsi), but you can understand English commands as well.
|
| 328 |
+
Be polite, precise, and efficient."""
|
| 329 |
|
| 330 |
# --- Routes ---
|
| 331 |
@app.route('/')
|
|
|
|
| 390 |
|
| 391 |
response = chat.send_message(user_message)
|
| 392 |
|
| 393 |
+
# Extract text (ignore possible function calls in this simplified customer flow)
|
| 394 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 395 |
parts = response.candidates[0].content.parts
|
| 396 |
text_parts = [part.text for part in parts if hasattr(part, "text") and part.text]
|
|
|
|
| 522 |
|
| 523 |
config = types.GenerateContentConfig(
|
| 524 |
system_instruction=ADMIN_SYSTEM_INSTRUCTION,
|
| 525 |
+
tools=[get_menu_items, add_menu_item, update_menu_item, delete_menu_item, set_item_availability],
|
| 526 |
thinking_config=types.ThinkingConfig(thinking_budget=0)
|
| 527 |
)
|
| 528 |
|
|
|
|
| 536 |
|
| 537 |
response = chat.send_message(last_user_message)
|
| 538 |
|
| 539 |
+
# Handle function calls loop
|
| 540 |
+
max_iterations = 10 # safety net
|
| 541 |
+
while max_iterations > 0:
|
| 542 |
+
function_calls = []
|
| 543 |
+
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 544 |
+
for part in response.candidates[0].content.parts:
|
| 545 |
+
if hasattr(part, "function_call") and part.function_call:
|
| 546 |
+
function_calls.append(part.function_call)
|
| 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
|
| 554 |
+
args = dict(fc.args) if hasattr(fc, "args") else {}
|
| 555 |
+
print(f"Admin function call: {func_name}({args})")
|
| 556 |
+
try:
|
| 557 |
+
if func_name == "get_menu_items":
|
| 558 |
+
result = get_menu_items()
|
| 559 |
+
elif func_name == "add_menu_item":
|
| 560 |
+
result = add_menu_item(**args)
|
| 561 |
+
elif func_name == "update_menu_item":
|
| 562 |
+
result = update_menu_item(**args)
|
| 563 |
+
elif func_name == "delete_menu_item":
|
| 564 |
+
result = delete_menu_item(**args)
|
| 565 |
+
elif func_name == "set_item_availability":
|
| 566 |
+
result = set_item_availability(**args)
|
| 567 |
+
else:
|
| 568 |
+
result = f"Unknown function: {func_name}"
|
| 569 |
+
except Exception as e:
|
| 570 |
+
result = f"Error executing {func_name}: {str(e)}"
|
| 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 |
+
)
|
| 578 |
+
max_iterations -= 1
|
| 579 |
+
|
| 580 |
+
# Extract final text
|
| 581 |
admin_response_text = ""
|
| 582 |
if response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
|
| 583 |
parts = response.candidates[0].content.parts
|
|
|
|
| 588 |
try:
|
| 589 |
admin_response_text = response.text or ""
|
| 590 |
except (ValueError, AttributeError):
|
| 591 |
+
admin_response_text = "تغییرات مورد نظر شما با موفقیت اعمال شد."
|
| 592 |
|
| 593 |
return jsonify({"success": True, "response": admin_response_text})
|
| 594 |
except Exception as e:
|
| 595 |
return jsonify({"error": str(e)}), 500
|
| 596 |
|
| 597 |
+
# --- Admin Menu CRUD (manual REST endpoints, kept for the admin UI table) ---
|
| 598 |
@app.route('/api/admin/menu', methods=['GET'])
|
| 599 |
def api_admin_get_menu():
|
|
|
|
| 600 |
menu = load_menu()
|
| 601 |
return jsonify(menu)
|
| 602 |
|
| 603 |
@app.route('/api/admin/menu/item', methods=['POST'])
|
| 604 |
def api_admin_add_menu_item():
|
|
|
|
| 605 |
data = request.json or {}
|
| 606 |
name = data.get("name", "").strip()
|
| 607 |
if not name:
|
|
|
|
| 628 |
|
| 629 |
@app.route('/api/admin/menu/item/<int:item_id>', methods=['PUT'])
|
| 630 |
def api_admin_update_menu_item(item_id):
|
|
|
|
| 631 |
data = request.json or {}
|
| 632 |
conn = get_db_connection()
|
| 633 |
try:
|
| 634 |
with conn.cursor() as cur:
|
|
|
|
| 635 |
allowed_fields = ["name", "description", "price", "available"]
|
| 636 |
updates = []
|
| 637 |
values = []
|
|
|
|
| 656 |
|
| 657 |
@app.route('/api/admin/menu/item/<int:item_id>', methods=['DELETE'])
|
| 658 |
def api_admin_delete_menu_item(item_id):
|
|
|
|
| 659 |
conn = get_db_connection()
|
| 660 |
try:
|
| 661 |
with conn.cursor() as cur:
|