1 line
1.1 MiB
Plaintext
1 line
1.1 MiB
Plaintext
{"repo": "barbelldwarf/sermonpilot", "files": {"ui/ui_pages/settings.py": {"ec5965e809436578132a5213fa4709fd5169fb48": {"t": 1785746918.500412, "n": 74251, "h": "c7b4af04c807f430", "c": "\"\"\"\nSettings Page for SermonPilot\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\",\n \"📝 Prompt Templates\"\n ], key=\"settings_tabs\")\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\n with tab8:\n show_prompt_templates()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_prompt_templates():\n \"\"\"Prompt template editor for LLM generation tasks.\"\"\"\n st.markdown(\"### 📝 Prompt Templates\")\n st.caption(\"Customize the instructions sent to the LLM for each generation task. \"\n \"Changes take effect immediately on save.\")\n\n config = st.session_state.get('config') or {}\n templates = config.get('prompt_templates', {})\n\n template_names = {\n \"title\": \"Title Generation\",\n \"short_title\": \"Short Title Generation\",\n \"description\": \"Description Generation\",\n \"hashtags\": \"Hashtag Generation\",\n \"hashtag_verification\": \"Hashtag Verification\",\n \"description_validation\": \"Description Validation\",\n }\n\n for key, label in template_names.items():\n tmpl = templates.get(key, {})\n with st.expander(f\"{'✅' if tmpl.get('enabled', True) else '⏸️'} {label}\", expanded=False):\n enabled = st.checkbox(\"Enabled\", value=tmpl.get('enabled', True), key=f\"pt_{key}_enabled\")\n system_prompt = st.text_area(\n \"System Prompt\",\n value=tmpl.get('system', ''),\n height=60,\n key=f\"pt_{key}_system\",\n help=\"System-level instruction for the LLM. Leave empty to omit.\"\n )\n user_prompt = st.text_area(\n \"User Prompt\",\n value=tmpl.get('user', ''),\n height=200,\n key=f\"pt_{key}_user\",\n help=\"User message template. Use {variable} placeholders for dynamic content.\"\n )\n st.caption(f\"Available variables: {_get_template_vars(key)}\")\n\n if st.button(\"💾 Save Prompt Templates\", type=\"primary\"):\n save_prompt_templates()\n st.success(\"✅ Prompt templates saved!\")\n\n\ndef _get_template_vars(template_key: str) -> str:\n vars_map = {\n \"title\": \"{context}, {transcript}\",\n \"short_title\": \"{full_title}\",\n \"description\": \"{role_desc}, {body_desc}, {transcript}, {speaker_instruction}\",\n \"hashtags\": \"{text}\",\n \"hashtag_verification\": \"{initial_hashtags}, {original_text}\",\n \"description_validation\": \"{context_info}, {criteria_text}, {description}\",\n }\n return vars_map.get(template_key, \"{}\")\n\n\ndef save_prompt_templates():\n \"\"\"Save prompt templates from session state to config.\"\"\"\n config = st.session_state.get('config', {})\n if 'prompt_templates' not in config:\n config['prompt_templates'] = {}\n\n template_keys = [\n \"title\", \"short_title\", \"description\", \"hashtags\",\n \"hashtag_verification\", \"description_validation\",\n ]\n\n for key in template_keys:\n enabled = st.session_state.get(f\"pt_{key}_enabled\", True)\n system_val = st.session_state.get(f\"pt_{key}_system\", \"\")\n user_val = st.session_state.get(f\"pt_{key}_user\", \"\")\n config['prompt_templates'][key] = {\n 'enabled': enabled,\n 'system': system_val,\n 'user': user_val,\n }\n\n from config_utils import save_config_to_file as _save_config\n _save_config(config)\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n full_config = st.session_state.get('config', {})\n llm_manager = LLMManager(full_config)\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response:\n cleaned = str(test_response).strip().strip('\"').strip(\"'\").strip()\n if cleaned.upper() == 'OK' or 'OK' in cleaned.upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {cleaned[:100]}\")\n else:\n st.error(f\"❌ {provider.title()} returned empty response\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "0ea6dd0874d2a394aa64b2c7ad14b9af80a8e245": {"t": 1785768967.9707778, "n": 74251, "h": "c7b4af04c807f430", "c": "\"\"\"\nSettings Page for SermonPilot\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\",\n \"📝 Prompt Templates\"\n ], key=\"settings_tabs\")\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\n with tab8:\n show_prompt_templates()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_prompt_templates():\n \"\"\"Prompt template editor for LLM generation tasks.\"\"\"\n st.markdown(\"### 📝 Prompt Templates\")\n st.caption(\"Customize the instructions sent to the LLM for each generation task. \"\n \"Changes take effect immediately on save.\")\n\n config = st.session_state.get('config') or {}\n templates = config.get('prompt_templates', {})\n\n template_names = {\n \"title\": \"Title Generation\",\n \"short_title\": \"Short Title Generation\",\n \"description\": \"Description Generation\",\n \"hashtags\": \"Hashtag Generation\",\n \"hashtag_verification\": \"Hashtag Verification\",\n \"description_validation\": \"Description Validation\",\n }\n\n for key, label in template_names.items():\n tmpl = templates.get(key, {})\n with st.expander(f\"{'✅' if tmpl.get('enabled', True) else '⏸️'} {label}\", expanded=False):\n enabled = st.checkbox(\"Enabled\", value=tmpl.get('enabled', True), key=f\"pt_{key}_enabled\")\n system_prompt = st.text_area(\n \"System Prompt\",\n value=tmpl.get('system', ''),\n height=60,\n key=f\"pt_{key}_system\",\n help=\"System-level instruction for the LLM. Leave empty to omit.\"\n )\n user_prompt = st.text_area(\n \"User Prompt\",\n value=tmpl.get('user', ''),\n height=200,\n key=f\"pt_{key}_user\",\n help=\"User message template. Use {variable} placeholders for dynamic content.\"\n )\n st.caption(f\"Available variables: {_get_template_vars(key)}\")\n\n if st.button(\"💾 Save Prompt Templates\", type=\"primary\"):\n save_prompt_templates()\n st.success(\"✅ Prompt templates saved!\")\n\n\ndef _get_template_vars(template_key: str) -> str:\n vars_map = {\n \"title\": \"{context}, {transcript}\",\n \"short_title\": \"{full_title}\",\n \"description\": \"{role_desc}, {body_desc}, {transcript}, {speaker_instruction}\",\n \"hashtags\": \"{text}\",\n \"hashtag_verification\": \"{initial_hashtags}, {original_text}\",\n \"description_validation\": \"{context_info}, {criteria_text}, {description}\",\n }\n return vars_map.get(template_key, \"{}\")\n\n\ndef save_prompt_templates():\n \"\"\"Save prompt templates from session state to config.\"\"\"\n config = st.session_state.get('config', {})\n if 'prompt_templates' not in config:\n config['prompt_templates'] = {}\n\n template_keys = [\n \"title\", \"short_title\", \"description\", \"hashtags\",\n \"hashtag_verification\", \"description_validation\",\n ]\n\n for key in template_keys:\n enabled = st.session_state.get(f\"pt_{key}_enabled\", True)\n system_val = st.session_state.get(f\"pt_{key}_system\", \"\")\n user_val = st.session_state.get(f\"pt_{key}_user\", \"\")\n config['prompt_templates'][key] = {\n 'enabled': enabled,\n 'system': system_val,\n 'user': user_val,\n }\n\n from config_utils import save_config_to_file as _save_config\n _save_config(config)\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n full_config = st.session_state.get('config', {})\n llm_manager = LLMManager(full_config)\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response:\n cleaned = str(test_response).strip().strip('\"').strip(\"'\").strip()\n if cleaned.upper() == 'OK' or 'OK' in cleaned.upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {cleaned[:100]}\")\n else:\n st.error(f\"❌ {provider.title()} returned empty response\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "64667502f5d637dfe548844b4de07e5a5eb790d7": {"t": 1785768969.6934521, "n": 74025, "h": "a673fab47df494ce", "c": "\"\"\"\nSettings Page for SermonPilot\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\",\n \"📝 Prompt Templates\"\n ], key=\"settings_tabs\")\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\n with tab8:\n show_prompt_templates()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_prompt_templates():\n \"\"\"Prompt template editor for LLM generation tasks.\"\"\"\n st.markdown(\"### 📝 Prompt Templates\")\n st.caption(\"Customize the instructions sent to the LLM for each generation task. \"\n \"Changes take effect immediately on save.\")\n\n config = st.session_state.get('config') or {}\n templates = config.get('prompt_templates', {})\n\n template_names = {\n \"title\": \"Title Generation\",\n \"short_title\": \"Short Title Generation\",\n \"description\": \"Description Generation\",\n \"hashtags\": \"Hashtag Generation\",\n \"hashtag_verification\": \"Hashtag Verification\",\n \"description_validation\": \"Description Validation\",\n }\n\n for key, label in template_names.items():\n tmpl = templates.get(key, {})\n with st.expander(f\"{'✅' if tmpl.get('enabled', True) else '⏸️'} {label}\", expanded=False):\n enabled = st.checkbox(\"Enabled\", value=tmpl.get('enabled', True), key=f\"pt_{key}_enabled\")\n system_prompt = st.text_area(\n \"System Prompt\",\n value=tmpl.get('system', ''),\n height=60,\n key=f\"pt_{key}_system\",\n help=\"System-level instruction for the LLM. Leave empty to omit.\"\n )\n user_prompt = st.text_area(\n \"User Prompt\",\n value=tmpl.get('user', ''),\n height=200,\n key=f\"pt_{key}_user\",\n help=\"User message template. Use {variable} placeholders for dynamic content.\"\n )\n st.caption(f\"Available variables: {_get_template_vars(key)}\")\n\n if st.button(\"💾 Save Prompt Templates\", type=\"primary\"):\n save_prompt_templates()\n st.success(\"✅ Prompt templates saved!\")\n\n\ndef _get_template_vars(template_key: str) -> str:\n vars_map = {\n \"title\": \"{context}, {transcript}\",\n \"short_title\": \"{full_title}\",\n \"description\": \"{role_desc}, {body_desc}, {transcript}, {speaker_instruction}\",\n \"hashtags\": \"{text}\",\n \"hashtag_verification\": \"{initial_hashtags}, {original_text}\",\n \"description_validation\": \"{context_info}, {criteria_text}, {description}\",\n }\n return vars_map.get(template_key, \"{}\")\n\n\ndef save_prompt_templates():\n \"\"\"Save prompt templates from session state to config.\"\"\"\n config = st.session_state.get('config', {})\n if 'prompt_templates' not in config:\n config['prompt_templates'] = {}\n\n template_keys = [\n \"title\", \"short_title\", \"description\", \"hashtags\",\n \"hashtag_verification\", \"description_validation\",\n ]\n\n for key in template_keys:\n enabled = st.session_state.get(f\"pt_{key}_enabled\", True)\n system_val = st.session_state.get(f\"pt_{key}_system\", \"\")\n user_val = st.session_state.get(f\"pt_{key}_user\", \"\")\n config['prompt_templates'][key] = {\n 'enabled': enabled,\n 'system': system_val,\n 'user': user_val,\n }\n\n from config_utils import save_config_to_file as _save_config\n _save_config(config)\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "80be777c87009f568f92ab04287a6f5ce92a0f70": {"t": 1785768974.446001, "n": 74004, "h": "a2ab44c0a0436b9e", "c": "\"\"\"\nSettings Page for SermonPilot\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\",\n \"📝 Prompt Templates\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\n with tab8:\n show_prompt_templates()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_prompt_templates():\n \"\"\"Prompt template editor for LLM generation tasks.\"\"\"\n st.markdown(\"### 📝 Prompt Templates\")\n st.caption(\"Customize the instructions sent to the LLM for each generation task. \"\n \"Changes take effect immediately on save.\")\n\n config = st.session_state.get('config') or {}\n templates = config.get('prompt_templates', {})\n\n template_names = {\n \"title\": \"Title Generation\",\n \"short_title\": \"Short Title Generation\",\n \"description\": \"Description Generation\",\n \"hashtags\": \"Hashtag Generation\",\n \"hashtag_verification\": \"Hashtag Verification\",\n \"description_validation\": \"Description Validation\",\n }\n\n for key, label in template_names.items():\n tmpl = templates.get(key, {})\n with st.expander(f\"{'✅' if tmpl.get('enabled', True) else '⏸️'} {label}\", expanded=False):\n enabled = st.checkbox(\"Enabled\", value=tmpl.get('enabled', True), key=f\"pt_{key}_enabled\")\n system_prompt = st.text_area(\n \"System Prompt\",\n value=tmpl.get('system', ''),\n height=60,\n key=f\"pt_{key}_system\",\n help=\"System-level instruction for the LLM. Leave empty to omit.\"\n )\n user_prompt = st.text_area(\n \"User Prompt\",\n value=tmpl.get('user', ''),\n height=200,\n key=f\"pt_{key}_user\",\n help=\"User message template. Use {variable} placeholders for dynamic content.\"\n )\n st.caption(f\"Available variables: {_get_template_vars(key)}\")\n\n if st.button(\"💾 Save Prompt Templates\", type=\"primary\"):\n save_prompt_templates()\n st.success(\"✅ Prompt templates saved!\")\n\n\ndef _get_template_vars(template_key: str) -> str:\n vars_map = {\n \"title\": \"{context}, {transcript}\",\n \"short_title\": \"{full_title}\",\n \"description\": \"{role_desc}, {body_desc}, {transcript}, {speaker_instruction}\",\n \"hashtags\": \"{text}\",\n \"hashtag_verification\": \"{initial_hashtags}, {original_text}\",\n \"description_validation\": \"{context_info}, {criteria_text}, {description}\",\n }\n return vars_map.get(template_key, \"{}\")\n\n\ndef save_prompt_templates():\n \"\"\"Save prompt templates from session state to config.\"\"\"\n config = st.session_state.get('config', {})\n if 'prompt_templates' not in config:\n config['prompt_templates'] = {}\n\n template_keys = [\n \"title\", \"short_title\", \"description\", \"hashtags\",\n \"hashtag_verification\", \"description_validation\",\n ]\n\n for key in template_keys:\n enabled = st.session_state.get(f\"pt_{key}_enabled\", True)\n system_val = st.session_state.get(f\"pt_{key}_system\", \"\")\n user_val = st.session_state.get(f\"pt_{key}_user\", \"\")\n config['prompt_templates'][key] = {\n 'enabled': enabled,\n 'system': system_val,\n 'user': user_val,\n }\n\n from config_utils import save_config_to_file as _save_config\n _save_config(config)\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "b4444520d4ff677f251991c5f15d9496a59b20a7": {"t": 1785768979.1068232, "n": 74014, "h": "9f81bfb6aa317e46", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\",\n \"📝 Prompt Templates\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\n with tab8:\n show_prompt_templates()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_prompt_templates():\n \"\"\"Prompt template editor for LLM generation tasks.\"\"\"\n st.markdown(\"### 📝 Prompt Templates\")\n st.caption(\"Customize the instructions sent to the LLM for each generation task. \"\n \"Changes take effect immediately on save.\")\n\n config = st.session_state.get('config') or {}\n templates = config.get('prompt_templates', {})\n\n template_names = {\n \"title\": \"Title Generation\",\n \"short_title\": \"Short Title Generation\",\n \"description\": \"Description Generation\",\n \"hashtags\": \"Hashtag Generation\",\n \"hashtag_verification\": \"Hashtag Verification\",\n \"description_validation\": \"Description Validation\",\n }\n\n for key, label in template_names.items():\n tmpl = templates.get(key, {})\n with st.expander(f\"{'✅' if tmpl.get('enabled', True) else '⏸️'} {label}\", expanded=False):\n enabled = st.checkbox(\"Enabled\", value=tmpl.get('enabled', True), key=f\"pt_{key}_enabled\")\n system_prompt = st.text_area(\n \"System Prompt\",\n value=tmpl.get('system', ''),\n height=60,\n key=f\"pt_{key}_system\",\n help=\"System-level instruction for the LLM. Leave empty to omit.\"\n )\n user_prompt = st.text_area(\n \"User Prompt\",\n value=tmpl.get('user', ''),\n height=200,\n key=f\"pt_{key}_user\",\n help=\"User message template. Use {variable} placeholders for dynamic content.\"\n )\n st.caption(f\"Available variables: {_get_template_vars(key)}\")\n\n if st.button(\"💾 Save Prompt Templates\", type=\"primary\"):\n save_prompt_templates()\n st.success(\"✅ Prompt templates saved!\")\n\n\ndef _get_template_vars(template_key: str) -> str:\n vars_map = {\n \"title\": \"{context}, {transcript}\",\n \"short_title\": \"{full_title}\",\n \"description\": \"{role_desc}, {body_desc}, {transcript}, {speaker_instruction}\",\n \"hashtags\": \"{text}\",\n \"hashtag_verification\": \"{initial_hashtags}, {original_text}\",\n \"description_validation\": \"{context_info}, {criteria_text}, {description}\",\n }\n return vars_map.get(template_key, \"{}\")\n\n\ndef save_prompt_templates():\n \"\"\"Save prompt templates from session state to config.\"\"\"\n config = st.session_state.get('config', {})\n if 'prompt_templates' not in config:\n config['prompt_templates'] = {}\n\n template_keys = [\n \"title\", \"short_title\", \"description\", \"hashtags\",\n \"hashtag_verification\", \"description_validation\",\n ]\n\n for key in template_keys:\n enabled = st.session_state.get(f\"pt_{key}_enabled\", True)\n system_val = st.session_state.get(f\"pt_{key}_system\", \"\")\n user_val = st.session_state.get(f\"pt_{key}_user\", \"\")\n config['prompt_templates'][key] = {\n 'enabled': enabled,\n 'system': system_val,\n 'user': user_val,\n }\n\n from config_utils import save_config_to_file as _save_config\n _save_config(config)\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "d0e33fb339864cda9817e226a8a94c6b4f86fe5e": {"t": 1785768982.340716, "n": 70841, "h": "b3f0c76853dee65f", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend_options = [\"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"]\n display_names = {\n \"faster_whisper_local\": \"Faster Whisper (Local)\",\n \"whisper_openai\": \"OpenAI Whisper API\",\n \"whisper_openrouter\": \"OpenRouter Whisper API\",\n }\n current_backend = transcription_cfg.get('backend', 'faster_whisper_local')\n current_index = backend_options.index(current_backend) if current_backend in backend_options else 0\n backend_key = st.selectbox(\n \"Transcription Backend\",\n options=backend_options,\n format_func=lambda x: display_names.get(x, x),\n index=current_index,\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend_key == \"faster_whisper_local\":\n local_cfg = transcription_cfg.get('faster_whisper_local', {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, local_model, device, compute_type, language, None, None, None)\n\n elif backend_key == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\n elif backend_key == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend_key, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "dae45f4f03aa0a5f4def6983fabbc1b68d68f20a": {"t": 1785768985.265895, "n": 70522, "h": "ab4a86df6d79214d", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"custom\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready. Custom: point to any ONNX model on HuggingFace.\"\n )\n\n if enhancement_method == \"custom\":\n st.caption(\"Configure a custom ONNX model from HuggingFace:\")\n custom_repo = st.text_input(\"HF Repo (e.g. tonythethompson/DeepFilterNet3-ONNX)\",\n value=config.get('clear_custom_repo', ''),\n key=\"settings_custom_repo\")\n custom_file = st.text_input(\"ONNX filename (e.g. model.onnx)\",\n value=config.get('clear_custom_file', ''),\n key=\"settings_custom_file\")\n if custom_repo and custom_file:\n st.info(f\"Will use: **{custom_repo}/{custom_file}**\")\n else:\n st.warning(\"Enter both a HuggingFace repo and ONNX filename.\")\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend = st.selectbox(\n \"Transcription Backend\",\n options=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"],\n index=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"].index(\n transcription_cfg.get('backend', 'faster_whisper_local')\n ),\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n local_cfg = transcription_cfg.get(backend, {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, local_model, device, compute_type, language, None, None, None)\n\n elif backend == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\n elif backend == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "d5f4edf0269b8c1e872e47d1feab4e1b182eb729": {"t": 1785768987.7587566, "n": 69710, "h": "d59513dedb9d9fee", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n methods = [\"deepfilternet\", \"clear-natural\", \"clear-studio\", \"none\"]\n current = config.get('audio_enhancement_method', 'deepfilternet')\n index = methods.index(current) if current in methods else 0\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=methods,\n index=index,\n help=\"DeepFilterNet: standard (best for speech). Clear-Natural: gentler noise suppression. Clear-Studio: aggressive, podcast-ready.\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend = st.selectbox(\n \"Transcription Backend\",\n options=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"],\n index=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"].index(\n transcription_cfg.get('backend', 'faster_whisper_local')\n ),\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n local_cfg = transcription_cfg.get(backend, {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, local_model, device, compute_type, language, None, None, None)\n\n elif backend == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\n elif backend == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "4dc004fcde08425d24680bff735e24d3b4bbc7a2": {"t": 1785768989.73923, "n": 69600, "h": "9c94fa4e4c2052fa", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"clear\", \"deepfilternet\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'clear' else\n 1 if config.get('audio_enhancement_method') == 'deepfilternet' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend = st.selectbox(\n \"Transcription Backend\",\n options=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"],\n index=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"].index(\n transcription_cfg.get('backend', 'faster_whisper_local')\n ),\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n local_cfg = transcription_cfg.get(backend, {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, local_model, device, compute_type, language, None, None, None)\n\n elif backend == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\n elif backend == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "46365ed70c1913c7b09aeb076c0d357aac2ddeca": {"t": 1785768991.5430737, "n": 69622, "h": "75ebb66312d499a6", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"📝 Transcription\",\n \"✅ Validation\",\n \"🗄️ Advanced\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_transcription_settings()\n\n with tab6:\n show_validation_settings()\n\n with tab7:\n show_advanced_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else\n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_transcription_settings():\n \"\"\"Transcription configuration\"\"\"\n st.markdown(\"### 📝 Transcription Configuration\")\n\n config = st.session_state.get('config') or {}\n transcription_cfg = config.get('transcription', {})\n\n st.markdown(\"#### 🔧 Backend\")\n backend = st.selectbox(\n \"Transcription Backend\",\n options=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"],\n index=[\"whisper_local\", \"faster_whisper_local\", \"whisper_openai\", \"whisper_openrouter\"].index(\n transcription_cfg.get('backend', 'faster_whisper_local')\n ),\n help=\"Local Whisper (runs on your machine) or API-based transcription\"\n )\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n local_cfg = transcription_cfg.get(backend, {})\n\n st.markdown(\"#### 💻 Local Whisper Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n local_model = st.selectbox(\n \"Model\",\n options=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"],\n index=[\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"].index(\n local_cfg.get('model', 'base')\n ) if local_cfg.get('model', 'base') in [\"tiny\", \"tiny.en\", \"base\", \"base.en\", \"small\", \"small.en\", \"medium\", \"medium.en\", \"large\", \"large-v2\", \"large-v3\", \"large-v3-turbo\"] else 2,\n help=\"Model size. Larger = better accuracy but slower\"\n )\n device = st.selectbox(\n \"Device\",\n options=[\"auto\", \"cpu\", \"cuda\"],\n index=[\"auto\", \"cpu\", \"cuda\"].index(local_cfg.get('device', 'auto')),\n help=\"Compute device\"\n )\n\n with col2:\n compute_type = st.selectbox(\n \"Compute Type\",\n options=[\"float16\", \"float32\", \"int8_float16\", \"int8\"],\n index=[\"float16\", \"float32\", \"int8_float16\", \"int8\"].index(local_cfg.get('compute_type', 'float16')),\n help=\"Floating point precision for the model\"\n )\n language = st.text_input(\n \"Language\",\n value=local_cfg.get('language', 'en'),\n help=\"Language code (e.g. 'en' for English)\"\n )\n\n if st.button(\"💾 Save Local Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, local_model, device, compute_type, language, None, None, None)\n\n elif backend == \"whisper_openai\":\n openai_cfg = transcription_cfg.get('whisper_openai', {})\n\n st.markdown(\"#### ☁️ OpenAI API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=openai_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenAI API key (or set OPENAI_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=openai_cfg.get('base_url', 'https://api.openai.com/v1'),\n help=\"OpenAI-compatible API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=openai_cfg.get('model', 'whisper-1'),\n help=\"API model name (e.g. whisper-1)\"\n )\n\n if st.button(\"💾 Save OpenAI Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\n elif backend == \"whisper_openrouter\":\n or_cfg = transcription_cfg.get('whisper_openrouter', {})\n\n st.markdown(\"#### 🌐 OpenRouter API Settings\")\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=or_cfg.get('api_key', ''),\n type=\"password\",\n help=\"OpenRouter API key (or set OPENROUTER_API_KEY env var)\"\n )\n base_url = st.text_input(\n \"Base URL\",\n value=or_cfg.get('base_url', 'https://openrouter.ai/api/v1'),\n help=\"OpenRouter API endpoint\"\n )\n\n with col2:\n model = st.text_input(\n \"Model\",\n value=or_cfg.get('model', 'openai/whisper-large-v3'),\n help=\"API model name\"\n )\n\n if st.button(\"💾 Save OpenRouter Transcription Settings\", type=\"primary\"):\n save_transcription_settings(backend, None, None, None, None, api_key, base_url, model)\n\ndef save_transcription_settings(backend, local_model, device, compute_type, language, api_key, base_url, model):\n \"\"\"Save transcription settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n config = st.session_state.config\n if 'transcription' not in config:\n config['transcription'] = {}\n\n config['transcription']['backend'] = backend\n\n if backend in (\"whisper_local\", \"faster_whisper_local\"):\n if backend not in config['transcription']:\n config['transcription'][backend] = {}\n config['transcription'][backend]['model'] = local_model\n config['transcription'][backend]['device'] = device\n config['transcription'][backend]['compute_type'] = compute_type\n config['transcription'][backend]['language'] = language\n\n elif backend == \"whisper_openai\":\n if 'whisper_openai' not in config['transcription']:\n config['transcription']['whisper_openai'] = {}\n if api_key:\n config['transcription']['whisper_openai']['api_key'] = api_key\n config['transcription']['whisper_openai']['base_url'] = base_url\n config['transcription']['whisper_openai']['model'] = model\n\n elif backend == \"whisper_openrouter\":\n if 'whisper_openrouter' not in config['transcription']:\n config['transcription']['whisper_openrouter'] = {}\n if api_key:\n config['transcription']['whisper_openrouter']['api_key'] = api_key\n config['transcription']['whisper_openrouter']['base_url'] = base_url\n config['transcription']['whisper_openrouter']['model'] = model\n\n from config_utils import save_config_to_file as _save_config\n if _save_config(config):\n st.success(\"✅ Transcription settings saved and configuration reloaded!\")\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\n\n\n\ndef show_advanced_settings():\n \"\"\"Backup, restore, and config management\"\"\"\n sub_tab1, sub_tab2 = st.tabs([\"💾 YAML Backup & Restore\", \"🗄️ SQL Config Manager\"])\n\n with sub_tab1:\n show_yaml_backup_restore()\n\n with sub_tab2:\n show_sql_config_management()\n\ndef show_yaml_backup_restore():\n \"\"\"Simple YAML-based backup and restore\"\"\"\n st.markdown(\"### 💾 YAML Backup & Restore\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n with st.expander(\"📄 Current Configuration\", expanded=True):\n st.code(config_yaml, language='yaml')\n st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n )\n\n st.markdown(\"#### 📤 Restore Configuration\")\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n st.markdown(\"#### 🔄 Reset to Defaults\")\n st.warning(\"⚠️ This will reset all settings to default values\")\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef show_sql_config_management():\n \"\"\"SQL-based config management editor, import/export, history\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent))\n from sermonaudio_api import SermonAudioAPI\n\n api = SermonAudioAPI(api_key=api_key, broadcaster_id=broadcaster_id)\n result = api.test_connection()\n if result:\n st.success(\"✅ API connection successful!\")\n else:\n st.error(\"❌ API connection failed\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n import sys\n from pathlib import Path\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import LLMManager\n\n llm_manager = LLMManager(st.session_state.get('config', {}))\n test_response = llm_manager.chat([{\"role\": \"user\", \"content\": \"Reply with just: OK\"}])\n if test_response and 'OK' in str(test_response).upper():\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n else:\n st.warning(f\"⚠️ {provider.title()} responded but unexpected: {str(test_response)[:100]}\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n config = st.session_state.config\n if 'metadata_processing' not in config:\n config['metadata_processing'] = {}\n\n mp = config['metadata_processing']\n\n criteria = []\n i = 0\n while f\"criteria_{i}\" in st.session_state:\n val = st.session_state[f\"criteria_{i}\"]\n if val.strip():\n criteria.append(val.strip())\n i += 1\n new_val = st.session_state.get('new_criterion', '')\n if new_val.strip():\n criteria.append(new_val.strip())\n\n if 'description' not in mp:\n mp['description'] = {}\n mp['description']['validation'] = {\n 'enabled': st.session_state.get('validation_enabled', True),\n 'criteria': criteria,\n }\n mp['description']['update_if_missing'] = st.session_state.get('desc_update_missing', True)\n mp['description']['update_if_minimal'] = st.session_state.get('desc_update_minimal', True)\n mp['description']['min_length_threshold'] = st.session_state.get('desc_min_length', 50)\n\n if 'hashtags' not in mp:\n mp['hashtags'] = {}\n mp['hashtags']['update_if_missing'] = st.session_state.get('hash_update_missing', True)\n mp['hashtags']['update_if_minimal'] = st.session_state.get('hash_update_minimal', True)\n mp['hashtags']['min_length_threshold'] = st.session_state.get('hash_min_length', 10)\n\n save_config_to_file(config)\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "acb017254e33b03c1dcf84d948d311a85bf41aaf": {"t": 1785768993.013884, "n": 70996, "h": "9cbce58e7d1ccc98", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nfrom ui.pages import jobs, library\nimport yaml\n\nOPENAI_PRESETS = {\n \"OpenAI\": {\"base_url\": \"\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4-turbo\", \"gpt-3.5-turbo\"]},\n \"Azure OpenAI\": {\"base_url\": \"https://YOUR_RESOURCE.openai.azure.com\", \"models\": [\"gpt-4o\", \"gpt-4o-mini\", \"gpt-4\"]},\n \"Groq\": {\"base_url\": \"https://api.groq.com/openai/v1\", \"models\": [\"llama-3.3-70b-versatile\", \"llama-3.1-70b-versatile\", \"llama-3.1-8b-instant\", \"mixtral-8x7b-32768\"]},\n \"OpenRouter\": {\"base_url\": \"https://openrouter.ai/api/v1\", \"models\": [\"openai/gpt-4o\", \"openai/gpt-4o-mini\", \"anthropic/claude-3.5-sonnet\", \"google/gemini-2.0-flash-exp\"]},\n \"xAI\": {\"base_url\": \"https://api.x.ai/v1\", \"models\": [\"grok-beta\", \"grok-2-1212\"]},\n \"DeepSeek\": {\"base_url\": \"https://api.deepseek.com\", \"models\": [\"deepseek-chat\", \"deepseek-reasoner\"]},\n \"Together AI\": {\"base_url\": \"https://api.together.xyz/v1\", \"models\": [\"mistralai/Mixtral-8x22B-Instruct-v0.1\", \"meta-llama/Llama-3.3-70B-Instruct-Turbo\"]},\n \"Perplexity\": {\"base_url\": \"https://api.perplexity.ai\", \"models\": [\"sonar-pro\", \"sonar\"]},\n \"Fireworks AI\": {\"base_url\": \"https://api.fireworks.ai/inference/v1\", \"models\": [\"accounts/fireworks/models/llama-v3p3-70b-instruct\", \"accounts/fireworks/models/llama-v3p1-8b-instruct\"]},\n \"Anyscale\": {\"base_url\": \"https://api.endpoints.anyscale.com/v1\", \"models\": [\"meta-llama/Meta-Llama-3.1-70B-Instruct\"]},\n}\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"✅ Validation\",\n \"📥 Import Sermons\",\n \"💾 Backup & Restore\",\n \"🗄️ Config Management\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_validation_settings()\n\n with tab6:\n show_import_sermons()\n\n with tab7:\n show_backup_restore()\n\n with tab8:\n show_config_management_settings()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n if f'{key_prefix}_ollama_api_key' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_api_key'] = settings.get('api_key', '')\n\n elif provider_type == 'openai':\n preset = settings.get('preset', 'OpenAI')\n if f'{key_prefix}_openai_preset' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_preset'] = preset\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-4o-mini')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n # Clear cached models when API key changes\n if provider_type in ['openai', 'anthropic', 'google']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_provider = primary_config.get('provider', 'ollama')\n if current_provider not in provider_options:\n current_provider = 'openai'\n provider_index = provider_options.index(current_provider)\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"google\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n if current_fallback_provider not in provider_options:\n current_fallback_provider = 'openai'\n fallback_provider_index = provider_options.index(current_fallback_provider)\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"google\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n if current_validator_provider not in provider_options:\n current_validator_provider = 'openai'\n validator_provider_index = provider_options.index(current_validator_provider)\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n api_key = st.text_input(\n f\"{label} Ollama API Key (Optional)\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_ollama_api_key\",\n help=\"Required if Ollama is behind an authenticated proxy\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host, 'api_key': api_key})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-compatible provider settings with preset dropdown\"\"\"\n preset_names = list(OPENAI_PRESETS.keys())\n stored_preset = config.get('preset', 'OpenAI')\n if stored_preset not in preset_names:\n stored_preset = 'OpenAI'\n preset_index = preset_names.index(stored_preset)\n\n selected_preset = st.selectbox(\n f\"{label} API Type\",\n options=preset_names,\n index=preset_index,\n key=f\"{key_prefix}_openai_preset\",\n help=\"Select the OpenAI-compatible API provider\"\n )\n\n preset = OPENAI_PRESETS[selected_preset]\n\n default_base_url = config.get('base_url', '') or preset['base_url']\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL\",\n value=default_base_url,\n placeholder=preset['base_url'] or \"https://api.openai.com/v1\",\n help=f\"Base URL for {selected_preset} API\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n model_options = list(preset['models'])\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models[:20]\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n model_options = cached_models[:20]\n\n current_model = config.get('model', preset['models'][0])\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else\n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_import_sermons():\n \"\"\"Import sermons from processed_sermons folder into database\"\"\"\n st.markdown(\"### 📥 Import Existing Sermons\")\n st.markdown(\"Scan the processed_sermons folder and import any missing sermons into the database.\")\n\n try:\n # Import the sermon importer\n sys.path.insert(0, str(Path(__file__).parent))\n from sermon_importer import get_import_status, import_missing_sermons, import_single_sermon\n\n # Get current import status\n status = get_import_status()\n\n # Display status\n st.markdown(\"#### 📊 Current Status\")\n\n col1, col2, col3 = st.columns(3)\n\n with col1:\n st.metric(\"Sermons in Folder\", status['total_in_folder'])\n\n with col2:\n st.metric(\"In Database\", status['in_database'])\n\n with col3:\n st.metric(\"Missing from Database\", status['missing_from_database'])\n\n st.markdown(f\"**Folder Path:** `{status['folder_path']}`\")\n\n # Show missing sermons if any\n if status['missing_sermon_ids']:\n st.markdown(\"#### 🔍 Missing Sermons (Preview)\")\n\n missing_count = status['missing_from_database']\n preview_sermons = status['missing_sermon_ids']\n\n if missing_count > 10:\n st.info(f\"Showing first 10 of {missing_count} missing sermons:\")\n\n for sermon_id in preview_sermons:\n col1, col2 = st.columns([3, 1])\n\n with col1:\n st.write(f\"📄 Sermon ID: `{sermon_id}`\")\n\n with col2:\n if st.button(\"Import\", key=f\"import_single_{sermon_id}\"):\n with st.spinner(f\"Importing sermon {sermon_id}...\"):\n success = import_single_sermon(sermon_id)\n if success:\n st.success(f\"✅ Successfully imported sermon {sermon_id}\")\n st.rerun()\n else:\n st.error(f\"❌ Failed to import sermon {sermon_id}\")\n\n st.divider()\n\n # Bulk import options\n st.markdown(\"#### 🚀 Bulk Import\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n # Regular import for missing sermons\n if st.button(\"📥 Import All Missing Sermons\", type=\"primary\"):\n if status['missing_from_database'] > 0:\n # Use job queue for background import\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Bulk Sermon Import\",\n description=f\"Importing {status['missing_from_database']} missing sermons from processed_sermons folder\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': False\n },\n priority=6 # Medium-high priority\n )\n\n st.success(f\"✅ Import job started! Job ID: {job_id[:8]}\")\n st.info(f\"📥 Importing {status['missing_from_database']} sermons in the background. Monitor progress on the Jobs page.\")\n\n # Add button to go to jobs page\n if st.button(\"📊 View Job Progress\", type=\"secondary\"):\n st.switch_page(jobs)\n\n except Exception as e:\n st.error(f\"❌ Failed to start import job: {e}\")\n # Fallback to old method\n with st.spinner(f\"Importing {status['missing_from_database']} sermons...\"):\n successful, failed, failed_ids = import_missing_sermons()\n\n if successful > 0:\n st.success(f\"✅ Successfully imported {successful} sermons!\")\n\n if failed > 0:\n st.error(f\"❌ Failed to import {failed} sermons\")\n if failed_ids:\n st.write(\"Failed sermon IDs:\")\n for failed_id in failed_ids[:5]: # Show first 5\n st.write(f\"• {failed_id}\")\n if len(failed_ids) > 5:\n st.write(f\"• ... and {len(failed_ids) - 5} more\")\n\n st.rerun()\n else:\n st.info(\"No missing sermons to import\")\n\n with col2:\n # Force re-import option\n st.markdown(\"**🔄 Force Re-import Options**\")\n force_refresh_api = st.checkbox(\"Refresh API metadata\", value=False, help=\"Re-fetch sermon metadata from SermonAudio API\")\n\n if st.button(\"🔄 Force Re-import All Sermons\", type=\"secondary\"):\n # Confirm before proceeding\n if st.session_state.get('confirm_reimport', False):\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Force Re-import All Sermons\",\n description=f\"Re-importing all {status['total_in_folder']} sermons with fresh API data\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': True,\n 'refresh_api_data': force_refresh_api\n },\n priority=5 # Medium priority\n )\n\n st.success(f\"✅ Force re-import job started! Job ID: {job_id[:8]}\")\n st.info(f\"🔄 Re-importing all {status['total_in_folder']} sermons in the background. Monitor progress on the Jobs page.\")\n st.session_state.confirm_reimport = False # Reset confirmation\n\n except Exception as e:\n st.error(f\"❌ Failed to start re-import job: {e}\")\n else:\n st.warning(\"⚠️ This will re-import ALL sermons and may take a long time. Click again to confirm.\")\n st.session_state.confirm_reimport = True\n\n else:\n # No missing sermons\n st.success(\"✅ All sermons in the processed_sermons folder are already in the database!\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n if st.button(\"🔄 Refresh Status\"):\n st.rerun()\n\n with col2:\n if st.button(\"📊 Go to Library\"):\n st.switch_page(library)\n\n # Help section\n st.markdown(\"#### ℹ️ How It Works\")\n\n with st.expander(\"📚 Import Process Details\"):\n st.markdown(\"\"\"\n **The import process will:**\n \n 1. **Scan the processed_sermons folder** for directories with numeric names (sermon IDs)\n 2. **Check each sermon ID** against the database to see if it already exists\n 3. **Extract metadata** from files in each sermon directory:\n - `{id}_description.txt` - Sermon description and title\n - `{id}_hashtags.txt` - Hashtags\n - `{id}_transcript.txt` - Full transcript (if available)\n - `processed_{id}.mp3` - Enhanced audio file\n - `{id}_ai_upscaled.wav` - AI enhanced audio (if available)\n - `{id}_processing_info.json` - Processing metadata (if available)\n - `{id}_qa_segments.json` - Q&A segment data (if available)\n \n 4. **Create database records** with extracted metadata and file paths\n 5. **Preserve all existing data** - only adds missing sermons, never overwrites\n \n **File Structure Expected:**\n ```\n processed_sermons/\n ├── 12345678/\n │ ├── 12345678_description.txt\n │ ├── 12345678_hashtags.txt\n │ ├── processed_12345678.mp3\n │ └── 12345678_ai_upscaled.wav\n └── 87654321/\n ├── 87654321_description.txt\n └── processed_87654321.mp3\n ```\n \n **Safe Operation:**\n - Only imports sermons that don't exist in the database\n - Does not modify or overwrite existing database entries\n - Gracefully handles missing files or incomplete data\n - Logs all actions for troubleshooting\n \"\"\")\n\n except ImportError as e:\n st.error(f\"❌ Could not load sermon importer: {e}\")\n st.info(\"Please ensure the sermon_importer module is available.\")\n except Exception as e:\n st.error(f\"❌ Error checking import status: {e}\")\n st.info(\"Please check that the processed_sermons directory exists and is accessible.\")\n\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n\n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n\n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n\n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n\n st.warning(\"⚠️ This will reset all settings to default values\")\n\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n api_key = st.session_state.get(f'{key_prefix}_ollama_api_key', '')\n provider_settings['host'] = host\n provider_settings['model'] = model\n if api_key:\n provider_settings['api_key'] = api_key\n\n elif provider_type == 'openai':\n preset = st.session_state.get(f'{key_prefix}_openai_preset', 'OpenAI')\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-4o-mini')\n provider_settings['preset'] = preset\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\ndef show_config_management_settings():\n \"\"\"Embedded configuration management within the Settings tab\"\"\"\n from ui.ui_pages.config_management import (\n show_config_editor, show_import_export, show_config_history,\n show_backup_restore as show_cm_backup_restore, show_database_management,\n SQL_CONFIG_AVAILABLE\n )\n\n if not SQL_CONFIG_AVAILABLE:\n st.error(\"❌ SQL Configuration system is not available.\")\n return\n\n db_path = \"sermon_config.db\"\n\n import os\n db_exists = os.path.exists(db_path)\n\n if not db_exists:\n st.info(\"Configuration database not found. Create one below.\")\n from ui.ui_pages.config_management import show_database_setup\n show_database_setup(db_path)\n return\n\n cm_tab1, cm_tab2, cm_tab3, cm_tab4 = st.tabs([\n \"📝 Edit Config\",\n \"📥 Import/Export\",\n \"📊 History\",\n \"💾 Backup & Restore\"\n ])\n\n with cm_tab1:\n show_config_editor(db_path)\n\n with cm_tab2:\n show_import_export(db_path)\n\n with cm_tab3:\n show_config_history(db_path)\n\n with cm_tab4:\n show_cm_backup_restore(db_path)\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "19bfcc22dab3375572ed511bbd205be538d392b5": {"t": 1785768994.8647983, "n": 76703, "h": "d578ed9f5b4c6aca", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nimport yaml\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n # Settings tabs\n tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🧠 Embeddings\",\n \"🎵 Audio Processing\",\n \"✅ Validation\",\n \"📥 Import Sermons\",\n \"💾 Backup & Restore\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_embedding_settings()\n\n with tab4:\n show_audio_settings()\n\n with tab5:\n show_validation_settings()\n\n with tab6:\n show_import_sermons()\n\n with tab7:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n\n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n\n # Clear cached models when API key changes for all providers that use API keys\n if provider_type in ['openai', 'anthropic', 'xai', 'google', 'groq']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n\n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n\n with col2:\n # Dynamic model loading for xAI\n available_models = []\n model_options = ['grok-beta', 'grok-vision-beta'] # Default fallbacks\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_xai_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n\n provider = XAIProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_xai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_xai_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'grok-beta')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Select from available xAI models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_xai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n\n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n\n with col2:\n # Dynamic model loading for Groq\n available_models = []\n model_options = [ # Default fallbacks\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_groq_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n\n provider = GroqProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_groq_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_groq_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'llama-3.1-70b-versatile')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Select from available Groq models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_groq_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n\n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_embedding_settings():\n \"\"\"Embedding provider configuration for RAG system\"\"\"\n st.markdown(\"### 🧠 Embedding Provider Configuration\")\n st.markdown(\"Configure embedding providers for the RAG (Retrieval-Augmented Generation) system used in analytics.\")\n\n config = st.session_state.get('config') or {}\n embeddings_config = config.get('embeddings', {})\n\n # Initialize session state from config if not already set\n initialize_embedding_session_state(embeddings_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Embedding Provider\")\n\n primary_config = embeddings_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"sentence_transformers\", \"openai\", \"ollama\"]\n current_provider = primary_config.get('provider', 'sentence_transformers')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to sentence_transformers if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Embedding Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_embedding_provider\",\n help=\"Choose the primary embedding provider for vector search\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Embedding Provider\"):\n test_embedding_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"sentence_transformers\":\n show_sentence_transformers_settings(\"Primary\", primary_config.get('sentence_transformers', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_embedding_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"ollama\":\n show_ollama_embedding_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n\n # Fallback Providers\n st.markdown(\"#### 🥈 Fallback Embedding Providers\")\n st.markdown(\"Configure multiple fallback providers that will be tried in order if the primary provider fails.\")\n\n fallback_config = embeddings_config.get('fallback', [])\n\n # Display existing fallback providers\n if fallback_config:\n st.markdown(\"**Current Fallback Providers:**\")\n for i, fallback in enumerate(fallback_config):\n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n st.write(f\"{i+1}. **{fallback.get('provider', 'unknown')}**\")\n \n with col2:\n st.write(f\"Model: `{fallback.get('model', 'default')}`\")\n \n with col3:\n if st.button(\"🗑️\", key=f\"remove_fallback_{i}\", help=\"Remove this fallback provider\"):\n fallback_config.pop(i)\n st.rerun()\n\n # Add new fallback provider\n st.markdown(\"**Add New Fallback Provider:**\")\n \n col1, col2, col3 = st.columns([2, 2, 1])\n \n with col1:\n new_fallback_provider = st.selectbox(\n \"Provider Type\",\n options=[\"sentence_transformers\", \"openai\", \"ollama\"],\n key=\"new_fallback_provider\"\n )\n \n with col2:\n if new_fallback_provider == \"sentence_transformers\":\n model_options = [\"all-MiniLM-L6-v2\", \"all-mpnet-base-v2\", \"all-distilroberta-v1\", \"paraphrase-multilingual-MiniLM-L12-v2\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_st_model\")\n elif new_fallback_provider == \"openai\":\n model_options = [\"text-embedding-3-small\", \"text-embedding-3-large\", \"text-embedding-ada-002\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_openai_model\")\n elif new_fallback_provider == \"ollama\":\n model_options = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n new_fallback_model = st.selectbox(\"Model\", options=model_options, key=\"new_fallback_ollama_model\")\n \n with col3:\n if st.button(\"➕ Add\", key=\"add_fallback_provider\"):\n new_fallback = {\n \"provider\": new_fallback_provider,\n \"model\": new_fallback_model\n }\n if new_fallback not in fallback_config:\n fallback_config.append(new_fallback)\n # Update the config in session state\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n st.session_state.config['embeddings']['fallback'] = fallback_config\n st.success(f\"Added {new_fallback_provider} with model {new_fallback_model}\")\n st.rerun()\n else:\n st.warning(\"This provider and model combination already exists\")\n\n # Info about hash fallback\n st.info(\"💡 **Hash-based Fallback**: A hash-based embedding provider is automatically added as the final fallback to ensure the system works offline without any external dependencies.\")\n\n # Save button\n if st.button(\"💾 Save Embedding Settings\", type=\"primary\"):\n save_embedding_settings()\n\ndef initialize_embedding_session_state(embeddings_config):\n \"\"\"Initialize session state values from embedding config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = embeddings_config.get('primary', {})\n if 'primary_embedding_provider' not in st.session_state:\n st.session_state.primary_embedding_provider = primary_config.get('provider', 'sentence_transformers')\n \n # Initialize primary provider-specific session state\n initialize_embedding_provider_session_state(primary_config, st.session_state.primary_embedding_provider, 'primary')\n\ndef initialize_embedding_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize embedding provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n if f'{key_prefix}_st_model' not in st.session_state:\n st.session_state[f'{key_prefix}_st_model'] = settings.get('model', 'all-MiniLM-L6-v2')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_embedding_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_embedding_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_url'] = settings.get('base_url', 'https://api.openai.com/v1')\n if f'{key_prefix}_openai_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_embedding_model'] = settings.get('model', 'text-embedding-3-small')\n \n elif provider_type == 'ollama':\n if f'{key_prefix}_ollama_embedding_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_embedding_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_embedding_model'] = settings.get('model', 'nomic-embed-text')\n\ndef show_sentence_transformers_settings(label, config, key_prefix):\n \"\"\"Show Sentence Transformers embedding settings\"\"\"\n st.markdown(f\"**{label} Sentence Transformers Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n model_options = [\n \"all-MiniLM-L6-v2\", # 384D - Fast, good quality (default)\n \"all-mpnet-base-v2\", # 768D - Higher quality, slower\n \"all-distilroberta-v1\", # 768D - Good balance\n \"paraphrase-multilingual-MiniLM-L12-v2\" # 384D - Multilingual\n ]\n \n current_model = config.get('model', 'all-MiniLM-L6-v2')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_st_model\",\n help=\"Select from available sentence transformer models\"\n )\n \n with col2:\n # Show model info\n model_info = {\n \"all-MiniLM-L6-v2\": \"384D • Fast • Good quality\",\n \"all-mpnet-base-v2\": \"768D • Higher quality • Slower\", \n \"all-distilroberta-v1\": \"768D • Good balance\",\n \"paraphrase-multilingual-MiniLM-L12-v2\": \"384D • Multilingual\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'Local sentence transformer model')}\")\n \n if st.button(f\"📥 Download {label} Model\", key=f\"{key_prefix}_download_st\"):\n download_sentence_transformer_model(model)\n\ndef show_openai_embedding_settings(label, config, key_prefix):\n \"\"\"Show OpenAI embedding settings\"\"\"\n st.markdown(f\"**{label} OpenAI Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_embedding_key\",\n help=\"Your OpenAI API key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL\",\n value=config.get('base_url', 'https://api.openai.com/v1'),\n key=f\"{key_prefix}_openai_embedding_url\",\n help=\"Base URL for OpenAI-compatible embedding API\"\n )\n \n with col2:\n model_options = [\n \"text-embedding-3-small\", # 1536D - Fast, cost-effective\n \"text-embedding-3-large\", # 3072D - Highest quality\n \"text-embedding-ada-002\" # 1536D - Legacy model\n ]\n \n current_model = config.get('model', 'text-embedding-3-small')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_embedding_model\",\n help=\"Select from available OpenAI embedding models\"\n )\n \n # Show model info\n model_info = {\n \"text-embedding-3-small\": \"1536D • Fast • Cost-effective\",\n \"text-embedding-3-large\": \"3072D • Highest quality\",\n \"text-embedding-ada-002\": \"1536D • Legacy model\"\n }\n \n st.info(f\"**Model Info:** {model_info.get(model, 'OpenAI embedding model')}\")\n\ndef show_ollama_embedding_settings(label, config, key_prefix):\n \"\"\"Show Ollama embedding settings with model refresh\"\"\"\n st.markdown(f\"**{label} Ollama Embeddings Configuration**\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_embedding_host\",\n help=\"Ollama server host URL\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_embedding_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_embedding_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n \n # Filter for embedding models\n embedding_models = [m for m in available_models if 'embed' in m.lower()]\n \n st.session_state[f\"{key_prefix}_ollama_embedding_models\"] = embedding_models\n \n if embedding_models:\n st.success(f\"Found {len(embedding_models)} embedding models\")\n else:\n st.warning(\"No embedding models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_embedding_models\", [])\n default_models = [\"nomic-embed-text\", \"mxbai-embed-large\", \"snowflake-arctic-embed:s\", \"snowflake-arctic-embed:m\", \"snowflake-arctic-embed:l\"]\n \n available_models = cached_models if cached_models else default_models\n \n current_model = config.get('model', 'nomic-embed-text')\n try:\n model_index = available_models.index(current_model)\n except ValueError:\n model_index = 0 if available_models else None\n \n if available_models:\n model = st.selectbox(\n f\"{label} Model\",\n options=available_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Select from available Ollama embedding models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_ollama_embedding_model\",\n help=\"Enter embedding model name (click Refresh to see available models)\"\n )\n \n # Show model info if known\n model_info = {\n \"nomic-embed-text\": \"768D • General purpose embedding\",\n \"mxbai-embed-large\": \"1024D • High quality embedding\",\n \"snowflake-arctic-embed:s\": \"384D • Small, fast embedding\",\n \"snowflake-arctic-embed:m\": \"768D • Medium quality embedding\", \n \"snowflake-arctic-embed:l\": \"1024D • Large, high quality\"\n }\n \n if model in model_info:\n st.info(f\"**Model Info:** {model_info[model]}\")\n\ndef download_sentence_transformer_model(model_name):\n \"\"\"Download a sentence transformer model\"\"\"\n try:\n with st.spinner(f\"Downloading {model_name}...\"):\n from sentence_transformers import SentenceTransformer\n # This will download the model if not already present\n SentenceTransformer(model_name)\n st.success(f\"✅ Successfully downloaded {model_name}\")\n except Exception as e:\n st.error(f\"❌ Failed to download {model_name}: {e}\")\n\ndef test_embedding_provider(provider, config):\n \"\"\"Test embedding provider connection\"\"\"\n try:\n with st.spinner(f\"Testing {provider} provider...\"):\n # Import the embedding manager\n sys.path.insert(0, str(Path(__file__).parent))\n from embedding_manager import EmbeddingManager\n \n test_config = {\n 'primary': {\n 'provider': provider,\n provider: config\n }\n }\n \n embedding_manager = EmbeddingManager(test_config)\n \n # Test with a simple sentence\n test_text = \"This is a test sentence for embedding generation.\"\n embeddings = embedding_manager.get_embeddings([test_text])\n \n if embeddings and len(embeddings) == 1 and len(embeddings[0]) > 0:\n dimensions = len(embeddings[0])\n st.success(f\"✅ {provider.title()} provider working! Generated {dimensions}D embedding.\")\n else:\n st.error(f\"❌ {provider.title()} provider test failed - no embeddings generated\")\n \n except Exception as e:\n st.error(f\"❌ {provider.title()} provider test failed: {e}\")\n\ndef save_embedding_settings():\n \"\"\"Save embedding settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize embeddings config if it doesn't exist\n if 'embeddings' not in st.session_state.config:\n st.session_state.config['embeddings'] = {}\n \n embeddings_config = st.session_state.config['embeddings']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_embedding_provider', 'sentence_transformers')\n if 'primary' not in embeddings_config:\n embeddings_config['primary'] = {}\n embeddings_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_embedding_provider_settings(embeddings_config['primary'], primary_provider, 'primary')\n \n # Save fallback providers (if they exist in the session)\n # Note: Fallback providers are managed through the UI directly modifying the config\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ Embedding settings saved and configuration reloaded!\")\n\ndef save_embedding_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save embedding provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'sentence_transformers':\n model = st.session_state.get(f'{key_prefix}_st_model', 'all-MiniLM-L6-v2')\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_embedding_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_embedding_url', 'https://api.openai.com/v1')\n model = st.session_state.get(f'{key_prefix}_openai_embedding_model', 'text-embedding-3-small')\n \n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_embedding_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_embedding_model', 'nomic-embed-text')\n \n provider_settings['host'] = host\n provider_settings['model'] = model\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else\n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_import_sermons():\n \"\"\"Import sermons from processed_sermons folder into database\"\"\"\n st.markdown(\"### 📥 Import Existing Sermons\")\n st.markdown(\"Scan the processed_sermons folder and import any missing sermons into the database.\")\n\n try:\n # Import the sermon importer\n sys.path.insert(0, str(Path(__file__).parent))\n from sermon_importer import get_import_status, import_missing_sermons, import_single_sermon\n\n # Get current import status\n status = get_import_status()\n\n # Display status\n st.markdown(\"#### 📊 Current Status\")\n\n col1, col2, col3 = st.columns(3)\n\n with col1:\n st.metric(\"Sermons in Folder\", status['total_in_folder'])\n\n with col2:\n st.metric(\"In Database\", status['in_database'])\n\n with col3:\n st.metric(\"Missing from Database\", status['missing_from_database'])\n\n st.markdown(f\"**Folder Path:** `{status['folder_path']}`\")\n\n # Show missing sermons if any\n if status['missing_sermon_ids']:\n st.markdown(\"#### 🔍 Missing Sermons (Preview)\")\n\n missing_count = status['missing_from_database']\n preview_sermons = status['missing_sermon_ids']\n\n if missing_count > 10:\n st.info(f\"Showing first 10 of {missing_count} missing sermons:\")\n\n for sermon_id in preview_sermons:\n col1, col2 = st.columns([3, 1])\n\n with col1:\n st.write(f\"📄 Sermon ID: `{sermon_id}`\")\n\n with col2:\n if st.button(\"Import\", key=f\"import_single_{sermon_id}\"):\n with st.spinner(f\"Importing sermon {sermon_id}...\"):\n success = import_single_sermon(sermon_id)\n if success:\n st.success(f\"✅ Successfully imported sermon {sermon_id}\")\n st.rerun()\n else:\n st.error(f\"❌ Failed to import sermon {sermon_id}\")\n\n st.divider()\n\n # Bulk import options\n st.markdown(\"#### 🚀 Bulk Import\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n # Regular import for missing sermons\n if st.button(\"📥 Import All Missing Sermons\", type=\"primary\"):\n if status['missing_from_database'] > 0:\n # Use job queue for background import\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Bulk Sermon Import\",\n description=f\"Importing {status['missing_from_database']} missing sermons from processed_sermons folder\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': False\n },\n priority=6 # Medium-high priority\n )\n\n st.success(f\"✅ Import job started! Job ID: {job_id[:8]}\")\n st.info(f\"📥 Importing {status['missing_from_database']} sermons in the background. Monitor progress on the Jobs page.\")\n\n # Add button to go to jobs page\n if st.button(\"📊 View Job Progress\", type=\"secondary\"):\n st.session_state.current_page = 'jobs'\n st.rerun()\n\n except Exception as e:\n st.error(f\"❌ Failed to start import job: {e}\")\n # Fallback to old method\n with st.spinner(f\"Importing {status['missing_from_database']} sermons...\"):\n successful, failed, failed_ids = import_missing_sermons()\n\n if successful > 0:\n st.success(f\"✅ Successfully imported {successful} sermons!\")\n\n if failed > 0:\n st.error(f\"❌ Failed to import {failed} sermons\")\n if failed_ids:\n st.write(\"Failed sermon IDs:\")\n for failed_id in failed_ids[:5]: # Show first 5\n st.write(f\"• {failed_id}\")\n if len(failed_ids) > 5:\n st.write(f\"• ... and {len(failed_ids) - 5} more\")\n\n st.rerun()\n else:\n st.info(\"No missing sermons to import\")\n\n with col2:\n # Force re-import option\n st.markdown(\"**🔄 Force Re-import Options**\")\n force_refresh_api = st.checkbox(\"Refresh API metadata\", value=False, help=\"Re-fetch sermon metadata from SermonAudio API\")\n\n if st.button(\"🔄 Force Re-import All Sermons\", type=\"secondary\"):\n # Confirm before proceeding\n if st.session_state.get('confirm_reimport', False):\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Force Re-import All Sermons\",\n description=f\"Re-importing all {status['total_in_folder']} sermons with fresh API data\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': True,\n 'refresh_api_data': force_refresh_api\n },\n priority=5 # Medium priority\n )\n\n st.success(f\"✅ Force re-import job started! Job ID: {job_id[:8]}\")\n st.info(f\"🔄 Re-importing all {status['total_in_folder']} sermons in the background. Monitor progress on the Jobs page.\")\n st.session_state.confirm_reimport = False # Reset confirmation\n\n except Exception as e:\n st.error(f\"❌ Failed to start re-import job: {e}\")\n else:\n st.warning(\"⚠️ This will re-import ALL sermons and may take a long time. Click again to confirm.\")\n st.session_state.confirm_reimport = True\n\n else:\n # No missing sermons\n st.success(\"✅ All sermons in the processed_sermons folder are already in the database!\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n if st.button(\"🔄 Refresh Status\"):\n st.rerun()\n\n with col2:\n if st.button(\"📊 Go to Library\"):\n st.session_state.current_page = 'library'\n st.rerun()\n\n # Help section\n st.markdown(\"#### ℹ️ How It Works\")\n\n with st.expander(\"📚 Import Process Details\"):\n st.markdown(\"\"\"\n **The import process will:**\n \n 1. **Scan the processed_sermons folder** for directories with numeric names (sermon IDs)\n 2. **Check each sermon ID** against the database to see if it already exists\n 3. **Extract metadata** from files in each sermon directory:\n - `{id}_description.txt` - Sermon description and title\n - `{id}_hashtags.txt` - Hashtags\n - `{id}_transcript.txt` - Full transcript (if available)\n - `processed_{id}.mp3` - Enhanced audio file\n - `{id}_ai_upscaled.wav` - AI enhanced audio (if available)\n - `{id}_processing_info.json` - Processing metadata (if available)\n - `{id}_qa_segments.json` - Q&A segment data (if available)\n \n 4. **Create database records** with extracted metadata and file paths\n 5. **Preserve all existing data** - only adds missing sermons, never overwrites\n \n **File Structure Expected:**\n ```\n processed_sermons/\n ├── 12345678/\n │ ├── 12345678_description.txt\n │ ├── 12345678_hashtags.txt\n │ ├── processed_12345678.mp3\n │ └── 12345678_ai_upscaled.wav\n └── 87654321/\n ├── 87654321_description.txt\n └── processed_87654321.mp3\n ```\n \n **Safe Operation:**\n - Only imports sermons that don't exist in the database\n - Does not modify or overwrite existing database entries\n - Gracefully handles missing files or incomplete data\n - Logs all actions for troubleshooting\n \"\"\")\n\n except ImportError as e:\n st.error(f\"❌ Could not load sermon importer: {e}\")\n st.info(\"Please ensure the sermon_importer module is available.\")\n except Exception as e:\n st.error(f\"❌ Error checking import status: {e}\")\n st.info(\"Please check that the processed_sermons directory exists and is accessible.\")\n\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n\n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n\n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n\n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n\n st.warning(\"⚠️ This will reset all settings to default values\")\n\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n\n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "0bf8942ae3c8b1fe8dd4bc87a2b9706cacc98a10": {"t": 1785768996.5613759, "n": 57931, "h": "c86766ea203f40af", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\nimport streamlit as st\nimport yaml\n\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n\n # Settings tabs\n tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([\n \"🔧 General\",\n \"🤖 LLM Providers\",\n \"🎵 Audio Processing\",\n \"✅ Validation\",\n \"� Import Sermons\",\n \"�💾 Backup & Restore\"\n ])\n\n with tab1:\n show_general_settings()\n\n with tab2:\n show_llm_settings()\n\n with tab3:\n show_audio_settings()\n\n with tab4:\n show_validation_settings()\n\n with tab5:\n show_import_sermons()\n\n with tab6:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n\n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n\n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n\n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n\n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n\n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n\n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n\n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n\n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n\n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n\n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n\n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n\n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n\n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n\n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n\n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n\n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n\n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n\n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n\n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n\n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n\n # Clear cached models when API key changes for all providers that use API keys\n if provider_type in ['openai', 'anthropic', 'xai', 'google', 'groq']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n\n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n\n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n\n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n\n primary_config = llm_config.get('primary', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n\n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n\n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n\n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n\n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n\n fallback_config = llm_config.get('fallback', {})\n\n col1, col2 = st.columns(2)\n\n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n\n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n\n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n\n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n\n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n\n validator_config = llm_config.get('validator', {})\n\n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n\n if validator_enabled:\n col1, col2 = st.columns(2)\n\n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n\n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n\n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n\n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n\n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n\n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n\n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n\n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n\n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n\n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n\n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n\n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n\n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n\n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n\n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n\n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n\n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n\n with col2:\n # Dynamic model loading for xAI\n available_models = []\n model_options = ['grok-beta', 'grok-vision-beta'] # Default fallbacks\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_xai_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n\n provider = XAIProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_xai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_xai_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'grok-beta')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Select from available xAI models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_xai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n\n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n\n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n\n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n\n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n\n with col2:\n # Dynamic model loading for Groq\n available_models = []\n model_options = [ # Default fallbacks\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n\n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_groq_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n\n provider = GroqProvider({'api_key': api_key})\n available_models = provider.list_models()\n\n if available_models:\n st.session_state[f\"{key_prefix}_groq_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n\n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_groq_models\", [])\n if cached_models:\n model_options = cached_models\n\n # Model selection\n current_model = config.get('model', 'llama-3.1-70b-versatile')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n\n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Select from available Groq models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_groq_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n\n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n\n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n\n config = st.session_state.get('config') or {}\n\n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n\n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else\n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n\n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n\n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n\n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n\n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n\n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n\n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n\n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n\n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n\n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n\n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n\n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n\n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n\n criteria = desc_validation.get('criteria', [])\n\n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n\n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n\n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n\n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n st.markdown(\"**Description Settings:**\")\n\n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n\n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n\n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n\n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n\n hashtag_config = metadata_config.get('hashtags', {})\n\n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n\n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n\n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n\n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_import_sermons():\n \"\"\"Import sermons from processed_sermons folder into database\"\"\"\n st.markdown(\"### 📥 Import Existing Sermons\")\n st.markdown(\"Scan the processed_sermons folder and import any missing sermons into the database.\")\n\n try:\n # Import the sermon importer\n sys.path.insert(0, str(Path(__file__).parent))\n from sermon_importer import get_import_status, import_missing_sermons, import_single_sermon\n\n # Get current import status\n status = get_import_status()\n\n # Display status\n st.markdown(\"#### 📊 Current Status\")\n\n col1, col2, col3 = st.columns(3)\n\n with col1:\n st.metric(\"Sermons in Folder\", status['total_in_folder'])\n\n with col2:\n st.metric(\"In Database\", status['in_database'])\n\n with col3:\n st.metric(\"Missing from Database\", status['missing_from_database'])\n\n st.markdown(f\"**Folder Path:** `{status['folder_path']}`\")\n\n # Show missing sermons if any\n if status['missing_sermon_ids']:\n st.markdown(\"#### 🔍 Missing Sermons (Preview)\")\n\n missing_count = status['missing_from_database']\n preview_sermons = status['missing_sermon_ids']\n\n if missing_count > 10:\n st.info(f\"Showing first 10 of {missing_count} missing sermons:\")\n\n for sermon_id in preview_sermons:\n col1, col2 = st.columns([3, 1])\n\n with col1:\n st.write(f\"📄 Sermon ID: `{sermon_id}`\")\n\n with col2:\n if st.button(\"Import\", key=f\"import_single_{sermon_id}\"):\n with st.spinner(f\"Importing sermon {sermon_id}...\"):\n success = import_single_sermon(sermon_id)\n if success:\n st.success(f\"✅ Successfully imported sermon {sermon_id}\")\n st.rerun()\n else:\n st.error(f\"❌ Failed to import sermon {sermon_id}\")\n\n st.divider()\n\n # Bulk import options\n st.markdown(\"#### 🚀 Bulk Import\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n # Regular import for missing sermons\n if st.button(\"📥 Import All Missing Sermons\", type=\"primary\"):\n if status['missing_from_database'] > 0:\n # Use job queue for background import\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Bulk Sermon Import\",\n description=f\"Importing {status['missing_from_database']} missing sermons from processed_sermons folder\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': False\n },\n priority=6 # Medium-high priority\n )\n\n st.success(f\"✅ Import job started! Job ID: {job_id[:8]}\")\n st.info(f\"📥 Importing {status['missing_from_database']} sermons in the background. Monitor progress on the Jobs page.\")\n\n # Add button to go to jobs page\n if st.button(\"📊 View Job Progress\", type=\"secondary\"):\n st.session_state.current_page = 'jobs'\n st.rerun()\n\n except Exception as e:\n st.error(f\"❌ Failed to start import job: {e}\")\n # Fallback to old method\n with st.spinner(f\"Importing {status['missing_from_database']} sermons...\"):\n successful, failed, failed_ids = import_missing_sermons()\n\n if successful > 0:\n st.success(f\"✅ Successfully imported {successful} sermons!\")\n\n if failed > 0:\n st.error(f\"❌ Failed to import {failed} sermons\")\n if failed_ids:\n st.write(\"Failed sermon IDs:\")\n for failed_id in failed_ids[:5]: # Show first 5\n st.write(f\"• {failed_id}\")\n if len(failed_ids) > 5:\n st.write(f\"• ... and {len(failed_ids) - 5} more\")\n\n st.rerun()\n else:\n st.info(\"No missing sermons to import\")\n\n with col2:\n # Force re-import option\n st.markdown(\"**🔄 Force Re-import Options**\")\n force_refresh_api = st.checkbox(\"Refresh API metadata\", value=False, help=\"Re-fetch sermon metadata from SermonAudio API\")\n\n if st.button(\"🔄 Force Re-import All Sermons\", type=\"secondary\"):\n # Confirm before proceeding\n if st.session_state.get('confirm_reimport', False):\n try:\n from job_queue import JobType, get_job_queue\n\n job_queue = get_job_queue()\n config = st.session_state.get('config', {})\n processed_sermons_dir = config.get('output_directory', 'processed_sermons')\n\n job_id = job_queue.add_job(\n job_type=JobType.SERMON_IMPORT,\n title=\"Force Re-import All Sermons\",\n description=f\"Re-importing all {status['total_in_folder']} sermons with fresh API data\",\n parameters={\n 'processed_sermons_dir': processed_sermons_dir,\n 'force_reimport': True,\n 'refresh_api_data': force_refresh_api\n },\n priority=5 # Medium priority\n )\n\n st.success(f\"✅ Force re-import job started! Job ID: {job_id[:8]}\")\n st.info(f\"🔄 Re-importing all {status['total_in_folder']} sermons in the background. Monitor progress on the Jobs page.\")\n st.session_state.confirm_reimport = False # Reset confirmation\n\n except Exception as e:\n st.error(f\"❌ Failed to start re-import job: {e}\")\n else:\n st.warning(\"⚠️ This will re-import ALL sermons and may take a long time. Click again to confirm.\")\n st.session_state.confirm_reimport = True\n\n else:\n # No missing sermons\n st.success(\"✅ All sermons in the processed_sermons folder are already in the database!\")\n\n col1, col2 = st.columns(2)\n\n with col1:\n if st.button(\"🔄 Refresh Status\"):\n st.rerun()\n\n with col2:\n if st.button(\"📊 Go to Library\"):\n st.session_state.current_page = 'library'\n st.rerun()\n\n # Help section\n st.markdown(\"#### ℹ️ How It Works\")\n\n with st.expander(\"📚 Import Process Details\"):\n st.markdown(\"\"\"\n **The import process will:**\n \n 1. **Scan the processed_sermons folder** for directories with numeric names (sermon IDs)\n 2. **Check each sermon ID** against the database to see if it already exists\n 3. **Extract metadata** from files in each sermon directory:\n - `{id}_description.txt` - Sermon description and title\n - `{id}_hashtags.txt` - Hashtags\n - `{id}_transcript.txt` - Full transcript (if available)\n - `processed_{id}.mp3` - Enhanced audio file\n - `{id}_ai_upscaled.wav` - AI enhanced audio (if available)\n - `{id}_processing_info.json` - Processing metadata (if available)\n - `{id}_qa_segments.json` - Q&A segment data (if available)\n \n 4. **Create database records** with extracted metadata and file paths\n 5. **Preserve all existing data** - only adds missing sermons, never overwrites\n \n **File Structure Expected:**\n ```\n processed_sermons/\n ├── 12345678/\n │ ├── 12345678_description.txt\n │ ├── 12345678_hashtags.txt\n │ ├── processed_12345678.mp3\n │ └── 12345678_ai_upscaled.wav\n └── 87654321/\n ├── 87654321_description.txt\n └── processed_87654321.mp3\n ```\n \n **Safe Operation:**\n - Only imports sermons that don't exist in the database\n - Does not modify or overwrite existing database entries\n - Gracefully handles missing files or incomplete data\n - Logs all actions for troubleshooting\n \"\"\")\n\n except ImportError as e:\n st.error(f\"❌ Could not load sermon importer: {e}\")\n st.info(\"Please ensure the sermon_importer module is available.\")\n except Exception as e:\n st.error(f\"❌ Error checking import status: {e}\")\n st.info(\"Please check that the processed_sermons directory exists and is accessible.\")\n\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n\n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n\n config = st.session_state.get('config') or {}\n\n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n\n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n\n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n\n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n\n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n\n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n\n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n\n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n\n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n\n st.warning(\"⚠️ This will reset all settings to default values\")\n\n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug,\n hashtag_verification, output_directory,\n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n\n llm_config = st.session_state.config['llm']\n\n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n\n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n\n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n\n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n\n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n\n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n\n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n\n provider_settings = provider_config[provider_type]\n\n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n\n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n\n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n\n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n\n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n\n with open(example_config_path) as f:\n default_config = yaml.safe_load(f)\n\n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n\n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()\n"}, "15f5f3fcb1f39ce5dc310aea4a1961fb0662e735": {"t": 1785768998.1110868, "n": 49172, "h": "fa4ac03250ec1d1b", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport streamlit as st\nimport yaml\nfrom pathlib import Path\nimport json\nimport sys\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n \n # Settings tabs\n tab1, tab2, tab3, tab4, tab5 = st.tabs([\n \"🔧 General\", \n \"🤖 LLM Providers\", \n \"🎵 Audio Processing\", \n \"✅ Validation\", \n \"💾 Backup & Restore\"\n ])\n \n with tab1:\n show_general_settings()\n \n with tab2:\n show_llm_settings()\n \n with tab3:\n show_audio_settings()\n \n with tab4:\n show_validation_settings()\n \n with tab5:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n \n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n \n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n \n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n \n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n \n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n \n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n \n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n \n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n \n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n \n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n \n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n \n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n \n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n \n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n \n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n \n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n \n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n \n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n \n # Clear cached models when API key changes for all providers that use API keys\n if provider_type in ['openai', 'anthropic', 'xai', 'google', 'groq']:\n current_key = st.session_state.get(f'{key_prefix}_{provider_type}_key', '')\n stored_key = settings.get('api_key', '')\n if current_key != stored_key and current_key:\n # API key changed, clear cached models to force refresh\n st.session_state.pop(f'{key_prefix}_{provider_type}_models', None)\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n \n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n \n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n \n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n \n primary_config = llm_config.get('primary', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n \n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n \n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n \n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n \n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n \n fallback_config = llm_config.get('fallback', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n \n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n \n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n \n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n \n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n \n validator_config = llm_config.get('validator', {})\n \n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n \n if validator_enabled:\n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n \n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n \n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n \n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n \n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n \n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n \n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n \n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n \n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n \n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n \n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n \n with col2:\n # Dynamic model loading for Anthropic\n available_models = []\n model_options = [ # Default fallbacks\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022', \n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_anthropic_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n \n provider = AnthropicProvider({'api_key': api_key})\n available_models = provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_anthropic_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_anthropic_models\", [])\n if cached_models:\n model_options = cached_models\n \n # Model selection\n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Select from available Claude models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n \n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n \n with col2:\n # Dynamic model loading for xAI\n available_models = []\n model_options = ['grok-beta', 'grok-vision-beta'] # Default fallbacks\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_xai_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n \n provider = XAIProvider({'api_key': api_key})\n available_models = provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_xai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_xai_models\", [])\n if cached_models:\n model_options = cached_models\n \n # Model selection\n current_model = config.get('model', 'grok-beta')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Select from available xAI models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_xai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n \n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n \n with col2:\n # Dynamic model loading for Google\n available_models = []\n model_options = [ # Default fallbacks\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_google_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n \n provider = GoogleProvider({'api_key': api_key})\n available_models = provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_google_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_google_models\", [])\n if cached_models:\n model_options = cached_models\n \n # Model selection\n current_model = config.get('model', 'gemini-1.5-flash')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Select from available Gemini models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_google_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n \n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with dynamic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n \n with col2:\n # Dynamic model loading for Groq\n available_models = []\n model_options = [ # Default fallbacks\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_groq_models\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n \n provider = GroqProvider({'api_key': api_key})\n available_models = provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_groq_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n model_options = available_models\n else:\n st.warning(\"No models found - using default options\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n st.info(\"Using default model options\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_groq_models\", [])\n if cached_models:\n model_options = cached_models\n \n # Model selection\n current_model = config.get('model', 'llama-3.1-70b-versatile')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Select from available Groq models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_groq_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n \n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n \n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else \n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n \n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n \n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n \n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n \n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n \n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n \n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n \n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n \n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n \n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n \n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n \n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n \n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n \n criteria = desc_validation.get('criteria', [])\n \n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n \n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n \n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n \n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n st.markdown(\"**Description Settings:**\")\n \n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n \n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n \n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n \n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n \n hashtag_config = metadata_config.get('hashtags', {})\n \n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n \n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n \n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n \n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n \n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n \n config = st.session_state.get('config') or {}\n \n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n \n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n \n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n \n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n \n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n \n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n \n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n \n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n \n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n \n st.warning(\"⚠️ This will reset all settings to default values\")\n \n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n \n llm_config = st.session_state.config['llm']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n \n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n \n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n \n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n \n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n \n with open(example_config_path, 'r') as f:\n default_config = yaml.safe_load(f)\n \n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n \n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()"}, "9418507ad2ae358ff1e77a280940de5c3322244b": {"t": 1785769000.129121, "n": 42507, "h": "63c5a28c346b7218", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport streamlit as st\nimport yaml\nfrom pathlib import Path\nimport json\nimport sys\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n \n # Settings tabs\n tab1, tab2, tab3, tab4, tab5 = st.tabs([\n \"🔧 General\", \n \"🤖 LLM Providers\", \n \"🎵 Audio Processing\", \n \"✅ Validation\", \n \"💾 Backup & Restore\"\n ])\n \n with tab1:\n show_general_settings()\n \n with tab2:\n show_llm_settings()\n \n with tab3:\n show_audio_settings()\n \n with tab4:\n show_validation_settings()\n \n with tab5:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n \n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n \n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n \n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n \n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n \n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n \n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n \n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n \n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n \n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n \n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n \n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n \n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n \n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n \n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n \n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n \n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n \n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n \n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n \n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n \n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n \n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n \n primary_config = llm_config.get('primary', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n \n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n \n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n \n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n \n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n \n fallback_config = llm_config.get('fallback', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n \n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n \n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n \n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n \n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n \n validator_config = llm_config.get('validator', {})\n \n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n \n if validator_enabled:\n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n \n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n \n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n \n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n \n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n \n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n \n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n \n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n \n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n \n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n \n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n \n with col2:\n # Auto-populate model list for Anthropic\n model_options = [\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022', \n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n \n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Claude model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n \n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n \n with col2:\n # Auto-populate model list for xAI\n model_options = [\n 'grok-beta',\n 'grok-vision-beta'\n ]\n \n current_model = config.get('model', 'grok-beta')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Grok model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n \n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n \n with col2:\n # Auto-populate model list for Google\n model_options = [\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n \n current_model = config.get('model', 'gemini-1.5-flash')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Gemini model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n \n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n \n with col2:\n # Auto-populate model list for Groq\n model_options = [\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n \n current_model = config.get('model', 'llama-3.1-70b-versatile')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Groq model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n \n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n \n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else \n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n \n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n \n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n \n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n \n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n \n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n \n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n \n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n \n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n \n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n \n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n \n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n \n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n \n criteria = desc_validation.get('criteria', [])\n \n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n \n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n \n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n \n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n st.markdown(\"**Description Settings:**\")\n \n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n \n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n \n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n \n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n \n hashtag_config = metadata_config.get('hashtags', {})\n \n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n \n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n \n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n \n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n \n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n \n config = st.session_state.get('config') or {}\n \n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n \n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n \n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n \n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n \n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n \n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n \n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n from config_utils import save_config_to_file\n if save_config_to_file(new_config):\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n \n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n \n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n \n st.warning(\"⚠️ This will reset all settings to default values\")\n \n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved and configuration reloaded!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n \n llm_config = st.session_state.config['llm']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n \n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n \n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n \n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n \n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n \n # Save to file\n if save_config_to_file(st.session_state.config):\n st.success(\"✅ LLM settings saved and configuration reloaded!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved and configuration reloaded!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file and reload in session\"\"\"\n from config_utils import save_config_to_file as _save_config\n return _save_config(config)\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n \n with open(example_config_path, 'r') as f:\n default_config = yaml.safe_load(f)\n \n st.session_state.config = default_config\n from config_utils import save_config_to_file\n save_config_to_file(default_config)\n \n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()"}, "a91147646503ee5428ccd2e7838cff29cac01ab3": {"t": 1785769004.1195, "n": 42566, "h": "1bc9c91f9a53dc3a", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport streamlit as st\nimport yaml\nfrom pathlib import Path\nimport json\nimport sys\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n \n # Settings tabs\n tab1, tab2, tab3, tab4, tab5 = st.tabs([\n \"🔧 General\", \n \"🤖 LLM Providers\", \n \"🎵 Audio Processing\", \n \"✅ Validation\", \n \"💾 Backup & Restore\"\n ])\n \n with tab1:\n show_general_settings()\n \n with tab2:\n show_llm_settings()\n \n with tab3:\n show_audio_settings()\n \n with tab4:\n show_validation_settings()\n \n with tab5:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n \n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n \n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n \n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n \n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n \n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n \n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n \n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n \n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n \n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n \n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n \n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n \n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n \n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n \n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n \n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n \n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n \n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n \n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n \n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n \n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n \n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n \n primary_config = llm_config.get('primary', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n \n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n \n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n \n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n \n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n \n fallback_config = llm_config.get('fallback', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n \n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n \n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n \n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n \n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n \n validator_config = llm_config.get('validator', {})\n \n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\",\n key=\"validator_enabled\"\n )\n \n if validator_enabled:\n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n \n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n \n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n \n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n \n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n \n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n \n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n \n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n \n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n \n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n \n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n \n with col2:\n # Auto-populate model list for Anthropic\n model_options = [\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022', \n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n \n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Claude model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n \n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n \n with col2:\n # Auto-populate model list for xAI\n model_options = [\n 'grok-beta',\n 'grok-vision-beta'\n ]\n \n current_model = config.get('model', 'grok-beta')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Grok model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n \n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n \n with col2:\n # Auto-populate model list for Google\n model_options = [\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n \n current_model = config.get('model', 'gemini-1.5-flash')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Gemini model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n \n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n \n with col2:\n # Auto-populate model list for Groq\n model_options = [\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n \n current_model = config.get('model', 'llama-3.1-70b-versatile')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Groq model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n \n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n \n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else \n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n \n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n \n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n \n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n \n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n \n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n \n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n \n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n \n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n \n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n \n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n \n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n \n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n \n criteria = desc_validation.get('criteria', [])\n \n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n \n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n \n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n \n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n st.markdown(\"**Description Settings:**\")\n \n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n \n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n \n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n \n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n \n hashtag_config = metadata_config.get('hashtags', {})\n \n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n \n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n \n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n \n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n \n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n \n config = st.session_state.get('config') or {}\n \n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n \n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n \n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n \n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n \n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n \n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n \n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n save_config_to_file(new_config)\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n \n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n \n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n \n st.warning(\"⚠️ This will reset all settings to default values\")\n \n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n \n llm_config = st.session_state.config['llm']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n \n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n \n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n \n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n \n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n \n # Save to file\n save_config_to_file(st.session_state.config)\n st.success(\"✅ LLM settings saved!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file\"\"\"\n try:\n project_root = Path(__file__).parent.parent.parent\n config_path = project_root / \"config.yaml\"\n \n with open(config_path, 'w') as f:\n yaml.dump(config, f, default_flow_style=False, sort_keys=True)\n \n st.info(f\"Configuration saved to {config_path}\")\n except Exception as e:\n st.error(f\"Failed to save configuration: {e}\")\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n \n with open(example_config_path, 'r') as f:\n default_config = yaml.safe_load(f)\n \n st.session_state.config = default_config\n save_config_to_file(default_config)\n \n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()"}, "53b6d058e7500c36a095659d7f273dd6a6282e7b": {"t": 1785769007.5215933, "n": 42533, "h": "00f0a3ace215cf54", "c": "\"\"\"\nSettings Page for SermonAudio Processor\n\nHandles configuration management, LLM provider setup, audio settings,\nand validation criteria with web-based editing interface.\n\"\"\"\n\nimport streamlit as st\nimport yaml\nfrom pathlib import Path\nimport json\nimport sys\n\ndef show_settings():\n \"\"\"Main settings management interface\"\"\"\n st.markdown('<div class=\"main-header\">⚙️ Settings</div>', unsafe_allow_html=True)\n \n # Settings tabs\n tab1, tab2, tab3, tab4, tab5 = st.tabs([\n \"🔧 General\", \n \"🤖 LLM Providers\", \n \"🎵 Audio Processing\", \n \"✅ Validation\", \n \"💾 Backup & Restore\"\n ])\n \n with tab1:\n show_general_settings()\n \n with tab2:\n show_llm_settings()\n \n with tab3:\n show_audio_settings()\n \n with tab4:\n show_validation_settings()\n \n with tab5:\n show_backup_restore()\n\ndef show_general_settings():\n \"\"\"General configuration settings\"\"\"\n st.markdown(\"### 🔧 General Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # API Configuration\n st.markdown(\"#### 🔗 SermonAudio API\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n \"API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n help=\"Your SermonAudio API key\"\n )\n \n with col2:\n broadcaster_id = st.text_input(\n \"Broadcaster ID\",\n value=config.get('broadcaster_id', ''),\n help=\"Your SermonAudio broadcaster ID\"\n )\n \n # Test API connection\n if api_key and broadcaster_id:\n if st.button(\"🔍 Test API Connection\"):\n test_api_connection(api_key, broadcaster_id)\n \n # Processing Options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n dry_run = st.checkbox(\n \"Dry Run Mode (Default)\",\n value=config.get('dry_run', False),\n help=\"Preview changes without uploading by default\"\n )\n \n debug = st.checkbox(\n \"Debug Mode\",\n value=config.get('debug', False),\n help=\"Enable verbose debug output\"\n )\n \n with col2:\n hashtag_verification = st.checkbox(\n \"Hashtag Verification\",\n value=config.get('hashtag_verification', True),\n help=\"Verify hashtags through second LLM pass\"\n )\n \n # Output Settings\n st.markdown(\"#### 📁 Output Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n output_directory = st.text_input(\n \"Output Directory\",\n value=config.get('output_directory', 'processed_sermons'),\n help=\"Directory to store processed sermon files\"\n )\n \n save_original_audio = st.checkbox(\n \"Save Original Audio\",\n value=config.get('save_original_audio', True),\n help=\"Keep copy of original audio file\"\n )\n \n with col2:\n save_transcript = st.checkbox(\n \"Save Transcript\",\n value=config.get('save_transcript', True),\n help=\"Save sermon transcript as text file\"\n )\n \n # Save button\n if st.button(\"💾 Save General Settings\", type=\"primary\"):\n save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript)\n\ndef initialize_llm_session_state(llm_config):\n \"\"\"Initialize session state values from config if not already set\"\"\"\n \n # Primary provider settings\n primary_config = llm_config.get('primary', {})\n if 'primary_provider' not in st.session_state:\n st.session_state.primary_provider = primary_config.get('provider', 'ollama')\n \n # Initialize primary provider-specific session state\n initialize_provider_session_state(primary_config, st.session_state.primary_provider, 'primary')\n \n # Fallback provider settings\n fallback_config = llm_config.get('fallback', {})\n if 'fallback_enabled' not in st.session_state:\n st.session_state.fallback_enabled = fallback_config.get('enabled', False)\n if 'fallback_provider' not in st.session_state:\n st.session_state.fallback_provider = fallback_config.get('provider', 'openai')\n \n # Initialize fallback provider-specific session state\n if st.session_state.fallback_enabled:\n initialize_provider_session_state(fallback_config, st.session_state.fallback_provider, 'fallback')\n \n # Validator provider settings\n validator_config = llm_config.get('validator', {})\n if 'validator_enabled' not in st.session_state:\n st.session_state.validator_enabled = validator_config.get('enabled', False)\n if 'validator_provider' not in st.session_state:\n st.session_state.validator_provider = validator_config.get('provider', 'ollama')\n \n # Initialize validator provider-specific session state\n if st.session_state.validator_enabled:\n initialize_provider_session_state(validator_config, st.session_state.validator_provider, 'validator')\n\ndef initialize_provider_session_state(provider_config, provider_type, key_prefix):\n \"\"\"Initialize provider-specific session state values from config\"\"\"\n if provider_type in provider_config:\n settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n if f'{key_prefix}_ollama_host' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_host'] = settings.get('host', 'http://localhost:11434')\n if f'{key_prefix}_ollama_model' not in st.session_state:\n st.session_state[f'{key_prefix}_ollama_model'] = settings.get('model', 'llama3')\n \n elif provider_type == 'openai':\n if f'{key_prefix}_openai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_openai_url' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_url'] = settings.get('base_url', '')\n if f'{key_prefix}_openai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_openai_model'] = settings.get('model', 'gpt-3.5-turbo')\n \n elif provider_type == 'anthropic':\n if f'{key_prefix}_anthropic_key' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_key'] = settings.get('api_key', '')\n if f'{key_prefix}_anthropic_model' not in st.session_state:\n st.session_state[f'{key_prefix}_anthropic_model'] = settings.get('model', 'claude-3-5-sonnet-20241022')\n \n elif provider_type == 'xai':\n if f'{key_prefix}_xai_key' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_key'] = settings.get('api_key', '')\n if f'{key_prefix}_xai_model' not in st.session_state:\n st.session_state[f'{key_prefix}_xai_model'] = settings.get('model', 'grok-beta')\n \n elif provider_type == 'google':\n if f'{key_prefix}_google_key' not in st.session_state:\n st.session_state[f'{key_prefix}_google_key'] = settings.get('api_key', '')\n if f'{key_prefix}_google_model' not in st.session_state:\n st.session_state[f'{key_prefix}_google_model'] = settings.get('model', 'gemini-1.5-flash')\n \n elif provider_type == 'groq':\n if f'{key_prefix}_groq_key' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_key'] = settings.get('api_key', '')\n if f'{key_prefix}_groq_model' not in st.session_state:\n st.session_state[f'{key_prefix}_groq_model'] = settings.get('model', 'llama-3.1-70b-versatile')\n\ndef show_llm_settings():\n \"\"\"LLM provider configuration\"\"\"\n st.markdown(\"### 🤖 LLM Provider Configuration\")\n \n config = st.session_state.get('config') or {}\n llm_config = config.get('llm', {})\n \n # Initialize session state from config if not already set\n initialize_llm_session_state(llm_config)\n \n # Primary Provider\n st.markdown(\"#### 🥇 Primary Provider\")\n \n primary_config = llm_config.get('primary', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_provider = primary_config.get('provider', 'ollama')\n try:\n provider_index = provider_options.index(current_provider)\n except ValueError:\n provider_index = 0 # Default to ollama if unknown provider\n \n primary_provider = st.selectbox(\n \"Primary Provider\",\n options=provider_options,\n index=provider_index,\n key=\"primary_provider\"\n )\n \n with col2:\n if st.button(\"🔍 Test Primary Provider\"):\n test_llm_provider(primary_provider, primary_config.get(primary_provider, {}))\n \n # Provider-specific settings\n if primary_provider == \"ollama\":\n show_ollama_settings(\"Primary\", primary_config.get('ollama', {}), \"primary\")\n elif primary_provider == \"openai\":\n show_openai_settings(\"Primary\", primary_config.get('openai', {}), \"primary\")\n elif primary_provider == \"anthropic\":\n show_anthropic_settings(\"Primary\", primary_config.get('anthropic', {}), \"primary\")\n elif primary_provider == \"xai\":\n show_xai_settings(\"Primary\", primary_config.get('xai', {}), \"primary\")\n elif primary_provider == \"google\":\n show_google_settings(\"Primary\", primary_config.get('google', {}), \"primary\")\n elif primary_provider == \"groq\":\n show_groq_settings(\"Primary\", primary_config.get('groq', {}), \"primary\")\n \n # Fallback Provider\n st.markdown(\"#### 🥈 Fallback Provider\")\n \n fallback_config = llm_config.get('fallback', {})\n \n col1, col2 = st.columns(2)\n \n with col1:\n fallback_enabled = st.checkbox(\n \"Enable Fallback Provider\",\n value=fallback_config.get('enabled', True),\n key=\"fallback_enabled\"\n )\n \n with col2:\n if fallback_enabled:\n provider_options = [\"openai\", \"ollama\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_fallback_provider = fallback_config.get('provider', 'openai')\n try:\n fallback_provider_index = provider_options.index(current_fallback_provider)\n except ValueError:\n fallback_provider_index = 0 # Default to openai if unknown provider\n \n fallback_provider = st.selectbox(\n \"Fallback Provider\",\n options=provider_options,\n index=fallback_provider_index,\n key=\"fallback_provider\"\n )\n \n if fallback_enabled:\n if fallback_provider == \"ollama\":\n show_ollama_settings(\"Fallback\", fallback_config.get('ollama', {}), \"fallback\")\n elif fallback_provider == \"openai\":\n show_openai_settings(\"Fallback\", fallback_config.get('openai', {}), \"fallback\")\n elif fallback_provider == \"anthropic\":\n show_anthropic_settings(\"Fallback\", fallback_config.get('anthropic', {}), \"fallback\")\n elif fallback_provider == \"xai\":\n show_xai_settings(\"Fallback\", fallback_config.get('xai', {}), \"fallback\")\n elif fallback_provider == \"google\":\n show_google_settings(\"Fallback\", fallback_config.get('google', {}), \"fallback\")\n elif fallback_provider == \"groq\":\n show_groq_settings(\"Fallback\", fallback_config.get('groq', {}), \"fallback\")\n \n # Validator Provider\n st.markdown(\"#### ✅ Validator Provider (Optional)\")\n \n validator_config = llm_config.get('validator', {})\n \n validator_enabled = st.checkbox(\n \"Enable Validation Provider\",\n value=validator_config.get('enabled', False),\n help=\"Use smaller model for description validation\"\n )\n \n if validator_enabled:\n col1, col2 = st.columns(2)\n \n with col1:\n provider_options = [\"ollama\", \"openai\", \"anthropic\", \"xai\", \"google\", \"groq\"]\n current_validator_provider = validator_config.get('provider', 'ollama')\n try:\n validator_provider_index = provider_options.index(current_validator_provider)\n except ValueError:\n validator_provider_index = 0 # Default to ollama if unknown provider\n \n validator_provider = st.selectbox(\n \"Validator Provider\",\n options=provider_options,\n index=validator_provider_index,\n key=\"validator_provider\"\n )\n \n if validator_provider == \"ollama\":\n show_ollama_settings(\"Validator\", validator_config.get('ollama', {}), \"validator\")\n elif validator_provider == \"openai\":\n show_openai_settings(\"Validator\", validator_config.get('openai', {}), \"validator\")\n elif validator_provider == \"anthropic\":\n show_anthropic_settings(\"Validator\", validator_config.get('anthropic', {}), \"validator\")\n elif validator_provider == \"xai\":\n show_xai_settings(\"Validator\", validator_config.get('xai', {}), \"validator\")\n elif validator_provider == \"google\":\n show_google_settings(\"Validator\", validator_config.get('google', {}), \"validator\")\n elif validator_provider == \"groq\":\n show_groq_settings(\"Validator\", validator_config.get('groq', {}), \"validator\")\n \n # Save button\n if st.button(\"💾 Save LLM Settings\", type=\"primary\"):\n save_llm_settings()\n\ndef show_ollama_settings(label, config, key_prefix):\n \"\"\"Show Ollama-specific settings with automatic model refresh\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n host = st.text_input(\n f\"{label} Ollama Host\",\n value=config.get('host', 'http://localhost:11434'),\n key=f\"{key_prefix}_ollama_host\"\n )\n \n with col2:\n # Auto-refresh models when host changes or button clicked\n available_models = []\n refresh_clicked = st.button(f\"🔄 Refresh {label} Models\", key=f\"{key_prefix}_refresh_models\")\n \n if refresh_clicked or not st.session_state.get(f\"{key_prefix}_ollama_models\"):\n try:\n # Import here to avoid issues if not installed\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OllamaProvider\n \n ollama_provider = OllamaProvider({'host': host})\n available_models = ollama_provider.list_models()\n st.session_state[f\"{key_prefix}_ollama_models\"] = available_models\n \n if available_models:\n st.success(f\"Found {len(available_models)} models\")\n else:\n st.warning(\"No models found - check Ollama connection\")\n except Exception as e:\n st.error(f\"Failed to fetch models: {e}\")\n st.info(f\"Make sure Ollama is running at {host}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_ollama_models\", [])\n \n if cached_models:\n current_model = config.get('model', 'llama3')\n try:\n model_index = cached_models.index(current_model)\n except ValueError:\n model_index = 0 if cached_models else None\n \n model = st.selectbox(\n f\"{label} Ollama Model\",\n options=cached_models,\n index=model_index,\n key=f\"{key_prefix}_ollama_model\",\n help=\"Select from available Ollama models\"\n )\n else:\n model = st.text_input(\n f\"{label} Ollama Model\",\n value=config.get('model', 'llama3'),\n key=f\"{key_prefix}_ollama_model\",\n help=\"Enter model name (click Refresh to see available models)\"\n )\n\ndef show_openai_settings(label, config, key_prefix):\n \"\"\"Show OpenAI-specific settings with automatic model loading\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_openai_key\"\n )\n \n base_url = st.text_input(\n f\"{label} Base URL (Optional)\",\n value=config.get('base_url', ''),\n placeholder=\"https://api.openai.com/v1\",\n help=\"For custom endpoints (xAI, Anthropic, etc.)\",\n key=f\"{key_prefix}_openai_url\"\n )\n \n with col2:\n # Auto-load models if API key is provided\n available_models = []\n model_options = ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k'] # Default options\n \n if api_key and st.button(f\"🔄 Load {label} Models\", key=f\"{key_prefix}_load_models\"):\n try:\n # Import OpenAI provider to list models\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import OpenAIProvider\n \n provider_config = {'api_key': api_key}\n if base_url:\n provider_config['base_url'] = base_url\n \n openai_provider = OpenAIProvider(provider_config)\n available_models = openai_provider.list_models()\n \n if available_models:\n st.session_state[f\"{key_prefix}_openai_models\"] = available_models\n st.success(f\"Loaded {len(available_models)} models\")\n # Filter to commonly used models for better UX\n model_options = [m for m in available_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = available_models[:10] # Take first 10 if no common ones found\n else:\n st.warning(\"No models found - check API credentials\")\n except Exception as e:\n st.error(f\"Failed to load models: {e}\")\n \n # Get cached models if available\n cached_models = st.session_state.get(f\"{key_prefix}_openai_models\", [])\n if cached_models:\n # Filter to commonly used models\n model_options = [m for m in cached_models if any(x in m.lower() for x in ['gpt-4', 'gpt-3.5', 'claude', 'grok', 'gemini', 'llama'])]\n if not model_options:\n model_options = cached_models[:10]\n \n # Model selection\n current_model = config.get('model', 'gpt-3.5-turbo')\n if model_options and len(model_options) > 1:\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_openai_model\",\n help=\"Select from available models\"\n )\n else:\n model = st.text_input(\n f\"{label} Model\",\n value=current_model,\n key=f\"{key_prefix}_openai_model\",\n help=\"Enter model name or click 'Load Models' to see available options\"\n )\n\ndef show_anthropic_settings(label, config, key_prefix):\n \"\"\"Show Anthropic-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_anthropic_key\",\n help=\"Your Anthropic API key (sk-ant-...)\"\n )\n \n with col2:\n # Auto-populate model list for Anthropic\n model_options = [\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022', \n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307'\n ]\n \n current_model = config.get('model', 'claude-3-5-sonnet-20241022')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_anthropic_model\",\n help=\"Claude model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_anthropic\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import AnthropicProvider\n \n provider = AnthropicProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Anthropic connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Anthropic endpoint (https://api.anthropic.com/v1) is automatically configured\")\n\ndef show_xai_settings(label, config, key_prefix):\n \"\"\"Show xAI-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_xai_key\",\n help=\"Your xAI API key\"\n )\n \n with col2:\n # Auto-populate model list for xAI\n model_options = [\n 'grok-beta',\n 'grok-vision-beta'\n ]\n \n current_model = config.get('model', 'grok-beta')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_xai_model\",\n help=\"Grok model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_xai\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import XAIProvider\n \n provider = XAIProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ xAI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 xAI endpoint (https://api.x.ai/v1) is automatically configured\")\n\ndef show_google_settings(label, config, key_prefix):\n \"\"\"Show Google-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_google_key\",\n help=\"Your Google AI Studio API key\"\n )\n \n with col2:\n # Auto-populate model list for Google\n model_options = [\n 'gemini-1.5-flash',\n 'gemini-1.5-pro',\n 'gemini-1.0-pro'\n ]\n \n current_model = config.get('model', 'gemini-1.5-flash')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_google_model\",\n help=\"Gemini model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_google\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GoogleProvider\n \n provider = GoogleProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Google AI connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Google AI endpoint (https://generativelanguage.googleapis.com/v1beta) is automatically configured\")\n\ndef show_groq_settings(label, config, key_prefix):\n \"\"\"Show Groq-specific settings with auto-populated defaults\"\"\"\n col1, col2 = st.columns(2)\n \n with col1:\n api_key = st.text_input(\n f\"{label} API Key\",\n value=config.get('api_key', ''),\n type=\"password\",\n key=f\"{key_prefix}_groq_key\",\n help=\"Your Groq API key (gsk-...)\"\n )\n \n with col2:\n # Auto-populate model list for Groq\n model_options = [\n 'llama-3.1-70b-versatile',\n 'llama-3.1-8b-instant',\n 'llama-3.2-90b-text-preview',\n 'mixtral-8x7b-32768',\n 'gemma2-9b-it'\n ]\n \n current_model = config.get('model', 'llama-3.1-70b-versatile')\n try:\n model_index = model_options.index(current_model)\n except ValueError:\n model_index = 0\n \n model = st.selectbox(\n f\"{label} Model\",\n options=model_options,\n index=model_index,\n key=f\"{key_prefix}_groq_model\",\n help=\"Groq model to use (auto-configured endpoint)\"\n )\n \n # Show test button if API key is provided\n if api_key and st.button(f\"🔍 Test {label} Connection\", key=f\"{key_prefix}_test_groq\"):\n try:\n sys.path.insert(0, str(Path(__file__).parent.parent.parent / \"src\"))\n from llm_manager import GroqProvider\n \n provider = GroqProvider({'api_key': api_key, 'model': model})\n test_response = provider.chat([{\"role\": \"user\", \"content\": \"Hello, this is a test.\"}])\n st.success(\"✅ Groq connection successful!\")\n except Exception as e:\n st.error(f\"❌ Connection failed: {e}\")\n \n st.info(\"💡 Groq endpoint (https://api.groq.com/openai/v1) is automatically configured\")\n\ndef show_audio_settings():\n \"\"\"Audio processing configuration\"\"\"\n st.markdown(\"### 🎵 Audio Processing Configuration\")\n \n config = st.session_state.get('config') or {}\n \n # Enhancement Method\n st.markdown(\"#### 🔧 Enhancement Method\")\n \n enhancement_method = st.selectbox(\n \"Audio Enhancement Method\",\n options=[\"deepfilternet\", \"resemble_enhance\", \"none\"],\n index=0 if config.get('audio_enhancement_method') == 'deepfilternet' else \n 1 if config.get('audio_enhancement_method') == 'resemble_enhance' else 2,\n help=\"Choose AI enhancement method for audio quality improvement\"\n )\n \n # Enhancement options\n st.markdown(\"#### ⚙️ Processing Options\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n use_audacity = st.checkbox(\n \"Use Audacity Integration\",\n value=config.get('use_audacity', False),\n help=\"Use Audacity with mod-script-pipe if available\"\n )\n \n audio_noise_reduction = st.checkbox(\n \"Noise Reduction\",\n value=config.get('audio_noise_reduction', True),\n help=\"Apply noise reduction during processing\"\n )\n \n audio_amplify = st.checkbox(\n \"Audio Amplification\",\n value=config.get('audio_amplify', True),\n help=\"Apply audio amplification\"\n )\n \n with col2:\n audio_normalize = st.checkbox(\n \"Audio Normalization\",\n value=config.get('audio_normalize', True),\n help=\"Normalize audio levels\"\n )\n \n audio_gain_db = st.slider(\n \"Gain (dB)\",\n min_value=-10.0,\n max_value=10.0,\n value=float(config.get('audio_gain_db', 0.5)),\n step=0.1,\n help=\"Audio gain adjustment in decibels\"\n )\n \n target_level_db = st.slider(\n \"Target Level (dB)\",\n min_value=-30.0,\n max_value=-10.0,\n value=float(config.get('audio_target_level_db', -22.0)),\n step=1.0,\n help=\"Target audio level for normalization\"\n )\n \n # Save button\n if st.button(\"💾 Save Audio Settings\", type=\"primary\"):\n save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db)\n\ndef show_validation_settings():\n \"\"\"Validation criteria configuration\"\"\"\n st.markdown(\"### ✅ Validation Configuration\")\n \n config = st.session_state.get('config') or {}\n metadata_config = config.get('metadata_processing', {})\n \n # Description validation\n st.markdown(\"#### 📝 Description Validation\")\n \n desc_config = metadata_config.get('description', {})\n desc_validation = desc_config.get('validation', {})\n \n validation_enabled = st.checkbox(\n \"Enable Description Validation\",\n value=desc_validation.get('enabled', True),\n help=\"Use AI to validate description quality\"\n )\n \n if validation_enabled:\n st.markdown(\"**Validation Criteria:**\")\n \n criteria = desc_validation.get('criteria', [])\n \n # Allow editing criteria\n new_criteria = []\n for i, criterion in enumerate(criteria):\n edited = st.text_input(f\"Criterion {i+1}\", value=criterion, key=f\"criteria_{i}\")\n if edited.strip():\n new_criteria.append(edited.strip())\n \n # Add new criterion\n new_criterion = st.text_input(\"Add new criterion:\", key=\"new_criterion\")\n if new_criterion.strip():\n new_criteria.append(new_criterion.strip())\n \n if st.button(\"➕ Add Criterion\"):\n st.rerun()\n \n # Metadata processing settings\n st.markdown(\"#### 🔧 Processing Settings\")\n \n col1, col2 = st.columns(2)\n \n with col1:\n st.markdown(\"**Description Settings:**\")\n \n desc_update_missing = st.checkbox(\n \"Update if Missing\",\n value=desc_config.get('update_if_missing', True),\n key=\"desc_update_missing\"\n )\n \n desc_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=desc_config.get('update_if_minimal', True),\n key=\"desc_update_minimal\"\n )\n \n desc_min_length = st.number_input(\n \"Min Length Threshold\",\n value=desc_config.get('min_length_threshold', 50),\n min_value=10,\n max_value=200,\n key=\"desc_min_length\"\n )\n \n with col2:\n st.markdown(\"**Hashtag Settings:**\")\n \n hashtag_config = metadata_config.get('hashtags', {})\n \n hash_update_missing = st.checkbox(\n \"Update if Missing\",\n value=hashtag_config.get('update_if_missing', True),\n key=\"hash_update_missing\"\n )\n \n hash_update_minimal = st.checkbox(\n \"Update if Minimal\",\n value=hashtag_config.get('update_if_minimal', True),\n key=\"hash_update_minimal\"\n )\n \n hash_min_length = st.number_input(\n \"Min Length Threshold\",\n value=hashtag_config.get('min_length_threshold', 10),\n min_value=5,\n max_value=50,\n key=\"hash_min_length\"\n )\n \n # Save button\n if st.button(\"💾 Save Validation Settings\", type=\"primary\"):\n save_validation_settings()\n\ndef show_backup_restore():\n \"\"\"Configuration backup and restore\"\"\"\n st.markdown(\"### 💾 Backup & Restore\")\n \n # Current config display\n st.markdown(\"#### 📄 Current Configuration\")\n \n config = st.session_state.get('config') or {}\n \n if config:\n # Show config as YAML\n config_yaml = yaml.dump(config, default_flow_style=False, sort_keys=True)\n st.code(config_yaml, language='yaml')\n \n # Download config\n if st.download_button(\n \"📥 Download Config\",\n data=config_yaml,\n file_name=\"config_backup.yaml\",\n mime=\"text/yaml\"\n ):\n st.success(\"Configuration downloaded!\")\n \n # Upload/restore config\n st.markdown(\"#### 📤 Restore Configuration\")\n \n uploaded_config = st.file_uploader(\n \"Upload Configuration File\",\n type=['yaml', 'yml'],\n help=\"Upload a configuration file to restore settings\"\n )\n \n if uploaded_config:\n try:\n config_content = uploaded_config.read().decode('utf-8')\n new_config = yaml.safe_load(config_content)\n \n st.success(\"✅ Configuration file loaded successfully!\")\n st.code(config_content, language='yaml')\n \n if st.button(\"🔄 Apply Configuration\", type=\"primary\"):\n st.session_state.config = new_config\n save_config_to_file(new_config)\n st.success(\"Configuration applied and saved!\")\n st.rerun()\n \n except Exception as e:\n st.error(f\"❌ Failed to load configuration: {e}\")\n \n # Reset to defaults\n st.markdown(\"#### 🔄 Reset to Defaults\")\n \n st.warning(\"⚠️ This will reset all settings to default values\")\n \n if st.button(\"🔄 Reset to Defaults\", type=\"secondary\"):\n if st.session_state.get('confirm_reset'):\n reset_to_defaults()\n st.success(\"Configuration reset to defaults!\")\n st.rerun()\n else:\n st.session_state.confirm_reset = True\n st.warning(\"Click again to confirm reset\")\n\ndef test_api_connection(api_key, broadcaster_id):\n \"\"\"Test SermonAudio API connection\"\"\"\n try:\n # This would test the actual API connection\n st.success(\"✅ API connection successful!\")\n except Exception as e:\n st.error(f\"❌ API connection failed: {e}\")\n\ndef test_llm_provider(provider, config):\n \"\"\"Test LLM provider connection\"\"\"\n try:\n # This would test the actual LLM provider\n st.success(f\"✅ {provider.title()} provider connection successful!\")\n except Exception as e:\n st.error(f\"❌ {provider.title()} provider connection failed: {e}\")\n\ndef save_general_settings(api_key, broadcaster_id, dry_run, debug, \n hashtag_verification, output_directory, \n save_original_audio, save_transcript):\n \"\"\"Save general settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'api_key': api_key,\n 'broadcaster_id': broadcaster_id,\n 'dry_run': dry_run,\n 'debug': debug,\n 'hashtag_verification': hashtag_verification,\n 'output_directory': output_directory,\n 'save_original_audio': save_original_audio,\n 'save_transcript': save_transcript\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ General settings saved!\")\n\ndef save_llm_settings():\n \"\"\"Save LLM settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n # Initialize LLM config if it doesn't exist\n if 'llm' not in st.session_state.config:\n st.session_state.config['llm'] = {}\n \n llm_config = st.session_state.config['llm']\n \n # Save Primary Provider Settings\n primary_provider = st.session_state.get('primary_provider', 'ollama')\n if 'primary' not in llm_config:\n llm_config['primary'] = {}\n llm_config['primary']['provider'] = primary_provider\n \n # Save primary provider-specific settings\n save_provider_settings(llm_config['primary'], primary_provider, 'primary')\n \n # Save Fallback Provider Settings\n fallback_enabled = st.session_state.get('fallback_enabled', False)\n if 'fallback' not in llm_config:\n llm_config['fallback'] = {}\n llm_config['fallback']['enabled'] = fallback_enabled\n \n if fallback_enabled:\n fallback_provider = st.session_state.get('fallback_provider', 'openai')\n llm_config['fallback']['provider'] = fallback_provider\n save_provider_settings(llm_config['fallback'], fallback_provider, 'fallback')\n \n # Save Validator Provider Settings\n validator_enabled = st.session_state.get('validator_enabled', False)\n if 'validator' not in llm_config:\n llm_config['validator'] = {}\n llm_config['validator']['enabled'] = validator_enabled\n \n if validator_enabled:\n validator_provider = st.session_state.get('validator_provider', 'ollama')\n llm_config['validator']['provider'] = validator_provider\n save_provider_settings(llm_config['validator'], validator_provider, 'validator')\n \n # Save to file\n save_config_to_file(st.session_state.config)\n st.success(\"✅ LLM settings saved!\")\n\ndef save_provider_settings(provider_config, provider_type, key_prefix):\n \"\"\"Save provider-specific settings from session state\"\"\"\n if provider_type not in provider_config:\n provider_config[provider_type] = {}\n \n provider_settings = provider_config[provider_type]\n \n if provider_type == 'ollama':\n host = st.session_state.get(f'{key_prefix}_ollama_host', 'http://localhost:11434')\n model = st.session_state.get(f'{key_prefix}_ollama_model', 'llama3')\n provider_settings['host'] = host\n provider_settings['model'] = model\n \n elif provider_type == 'openai':\n api_key = st.session_state.get(f'{key_prefix}_openai_key', '')\n base_url = st.session_state.get(f'{key_prefix}_openai_url', '')\n model = st.session_state.get(f'{key_prefix}_openai_model', 'gpt-3.5-turbo')\n if api_key:\n provider_settings['api_key'] = api_key\n if base_url:\n provider_settings['base_url'] = base_url\n provider_settings['model'] = model\n \n elif provider_type == 'anthropic':\n api_key = st.session_state.get(f'{key_prefix}_anthropic_key', '')\n model = st.session_state.get(f'{key_prefix}_anthropic_model', 'claude-3-5-sonnet-20241022')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'xai':\n api_key = st.session_state.get(f'{key_prefix}_xai_key', '')\n model = st.session_state.get(f'{key_prefix}_xai_model', 'grok-beta')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'google':\n api_key = st.session_state.get(f'{key_prefix}_google_key', '')\n model = st.session_state.get(f'{key_prefix}_google_model', 'gemini-1.5-flash')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n \n elif provider_type == 'groq':\n api_key = st.session_state.get(f'{key_prefix}_groq_key', '')\n model = st.session_state.get(f'{key_prefix}_groq_model', 'llama-3.1-70b-versatile')\n if api_key:\n provider_settings['api_key'] = api_key\n provider_settings['model'] = model\n\ndef save_audio_settings(enhancement_method, use_audacity, audio_noise_reduction,\n audio_amplify, audio_normalize, audio_gain_db, target_level_db):\n \"\"\"Save audio settings to configuration\"\"\"\n if not st.session_state.get('config'):\n st.session_state.config = {}\n \n st.session_state.config.update({\n 'audio_enhancement_method': enhancement_method,\n 'use_audacity': use_audacity,\n 'audio_noise_reduction': audio_noise_reduction,\n 'audio_amplify': audio_amplify,\n 'audio_normalize': audio_normalize,\n 'audio_gain_db': audio_gain_db,\n 'audio_target_level_db': target_level_db\n })\n \n save_config_to_file(st.session_state.config)\n st.success(\"✅ Audio settings saved!\")\n\ndef save_validation_settings():\n \"\"\"Save validation settings to configuration\"\"\"\n # This would collect validation settings and save them\n st.success(\"✅ Validation settings saved!\")\n\ndef save_config_to_file(config):\n \"\"\"Save configuration to config.yaml file\"\"\"\n try:\n project_root = Path(__file__).parent.parent.parent\n config_path = project_root / \"config.yaml\"\n \n with open(config_path, 'w') as f:\n yaml.dump(config, f, default_flow_style=False, sort_keys=True)\n \n st.info(f\"Configuration saved to {config_path}\")\n except Exception as e:\n st.error(f\"Failed to save configuration: {e}\")\n\ndef reset_to_defaults():\n \"\"\"Reset configuration to default values\"\"\"\n # Load example config as defaults\n try:\n project_root = Path(__file__).parent.parent.parent\n example_config_path = project_root / \"config.example.yaml\"\n \n with open(example_config_path, 'r') as f:\n default_config = yaml.safe_load(f)\n \n st.session_state.config = default_config\n save_config_to_file(default_config)\n \n except Exception as e:\n st.error(f\"Failed to reset to defaults: {e}\")\n\nif __name__ == \"__main__\":\n show_settings()"}}}} |