Files

1 line
1.1 MiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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()"}}}}