Spaces:
Build error
Build error
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>CPU RAG · Qwen3.5-0.8B + FAISS</title> | |
| <style> | |
| :root { color-scheme: light dark; --bg:#0f1220; --panel:#1a1e33; --acc:#7c8cff; --mut:#8b90a8; } | |
| * { box-sizing: border-box; } | |
| body { margin:0; font-family: system-ui, sans-serif; background:var(--bg); color:#e7e9f3; } | |
| header { padding:14px 18px; border-bottom:1px solid #2a2f4a; display:flex; gap:12px; align-items:center; } | |
| header h1 { font-size:15px; margin:0; font-weight:600; } | |
| header .tag { font-size:11px; color:var(--mut); background:var(--panel); padding:3px 8px; border-radius:20px; } | |
| main { max-width:820px; margin:0 auto; padding:18px; } | |
| #chat { display:flex; flex-direction:column; gap:12px; min-height:50vh; } | |
| .msg { padding:11px 14px; border-radius:12px; max-width:88%; white-space:pre-wrap; line-height:1.45; font-size:14px; } | |
| .user { align-self:flex-end; background:var(--acc); color:#fff; } | |
| .bot { align-self:flex-start; background:var(--panel); } | |
| .src { align-self:flex-start; font-size:11px; color:var(--mut); margin-top:-6px; } | |
| form { display:flex; gap:8px; margin-top:16px; } | |
| textarea { flex:1; resize:none; padding:11px; border-radius:10px; border:1px solid #2a2f4a; background:#12152a; color:#e7e9f3; font-size:14px; } | |
| button { padding:0 16px; border:0; border-radius:10px; background:var(--acc); color:#fff; font-weight:600; cursor:pointer; } | |
| button:disabled { opacity:.5; cursor:default; } | |
| .bar { display:flex; gap:10px; align-items:center; margin-bottom:14px; font-size:12px; color:var(--mut); } | |
| .bar input[type=file] { font-size:12px; color:var(--mut); } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>🦅 CPU RAG</h1> | |
| <span class="tag">Qwen3.5-0.8B · bge-small · FAISS</span> | |
| <span class="tag" id="stat">…</span> | |
| </header> | |
| <main> | |
| <div class="bar"> | |
| <label>Add a document (.txt/.md):</label> | |
| <input type="file" id="file" accept=".txt,.md" /> | |
| <button id="up" type="button">Upload</button> | |
| <span id="upmsg"></span> | |
| </div> | |
| <div id="chat"></div> | |
| <form id="form"> | |
| <textarea id="q" rows="2" placeholder="Ask about your documents…"></textarea> | |
| <button id="send" type="submit">Send</button> | |
| </form> | |
| </main> | |
| <script> | |
| const chat = document.getElementById('chat'); | |
| const stat = document.getElementById('stat'); | |
| function add(cls, text) { | |
| const d = document.createElement('div'); | |
| d.className = 'msg ' + cls; d.textContent = text; | |
| chat.appendChild(d); chat.scrollIntoView({block:'end'}); return d; | |
| } | |
| async function refreshStats(){ | |
| try { const s = await (await fetch('/stats')).json(); | |
| stat.textContent = s.indexed_chunks + ' chunks indexed'; } catch(e){} | |
| } | |
| refreshStats(); | |
| document.getElementById('up').onclick = async () => { | |
| const f = document.getElementById('file').files[0]; | |
| if (!f) return; | |
| document.getElementById('upmsg').textContent = 'uploading…'; | |
| const fd = new FormData(); fd.append('file', f); | |
| const r = await (await fetch('/ingest', {method:'POST', body:fd})).json(); | |
| document.getElementById('upmsg').textContent = '+' + r.added_chunks + ' chunks'; | |
| refreshStats(); | |
| }; | |
| document.getElementById('form').onsubmit = async (e) => { | |
| e.preventDefault(); | |
| const q = document.getElementById('q').value.trim(); | |
| if (!q) return; | |
| document.getElementById('q').value = ''; | |
| document.getElementById('send').disabled = true; | |
| add('user', q); | |
| const thinking = add('bot', '…'); | |
| try { | |
| // Stream tokens so the first word shows in ~1s instead of waiting for the | |
| // whole answer (decode is ~5-6 tok/s on 2 vCPU). | |
| const r = await fetch('/v1/chat/completions', { | |
| method:'POST', headers:{'Content-Type':'application/json'}, | |
| body: JSON.stringify({ messages:[{role:'user', content:q}], temperature:0.3, stream:true }) | |
| }); | |
| const reader = r.body.getReader(); | |
| const dec = new TextDecoder(); | |
| let buf = '', out = ''; | |
| while (true) { | |
| const {value, done} = await reader.read(); | |
| if (done) break; | |
| buf += dec.decode(value, {stream:true}); | |
| const lines = buf.split('\n'); | |
| buf = lines.pop(); // keep partial line | |
| for (const line of lines) { | |
| const s = line.trim(); | |
| if (!s.startsWith('data:')) continue; | |
| const payload = s.slice(5).trim(); | |
| if (payload === '[DONE]') continue; | |
| try { | |
| const tok = JSON.parse(payload).choices?.[0]?.delta?.content; | |
| if (tok) { out += tok; thinking.textContent = out; chat.scrollIntoView({block:'end'}); } | |
| } catch (_) {} | |
| } | |
| } | |
| if (!out) thinking.textContent = '(no answer)'; | |
| } catch (err) { | |
| thinking.textContent = 'Error: ' + err; | |
| } | |
| document.getElementById('send').disabled = false; | |
| }; | |
| </script> | |
| </body> | |
| </html> | |