feat(settings): tombol Test API di AI (tanpa simpan)
All checks were successful
CI / test (push) Successful in 2m26s

- endpoint POST /admin/settings/test-ai (provider, key, base URL, model)
- UI status valid/tidak + latensi
- versi 0.5.14
This commit is contained in:
oxpoe
2026-11-05 17:49:54 +07:00
parent 7db4cb81a3
commit 1e3067989a
6 changed files with 122 additions and 3 deletions

View File

@@ -238,6 +238,10 @@
<div class="fg"><label class="fl">Custom Base URL</label><input class="fc" name="custom_ai_base_url" value="<%= settings.custom_ai_base_url || '' %>" placeholder="http://localhost:11434/v1"></div>
<div class="fg"><label class="fl">Custom API Key</label><input class="fc" type="password" autocomplete="new-password" name="custom_ai_api_key" value="<%= settings.custom_ai_api_key || '' %>" placeholder="opsional untuk Ollama"></div>
<div class="fg"><label class="fl">Custom Model</label><input class="fc" name="custom_ai_model" value="<%= settings.custom_ai_model || 'llama3' %>"></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:12px">
<button type="button" class="btn btn-g" onclick="testAiApi(this)"><i class="bi bi-plug"></i> Test API</button>
<span id="ai-test-status" style="font-size:12px;color:var(--muted)">Menguji API key &amp; model yang sedang diisi (tanpa simpan).</span>
</div>
</div>
<div>
<h6 style="font-size:13px;color:var(--muted);margin-bottom:12px;text-transform:uppercase;letter-spacing:1px"><i class="bi bi-activity"></i> Auto Repair</h6>
@@ -1252,6 +1256,46 @@ function setSearch(q) {
})();
</script>
<script>
async function testAiApi(btn) {
var st = document.getElementById('ai-test-status');
var provider = (document.querySelector('select[name="ai_provider"]') || {}).value || 'deepseek';
var map = {
deepseek: { key: 'deepseek_api_key', model: 'deepseek_model', base: '' },
openai: { key: 'openai_api_key', model: 'openai_model', base: '' },
groq: { key: 'groq_api_key', model: 'groq_model', base: '' },
openrouter: { key: 'openrouter_api_key', model: 'openrouter_model', base: '' },
custom: { key: 'custom_ai_api_key', model: 'custom_ai_model', base: 'custom_ai_base_url' }
};
var m = map[provider] || map.deepseek;
function val(name) { var el = document.querySelector('[name="' + name + '"]'); return el ? el.value : ''; }
var old = btn ? btn.innerHTML : '';
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Menguji...'; }
if (st) { st.style.color = 'var(--muted)'; st.textContent = 'Menguji ' + provider + '...'; }
try {
var r = await fetch('/admin/settings/test-ai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: provider,
apiKey: val(m.key),
model: val(m.model),
baseUrl: m.base ? val(m.base) : ''
})
});
var d = await r.json();
if (st) {
st.style.color = d.ok ? 'var(--success)' : 'var(--danger)';
st.textContent = (d.ok ? '✅ ' : '❌ ') + (d.message || (d.ok ? 'API valid.' : 'API tidak valid.'));
}
} catch (e) {
if (st) { st.style.color = 'var(--danger)'; st.textContent = '❌ Gagal menguji: ' + e.message; }
} finally {
if (btn) { btn.disabled = false; btn.innerHTML = old; }
}
}
</script>
<script>
async function selfHeal(mode) {
const out = document.getElementById('selfheal-out');