v1.8.0: bge-m3 embeddings, qwen3 LLM, retrieval fixes, input validation, memory poisoning fix

This commit is contained in:
2026-05-28 18:54:05 +00:00
parent e87b33f403
commit 7ce5c6f7d0
1433 changed files with 63814 additions and 199 deletions
+237 -5
View File
@@ -242,6 +242,49 @@ a[role=button]:active {
Sign In
</button>
<p id="login-err" class="text-error text-xs mt-3 hidden">Invalid credentials</p>
<section id="page-pages" class="page hidden">
<div class="flex items-center justify-between mb-6">
<div>
<h2 class="text-xl font-bold">Pages</h2>
<p class="text-sm text-on-surface-variant mt-1">Edit site pages content</p>
</div>
</div>
<!-- Page Tabs -->
<div class="flex gap-2 mb-6">
<button id="pages-tab-privacy" onclick="switchPageTab('privacy')"
class="px-4 py-2 rounded-xl text-sm font-semibold bg-primary text-surface transition-all">
سياسة الخصوصية
</button>
<button id="pages-tab-terms" onclick="switchPageTab('terms')"
class="px-4 py-2 rounded-xl text-sm font-semibold glass text-on-surface-variant hover:text-primary transition-all">
شروط الخدمة
</button>
</div>
<!-- Editor -->
<div class="glass rounded-2xl p-5 space-y-4" style="border:1px solid rgba(78,222,163,0.1)">
<div class="flex items-center justify-between">
<p class="text-sm text-on-surface-variant">تعديل محتوى HTML للصفحة</p>
<div class="flex gap-2">
<a id="pages-preview-btn" href="/privacy" target="_blank"
class="glass px-3 py-1.5 rounded-lg text-xs text-on-surface-variant hover:text-primary transition-colors flex items-center gap-1">
<span class="msym text-sm">open_in_new</span> معاينة
</a>
<button onclick="savePageContent()" id="pages-save-btn"
class="bg-primary text-surface font-bold px-4 py-1.5 rounded-lg text-xs flex items-center gap-1 hover:shadow-[0_0_15px_rgba(78,222,163,0.4)] transition-all">
<span class="msym text-sm">save</span> حفظ
</button>
</div>
</div>
<textarea id="pages-editor"
class="w-full bg-surface/60 border border-white/10 rounded-xl p-4 text-xs font-mono text-on-surface focus:outline-none focus:border-primary/50 transition-colors resize-none"
style="height:420px;direction:ltr"></textarea>
<p id="pages-save-status" class="text-xs text-primary hidden">✅ تم الحفظ بنجاح</p>
</div>
</section>
</div>
</div>
@@ -292,6 +335,9 @@ 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('settings',this)">
<span class="msym text-lg">settings</span><span>Settings</span>
</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)">
<span class="msym text-lg">description</span><span>Pages</span>
</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('branding',this)">
<span class="msym text-lg">palette</span><span>Branding</span>
</button>
@@ -516,6 +562,7 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
<div id="settings-ai" class="glass rounded-2xl p-6"></div>
<div id="settings-system" class="glass rounded-2xl p-6"></div>
</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>
</section>
@@ -805,7 +852,7 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
</div>
<script>
let ADMIN_KEY = 'afakadmin-Ihb7kflFA682Ax3EICnV2ymr4KuNZSo7';
// ADMIN_KEY removed — JWT-only auth
let JWT_TOKEN = localStorage.getItem('admin_jwt') || '';
const RAG_KEY = 'zRf77E_brSEnA2BUi40uxLxT4uofAk661WMZb-cdv4M';
let keyVisible = false;
@@ -815,7 +862,7 @@ if (!JWT_TOKEN) document.getElementById('login').style.display = 'flex';
// ── Core API ──────────────────────────────────────────────────────────────────
async function api(path, opts={}) {
const headers = {'Content-Type':'application/json', 'X-API-Key': ADMIN_KEY};
const headers = {'Content-Type':'application/json', 'Authorization':'Bearer '+JWT_TOKEN};
if (JWT_TOKEN) headers['Authorization'] = 'Bearer ' + JWT_TOKEN;
const r = await fetch(path, {...opts, headers:{...headers, ...(opts.headers||{})}});
if (r.status === 401) {
@@ -967,7 +1014,7 @@ async function deleteBook(id, filename, btn) {
if (!confirm(`حذف الكتاب؟\n"${filename}"\n\nسيُحذف ملف PDF والمتجهات والسجل نهائياً.`)) return;
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm animate-spin">autorenew</span>';
try {
const r = await fetch(`/api/library/${id}`, {method:'DELETE', headers:{'X-API-Key':ADMIN_KEY,'Authorization':'Bearer '+JWT_TOKEN}});
const r = await fetch(`/api/library/${id}`, {method:'DELETE', headers:{'Authorization':'Bearer '+JWT_TOKEN}});
const d = await r.json();
if (r.ok) { document.getElementById('book-row-'+id)?.remove(); loadOverview(); toast('Book deleted','success'); }
else { alert('فشل الحذف: '+(d.detail||'خطأ')); btn.disabled=false; btn.innerHTML='<span class="msym text-sm">delete</span>'; }
@@ -1078,6 +1125,7 @@ async function loadResources() {
}
async function loadSettings() {
loadLLMConfig();
try {
const d = await api('/admin/settings');
document.getElementById('settings-ai').innerHTML = `
@@ -1236,7 +1284,7 @@ async function loadBranding() {
async function saveBranding() {
try {
const r = await fetch('/admin/branding', {method:'POST', headers:{'Content-Type':'application/json','X-API-Key':ADMIN_KEY,'Authorization':'Bearer '+JWT_TOKEN}, body:JSON.stringify({site_name:document.getElementById('b-site-name').value, site_subtitle:document.getElementById('b-subtitle').value, logo_url:document.getElementById('b-logo').value, primary_color:document.getElementById('b-primary-hex').value, secondary_color:document.getElementById('b-secondary-hex').value})});
const r = await fetch('/admin/branding', {method:'POST', headers:{'Content-Type':'application/json','Authorization':'Bearer '+JWT_TOKEN}, body:JSON.stringify({site_name:document.getElementById('b-site-name').value, site_subtitle:document.getElementById('b-subtitle').value, logo_url:document.getElementById('b-logo').value, primary_color:document.getElementById('b-primary-hex').value, secondary_color:document.getElementById('b-secondary-hex').value})});
if (r.ok) toast('Branding saved!','success'); else alert('Save failed');
} catch(e) { alert('Error: '+e.message); }
}
@@ -1338,7 +1386,7 @@ async function saveClaudeKey() {
const key = document.getElementById('claude-key-input').value.trim();
if (!key) return;
try {
const r = await fetch('/admin/claude/key', {method:'POST', headers:{'Content-Type':'application/json','X-API-Key':ADMIN_KEY,'Authorization':'Bearer '+JWT_TOKEN}, body:JSON.stringify({api_key:key})});
const r = await fetch('/admin/claude/key', {method:'POST', headers:{'Content-Type':'application/json','Authorization':'Bearer '+JWT_TOKEN}, body:JSON.stringify({api_key:key})});
if (r.ok) { toast('Claude key saved!','success'); loadClaude(); document.getElementById('claude-key-input').value=''; }
else toast('Failed to save key');
} catch(e) { toast('Error: '+e.message); }
@@ -1777,6 +1825,122 @@ function applyScanResult(item) {
}
async function loadLLMConfig() {
try {
const d = await api('/admin/llm-config');
const isLocal = d.provider !== 'cloudflare';
const isCF = d.provider === 'cloudflare';
window._llmProvider = d.provider || 'local';
document.getElementById('settings-llm').innerHTML = `
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-5 flex items-center gap-2">
<span class="msym text-base">hub</span>LLM Provider
<span class="ml-auto text-[10px] font-normal glass px-2 py-0.5 rounded-full ${isCF ? 'text-blue-400' : 'text-emerald-400'}">
${isCF ? '⚡ Cloudflare GPU' : '🖥 Local Ollama'}
</span>
</h3>
<div class="flex gap-2 mb-5">
<button onclick="llmSetProvider('local')" id="llm-btn-local"
class="flex-1 py-2.5 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${isLocal ? 'bg-primary text-surface' : 'glass glass-hover text-on-surface-variant'}">
<span class="msym text-base">computer</span> Local (Ollama)
</button>
<button onclick="llmSetProvider('cloudflare')" id="llm-btn-cf"
class="flex-1 py-2.5 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${isCF ? 'bg-blue-500 text-white' : 'glass glass-hover text-on-surface-variant'}">
<span class="msym text-base">cloud</span> Cloudflare GPU
</button>
</div>
<div id="llm-local-info" class="${isLocal ? '' : 'hidden'} glass rounded-xl p-4 mb-4 text-xs text-on-surface-variant space-y-1">
<p class="flex items-center gap-2"><span class="msym text-base text-emerald-400">check_circle</span>Running qwen2:7b on your VPS CPU</p>
<p class="flex items-center gap-2"><span class="msym text-base text-yellow-400">schedule</span>~30-50s per query — streaming masks latency</p>
<p class="flex items-center gap-2"><span class="msym text-base text-primary">lock</span>Fully private — no external requests</p>
</div>
<div id="llm-cf-fields" class="${isCF ? '' : 'hidden'} space-y-3 mb-4">
<div class="glass rounded-xl p-4 text-xs text-on-surface-variant space-y-1 mb-3">
<p class="flex items-center gap-2"><span class="msym text-base text-blue-400">bolt</span>GPU inference — 3-5s per query</p>
<p class="flex items-center gap-2"><span class="msym text-base text-primary">all_inclusive</span>~900 free queries/day on free tier</p>
<p class="flex items-center gap-2"><span class="msym text-base text-on-surface-variant">storage</span>Your PDFs stay local — only text prompt sent</p>
</div>
<div>
<label class="text-xs text-on-surface-variant block mb-1.5">Account ID</label>
<input id="llm-cf-account" type="text" dir="ltr" value="${d.cf_account_id||''}"
placeholder="Find in dash.cloudflare.com URL bar"
class="w-full bg-surface-high border border-outline-variant rounded-xl px-3 py-2 text-xs font-mono outline-none focus:border-primary transition-colors"/>
</div>
<div>
<label class="text-xs text-on-surface-variant block mb-1.5">API Token
${d.cf_api_token_configured ? '<span class="text-emerald-400 text-[10px] ml-2">✓ Saved ('+d.cf_api_token_masked+')</span>' : ''}
</label>
<input id="llm-cf-token" type="password" dir="ltr"
placeholder="${d.cf_api_token_configured ? 'Leave blank to keep saved token' : 'Workers AI API token'}"
class="w-full bg-surface-high border border-outline-variant rounded-xl px-3 py-2 text-xs font-mono outline-none focus:border-primary transition-colors"/>
</div>
<div>
<label class="text-xs text-on-surface-variant block mb-1.5">Model</label>
<select id="llm-cf-model" class="w-full bg-surface-high border border-outline-variant rounded-xl px-3 py-2 text-xs outline-none focus:border-primary transition-colors">
<option value="@cf/meta/llama-3.1-8b-instruct-fp8-fast" ${(d.cf_model||'').includes('fp8-fast')?'selected':''}>Llama 3.1 8B FP8 Fast ⚡ (Recommended)</option>
<option value="@cf/meta/llama-3.1-8b-instruct" ${d.cf_model==='@cf/meta/llama-3.1-8b-instruct'?'selected':''}>Llama 3.1 8B Standard</option>
<option value="@cf/meta/llama-3.2-3b-instruct" ${(d.cf_model||'').includes('3.2-3b')?'selected':''}>Llama 3.2 3B (Faster)</option>
<option value="@cf/zai-org/glm-4.7-flash" ${(d.cf_model||'').includes('glm')?'selected':''}>GLM-4.7 Flash (100+ languages)</option>
<option value="@cf/qwen/qwen3-30b-a3b-fp8" ${(d.cf_model||'').includes('qwen3-30b')?'selected':''}>Qwen 3 30B ⭐ (Best Arabic)</option>
<option value="@cf/qwen/qwq-32b" ${(d.cf_model||'').includes('qwq-32b')?'selected':''}>Qwen QwQ 32B (Reasoning)</option>
<option value="@cf/deepseek-ai/deepseek-r1-distill-qwen-32b" ${(d.cf_model||'').includes('deepseek-r1')?'selected':''}>DeepSeek R1 32B (Reasoning)</option>
</select>
</div>
</div>
<div class="flex gap-2">
<button onclick="saveLLMConfig()" class="flex-1 bg-primary text-surface font-bold py-2.5 rounded-xl text-sm flex items-center justify-center gap-2 hover:shadow-[0_0_20px_rgba(78,222,163,0.4)] transition-all">
<span class="msym text-base">save</span> Save
</button>
<button onclick="testLLMConfig()" class="glass glass-hover px-4 py-2.5 rounded-xl text-sm text-on-surface-variant flex items-center gap-2">
<span class="msym text-base">wifi_tethering</span> Test
</button>
</div>
<p id="llm-status" class="text-xs text-center mt-3 hidden"></p>`;
} catch(e) { showErr('settings-llm', e.message); }
}
function llmSetProvider(p) {
window._llmProvider = p;
const isLocal = p !== 'cloudflare', isCF = p === 'cloudflare';
document.getElementById('llm-btn-local').className = `flex-1 py-2.5 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${isLocal?'bg-primary text-surface':'glass glass-hover text-on-surface-variant'}`;
document.getElementById('llm-btn-cf').className = `flex-1 py-2.5 rounded-xl text-xs font-bold transition-all flex items-center justify-center gap-2 ${isCF?'bg-blue-500 text-white':'glass glass-hover text-on-surface-variant'}`;
document.getElementById('llm-local-info').classList.toggle('hidden', isCF);
document.getElementById('llm-cf-fields').classList.toggle('hidden', isLocal);
}
async function saveLLMConfig() {
const status = document.getElementById('llm-status');
const payload = {
provider: window._llmProvider || 'local',
cf_account_id: (document.getElementById('llm-cf-account')?.value||'').trim(),
cf_api_token: (document.getElementById('llm-cf-token')?.value||'').trim(),
cf_model: document.getElementById('llm-cf-model')?.value || '@cf/meta/llama-3.1-8b-instruct-fp8-fast',
};
status.textContent = 'Saving...'; status.classList.remove('hidden'); status.style.color = '';
try {
await api('/admin/llm-config', {method:'POST', body:JSON.stringify(payload)});
status.textContent = '✅ Saved — restart API container to apply';
status.style.color = '#4edea3';
setTimeout(loadLLMConfig, 1500);
} catch(e) { status.textContent = '❌ '+e.message; status.style.color = '#f87171'; }
}
async function testLLMConfig() {
const status = document.getElementById('llm-status');
const payload = {
provider: window._llmProvider || 'local',
cf_account_id: (document.getElementById('llm-cf-account')?.value||'').trim(),
cf_api_token: (document.getElementById('llm-cf-token')?.value||'').trim(),
cf_model: document.getElementById('llm-cf-model')?.value || '@cf/meta/llama-3.1-8b-instruct-fp8-fast',
};
status.textContent = 'Testing...'; status.classList.remove('hidden'); status.style.color = '';
try {
const r = await api('/admin/llm-test', {method:'POST', body:JSON.stringify(payload)});
status.textContent = r.ok ? '✅ '+r.detail : '❌ '+r.detail;
status.style.color = r.ok ? '#4edea3' : '#f87171';
} catch(e) { status.textContent = '❌ '+e.message; status.style.color = '#f87171'; }
}
// design-taste-frontend: Spotlight border cards
document.querySelectorAll('.spotlight-card').forEach(card => {
card.addEventListener('mousemove', e => {
@@ -1917,5 +2081,73 @@ async function retryMetaExtraction() {
</div>
</div>
<script>
// ── Pages Editor ─────────────────────────────────────────────────────────────
let _currentPage = 'privacy';
function switchPageTab(page) {
_currentPage = page;
['privacy','terms'].forEach(p => {
const btn = document.getElementById('pages-tab-' + p);
if (p === page) {
btn.className = 'px-4 py-2 rounded-xl text-sm font-semibold bg-primary text-surface transition-all';
} else {
btn.className = 'px-4 py-2 rounded-xl text-sm font-semibold glass text-on-surface-variant hover:text-primary transition-all';
}
});
document.getElementById('pages-preview-btn').href = '/' + page;
document.getElementById('pages-save-status').classList.add('hidden');
loadPageContent(page);
}
async function loadPageContent(page) {
const editor = document.getElementById('pages-editor');
editor.value = 'جاري التحميل...';
try {
const res = await fetch('/admin/pages/' + page, {
headers: { 'Authorization': 'Bearer ' + JWT_TOKEN }
});
const data = await res.json();
editor.value = data.content || '';
} catch(e) {
editor.value = 'خطأ في التحميل: ' + e.message;
}
}
async function savePageContent() {
const btn = document.getElementById('pages-save-btn');
const status = document.getElementById('pages-save-status');
const content = document.getElementById('pages-editor').value;
btn.disabled = true;
btn.innerHTML = '<span class="msym text-sm">progress_activity</span> جاري الحفظ...';
try {
const res = await fetch('/admin/pages/' + _currentPage, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + JWT_TOKEN },
body: JSON.stringify({ content })
});
if (res.ok) {
status.classList.remove('hidden');
setTimeout(() => status.classList.add('hidden'), 3000);
}
} catch(e) {
alert('فشل الحفظ: ' + e.message);
}
btn.disabled = false;
btn.innerHTML = '<span class="msym text-sm">save</span> حفظ';
}
document.addEventListener('DOMContentLoaded', () => {
const origShowPage = window.showPage;
if (origShowPage) {
window.showPage = function(page, btn) {
origShowPage(page, btn);
if (page === 'pages') loadPageContent(_currentPage);
};
}
});
</script>
</body>
</html>