import streamlit as st from google import genai from google.genai import types # 引入 types 用來設定對話歷史與系統指令 import requests import os import time # 記得在程式碼最上方加入這個內建套件(如果還沒加的話) # 0. 頁面配置與 CSS 注入(隱藏側邊欄捲軸) st.set_page_config(page_title="CIE CIE TAIPEI 線上客服", page_icon="🍸", layout="wide") st.markdown( """ """, unsafe_allow_html=True ) # 從環境變數中取得 API Key api_key = os.environ.get("GEMINI_API_KEY") if not api_key: st.error("請確認已經在 Space 的 Settings 設定了 GEMINI_API_KEY") st.stop() # 【更新】使用新版 SDK 建立 Client client = genai.Client(api_key=api_key) # 1. 基礎設定與連結 # ⚠️ 注意:這裡預設你把 md 檔放在 github 的 ai-knowledge 資料夾下。如果是放在根目錄,請把 ai-knowledge/ 刪除。 MD_BASE_URL = "https://raw.githubusercontent.com/ciecietaipei/ciecietaipei.github.io/main/ai-knowledge/" # 從你們的 assets 資料夾抓取 Logo LOGO_URL = "https://raw.githubusercontent.com/ciecietaipei/ciecietaipei.github.io/main/assets/ciecie_logo_circle.png" HOME_URL = "https://ciecietaipei.github.io" KNOWLEDGE_MAP = { "🍸 原創調酒 (Cocktails)": { "raw_url": f"{MD_BASE_URL}cocktails.md", "page_url": f"{HOME_URL}/cocktails.html", "repo_url": "https://github.com/ciecietaipei/ciecietaipei.github.io/blob/main/ai-knowledge/cocktails.md" }, "🍷 經典酒水 (Drinks)": { "raw_url": f"{MD_BASE_URL}drinks.md", "page_url": f"{HOME_URL}/drinks.html", "repo_url": "https://github.com/ciecietaipei/ciecietaipei.github.io/blob/main/ai-knowledge/drinks.md" }, "🍽️ 精緻餐點 (Foods)": { "raw_url": f"{MD_BASE_URL}foods.md", "page_url": f"{HOME_URL}/foods.html", "repo_url": "https://github.com/ciecietaipei/ciecietaipei.github.io/blob/main/ai-knowledge/foods.md" }, "🛋️ 空間環境 (Environment)": { "raw_url": f"{MD_BASE_URL}environment.md", "page_url": f"{HOME_URL}/environment.html", "repo_url": "https://github.com/ciecietaipei/ciecietaipei.github.io/blob/main/ai-knowledge/environment.md" } } # 2. 批量抓取內容 def fetch_all_knowledge(): combined_knowledge = "" with st.spinner("正在同步 CIE CIE 最新菜單與資訊..."): for category, info in KNOWLEDGE_MAP.items(): try: response = requests.get(info["raw_url"]) response.raise_for_status() combined_knowledge += f"\n\n## 【領域:{category}】\n" combined_knowledge += response.text except Exception as e: st.warning(f"無法同步 {category} 的資料:請確認 GitHub 路徑是否正確。錯誤訊息:{e}") return combined_knowledge # 初始化 Session State if "knowledge" not in st.session_state: st.session_state.knowledge = fetch_all_knowledge() if "messages" not in st.session_state: st.session_state.messages = [] if "example_prompt" not in st.session_state: st.session_state.example_prompt = None # 3. 側邊欄設計 with st.sidebar: # 放入 CIE CIE 的圓形 Logo st.markdown(f'', unsafe_allow_html=True) st.title("⚙️ 知識庫狀態") for category, info in KNOWLEDGE_MAP.items(): with st.expander(category): st.markdown(f"🔗 [瀏覽網頁]({info['page_url']})") st.markdown(f"📂 [GitHub 原始碼]({info['repo_url']})") st.markdown("---") if st.button("🔄 手動更新菜單資訊"): st.session_state.knowledge = fetch_all_knowledge() st.success("資料已重新抓取!") # 4. 主介面與範例問句 st.title("🍸 CIECIE 微醺客服") st.caption("關於酒單、餐點、環境或訂位資訊與規範,歡迎直接詢問!台北市大安區信義路四段390號;02-2709-3446") # 顯示專屬範例問句按鈕 example_cols = st.columns(6) examples = [ "🍸 有推薦的『茶香』或『花果系』原創調酒嗎?", "🍜 深夜想吃點熱的主食,聽說有厲害的餐點?", "🛋️ 幫朋友慶生,請問二樓的沙發區環境如何?", "🪵 想獨飲,推薦一杯酒感重、帶木質調的大人味特調?", "🥃 請問你們有供應單一麥芽威士忌的單杯純飲嗎?", "🍤 有什麼推薦適合四個人的下酒菜分享?" ] for col, ex in zip(example_cols, examples): if col.button(ex): st.session_state.example_prompt = ex # 5. 【更新】模型回覆邏輯(加入防呆重試機制 + 標示參考酒單/菜單) def get_gemini_response(user_input): system_instruction = f""" 你現在是位於台北市大安區信義安和的微醺酒吧『CIE CIE TAIPEI』的專業線上客服小助手。 你的說話風格熱情、有質感且帶有一點輕鬆微醺的氛圍。 以下是從官方網站最新同步的資訊(包含原創調酒、經典酒水、精緻餐點、環境與規範): --- {st.session_state.knowledge} --- 【核心任務】: 請嚴格基於上述提供的資訊來回答客人的問題。 如果客人問了菜單上沒有的品項,或超出了上述資訊範圍,請禮貌地告知:「目前小助手手邊沒有這項資訊,建議您直接私訊我們的 IG 或是現場詢問調酒師喔!」 【嚴格輸出限制】: 1. 在回覆的最一開始,請務必先明確標示你是參考了文本中的哪個分類來作答(請擷取對應的【領域:XXX】標題)。例如:「🍸 **為您翻閱:【領域:🍸 原創調酒 (Cocktails)】**」,換行後再開始熱情地回答。 2. 絕對禁止輸出任何你的內部思考過程、計畫草稿、英文標籤、或是角色設定分析。 3. 所有的輸出內容必須 100% 都是直接面對客人溝通的繁體中文對話。 """ # 加入自動重試迴圈,最多嘗試 2 次 max_retries = 2 for attempt in range(max_retries): try: # 將 Streamlit 儲存的歷史紀錄,轉換為新版 SDK 規定的格式 history_format = [] for msg in st.session_state.messages: role = "user" if msg["role"] == "user" else "model" history_format.append( types.Content(role=role, parts=[types.Part.from_text(text=msg["content"])]) ) # 使用新版 client 建立對話,並帶入歷史紀錄 chat = client.chats.create( model="gemma-4-31b-it", config=types.GenerateContentConfig( system_instruction=system_instruction, temperature=0.4, # 稍微帶點彈性,讓微醺語氣更生動自然 ), history=history_format ) response = chat.send_message(user_input) return response.text except Exception as e: error_msg = str(e) # 遇到 500 錯誤時,如果在重試次數內,暫停 1.5 秒後自動重試 if "500" in error_msg and attempt < (max_retries - 1): time.sleep(5) continue # 錯誤處理:根據酒吧氛圍客製化錯誤訊息 if "429" in error_msg or "quota" in error_msg.lower(): return "⚠️ **系統提示:**\n\n目前吧台前詢問的客人較多,小助手稍微忙不過來了!請您先喝杯水,稍等幾分鐘後再試喔!" elif "500" in error_msg: return "🔄 **系統提示:**\n\n小助手剛剛去倒了杯酒(連線短暫不穩定),請您再點擊發送一次剛剛的問題喔!" else: return f"❌ **發生預期外錯誤**\n\n訊息:{error_msg}" # 6. 對話邏輯 prompt = st.chat_input("想喝點什麼?或是想了解什麼資訊呢?") if st.session_state.example_prompt: prompt = st.session_state.example_prompt st.session_state.example_prompt = None if prompt: st.session_state.messages.append({"role": "user", "content": prompt}) for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) with st.chat_message("assistant"): response_text = get_gemini_response(prompt) st.markdown(response_text) st.session_state.messages.append({"role": "assistant", "content": response_text}) st.rerun() else: for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"])