# ui/components/modals.py import gradio as gr from config import MODEL_OPTIONS, DEFAULT_PROVIDER, DEFAULT_MODEL, GROQ_FAST_MODEL_OPTIONS from config import BASE_DIR import re def create_validated_input(label, placeholder, type="password"): """ Helper: 創建自動驗證的輸入框組件 (無按鈕版) 回傳: (textbox, status_markdown) """ with gr.Group(): key_input = gr.Textbox( label=label, placeholder=placeholder, type=type, elem_classes="modern-input" ) # 狀態訊息預設為空,驗證後顯示 status_output = gr.Markdown(value="", elem_classes="api-status-msg") return key_input, status_output def create_settings_modal(): with gr.Group(visible=False, elem_classes="modal-overlay", elem_id="settings-modal") as modal: with gr.Group(elem_classes="modal-box"): with gr.Row(elem_classes="modal-header"): gr.Markdown("### ⚙️ Hybrid System Configuration", elem_classes="modal-title") with gr.Column(elem_classes="modal-content"): with gr.Tabs(): # === 1. Services === with gr.TabItem("🌍 Services"): gr.Markdown("External services for maps and weather.", elem_classes="tab-desc") # 🔥 使用新 Helper (沒有按鈕了) g_key, g_stat = create_validated_input("Google Maps Key", "AIza...") w_key, w_stat = create_validated_input("OpenWeather Key", "Enter key...") # === 2. Primary Brain === with gr.TabItem("🧠 Primary Brain"): gr.Markdown("Select the main intelligence.", elem_classes="tab-desc") # 這裡佈局稍微調整,把 Key 單獨放一行,比較乾淨 llm_provider = gr.Dropdown( choices=list(MODEL_OPTIONS.keys()), value=DEFAULT_PROVIDER, label="Main Provider", interactive=True, elem_classes="modern-dropdown" ) # 主模型 Key main_key, main_stat = create_validated_input("Main Model API Key", "sk-...") model_sel = gr.Dropdown( choices=MODEL_OPTIONS[DEFAULT_PROVIDER], value=DEFAULT_MODEL, label="Main Model", interactive=True, elem_classes="modern-dropdown" ) # === 3. Acceleration === with gr.TabItem("⚡ Acceleration"): gr.Markdown("Configure Groq for speed.", elem_classes="tab-desc") fast_mode_chk = gr.Checkbox( label="Enable Fast Sub-Mode", value=False, elem_classes="modern-checkbox" ) groq_model_sel = gr.Dropdown( choices=GROQ_FAST_MODEL_OPTIONS, value=GROQ_FAST_MODEL_OPTIONS[0][1], label="Model", elem_classes="modern-dropdown", visible = False # <--- 預設隱藏 ) # Groq Key groq_key, groq_stat = create_validated_input("Groq API Key", "gsk_...") # 手動設定初始狀態為隱藏 (Gradio 小技巧: 定義後再 update) groq_key.visible = False # 全局狀態 set_stat = gr.Markdown(value="", visible=True) with gr.Row(elem_classes="modal-footer"): close_btn = gr.Button("Cancel", variant="secondary", elem_classes="btn-cancel") save_btn = gr.Button("💾 Save", variant="primary", elem_id="btn-save-config") # 🔥 回傳清單變短了 (少了 4 個按鈕) return (modal, g_key, g_stat, w_key, w_stat, llm_provider, main_key, main_stat, model_sel, fast_mode_chk, groq_model_sel, groq_key, groq_stat, close_btn, save_btn, set_stat) def create_doc_modal(): """ 創建文檔模態框 功能:自動讀取 README.md 並【強力過濾】YAML Front Matter """ readme_path = BASE_DIR / "README.md" doc_content = "" try: if readme_path.exists(): with open(readme_path, "r", encoding="utf-8") as f: raw_content = f.read() # 🔥🔥🔥 [Regex 強力修正] 🔥🔥🔥 # 說明: # ^---\s*\n -> 匹配開頭的 --- (允許後面有空白) 和 換行 # .*? -> 非貪婪匹配中間的所有內容 (包含換行) # \n---\s*\n? -> 匹配結尾的換行 + --- + 結尾可能有的換行 # re.DOTALL -> 讓 . 可以匹配換行符號 # re.MULTILINE-> 讓 ^ 可以匹配檔案開頭 yaml_pattern = r"^---\s*\n.*?\n---\s*\n?" # 使用 sub 將匹配到的 YAML 區塊替換為空字串 clean_content = re.sub(yaml_pattern, "", raw_content, count=1, flags=re.DOTALL | re.MULTILINE) doc_content = clean_content.strip() else: doc_content = "## ⚠️ Documentation Not Found" except Exception as e: doc_content = f"## ❌ Error Loading Documentation\n\n{str(e)}" with gr.Group(visible=False, elem_classes="modal-overlay", elem_id="doc-modal") as doc_modal: with gr.Group(elem_classes="modal-box"): with gr.Row(elem_classes="modal-header"): gr.Markdown("### 📖 Documentation", elem_classes="modal-title") with gr.Column(elem_classes="modal-content"): gr.Markdown(doc_content) with gr.Row(elem_classes="modal-footer"): close_doc_btn = gr.Button("Close", variant="secondary", elem_classes="btn-cancel") return doc_modal, close_doc_btn