1 line
40 KiB
JSON
1 line
40 KiB
JSON
{"repo": "c-s-v-s-subrahmanyam/edubot", "files": {"frontend/app/settings/components/ModelSection.tsx": {"45478554f5cf2d60f269eb9b1fcff760c6dcd9c7": {"t": 1785746580.0731854, "n": 19609, "h": "5d7657b8edc1c520", "c": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useRouter } from 'next/navigation';\nimport styles from '../settings.module.css';\nimport CustomSelect from '../../components/CustomSelect';\n\nconst API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8000/api';\n\ninterface ModelSectionProps {\n formData: {\n ai_provider: string;\n };\n setFormData: (data: any) => void;\n apiKeys: {\n openai_key: string;\n openai_model: string;\n gemini_key: string;\n gemini_model: string;\n ollama_url: string;\n ollama_model: string;\n deepseek_key: string;\n deepseek_model: string;\n };\n setApiKeys: (keys: any) => void;\n providerDefaults?: Record<string, boolean>;\n}\n\nexport default function ModelSection({ \n formData, \n setFormData, \n apiKeys, \n setApiKeys,\n providerDefaults = {},\n}: ModelSectionProps) {\n const router = useRouter();\n \n const [ollamaTestStatus, setOllamaTestStatus] = useState('');\n const [testingOllama, setTestingOllama] = useState(false);\n \n const [openaiTestStatus, setOpenaiTestStatus] = useState('');\n const [testingOpenai, setTestingOpenai] = useState(false);\n \n const [geminiTestStatus, setGeminiTestStatus] = useState('');\n const [testingGemini, setTestingGemini] = useState(false);\n\n const [deepseekTestStatus, setDeepseekTestStatus] = useState('');\n const [testingDeepseek, setTestingDeepseek] = useState(false);\n\n const testOllamaConnection = async () => {\n setTestingOllama(true);\n setOllamaTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'ollama',\n ollama_url: apiKeys.ollama_url\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setOllamaTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setOllamaTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setOllamaTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setOllamaTestStatus('✗ Cannot connect. Make sure Ollama is running.');\n } finally {\n setTestingOllama(false);\n setTimeout(() => setOllamaTestStatus(''), 3000);\n }\n };\n \n const testOpenAIConnection = async () => {\n if (!apiKeys.openai_key && !providerDefaults.openai) {\n setOpenaiTestStatus('✗ Please enter an OpenAI API key first');\n setTimeout(() => setOpenaiTestStatus(''), 3000);\n return;\n }\n \n setTestingOpenai(true);\n setOpenaiTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'openai',\n api_key: apiKeys.openai_key\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setOpenaiTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setOpenaiTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setOpenaiTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setOpenaiTestStatus('✗ Cannot connect to OpenAI');\n } finally {\n setTestingOpenai(false);\n setTimeout(() => setOpenaiTestStatus(''), 3000);\n }\n };\n \n const testGeminiConnection = async () => {\n if (!apiKeys.gemini_key && !providerDefaults.gemini) {\n setGeminiTestStatus('✗ Please enter a Gemini API key first');\n setTimeout(() => setGeminiTestStatus(''), 3000);\n return;\n }\n \n setTestingGemini(true);\n setGeminiTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'gemini',\n api_key: apiKeys.gemini_key\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setGeminiTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setGeminiTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setGeminiTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setGeminiTestStatus('✗ Cannot connect to Gemini');\n } finally {\n setTestingGemini(false);\n setTimeout(() => setGeminiTestStatus(''), 3000);\n }\n };\n\n const testDeepSeekConnection = async () => {\n if (!apiKeys.deepseek_key && !providerDefaults.deepseek) {\n setDeepseekTestStatus('✗ Please enter a DeepSeek API key first');\n setTimeout(() => setDeepseekTestStatus(''), 3000);\n return;\n }\n\n setTestingDeepseek(true);\n setDeepseekTestStatus('');\n\n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n\n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'deepseek',\n api_key: apiKeys.deepseek_key,\n }),\n });\n\n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setDeepseekTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setDeepseekTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setDeepseekTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setDeepseekTestStatus('✗ Cannot connect to DeepSeek');\n } finally {\n setTestingDeepseek(false);\n setTimeout(() => setDeepseekTestStatus(''), 3000);\n }\n };\n\n return (\n <div className={styles.sectionContent}>\n <div className={styles.sectionIntro}>\n <h3>🤖 AI Configuration</h3>\n <p>Select your preferred AI provider and configure the model settings</p>\n </div>\n\n <div className={styles.privacyNotice}>\n <span className={styles.lockIcon}>🔒</span>\n <div>\n <strong>Privacy Notice:</strong> API keys are stored locally in your browser only. \n We do NOT store your API keys on our servers.\n </div>\n </div>\n\n {/* Server Defaults Banner */}\n {(providerDefaults.openai || providerDefaults.gemini || providerDefaults.deepseek) && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>🏫</span>\n <div>\n <strong>University Defaults Available:</strong> The server has pre-configured API keys for{' '}\n {[providerDefaults.openai && 'OpenAI', providerDefaults.gemini && 'Gemini', providerDefaults.deepseek && 'DeepSeek'].filter(Boolean).join(', ')}.\n You can use them by selecting the provider — no personal API key needed.\n Choose <strong>Auto</strong> to use them with automatic fallback.\n </div>\n </div>\n )}\n \n <div className={styles.formGroup}>\n <label htmlFor=\"ai_provider\">Select Provider</label>\n <CustomSelect\n id=\"ai_provider\"\n value={formData.ai_provider}\n onChange={(value) => setFormData({ ...formData, ai_provider: value })}\n options={[\n { value: 'ollama', label: 'Local Ollama (Recommended)' },\n { value: 'openai', label: 'OpenAI GPT-4' },\n { value: 'gemini', label: 'Google Gemini' },\n { value: 'deepseek', label: 'DeepSeek' },\n { value: 'auto', label: 'Auto (University-Provided API with Fallback)' },\n ]}\n />\n </div>\n \n {/* Ollama Configuration */}\n {formData.ai_provider === 'ollama' && (\n <div className={styles.providerConfig}>\n <div className={styles.formGroup}>\n <label htmlFor=\"ollama_url\">Ollama Server URL</label>\n <input\n type=\"text\"\n id=\"ollama_url\"\n value={apiKeys.ollama_url}\n onChange={(e) => {\n const newKeys = { ...apiKeys, ollama_url: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder=\"http://localhost:11434\" \n className={styles.input}\n />\n <small className={styles.hint}>\n URL of your local Ollama instance. Default: http://localhost:11434\n </small>\n </div>\n \n <div className={styles.formGroup}>\n <label htmlFor=\"ollama_model\">Ollama Model</label>\n <input\n type=\"text\"\n id=\"ollama_model\"\n value={apiKeys.ollama_model}\n onChange={(e) => {\n const newKeys = { ...apiKeys, ollama_model: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder=\"llama3.1:8b\" \n className={styles.input}\n />\n <small className={styles.hint}>\n Model name (e.g., llama3.1:8b, llama2, mistral). Use 'ollama list' to see available models.\n </small>\n </div>\n \n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testOllamaConnection}\n disabled={testingOllama}\n className={styles.testButton}\n >\n {testingOllama ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {ollamaTestStatus && (\n <div className={ollamaTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {ollamaTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n \n {/* OpenAI Configuration */}\n {formData.ai_provider === 'openai' && (\n <div className={styles.providerConfig}>\n {providerDefaults.openai && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided OpenAI key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"openai_key\">OpenAI API Key</label>\n <input\n type=\"password\"\n id=\"openai_key\"\n value={apiKeys.openai_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, openai_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.openai ? \"Using server default (enter to override)\" : \"sk-...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from <a href=\"https://platform.openai.com/api-keys\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAI Platform</a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"openai_model\">OpenAI Model</label>\n <CustomSelect\n id=\"openai_model\"\n value={apiKeys.openai_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, openai_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Recommended for Chatbots & QA)' },\n ]}\n />\n <small className={styles.hint}>\n Select the OpenAI model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testOpenAIConnection}\n disabled={testingOpenai || (!apiKeys.openai_key && !providerDefaults.openai)}\n className={styles.testButton}\n >\n {testingOpenai ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {openaiTestStatus && (\n <div className={openaiTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {openaiTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n \n {/* Gemini Configuration */}\n {formData.ai_provider === 'gemini' && (\n <div className={styles.providerConfig}>\n {providerDefaults.gemini && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided Gemini key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"gemini_key\">Google Gemini API Key</label>\n <input\n type=\"password\"\n id=\"gemini_key\"\n value={apiKeys.gemini_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, gemini_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.gemini ? \"Using server default (enter to override)\" : \"AI...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from <a href=\"https://makersuite.google.com/app/apikey\" target=\"_blank\" rel=\"noopener noreferrer\">Google AI Studio</a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"gemini_model\">Gemini Model</label>\n <CustomSelect\n id=\"gemini_model\"\n value={apiKeys.gemini_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, gemini_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash (Recommended - Fast and efficient)' },\n { value: 'gemini-flash-latest', label: 'Gemini Flash Latest (Always latest flash model)' },\n ]}\n />\n <small className={styles.hint}>\n Select the Gemini model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testGeminiConnection}\n disabled={testingGemini || (!apiKeys.gemini_key && !providerDefaults.gemini)}\n className={styles.testButton}\n >\n {testingGemini ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {geminiTestStatus && (\n <div className={geminiTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {geminiTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* DeepSeek Configuration */}\n {formData.ai_provider === 'deepseek' && (\n <div className={styles.providerConfig}>\n {providerDefaults.deepseek && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided DeepSeek key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"deepseek_key\">DeepSeek API Key</label>\n <input\n type=\"password\"\n id=\"deepseek_key\"\n value={apiKeys.deepseek_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, deepseek_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.deepseek ? \"Using server default (enter to override)\" : \"sk-...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from{' '}\n <a href=\"https://platform.deepseek.com/api_keys\" target=\"_blank\" rel=\"noopener noreferrer\">\n DeepSeek Platform\n </a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"deepseek_model\">DeepSeek Model</label>\n <CustomSelect\n id=\"deepseek_model\"\n value={apiKeys.deepseek_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, deepseek_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'deepseek-chat', label: 'DeepSeek Chat (Recommended - V3, fast & capable)' },\n { value: 'deepseek-reasoner', label: 'DeepSeek Reasoner (R1 — advanced reasoning)' },\n ]}\n />\n <small className={styles.hint}>\n Select the DeepSeek model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testDeepSeekConnection}\n disabled={testingDeepseek || (!apiKeys.deepseek_key && !providerDefaults.deepseek)}\n className={styles.testButton}\n >\n {testingDeepseek ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {deepseekTestStatus && (\n <div className={deepseekTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {deepseekTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n"}, "16c5aa58d79eea4e4ea3981339f6ae66286581b1": {"t": 1785765936.5022936, "n": 19609, "h": "5d7657b8edc1c520", "c": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { useRouter } from 'next/navigation';\nimport styles from '../settings.module.css';\nimport CustomSelect from '../../components/CustomSelect';\n\nconst API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8000/api';\n\ninterface ModelSectionProps {\n formData: {\n ai_provider: string;\n };\n setFormData: (data: any) => void;\n apiKeys: {\n openai_key: string;\n openai_model: string;\n gemini_key: string;\n gemini_model: string;\n ollama_url: string;\n ollama_model: string;\n deepseek_key: string;\n deepseek_model: string;\n };\n setApiKeys: (keys: any) => void;\n providerDefaults?: Record<string, boolean>;\n}\n\nexport default function ModelSection({ \n formData, \n setFormData, \n apiKeys, \n setApiKeys,\n providerDefaults = {},\n}: ModelSectionProps) {\n const router = useRouter();\n \n const [ollamaTestStatus, setOllamaTestStatus] = useState('');\n const [testingOllama, setTestingOllama] = useState(false);\n \n const [openaiTestStatus, setOpenaiTestStatus] = useState('');\n const [testingOpenai, setTestingOpenai] = useState(false);\n \n const [geminiTestStatus, setGeminiTestStatus] = useState('');\n const [testingGemini, setTestingGemini] = useState(false);\n\n const [deepseekTestStatus, setDeepseekTestStatus] = useState('');\n const [testingDeepseek, setTestingDeepseek] = useState(false);\n\n const testOllamaConnection = async () => {\n setTestingOllama(true);\n setOllamaTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'ollama',\n ollama_url: apiKeys.ollama_url\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setOllamaTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setOllamaTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setOllamaTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setOllamaTestStatus('✗ Cannot connect. Make sure Ollama is running.');\n } finally {\n setTestingOllama(false);\n setTimeout(() => setOllamaTestStatus(''), 3000);\n }\n };\n \n const testOpenAIConnection = async () => {\n if (!apiKeys.openai_key && !providerDefaults.openai) {\n setOpenaiTestStatus('✗ Please enter an OpenAI API key first');\n setTimeout(() => setOpenaiTestStatus(''), 3000);\n return;\n }\n \n setTestingOpenai(true);\n setOpenaiTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'openai',\n api_key: apiKeys.openai_key\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setOpenaiTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setOpenaiTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setOpenaiTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setOpenaiTestStatus('✗ Cannot connect to OpenAI');\n } finally {\n setTestingOpenai(false);\n setTimeout(() => setOpenaiTestStatus(''), 3000);\n }\n };\n \n const testGeminiConnection = async () => {\n if (!apiKeys.gemini_key && !providerDefaults.gemini) {\n setGeminiTestStatus('✗ Please enter a Gemini API key first');\n setTimeout(() => setGeminiTestStatus(''), 3000);\n return;\n }\n \n setTestingGemini(true);\n setGeminiTestStatus('');\n \n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n \n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'gemini',\n api_key: apiKeys.gemini_key\n }),\n });\n \n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setGeminiTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setGeminiTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setGeminiTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setGeminiTestStatus('✗ Cannot connect to Gemini');\n } finally {\n setTestingGemini(false);\n setTimeout(() => setGeminiTestStatus(''), 3000);\n }\n };\n\n const testDeepSeekConnection = async () => {\n if (!apiKeys.deepseek_key && !providerDefaults.deepseek) {\n setDeepseekTestStatus('✗ Please enter a DeepSeek API key first');\n setTimeout(() => setDeepseekTestStatus(''), 3000);\n return;\n }\n\n setTestingDeepseek(true);\n setDeepseekTestStatus('');\n\n try {\n const token = localStorage.getItem('token');\n if (!token) {\n router.push('/login');\n return;\n }\n\n const response = await fetch(`${API_BASE}/settings/test-connection`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n provider: 'deepseek',\n api_key: apiKeys.deepseek_key,\n }),\n });\n\n if (response.ok) {\n const result = await response.json();\n if (result.success) {\n setDeepseekTestStatus(`✓ ${result.message}. ${result.details || ''}`);\n } else {\n setDeepseekTestStatus(`✗ ${result.message}. ${result.details || ''}`);\n }\n } else {\n setDeepseekTestStatus('✗ Connection test failed');\n }\n } catch (err) {\n setDeepseekTestStatus('✗ Cannot connect to DeepSeek');\n } finally {\n setTestingDeepseek(false);\n setTimeout(() => setDeepseekTestStatus(''), 3000);\n }\n };\n\n return (\n <div className={styles.sectionContent}>\n <div className={styles.sectionIntro}>\n <h3>🤖 AI Configuration</h3>\n <p>Select your preferred AI provider and configure the model settings</p>\n </div>\n\n <div className={styles.privacyNotice}>\n <span className={styles.lockIcon}>🔒</span>\n <div>\n <strong>Privacy Notice:</strong> API keys are stored locally in your browser only. \n We do NOT store your API keys on our servers.\n </div>\n </div>\n\n {/* Server Defaults Banner */}\n {(providerDefaults.openai || providerDefaults.gemini || providerDefaults.deepseek) && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>🏫</span>\n <div>\n <strong>University Defaults Available:</strong> The server has pre-configured API keys for{' '}\n {[providerDefaults.openai && 'OpenAI', providerDefaults.gemini && 'Gemini', providerDefaults.deepseek && 'DeepSeek'].filter(Boolean).join(', ')}.\n You can use them by selecting the provider — no personal API key needed.\n Choose <strong>Auto</strong> to use them with automatic fallback.\n </div>\n </div>\n )}\n \n <div className={styles.formGroup}>\n <label htmlFor=\"ai_provider\">Select Provider</label>\n <CustomSelect\n id=\"ai_provider\"\n value={formData.ai_provider}\n onChange={(value) => setFormData({ ...formData, ai_provider: value })}\n options={[\n { value: 'ollama', label: 'Local Ollama (Recommended)' },\n { value: 'openai', label: 'OpenAI GPT-4' },\n { value: 'gemini', label: 'Google Gemini' },\n { value: 'deepseek', label: 'DeepSeek' },\n { value: 'auto', label: 'Auto (University-Provided API with Fallback)' },\n ]}\n />\n </div>\n \n {/* Ollama Configuration */}\n {formData.ai_provider === 'ollama' && (\n <div className={styles.providerConfig}>\n <div className={styles.formGroup}>\n <label htmlFor=\"ollama_url\">Ollama Server URL</label>\n <input\n type=\"text\"\n id=\"ollama_url\"\n value={apiKeys.ollama_url}\n onChange={(e) => {\n const newKeys = { ...apiKeys, ollama_url: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder=\"http://localhost:11434\" \n className={styles.input}\n />\n <small className={styles.hint}>\n URL of your local Ollama instance. Default: http://localhost:11434\n </small>\n </div>\n \n <div className={styles.formGroup}>\n <label htmlFor=\"ollama_model\">Ollama Model</label>\n <input\n type=\"text\"\n id=\"ollama_model\"\n value={apiKeys.ollama_model}\n onChange={(e) => {\n const newKeys = { ...apiKeys, ollama_model: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder=\"llama3.1:8b\" \n className={styles.input}\n />\n <small className={styles.hint}>\n Model name (e.g., llama3.1:8b, llama2, mistral). Use 'ollama list' to see available models.\n </small>\n </div>\n \n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testOllamaConnection}\n disabled={testingOllama}\n className={styles.testButton}\n >\n {testingOllama ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {ollamaTestStatus && (\n <div className={ollamaTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {ollamaTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n \n {/* OpenAI Configuration */}\n {formData.ai_provider === 'openai' && (\n <div className={styles.providerConfig}>\n {providerDefaults.openai && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided OpenAI key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"openai_key\">OpenAI API Key</label>\n <input\n type=\"password\"\n id=\"openai_key\"\n value={apiKeys.openai_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, openai_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.openai ? \"Using server default (enter to override)\" : \"sk-...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from <a href=\"https://platform.openai.com/api-keys\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAI Platform</a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"openai_model\">OpenAI Model</label>\n <CustomSelect\n id=\"openai_model\"\n value={apiKeys.openai_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, openai_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Recommended for Chatbots & QA)' },\n ]}\n />\n <small className={styles.hint}>\n Select the OpenAI model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testOpenAIConnection}\n disabled={testingOpenai || (!apiKeys.openai_key && !providerDefaults.openai)}\n className={styles.testButton}\n >\n {testingOpenai ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {openaiTestStatus && (\n <div className={openaiTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {openaiTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n \n {/* Gemini Configuration */}\n {formData.ai_provider === 'gemini' && (\n <div className={styles.providerConfig}>\n {providerDefaults.gemini && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided Gemini key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"gemini_key\">Google Gemini API Key</label>\n <input\n type=\"password\"\n id=\"gemini_key\"\n value={apiKeys.gemini_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, gemini_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.gemini ? \"Using server default (enter to override)\" : \"AI...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from <a href=\"https://makersuite.google.com/app/apikey\" target=\"_blank\" rel=\"noopener noreferrer\">Google AI Studio</a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"gemini_model\">Gemini Model</label>\n <CustomSelect\n id=\"gemini_model\"\n value={apiKeys.gemini_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, gemini_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash (Recommended - Fast and efficient)' },\n { value: 'gemini-flash-latest', label: 'Gemini Flash Latest (Always latest flash model)' },\n ]}\n />\n <small className={styles.hint}>\n Select the Gemini model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testGeminiConnection}\n disabled={testingGemini || (!apiKeys.gemini_key && !providerDefaults.gemini)}\n className={styles.testButton}\n >\n {testingGemini ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {geminiTestStatus && (\n <div className={geminiTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {geminiTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* DeepSeek Configuration */}\n {formData.ai_provider === 'deepseek' && (\n <div className={styles.providerConfig}>\n {providerDefaults.deepseek && (\n <div className={styles.privacyNotice} style={{ borderLeft: '4px solid #10b981' }}>\n <span className={styles.lockIcon}>✅</span>\n <div>\n <strong>Server default configured.</strong> A university-provided DeepSeek key is active.\n You can still enter your own key below to override it.\n </div>\n </div>\n )}\n <div className={styles.formGroup}>\n <label htmlFor=\"deepseek_key\">DeepSeek API Key</label>\n <input\n type=\"password\"\n id=\"deepseek_key\"\n value={apiKeys.deepseek_key}\n onChange={(e) => {\n const newKeys = { ...apiKeys, deepseek_key: e.target.value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n placeholder={providerDefaults.deepseek ? \"Using server default (enter to override)\" : \"sk-...\"}\n className={styles.input}\n />\n <small className={styles.hint}>\n Get your API key from{' '}\n <a href=\"https://platform.deepseek.com/api_keys\" target=\"_blank\" rel=\"noopener noreferrer\">\n DeepSeek Platform\n </a>\n </small>\n </div>\n <div className={styles.formGroup}>\n <label htmlFor=\"deepseek_model\">DeepSeek Model</label>\n <CustomSelect\n id=\"deepseek_model\"\n value={apiKeys.deepseek_model}\n onChange={(value) => {\n const newKeys = { ...apiKeys, deepseek_model: value };\n setApiKeys(newKeys);\n localStorage.setItem('edubot_api_keys', JSON.stringify(newKeys));\n }}\n options={[\n { value: 'deepseek-chat', label: 'DeepSeek Chat (Recommended - V3, fast & capable)' },\n { value: 'deepseek-reasoner', label: 'DeepSeek Reasoner (R1 — advanced reasoning)' },\n ]}\n />\n <small className={styles.hint}>\n Select the DeepSeek model to use\n </small>\n </div>\n <div className={styles.formGroup}>\n <button\n type=\"button\"\n onClick={testDeepSeekConnection}\n disabled={testingDeepseek || (!apiKeys.deepseek_key && !providerDefaults.deepseek)}\n className={styles.testButton}\n >\n {testingDeepseek ? 'Testing...' : '🔍 Test Connection'}\n </button>\n {deepseekTestStatus && (\n <div className={deepseekTestStatus.startsWith('✓') ? styles.testSuccess : styles.testError}>\n {deepseekTestStatus}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n"}}}} |