Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Scripture Detector — Settings</title> | |
| <link rel="icon" type="image/svg+xml" href="/static/favicon.svg"> | |
| <link rel="stylesheet" href="/static/style.css"> | |
| <style> | |
| .container { max-width: 640px; } | |
| .card { margin-bottom: 20px; } | |
| .radio-group { display: flex; gap: 12px; margin-bottom: 18px; } | |
| .radio-card { | |
| flex: 1; | |
| padding: 18px; | |
| border: 2px solid var(--border); | |
| border-radius: var(--radius); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| text-align: center; | |
| background: var(--surface); | |
| } | |
| .radio-card:hover { border-color: #a5b4fc; background: var(--primary-subtle); } | |
| .radio-card.selected { border-color: var(--primary); background: var(--primary-subtle); } | |
| .radio-card input { display: none; } | |
| .radio-card .rc-title { font-size: .9rem; font-weight: 700; margin-bottom: 3px; color: var(--text); } | |
| .radio-card .rc-desc { font-size: .74rem; color: var(--muted); } | |
| .conditional { display: none; } | |
| .conditional.visible { display: block; } | |
| .actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 28px; align-items: center; } | |
| .status-badge { display: inline-block; padding: 3px 10px; border-radius: 20px; | |
| font-size: .76rem; font-weight: 600; } | |
| .status-ok { background: rgba(22,163,74,.12); color: var(--green); } | |
| .status-warn { background: rgba(245,158,11,.12); color: #92400e; } | |
| .btn-success { background: linear-gradient(135deg, #16a34a, #15803d); color: #fff; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <a href="/" class="header-brand"> | |
| <img src="/static/logo.svg" alt="Logo" class="header-logo"> | |
| <div> | |
| <div class="header-title">Scripture Detector</div> | |
| <div class="header-subtitle">Settings</div> | |
| </div> | |
| </a> | |
| <nav> | |
| <a href="/">Sources</a> | |
| <a href="/dashboard">Dashboard</a> | |
| <a href="/about">About</a> | |
| <a href="/settings" class="active">Settings</a> | |
| </nav> | |
| </div> | |
| <div class="container"> | |
| <div class="card"> | |
| <h2>API Configuration</h2> | |
| <label style="font-size:.82rem;font-weight:600;margin-bottom:10px;display:block;color:var(--text-secondary)">API Provider</label> | |
| <div class="radio-group"> | |
| <label class="radio-card" id="rc-gemini" onclick="selectProvider('gemini')"> | |
| <input type="radio" name="provider" value="gemini"> | |
| <div class="rc-title">Gemini API</div> | |
| <div class="rc-desc">Google AI Studio API Key</div> | |
| </label> | |
| <label class="radio-card" id="rc-vertex" onclick="selectProvider('vertex')"> | |
| <input type="radio" name="provider" value="vertex"> | |
| <div class="rc-title">Vertex AI</div> | |
| <div class="rc-desc">Google Cloud Project</div> | |
| </label> | |
| </div> | |
| <div class="conditional" id="gemini-fields"> | |
| <div class="field"> | |
| <label for="api-key">API Key</label> | |
| <input type="password" id="api-key" placeholder="AIza..."> | |
| <div class="hint">Get your key from <a href="https://aistudio.google.com/apikey" target="_blank">Google AI Studio</a></div> | |
| </div> | |
| </div> | |
| <div class="conditional" id="vertex-fields"> | |
| <div class="field"> | |
| <label for="project-id">Project ID</label> | |
| <input type="text" id="project-id" placeholder="my-gcp-project"> | |
| </div> | |
| <div class="field"> | |
| <label for="location">Location</label> | |
| <input type="text" id="location" placeholder="global" value="global"> | |
| <div class="hint">e.g. global, us-central1, europe-west1</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>Model Selection</h2> | |
| <div class="field"> | |
| <label for="model-select">Model</label> | |
| <select id="model-select"> | |
| {% for m in models %} | |
| <option value="{{ m.id }}">{{ m.name }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <span id="save-status"></span> | |
| <button class="btn btn-primary" id="save-btn" onclick="saveSettings()">Save Settings</button> | |
| </div> | |
| </div> | |
| <script> | |
| let currentProvider = 'gemini'; | |
| function showToast(msg, ok) { | |
| const el = document.createElement('div'); | |
| el.className = 'toast ' + (ok ? 'toast-ok' : 'toast-err'); | |
| el.textContent = msg; | |
| document.body.appendChild(el); | |
| setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 400); }, 3500); | |
| } | |
| function selectProvider(p) { | |
| currentProvider = p; | |
| document.getElementById('rc-gemini').classList.toggle('selected', p === 'gemini'); | |
| document.getElementById('rc-vertex').classList.toggle('selected', p === 'vertex'); | |
| document.getElementById('gemini-fields').classList.toggle('visible', p === 'gemini'); | |
| document.getElementById('vertex-fields').classList.toggle('visible', p === 'vertex'); | |
| } | |
| function loadSettings() { | |
| fetch('/api/settings') | |
| .then(r => r.json()) | |
| .then(s => { | |
| const provider = s.api_provider || 'gemini'; | |
| selectProvider(provider); | |
| if (s.gemini_api_key) document.getElementById('api-key').value = s.gemini_api_key; | |
| if (s.vertex_project_id) document.getElementById('project-id').value = s.vertex_project_id; | |
| if (s.vertex_location) document.getElementById('location').value = s.vertex_location; | |
| if (s.model) document.getElementById('model-select').value = s.model; | |
| }); | |
| } | |
| function saveSettings() { | |
| const btn = document.getElementById('save-btn'); | |
| btn.disabled = true; | |
| const payload = { | |
| api_provider: currentProvider, | |
| model: document.getElementById('model-select').value, | |
| }; | |
| if (currentProvider === 'gemini') { | |
| const key = document.getElementById('api-key').value.trim(); | |
| if (key) payload.gemini_api_key = key; | |
| } else { | |
| payload.vertex_project_id = document.getElementById('project-id').value.trim(); | |
| payload.vertex_location = document.getElementById('location').value.trim() || 'global'; | |
| } | |
| fetch('/api/settings', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(payload), | |
| }) | |
| .then(r => r.json()) | |
| .then(() => { | |
| showToast('Settings saved!', true); | |
| btn.classList.remove('btn-primary'); | |
| btn.classList.add('btn-success'); | |
| btn.textContent = 'Saved!'; | |
| setTimeout(() => { | |
| btn.classList.remove('btn-success'); | |
| btn.classList.add('btn-primary'); | |
| btn.textContent = 'Save Settings'; | |
| }, 2000); | |
| }) | |
| .catch(e => showToast('Error: ' + e.message, false)) | |
| .finally(() => { btn.disabled = false; }); | |
| } | |
| loadSettings(); | |
| </script> | |
| </body> | |
| </html> | |