Update main.py
Browse files
main.py
CHANGED
|
@@ -7,6 +7,7 @@ from linebot.exceptions import InvalidSignatureError
|
|
| 7 |
from linebot.models import MessageEvent, TextMessage, TextSendMessage
|
| 8 |
import PyPDF2
|
| 9 |
import logging
|
|
|
|
| 10 |
from datetime import datetime, timedelta
|
| 11 |
from google.oauth2.credentials import Credentials
|
| 12 |
from google.oauth2 import service_account
|
|
@@ -275,7 +276,7 @@ def parse_date(date_str):
|
|
| 275 |
except ValueError:
|
| 276 |
pass
|
| 277 |
|
| 278 |
-
return None, "❌ 日期格式不正確,請重新輸入。\n例如:明天、
|
| 279 |
|
| 280 |
def validate_time(time_str):
|
| 281 |
time_keywords = ["點", "時", ":", "-", "到", "至", "~"]
|
|
@@ -496,11 +497,186 @@ def root():
|
|
| 496 |
"google_calendar": calendar_service is not None,
|
| 497 |
"ai_qa": ai_enabled,
|
| 498 |
"pdf_loaded": len(pdf_content) > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
}
|
| 500 |
}
|
| 501 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
@app.get("/bookings")
|
| 503 |
def get_bookings():
|
|
|
|
| 504 |
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 505 |
if bookings_file.exists():
|
| 506 |
with open(bookings_file, 'r', encoding='utf-8') as f:
|
|
@@ -508,6 +684,45 @@ def get_bookings():
|
|
| 508 |
return {"total": len(bookings), "bookings": bookings}
|
| 509 |
return {"total": 0, "bookings": []}
|
| 510 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
@app.post("/webhook")
|
| 512 |
async def webhook(
|
| 513 |
request: Request,
|
|
@@ -573,7 +788,7 @@ def handle_message(event):
|
|
| 573 |
full_prompt = user_message
|
| 574 |
|
| 575 |
response = client.models.generate_content(
|
| 576 |
-
model="gemini-
|
| 577 |
contents=full_prompt
|
| 578 |
)
|
| 579 |
|
|
|
|
| 7 |
from linebot.models import MessageEvent, TextMessage, TextSendMessage
|
| 8 |
import PyPDF2
|
| 9 |
import logging
|
| 10 |
+
from fastapi.responses import HTMLResponse
|
| 11 |
from datetime import datetime, timedelta
|
| 12 |
from google.oauth2.credentials import Credentials
|
| 13 |
from google.oauth2 import service_account
|
|
|
|
| 276 |
except ValueError:
|
| 277 |
pass
|
| 278 |
|
| 279 |
+
return None, "❌ 日期格式不正確,請重新輸入。\n例如:明天、2025-12-30"
|
| 280 |
|
| 281 |
def validate_time(time_str):
|
| 282 |
time_keywords = ["點", "時", ":", "-", "到", "至", "~"]
|
|
|
|
| 497 |
"google_calendar": calendar_service is not None,
|
| 498 |
"ai_qa": ai_enabled,
|
| 499 |
"pdf_loaded": len(pdf_content) > 0
|
| 500 |
+
},
|
| 501 |
+
"endpoints": {
|
| 502 |
+
"查看所有預約": "/bookings",
|
| 503 |
+
"下載預約資料": "/bookings/download",
|
| 504 |
+
"查看最新預約": "/bookings/latest",
|
| 505 |
+
"管理介面": "/admin"
|
| 506 |
}
|
| 507 |
}
|
| 508 |
|
| 509 |
+
@app.get("/admin")
|
| 510 |
+
def admin_panel():
|
| 511 |
+
"""簡單的管理介面"""
|
| 512 |
+
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 513 |
+
bookings = []
|
| 514 |
+
|
| 515 |
+
if bookings_file.exists():
|
| 516 |
+
with open(bookings_file, 'r', encoding='utf-8') as f:
|
| 517 |
+
bookings = json.load(f)
|
| 518 |
+
|
| 519 |
+
# 生成 HTML
|
| 520 |
+
html = """
|
| 521 |
+
<!DOCTYPE html>
|
| 522 |
+
<html>
|
| 523 |
+
<head>
|
| 524 |
+
<meta charset="UTF-8">
|
| 525 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 526 |
+
<title>琴房預約管理</title>
|
| 527 |
+
<style>
|
| 528 |
+
body {
|
| 529 |
+
font-family: Arial, sans-serif;
|
| 530 |
+
max-width: 1200px;
|
| 531 |
+
margin: 0 auto;
|
| 532 |
+
padding: 20px;
|
| 533 |
+
background: #f5f5f5;
|
| 534 |
+
}
|
| 535 |
+
h1 {
|
| 536 |
+
color: #333;
|
| 537 |
+
text-align: center;
|
| 538 |
+
}
|
| 539 |
+
.stats {
|
| 540 |
+
display: flex;
|
| 541 |
+
gap: 20px;
|
| 542 |
+
margin-bottom: 30px;
|
| 543 |
+
}
|
| 544 |
+
.stat-card {
|
| 545 |
+
flex: 1;
|
| 546 |
+
background: white;
|
| 547 |
+
padding: 20px;
|
| 548 |
+
border-radius: 8px;
|
| 549 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 550 |
+
}
|
| 551 |
+
.stat-card h3 {
|
| 552 |
+
margin: 0 0 10px 0;
|
| 553 |
+
color: #666;
|
| 554 |
+
font-size: 14px;
|
| 555 |
+
}
|
| 556 |
+
.stat-card .number {
|
| 557 |
+
font-size: 32px;
|
| 558 |
+
font-weight: bold;
|
| 559 |
+
color: #4CAF50;
|
| 560 |
+
}
|
| 561 |
+
table {
|
| 562 |
+
width: 100%;
|
| 563 |
+
background: white;
|
| 564 |
+
border-collapse: collapse;
|
| 565 |
+
border-radius: 8px;
|
| 566 |
+
overflow: hidden;
|
| 567 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 568 |
+
}
|
| 569 |
+
th {
|
| 570 |
+
background: #4CAF50;
|
| 571 |
+
color: white;
|
| 572 |
+
padding: 12px;
|
| 573 |
+
text-align: left;
|
| 574 |
+
}
|
| 575 |
+
td {
|
| 576 |
+
padding: 12px;
|
| 577 |
+
border-bottom: 1px solid #ddd;
|
| 578 |
+
}
|
| 579 |
+
tr:hover {
|
| 580 |
+
background: #f9f9f9;
|
| 581 |
+
}
|
| 582 |
+
.status-pending {
|
| 583 |
+
color: #FF9800;
|
| 584 |
+
font-weight: bold;
|
| 585 |
+
}
|
| 586 |
+
.calendar-link {
|
| 587 |
+
color: #2196F3;
|
| 588 |
+
text-decoration: none;
|
| 589 |
+
}
|
| 590 |
+
.calendar-link:hover {
|
| 591 |
+
text-decoration: underline;
|
| 592 |
+
}
|
| 593 |
+
.refresh-btn {
|
| 594 |
+
background: #4CAF50;
|
| 595 |
+
color: white;
|
| 596 |
+
padding: 10px 20px;
|
| 597 |
+
border: none;
|
| 598 |
+
border-radius: 4px;
|
| 599 |
+
cursor: pointer;
|
| 600 |
+
font-size: 16px;
|
| 601 |
+
margin-bottom: 20px;
|
| 602 |
+
}
|
| 603 |
+
.refresh-btn:hover {
|
| 604 |
+
background: #45a049;
|
| 605 |
+
}
|
| 606 |
+
</style>
|
| 607 |
+
</head>
|
| 608 |
+
<body>
|
| 609 |
+
<h1>🎹 琴房預約管理系統</h1>
|
| 610 |
+
|
| 611 |
+
<div class="stats">
|
| 612 |
+
<div class="stat-card">
|
| 613 |
+
<h3>總預約數</h3>
|
| 614 |
+
<div class="number">""" + str(len(bookings)) + """</div>
|
| 615 |
+
</div>
|
| 616 |
+
<div class="stat-card">
|
| 617 |
+
<h3>待確認</h3>
|
| 618 |
+
<div class="number">""" + str(len([b for b in bookings if b.get('status') == 'pending'])) + """</div>
|
| 619 |
+
</div>
|
| 620 |
+
<div class="stat-card">
|
| 621 |
+
<h3>Google Calendar</h3>
|
| 622 |
+
<div class="number">""" + ('✓' if calendar_service else '✗') + """</div>
|
| 623 |
+
</div>
|
| 624 |
+
</div>
|
| 625 |
+
|
| 626 |
+
<button class="refresh-btn" onclick="location.reload()">🔄 重新整理</button>
|
| 627 |
+
|
| 628 |
+
<table>
|
| 629 |
+
<thead>
|
| 630 |
+
<tr>
|
| 631 |
+
<th>預約編號</th>
|
| 632 |
+
<th>日期</th>
|
| 633 |
+
<th>時段</th>
|
| 634 |
+
<th>人數</th>
|
| 635 |
+
<th>琴房</th>
|
| 636 |
+
<th>狀態</th>
|
| 637 |
+
<th>建立時間</th>
|
| 638 |
+
<th>日曆</th>
|
| 639 |
+
</tr>
|
| 640 |
+
</thead>
|
| 641 |
+
<tbody>
|
| 642 |
+
"""
|
| 643 |
+
|
| 644 |
+
# 按時間倒序排列
|
| 645 |
+
for booking in reversed(bookings):
|
| 646 |
+
calendar_icon = '📆' if booking.get('calendar_link') else '❌'
|
| 647 |
+
html += f"""
|
| 648 |
+
<tr>
|
| 649 |
+
<td>{booking['booking_id']}</td>
|
| 650 |
+
<td>{booking['date']}</td>
|
| 651 |
+
<td>{booking['time']}</td>
|
| 652 |
+
<td>{booking['people']}人</td>
|
| 653 |
+
<td>{booking['room']}</td>
|
| 654 |
+
<td class="status-pending">{booking['status']}</td>
|
| 655 |
+
<td>{booking['created_at']}</td>
|
| 656 |
+
<td>
|
| 657 |
+
"""
|
| 658 |
+
if booking.get('calendar_link'):
|
| 659 |
+
html += f'<a href="{booking["calendar_link"]}" target="_blank" class="calendar-link">{calendar_icon} 查看</a>'
|
| 660 |
+
else:
|
| 661 |
+
html += calendar_icon
|
| 662 |
+
html += """
|
| 663 |
+
</td>
|
| 664 |
+
</tr>
|
| 665 |
+
"""
|
| 666 |
+
|
| 667 |
+
html += """
|
| 668 |
+
</tbody>
|
| 669 |
+
</table>
|
| 670 |
+
</body>
|
| 671 |
+
</html>
|
| 672 |
+
"""
|
| 673 |
+
|
| 674 |
+
from fastapi.responses import HTMLResponse
|
| 675 |
+
return HTMLResponse(content=html)
|
| 676 |
+
|
| 677 |
@app.get("/bookings")
|
| 678 |
def get_bookings():
|
| 679 |
+
"""查看所有預約(管理用)"""
|
| 680 |
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 681 |
if bookings_file.exists():
|
| 682 |
with open(bookings_file, 'r', encoding='utf-8') as f:
|
|
|
|
| 684 |
return {"total": len(bookings), "bookings": bookings}
|
| 685 |
return {"total": 0, "bookings": []}
|
| 686 |
|
| 687 |
+
@app.get("/bookings/download")
|
| 688 |
+
def download_bookings():
|
| 689 |
+
"""下載預約資料(JSON 格式)"""
|
| 690 |
+
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 691 |
+
if bookings_file.exists():
|
| 692 |
+
with open(bookings_file, 'r', encoding='utf-8') as f:
|
| 693 |
+
return json.load(f)
|
| 694 |
+
return []
|
| 695 |
+
|
| 696 |
+
@app.get("/bookings/latest")
|
| 697 |
+
def get_latest_booking():
|
| 698 |
+
"""查看最新一筆預約"""
|
| 699 |
+
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 700 |
+
if bookings_file.exists():
|
| 701 |
+
with open(bookings_file, 'r', encoding='utf-8') as f:
|
| 702 |
+
bookings = json.load(f)
|
| 703 |
+
if bookings:
|
| 704 |
+
return bookings[-1] # 返回最後一筆
|
| 705 |
+
return {"message": "尚無預約資料"}
|
| 706 |
+
|
| 707 |
+
@app.delete("/bookings/{booking_id}")
|
| 708 |
+
def delete_booking(booking_id: str):
|
| 709 |
+
"""刪除指定預約"""
|
| 710 |
+
bookings_file = pathlib.Path("bookings/bookings.json")
|
| 711 |
+
if bookings_file.exists():
|
| 712 |
+
with open(bookings_file, 'r', encoding='utf-8') as f:
|
| 713 |
+
bookings = json.load(f)
|
| 714 |
+
|
| 715 |
+
# 找到並移除指定預約
|
| 716 |
+
updated_bookings = [b for b in bookings if b['booking_id'] != booking_id]
|
| 717 |
+
|
| 718 |
+
if len(updated_bookings) < len(bookings):
|
| 719 |
+
with open(bookings_file, 'w', encoding='utf-8') as f:
|
| 720 |
+
json.dump(updated_bookings, f, ensure_ascii=False, indent=2)
|
| 721 |
+
return {"message": f"已刪除預約 {booking_id}"}
|
| 722 |
+
else:
|
| 723 |
+
return {"message": f"找不到預約 {booking_id}"}
|
| 724 |
+
return {"message": "沒有預約資料"}
|
| 725 |
+
|
| 726 |
@app.post("/webhook")
|
| 727 |
async def webhook(
|
| 728 |
request: Request,
|
|
|
|
| 788 |
full_prompt = user_message
|
| 789 |
|
| 790 |
response = client.models.generate_content(
|
| 791 |
+
model="gemini-2.5-flash", # 使用最新版本
|
| 792 |
contents=full_prompt
|
| 793 |
)
|
| 794 |
|