Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import google.generativeai as genai | |
| import os | |
| # 0. 頁面配置與 CSS 注入(隱藏側邊欄捲軸) | |
| st.set_page_config(page_title="育兒成 - 兒童發展線上小助手", page_icon="🧸", layout="wide") | |
| st.markdown( | |
| """ | |
| <style> | |
| /* 1. 隱藏側邊欄捲軸 */ | |
| [data-testid="stSidebar"] section::-webkit-scrollbar { display: none; } | |
| [data-testid="stSidebar"] section { -ms-overflow-style: none; scrollbar-width: none; } | |
| /* 2. 全域字體縮小,適應手機螢幕 */ | |
| html, body, [class*="st-"] { | |
| font-size: 14px !important; | |
| } | |
| /* 3. 調整範例按鈕樣式:變矮、字體變小、支援多行自動折行 */ | |
| .stButton button { | |
| width: 100%; | |
| padding: 6px 10px; /* 縮小上下內距 */ | |
| border-radius: 12px; | |
| border: 1px solid #4CAF50; /* 溫暖的綠色系 */ | |
| color: #4CAF50; | |
| background-color: transparent; | |
| font-size: 13px !important; | |
| line-height: 1.3; | |
| min-height: auto; | |
| white-space: normal; | |
| height: auto; | |
| } | |
| /* 4. 極度壓縮主畫面空白 */ | |
| .block-container { | |
| padding-top: 1.2rem !important; | |
| padding-bottom: 5rem !important; | |
| padding-left: 0.8rem !important; | |
| padding-right: 0.8rem !important; | |
| } | |
| /* 5. 隱藏 Header 背景與右側選單,但保留左側展開按鈕 */ | |
| header[data-testid="stHeader"] { | |
| background-color: rgba(0,0,0,0) !important; | |
| } | |
| header[data-testid="stHeader"] #MainMenu { | |
| visibility: hidden; | |
| } | |
| div[data-testid="collapsedControl"] { | |
| visibility: visible !important; | |
| background-color: #F0F2F6 !important; | |
| color: #4CAF50 !important; | |
| border-radius: 0 5px 5px 0; | |
| top: 5px; | |
| } | |
| </style> | |
| """, | |
| 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() | |
| genai.configure(api_key=api_key) | |
| # 1. 基礎設定與連結 | |
| HOME_URL = "https://kidaid.org.tw/" | |
| # 設定本地端的 MD 檔案對應字典 (大小寫必須與上傳的檔名完全一致) | |
| KNOWLEDGE_MAP = { | |
| "🧠 認知發展": "Cognition_Content.md", | |
| "🗣️ 語言發展": "Language.md", | |
| "🏃 粗大動作": "GrossMotor.md", | |
| "🖐️ 精細動作": "FineMotor.md", | |
| "❤️ 社會情緒": "Emotion.md", | |
| "🥦 飲食對策": "CountermeasuresDietary.md", | |
| "🧩 ASD/早療對策": "CountermeasuresADHD.md" | |
| } | |
| # 2. 批量讀取本地端檔案內容 (使用絕對路徑) | |
| def fetch_all_knowledge(): | |
| combined_knowledge = "" | |
| # 自動獲取 app.py 所在的資料夾絕對路徑,確保雲端環境找得到檔案 | |
| base_dir = os.path.dirname(os.path.abspath(__file__)) | |
| with st.spinner("正在載入育兒發展與療育資訊..."): | |
| for category, filename in KNOWLEDGE_MAP.items(): | |
| # 將資料夾路徑與檔案名稱結合 | |
| file_path = os.path.join(base_dir, filename) | |
| try: | |
| with open(file_path, "r", encoding="utf-8") as f: | |
| combined_knowledge += f"\n\n## 【領域:{category}】\n" | |
| combined_knowledge += f.read() | |
| except FileNotFoundError: | |
| st.warning(f"無法載入 {category}:系統在路徑 `{file_path}` 找不到檔案,請確認檔名大小寫與上傳位置。") | |
| except Exception as e: | |
| st.error(f"讀取 {filename} 時發生錯誤:{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: | |
| st.title("⚙️ 知識庫狀態") | |
| st.caption("以下資料嘗試從系統載入中:") | |
| for category, filename in KNOWLEDGE_MAP.items(): | |
| st.markdown(f"📄 **{category}** (`{filename}`)") | |
| st.markdown("---") | |
| st.markdown(f"🔗 [前往 育兒成 官方網站]({HOME_URL})") | |
| if st.button("🔄 重新載入知識庫"): | |
| st.session_state.knowledge = fetch_all_knowledge() | |
| st.success("資料已重新載入!") | |
| st.markdown("---") | |
| # 開發者偵錯工具:按下去可以看到系統當下資料夾裡到底有哪些檔案 | |
| with st.expander("🛠️ 開發者偵錯:檢查檔案列表"): | |
| base_dir = os.path.dirname(os.path.abspath(__file__)) | |
| st.write(f"目前工作目錄:`{base_dir}`") | |
| st.write("目前資料夾內的所有檔案與資料夾:") | |
| try: | |
| st.write(os.listdir(base_dir)) | |
| except Exception as e: | |
| st.write(f"無法讀取目錄內容:{e}") | |
| # 4. 主介面與範例問句 | |
| st.title("🧸 育兒成 - 線上衛教小助手") | |
| st.caption("關於 0-5 歲兒童的認知、語言、動作、情緒、飲食及早療對策,歡迎直接詢問!(資料來源:育兒成 | 全方位兒童發展整合照護平臺)") | |
| # 顯示專屬範例問句按鈕 | |
| example_cols = st.columns(5) | |
| examples = [ | |
| "🥦 小孩挑食不吃青菜,有什麼應對的小撇步嗎?", | |
| "🏃 2-3歲的孩子粗大動作應該發展到什麼程度?", | |
| "🖐️ 怎麼訓練 4-5 歲幼兒的握筆姿勢?", | |
| "🧩 ASD 孩子一直重複旋轉硬幣,我該立刻制止他嗎?", | |
| "❤️ 孩子生氣時容易崩潰,該怎麼設立冷靜角落?" | |
| ] | |
| 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""" | |
| 你現在是『育兒成 | 全方位兒童發展整合照護平臺』的專業線上客服與育兒小助手。 | |
| 你的說話風格溫柔、有耐心、具備同理心,且充滿專業知識,就像一位溫暖的早期療育專家或兒童發展衛教師。 | |
| 以下是本平台最新的衛教資訊(包含認知、語言、粗大動作、精細動作、社會情緒、飲食、ASD早療對策等): | |
| --- | |
| {st.session_state.knowledge} | |
| --- | |
| 請嚴格基於上述提供的資訊來回答家長的問題。將複雜的衛教知識轉化為容易理解的實作建議。 | |
| 如果家長問了超出了上述資訊範圍的醫療診斷問題,請溫和地告知:「小助手目前手邊沒有相關資訊。每個孩子的發展狀況不同,建議您可以諮詢專業的小兒科醫師或尋求早期療育評估喔!」 | |
| """ | |
| try: | |
| model = genai.GenerativeModel( | |
| model_name="gemini-flash-lite-latest", | |
| system_instruction=system_instruction | |
| ) | |
| chat = model.start_chat(history=[]) | |
| response = chat.send_message(user_input) | |
| return response.text | |
| except Exception as e: | |
| error_msg = str(e) | |
| if "429" in error_msg or "quota" in error_msg.lower(): | |
| 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"]) |