v1.8.3: admin — rename Settings to LLM, merge Claude AI into LLM page, hide Claude nav

This commit is contained in:
2026-05-28 19:33:25 +00:00
parent f721ebaf4d
commit 21610345bd
15 changed files with 148 additions and 11 deletions
+23
View File
@@ -542,6 +542,29 @@ def admin_library_health(_=Depends(require_admin)):
"vector_count": vector_count, "vector_count": vector_count,
} }
# ── Ingest single book ─────────────────────────────────────────────────────────
@app.post("/admin/ingest-book/{book_id}")
def admin_ingest_book(book_id: int, _=Depends(require_admin)):
import os as _os, sys as _sys
with get_db() as conn:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT filename FROM books WHERE id=%s", (book_id,))
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Book not found")
filename = row["filename"]
path = f"/mnt/rag-media/pdfs/{filename}"
if not _os.path.exists(path):
raise HTTPException(status_code=404, detail=f"PDF file not found: {filename}")
_sys.path.insert(0, "/opt/rag")
import ingest as _ingest
try:
_ingest.ingest_pdf(path)
return {"status": "ok", "filename": filename}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# ── Rebuild BM25 ─────────────────────────────────────────────────────────────── # ── Rebuild BM25 ───────────────────────────────────────────────────────────────
@app.post("/admin/rebuild-bm25") @app.post("/admin/rebuild-bm25")
def admin_rebuild_bm25(_=Depends(require_admin)): def admin_rebuild_bm25(_=Depends(require_admin)):
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -52006,3 +52006,56 @@ $42
rag:cache:29818f3e30b650fe3989c671faac13a7 rag:cache:29818f3e30b650fe3989c671faac13a7
$42 $42
rag:cache:0af1b0865c46edc9187b4091cda597a9 rag:cache:0af1b0865c46edc9187b4091cda597a9
*2
$6
SELECT
$1
1
*5
$3
SET
$13
unacked_mutex
$32
6977fbd25acb11f199e682dacbd0314b
$4
PXAT
$13
1779996811953
*2
$3
del
$13
unacked_mutex
*5
$3
SET
$13
unacked_mutex
$32
a51524305acb11f199e682dacbd0314b
$4
PXAT
$13
1779996911968
*2
$3
del
$13
unacked_mutex
*5
$3
SET
$13
unacked_mutex
$32
e0b22a605acb11f199e682dacbd0314b
$4
PXAT
$13
1779997011983
*2
$3
del
$13
unacked_mutex
+72 -11
View File
@@ -326,14 +326,14 @@ a[role=button]:active {
<button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('api',this)"> <button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('api',this)">
<span class="msym text-lg">key</span><span>API</span> <span class="msym text-lg">key</span><span>API</span>
</button> </button>
<button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('claude',this)"> <!-- REMOVED claude nav --><button class="nav-btn w-full hidden flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('claude',this)">
<span class="msym text-lg">neurology</span><span>Claude AI</span> <span class="msym text-lg">neurology</span><span>Claude AI</span>
</button> </button>
<button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('tracking',this)"> <button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('tracking',this)">
<span class="msym text-lg">troubleshoot</span><span>Tracking</span> <span class="msym text-lg">troubleshoot</span><span>Tracking</span>
</button> </button>
<button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('settings',this)"> <button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('settings',this)">
<span class="msym text-lg">settings</span><span>Settings</span> <span class="msym text-lg">smart_toy</span><span>LLM</span>
</button> </button>
<button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('pages',this)"> <button class="nav-btn w-full flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-semibold transition-all" onclick="showPage('pages',this)">
<span class="msym text-lg">description</span><span>Pages</span> <span class="msym text-lg">description</span><span>Pages</span>
@@ -577,8 +577,8 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
<!-- SETTINGS --> <!-- SETTINGS -->
<section id="page-settings" class="page hidden"> <section id="page-settings" class="page hidden">
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<div><h2 class="text-2xl font-bold">Settings</h2><p class="text-on-surface-variant text-sm mt-1">AI & system configuration</p></div> <div><h2 class="text-2xl font-bold">LLM</h2><p class="text-on-surface-variant text-sm mt-1">AI provider, keys & system configuration</p></div>
<button onclick="loadSettings()" class="glass glass-hover px-4 py-2 rounded-xl text-sm flex items-center gap-2"> <button onclick="loadSettings();loadClaude();" class="glass glass-hover px-4 py-2 rounded-xl text-sm flex items-center gap-2">
<span class="msym text-base">refresh</span> Refresh <span class="msym text-base">refresh</span> Refresh
</button> </button>
</div> </div>
@@ -588,10 +588,48 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
</div> </div>
<div id="settings-llm" class="glass rounded-2xl p-6 mt-6"></div> <div id="settings-llm" class="glass rounded-2xl p-6 mt-6"></div>
<div id="settings-r2" class="glass rounded-2xl p-6 mt-6"></div> <div id="settings-r2" class="glass rounded-2xl p-6 mt-6"></div>
<!-- ── Claude AI Section (moved from Claude page) ──────────────────── -->
<div class="glass rounded-2xl p-6 mt-6">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-5 flex items-center gap-2">
<span class="msym text-base">smart_toy</span> Claude AI
</h3>
<!-- Key input -->
<div class="glass rounded-2xl p-6 mb-6">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-5 flex items-center gap-2"><span class="msym text-base">key</span>API Key</h3>
<div class="flex gap-3 mb-3">
<input type="password" id="claude-key-input" placeholder="sk-ant-..."
class="flex-1 bg-surface-high border border-outline-variant rounded-xl px-4 py-3 text-sm text-on-surface outline-none focus:border-primary transition-colors font-mono"/>
<button onclick="toggleClaudeKey()" id="claude-key-toggle" class="glass glass-hover px-4 py-2 rounded-xl text-xs text-on-surface-variant">Show</button>
</div>
<div class="flex items-center gap-3 mb-4">
<div id="claude-status" class="flex items-center gap-2 text-xs text-on-surface-variant">
<span class="w-2 h-2 rounded-full bg-outline-variant inline-block"></span> Not configured
</div>
</div>
<button onclick="saveClaudeKey()" class="bg-primary text-surface font-bold px-6 py-2.5 rounded-xl text-sm hover:shadow-[0_0_20px_rgba(78,222,163,0.4)] transition-all flex items-center gap-2">
<span class="msym text-base">save</span> Save Key
</button>
<p class="text-on-surface-variant text-xs mt-3">Key is stored securely in the database and loaded at runtime. Get yours at <span class="text-primary">console.anthropic.com</span></p>
</div>
<!-- Usage stats -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6" id="claude-stats"></div>
<!-- Daily usage chart -->
<div class="glass rounded-2xl p-6 mb-6">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-4 flex items-center gap-2"><span class="msym text-base">bar_chart</span>Daily Usage</h3>
<div id="claude-daily-chart"></div>
</div>
<!-- Recent calls -->
<div class="glass rounded-2xl p-6">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-4 flex items-center gap-2"><span class="msym text-base">history</span>Recent Calls</h3>
<div id="claude-usage-list" class="space-y-2"></div>
</div>
</section>
</div>
</section> </section>
<!-- CLAUDE --> <!-- CLAUDE -->
<section id="page-claude" class="page hidden"> <section id="page-claude" class="page hidden" style="display:none!important">
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<div><h2 class="text-2xl font-bold">Claude AI</h2><p class="text-on-surface-variant text-sm mt-1">API key & usage tracking</p></div> <div><h2 class="text-2xl font-bold">Claude AI</h2><p class="text-on-surface-variant text-sm mt-1">API key & usage tracking</p></div>
<button onclick="loadClaude()" class="glass glass-hover px-4 py-2 rounded-xl text-sm flex items-center gap-2"> <button onclick="loadClaude()" class="glass glass-hover px-4 py-2 rounded-xl text-sm flex items-center gap-2">
@@ -941,7 +979,7 @@ function showPage(name, btn) {
if (btn) { btn.classList.add('active','bg-primary/10','text-primary'); btn.classList.remove('text-on-surface-variant'); } if (btn) { btn.classList.add('active','bg-primary/10','text-primary'); btn.classList.remove('text-on-surface-variant'); }
closeSidebar(); closeSidebar();
const loaders = {overview:loadOverview, books:loadBooks, users:loadUsers, const loaders = {overview:loadOverview, books:loadBooks, users:loadUsers,
conversations:loadConversations, resources:loadResources, settings:loadSettings, conversations:loadConversations, resources:loadResources, settings:()=>{loadSettings();loadClaude();},
branding:loadBranding, members:loadMembers, claude:loadClaude, tracking:loadTracking}; branding:loadBranding, members:loadMembers, claude:loadClaude, tracking:loadTracking};
if (loaders[name]) loaders[name](); if (loaders[name]) loaders[name]();
} }
@@ -1001,19 +1039,26 @@ async function loadLibraryHealth() {
<div class="text-xs text-on-surface-variant mt-1">Missing Chunks</div> <div class="text-xs text-on-surface-variant mt-1">Missing Chunks</div>
</div> </div>
<div class="bg-surface-high rounded-xl p-3 text-center"> <div class="bg-surface-high rounded-xl p-3 text-center">
<div class="text-xl font-bold text-blue-400">${d.vectors?.toLocaleString()}</div> <div class="text-xl font-bold text-blue-400">${(d.vector_count||d.total_chunks||0).toLocaleString()}</div>
<div class="text-xs text-on-surface-variant mt-1">Vectors</div> <div class="text-xs text-on-surface-variant mt-1">Vectors</div>
</div> </div>
</div>`; </div>`;
// Books with no chunks // Books with no chunks
if (d.no_chunks?.length > 0) { if (d.missing_books?.length > 0) {
html += `<div class="mb-4"> html += `<div class="mb-4">
<div class="text-xs font-bold text-red-400 uppercase tracking-widest mb-2 flex items-center gap-1"> <div class="text-xs font-bold text-red-400 uppercase tracking-widest mb-2 flex items-center gap-1">
<span class="msym text-sm">warning</span> Books with no chunks (${d.no_chunks.length}) <span class="msym text-sm">warning</span> Not Indexed (${d.missing_books.length})
</div> </div>
<div class="space-y-1 max-h-40 overflow-y-auto"> <div class="space-y-1 max-h-60 overflow-y-auto">
${d.no_chunks.map(b => `<div class="bg-red-900/20 border border-red-900/30 rounded-lg px-3 py-2 text-xs text-on-surface-variant">${b.title || b.filename}</div>`).join('')} ${d.missing_books.map(b => `
<div class="bg-red-900/20 border border-red-900/30 rounded-lg px-3 py-2 text-xs flex items-center justify-between gap-2">
<span class="text-on-surface-variant truncate">${b.title || b.filename}</span>
<button onclick="ingestBook(${b.id}, '${b.filename.replace(/'/g,"\'")}', this)"
class="shrink-0 glass glass-hover px-2 py-1 rounded-lg text-xs font-bold text-emerald-400 flex items-center gap-1">
<span class="msym text-sm">sync</span> Ingest Now
</button>
</div>`).join('')}
</div> </div>
</div>`; </div>`;
} }
@@ -1081,6 +1126,22 @@ async function clearCache() {
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clear Cache'; btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clear Cache';
} }
async function ingestBook(bookId, filename, btn) {
btn.disabled = true;
btn.innerHTML = '<span class="msym text-sm animate-spin">sync</span> Ingesting...';
try {
const r = await api(`/admin/ingest-book/${bookId}`, {method:'POST'});
btn.innerHTML = '<span class="msym text-sm">check_circle</span> Done';
btn.classList.replace('text-emerald-400','text-primary');
toast('Ingested: ' + filename.substring(0,40));
setTimeout(loadLibraryHealth, 2000);
} catch(e) {
btn.innerHTML = '<span class="msym text-sm">error</span> Failed';
btn.classList.replace('text-emerald-400','text-red-400');
toast('Error: ' + e.message);
}
}
async function reingestMissing() { async function reingestMissing() {
const btn = document.getElementById('btn-reingest'); const btn = document.getElementById('btn-reingest');
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm">sync</span> Queuing...'; btn.disabled = true; btn.innerHTML = '<span class="msym text-sm">sync</span> Queuing...';