William Mattingly
Add scripture detector app
a9a9428
<!DOCTYPE html>
<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>