v1.8.6: branding — colors apply instantly, logo upload with size control, fix tracking width

This commit is contained in:
2026-05-28 19:48:25 +00:00
parent 881c3617da
commit 8e7b1f5938
17 changed files with 175 additions and 16 deletions
+67 -12
View File
@@ -484,12 +484,23 @@ a[role=button]:active {
<input id="b-subtitle" class="w-full 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"/>
</div>
<div>
<label class="text-xs text-on-surface-variant mb-2 block">Logo URL</label>
<input id="b-logo" placeholder="https://example.com/logo.png" class="w-full 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"/>
</div>
<div id="logo-preview" class="hidden">
<label class="text-xs text-on-surface-variant mb-2 block">Preview</label>
<img id="logo-img" class="h-12 rounded-xl" />
<label class="text-xs text-on-surface-variant mb-2 block">Logo</label>
<div class="flex gap-2 mb-2">
<input id="b-logo" placeholder="https://example.com/logo.png"
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"
oninput="updateLogoPreview()"/>
<label class="glass glass-hover px-3 py-2 rounded-xl text-xs text-primary font-bold cursor-pointer flex items-center gap-1 shrink-0">
<span class="msym text-sm">upload</span> Upload
<input type="file" accept="image/*" class="hidden" onchange="uploadLogo(this)"/>
</label>
</div>
<img id="b-logo-preview" class="hidden rounded-xl mb-2 border border-outline-variant" style="max-width:200px"/>
<div class="flex items-center gap-3">
<label class="text-xs text-on-surface-variant shrink-0">Size</label>
<input type="range" id="b-logo-size" min="20" max="120" value="40" step="4"
class="flex-1 accent-primary" oninput="updateLogoPreview()"/>
<span id="b-logo-size-val" class="text-xs text-on-surface-variant w-10 text-right">40px</span>
</div>
</div>
</div>
<div class="glass rounded-2xl p-6 space-y-5">
@@ -515,7 +526,7 @@ a[role=button]:active {
<div id="preview-secondary" class="flex-1 h-10 rounded-lg"></div>
</div>
</div>
<p class="text-xs text-on-surface-variant">Changes apply on next page load after saving.</p>
<p class="text-xs text-on-surface-variant">Colors apply instantly after saving.</p>
</div>
</div>
</section>
@@ -589,7 +600,6 @@ curl -X POST https://rag.afaqzaid.com/ingest-url \
<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>
</div>
</section>
<!-- CLAUDE -->
@@ -1300,13 +1310,13 @@ async function loadSettings() {
const d = await api('/admin/settings');
document.getElementById('settings-ai').innerHTML = `
<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>AI Configuration</h3>
${[['Local LLM',d.ollama_model,''],['Claude API',d.claude_api_configured?'✓ Configured':'✗ Not set',d.claude_api_configured?'text-emerald-400':'text-red-400'],['Complexity Threshold',d.complexity_threshold_words+' words',''],['Confidence Threshold',d.confidence_threshold,''],['Cache TTL',d.cache_ttl_hours+'h','']].map(([l,v,cls])=>`
${[['Local LLM',d.ollama_model,''],['Claude API',d.claude_api_configured?'✓ Configured':'✗ Not set',d.claude_api_configured?'text-emerald-400':'text-red-400'],['Confidence Threshold',d.confidence_threshold,''],['Cache TTL',d.cache_ttl_hours+'h','']].map(([l,v,cls])=>`
<div class="flex justify-between items-center py-3 border-b border-outline-variant/20 last:border-0">
<span class="text-sm text-on-surface-variant">${l}</span><span class="text-xs font-mono glass px-2 py-1 rounded-lg ${cls||'text-primary'}">${v}</span>
</div>`).join('')}`;
document.getElementById('settings-system').innerHTML = `
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-5 flex items-center gap-2"><span class="msym text-base">build</span>Ingestion Settings</h3>
${[['Chunk Size',d.chunk_size+' words'],['Chunk Overlap',d.chunk_overlap+' words'],['Qdrant URL',d.qdrant_url],['Ollama URL',d.ollama_url]].map(([l,v])=>`
${[['Chunk Size',d.chunk_size+' words'],['Chunk Overlap',d.chunk_overlap+' sentences'],['Qdrant URL',d.qdrant_url],['Ollama URL',d.ollama_url]].map(([l,v])=>`
<div class="flex justify-between items-center py-3 border-b border-outline-variant/20 last:border-0">
<span class="text-sm text-on-surface-variant">${l}</span><span class="text-xs font-mono glass px-2 py-1 rounded-lg text-primary truncate max-w-[140px]">${v}</span>
</div>`).join('')}`;
@@ -1431,6 +1441,39 @@ async function testR2Connection() {
} catch(e) { status.textContent = '❌ '+e.message; status.style.color = '#f87171'; }
}
function updateLogoPreview() {
const url = document.getElementById('b-logo')?.value;
const size = document.getElementById('b-logo-size')?.value || 40;
document.getElementById('b-logo-size-val').textContent = size + 'px';
const img = document.getElementById('b-logo-preview');
if (url) { img.src = url; img.style.height = size + 'px'; img.classList.remove('hidden'); }
else img.classList.add('hidden');
}
async function uploadLogo(input) {
const file = input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
try {
const base64 = e.target.result.split(',')[1];
const mime = file.type;
const r = await fetch('/admin/upload-logo', {
method: 'POST',
headers: {'Content-Type':'application/json','Authorization':'Bearer '+JWT_TOKEN},
body: JSON.stringify({data: base64, mime_type: mime, filename: file.name})
});
const d = await r.json();
if (d.url) {
document.getElementById('b-logo').value = d.url;
updateLogoPreview();
toast('Logo uploaded!', 'success');
}
} catch(e) { toast('Upload failed: '+e.message); }
};
reader.readAsDataURL(file);
}
async function loadBranding() {
try {
const d = await api('/admin/branding');
@@ -1443,6 +1486,13 @@ async function loadBranding() {
document.getElementById('b-secondary-hex').value = d.secondary_color||'#95d3ba';
document.getElementById('preview-primary').style.background = d.primary_color||'#4edea3';
document.getElementById('preview-secondary').style.background = d.secondary_color||'#95d3ba';
// Apply colors live to admin CSS variables
if(d.primary_color) document.documentElement.style.setProperty('--primary', d.primary_color);
if(d.secondary_color) document.documentElement.style.setProperty('--secondary', d.secondary_color);
// Logo
if(d.logo_url) document.getElementById('b-logo').value = d.logo_url;
if(d.logo_size) document.getElementById('b-logo-size').value = d.logo_size;
if(d.logo_url) { const img=document.getElementById('b-logo-preview'); img.src=d.logo_url; img.style.height=(d.logo_size||40)+'px'; img.classList.remove('hidden'); }
if (d.logo_url) { document.getElementById('logo-img').src=d.logo_url; document.getElementById('logo-preview').classList.remove('hidden'); }
document.getElementById('b-primary').oninput = function(){ document.getElementById('b-primary-hex').value=this.value; document.getElementById('preview-primary').style.background=this.value; };
document.getElementById('b-secondary').oninput = function(){ document.getElementById('b-secondary-hex').value=this.value; document.getElementById('preview-secondary').style.background=this.value; };
@@ -1454,8 +1504,13 @@ async function loadBranding() {
async function saveBranding() {
try {
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');
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, logo_size:document.getElementById('b-logo-size')?.value||'40', primary_color:document.getElementById('b-primary-hex').value, secondary_color:document.getElementById('b-secondary-hex').value})});
if (r.ok) toast('Branding saved!','success');
const p=document.getElementById('b-primary-hex')?.value;
const s=document.getElementById('b-secondary-hex')?.value;
if(p) document.documentElement.style.setProperty('--primary',p);
if(s) document.documentElement.style.setProperty('--secondary',s);
} else alert('Save failed');
} catch(e) { alert('Error: '+e.message); }
}