| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Aria β Abhishek's AI Assistant</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Syne:wght@600;700&display=swap" rel="stylesheet"> |
| <style> |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| body { background: #0a0a0f; min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: 'Inter', sans-serif; color: #e2e8f0; } |
| .demo { text-align: center; padding: 40px 24px; } |
| .demo h1 { font-family: 'Syne', sans-serif; font-size: 3rem; background: linear-gradient(135deg, #fff 0%, #818cf8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; } |
| .demo p { color: #64748b; font-size: 15px; margin-bottom: 10px; } |
| .demo-pills { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 14px; } |
| .demo-pill { background: rgba(129,140,248,0.1); border: 1px solid rgba(129,140,248,0.2); border-radius: 20px; padding: 5px 14px; font-size: 11px; color: #818cf8; } |
|
|
| :root { |
| --bg: #0d0d14; --s1: #12121c; --s2: #181824; --s3: #1e1e2e; |
| --bdr: rgba(129,140,248,0.12); --bdr-f: rgba(129,140,248,0.4); |
| --p: #818cf8; --pd: #6366f1; --pglow: rgba(99,102,241,0.35); |
| --tx: #e2e8f0; --tm: #64748b; --td: #94a3b8; |
| --ub: #6366f1; --bb: #181824; |
| --green: #22d3a0; --amber: #f59e0b; |
| --font: 'Inter', system-ui, sans-serif; |
| } |
|
|
| #aria-btn { |
| position: fixed; bottom: 28px; right: 28px; width: 58px; height: 58px; |
| border-radius: 50%; background: linear-gradient(135deg, #6366f1, #818cf8); |
| border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; |
| z-index: 9999; outline: none; |
| box-shadow: 0 8px 32px var(--pglow), 0 0 0 1px rgba(129,140,248,0.2); |
| transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1); |
| } |
| #aria-btn:hover { transform: scale(1.08) translateY(-2px); box-shadow: 0 16px 48px var(--pglow); } |
| #aria-btn:active { transform: scale(0.95); } |
| .btn-ring { position: absolute; width: 100%; height: 100%; border-radius: 50%; border: 1.5px solid rgba(129,140,248,0.5); animation: rp 2.5s ease-out infinite; pointer-events: none; } |
| @keyframes rp { 0%{transform:scale(1);opacity:.6} 100%{transform:scale(1.75);opacity:0} } |
| .ico-open, .ico-close { position: absolute; transition: opacity .2s, transform .3s cubic-bezier(0.34,1.56,0.64,1); } |
| #aria-btn.open .ico-open { opacity:0; transform:rotate(90deg) scale(.5); } |
| #aria-btn:not(.open) .ico-close { opacity:0; transform:rotate(-90deg) scale(.5); } |
|
|
| #aria-win { |
| position: fixed; bottom: 100px; right: 28px; width: 400px; height: 590px; |
| max-height: calc(100vh - 120px); background: var(--bg); border-radius: 22px; |
| border: 1px solid var(--bdr); box-shadow: 0 40px 100px rgba(0,0,0,.7), 0 0 0 1px rgba(129,140,248,.06); |
| display: flex; flex-direction: column; z-index: 9998; font-family: var(--font); overflow: hidden; |
| opacity: 0; transform: translateY(16px) scale(.96); pointer-events: none; |
| transition: opacity .28s ease, transform .32s cubic-bezier(0.34,1.56,0.64,1); |
| transform-origin: bottom right; |
| } |
| #aria-win.open { opacity:1; transform:translateY(0) scale(1); pointer-events:all; } |
|
|
| /* Header */ |
| .a-hdr { display:flex; align-items:center; gap:12px; padding:15px 18px; background:linear-gradient(135deg,rgba(99,102,241,.1),rgba(129,140,248,.04)); border-bottom:1px solid var(--bdr); flex-shrink:0; } |
| .a-av { width:40px; height:40px; border-radius:12px; background:linear-gradient(135deg,#6366f1,#818cf8); display:flex; align-items:center; justify-content:center; font-size:18px; flex-shrink:0; box-shadow:0 4px 12px rgba(99,102,241,.3); position:relative; } |
| .a-dot { position:absolute; bottom:-2px; right:-2px; width:10px; height:10px; background:var(--green); border-radius:50%; border:2px solid var(--bg); } |
| .a-info h3 { font-size:14px; font-weight:600; color:var(--tx); margin-bottom:2px; } |
| .a-status { font-size:11px; color:var(--green); display:flex; align-items:center; gap:4px; } |
| .a-status::before { content:''; width:5px; height:5px; background:var(--green); border-radius:50%; display:inline-block; } |
| .a-acts { margin-left:auto; } |
| .a-ibtn { background:none; border:none; cursor:pointer; width:30px; height:30px; border-radius:8px; display:flex; align-items:center; justify-content:center; color:var(--tm); transition:background .2s,color .2s; } |
| .a-ibtn:hover { background:rgba(255,255,255,.05); color:var(--td); } |
|
|
| /* Messages */ |
| .a-msgs { flex:1; overflow-y:auto; padding:14px 13px; display:flex; flex-direction:column; gap:11px; scroll-behavior:smooth; } |
| .a-msgs::-webkit-scrollbar { width:3px; } |
| .a-msgs::-webkit-scrollbar-thumb { background:rgba(129,140,248,.2); border-radius:3px; } |
|
|
| /* Welcome */ |
| .a-welcome { text-align:center; padding:10px 6px; animation:mi .5s ease forwards; } |
| .w-icon { width:54px; height:54px; border-radius:16px; background:linear-gradient(135deg,#6366f1,#818cf8); display:flex; align-items:center; justify-content:center; font-size:24px; margin:0 auto 13px; box-shadow:0 8px 24px rgba(99,102,241,.3); } |
| .a-welcome h4 { font-family:'Syne',sans-serif; font-size:16px; font-weight:700; color:var(--tx); margin-bottom:5px; } |
| .a-welcome p { font-size:12.5px; color:var(--tm); line-height:1.5; max-width:270px; margin:0 auto; } |
| .a-chips { display:flex; flex-wrap:wrap; gap:6px; justify-content:center; margin-top:13px; } |
| .a-chip { background:var(--s2); border:1px solid var(--bdr); border-radius:20px; padding:5px 12px; font-size:11.5px; font-weight:500; color:var(--tm); cursor:pointer; font-family:var(--font); transition:all .2s ease; } |
| .a-chip:hover { background:rgba(99,102,241,.12); border-color:rgba(129,140,248,.4); color:var(--p); transform:translateY(-1px); } |
|
|
| /* Message bubbles */ |
| .a-msg { display:flex; gap:8px; animation:mi .28s cubic-bezier(0.34,1.56,0.64,1) forwards; } |
| @keyframes mi { from{opacity:0;transform:translateY(8px) scale(.98)} to{opacity:1;transform:translateY(0) scale(1)} } |
| .a-msg.user { flex-direction:row-reverse; } |
| .m-av { width:26px; height:26px; border-radius:8px; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:12px; margin-top:3px; } |
| .a-msg.bot .m-av { background:linear-gradient(135deg,#6366f1,#818cf8); } |
| .a-msg.user .m-av { background:var(--s3); color:var(--tm); font-size:14px; } |
| .m-wrap { display:flex; flex-direction:column; gap:4px; max-width:calc(100% - 42px); } |
| .m-bbl { padding:10px 14px; border-radius:16px; font-size:13.5px; line-height:1.65; word-wrap:break-word; } |
| .a-msg.bot .m-bbl { background:var(--bb); color:var(--tx); border:1px solid var(--bdr); border-bottom-left-radius:5px; } |
| .a-msg.user .m-bbl { background:var(--ub); color:white; border-bottom-right-radius:5px; } |
| .m-bbl strong { color:#a5b4fc; font-weight:600; } |
| .m-bbl code { background:rgba(99,102,241,.15); border:1px solid rgba(99,102,241,.2); border-radius:4px; padding:1px 5px; font-size:12px; color:#a5b4fc; } |
| .m-bbl a { color:var(--p); text-decoration:none; } |
| .m-bbl a:hover { text-decoration:underline; } |
| .m-bbl ul,.m-bbl ol { margin:6px 0; padding-left:18px; } |
| .m-bbl li { margin-bottom:3px; } |
|
|
| /* Source badge */ |
| .m-badge { display:inline-flex; align-items:center; gap:4px; font-size:10px; font-weight:500; padding:2px 8px; border-radius:10px; width:fit-content; } |
| .m-badge.portfolio { background:rgba(99,102,241,.15); color:#818cf8; border:1px solid rgba(99,102,241,.2); } |
| .m-badge.general { background:rgba(245,158,11,.12); color:#f59e0b; border:1px solid rgba(245,158,11,.2); } |
|
|
| /* Typing */ |
| .a-typing { display:flex; gap:8px; align-items:flex-end; } |
| .t-dots { background:var(--bb); border:1px solid var(--bdr); border-radius:16px; border-bottom-left-radius:5px; padding:13px 16px; display:flex; gap:4px; align-items:center; } |
| .t-d { width:6px; height:6px; background:var(--p); border-radius:50%; animation:tb 1.3s ease-in-out infinite; } |
| .t-d:nth-child(2){animation-delay:.18s} .t-d:nth-child(3){animation-delay:.36s} |
| @keyframes tb { 0%,60%,100%{transform:translateY(0);opacity:.4} 30%{transform:translateY(-7px);opacity:1} } |
|
|
| /* Input */ |
| .a-inp-wrap { padding:11px 13px 15px; border-top:1px solid var(--bdr); background:var(--s1); flex-shrink:0; } |
| .a-inp-row { display:flex; gap:8px; align-items:flex-end; background:var(--s2); border:1px solid var(--bdr); border-radius:14px; padding:8px 10px; transition:border-color .2s,box-shadow .2s; } |
| .a-inp-row:focus-within { border-color:var(--bdr-f); box-shadow:0 0 0 3px rgba(99,102,241,.08); } |
| #a-input { flex:1; background:none; border:none; outline:none; color:var(--tx); font-family:var(--font); font-size:13.5px; resize:none; max-height:100px; line-height:1.5; padding:1px 0; } |
| #a-input::placeholder { color:var(--tm); } |
| #a-send { background:linear-gradient(135deg,#6366f1,#818cf8); border:none; cursor:pointer; width:32px; height:32px; border-radius:9px; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-bottom:1px; transition:opacity .2s,transform .15s; box-shadow:0 4px 12px rgba(99,102,241,.3); } |
| #a-send:hover { opacity:.9; transform:scale(1.05); } |
| #a-send:active { transform:scale(.93); } |
| #a-send:disabled { opacity:.3; cursor:not-allowed; transform:none; } |
| .a-footer { display:flex; justify-content:center; margin-top:7px; } |
| .a-brand { font-size:10px; color:var(--tm); opacity:.55; display:flex; align-items:center; gap:4px; } |
| .a-brand b { color:var(--p); font-weight:600; } |
|
|
| .a-err { background:rgba(239,68,68,.08); border:1px solid rgba(239,68,68,.2); color:#fca5a5; border-radius:10px; padding:9px 13px; font-size:12.5px; text-align:center; } |
| .a-cur { animation:blink .65s step-end infinite; } |
| @keyframes blink { 50%{opacity:0} } |
|
|
| @media(max-width:480px) { |
| #aria-win { width:calc(100vw - 20px); right:10px; bottom:86px; height:calc(100vh - 106px); max-height:none; } |
| #aria-btn { right:18px; bottom:18px; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="demo"> |
| <h1>Aria β¦</h1> |
| <p>Abhishek Kumar's Personal AI Assistant</p> |
| <div class="demo-pills"> |
| <span class="demo-pill">π§ Portfolio β Search</span> |
| <span class="demo-pill">π General β Web Search</span> |
| <span class="demo-pill">β‘ Groq LLM</span> |
| </div> |
| </div> |
|
|
| |
| <button id="aria-btn" aria-label="Open Aria"> |
| <div class="btn-ring"></div> |
| <svg class="ico-open" width="22" height="22" viewBox="0 0 24 24" fill="white"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z"/></svg> |
| <svg class="ico-close" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg> |
| </button> |
|
|
| |
| <div id="aria-win" role="dialog" aria-label="Aria AI Assistant"> |
|
|
| <div class="a-hdr"> |
| <div class="a-av">β¦<div class="a-dot"></div></div> |
| <div class="a-info"> |
| <h3>Aria</h3> |
| <div class="a-status">Online</div> |
| </div> |
| <div class="a-acts"> |
| <button class="a-ibtn" id="a-clear" title="New chat"> |
| <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-3.1"/></svg> |
| </button> |
| </div> |
| </div> |
|
|
| <div class="a-msgs" id="a-msgs"> |
| <div class="a-welcome"> |
| <div class="w-icon">β¦</div> |
| <h4>Hi, I'm Aria!</h4> |
| <p>Abhishek's personal AI. Ask me about him β or anything else. I'll search the web for general questions!</p> |
| <div class="a-chips"> |
| <button class="a-chip" data-q="Who is Abhishek Kumar?">π¨βπ» About Abhishek</button> |
| <button class="a-chip" data-q="What are Abhishek's skills?">π His Skills</button> |
| <button class="a-chip" data-q="What projects has Abhishek built?">π Projects</button> |
| <button class="a-chip" data-q="Tell me about Delhi city">π Delhi</button> |
| <button class="a-chip" data-q="What is machine learning?">π€ What is ML?</button> |
| <button class="a-chip" data-q="Is Abhishek available for hire?">π€ Hire Him</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="a-inp-wrap"> |
| <div class="a-inp-row"> |
| <textarea id="a-input" placeholder="Ask anything..." rows="1" aria-label="Message"></textarea> |
| <button id="a-send" aria-label="Send"> |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="white"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> |
| </button> |
| </div> |
| <div class="a-footer"> |
| <span class="a-brand">Built by <b>Abhishek Kumar</b> with Love</span> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| (function() { |
| const API = 'http://127.0.0.1:8080'; |
| let isOpen=false, loading=false, sessionId=null, typingEl=null; |
| const btn=document.getElementById('aria-btn'), win=document.getElementById('aria-win'); |
| const msgs=document.getElementById('a-msgs'), inp=document.getElementById('a-input'); |
| const send=document.getElementById('a-send'), clr=document.getElementById('a-clear'); |
| |
| btn.addEventListener('click', toggle); |
| send.addEventListener('click', submit); |
| clr.addEventListener('click', clearChat); |
| inp.addEventListener('keydown', e=>{ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();submit();} }); |
| inp.addEventListener('input', ()=>{ inp.style.height='auto'; inp.style.height=Math.min(inp.scrollHeight,100)+'px'; }); |
| msgs.addEventListener('click', e=>{ const c=e.target.closest('.a-chip'); if(c){inp.value=c.dataset.q;submit();} }); |
| document.addEventListener('click', e=>{ if(isOpen&&!win.contains(e.target)&&e.target!==btn&&!btn.contains(e.target))toggle(); }); |
| |
| function toggle(){ |
| isOpen=!isOpen; |
| btn.classList.toggle('open',isOpen); |
| win.classList.toggle('open',isOpen); |
| if(isOpen) setTimeout(()=>inp.focus(),320); |
| } |
| |
| async function submit(){ |
| const q=inp.value.trim(); |
| if(!q||loading) return; |
| addMsg('user',q); |
| inp.value=''; inp.style.height='auto'; |
| setLoad(true); |
| await doStream(q); |
| } |
| |
| async function doStream(q){ |
| showTyping(); |
| let qtype='general'; |
| try{ |
| const res=await fetch(`${API}/chat/stream`,{ |
| method:'POST', headers:{'Content-Type':'application/json'}, |
| body:JSON.stringify({query:q,session_id:sessionId,top_k:5}) |
| }); |
| if(!res.ok) throw new Error(`HTTP ${res.status}`); |
| hideTyping(); |
| const el=addMsg('bot','',qtype); |
| const bbl=el.querySelector('.m-bbl'), bdg=el.querySelector('.m-badge'); |
| let full=''; |
| const reader=res.body.getReader(), dec=new TextDecoder(); |
| while(true){ |
| const{done,value}=await reader.read(); |
| if(done) break; |
| for(const line of dec.decode(value).split('\n')){ |
| if(!line.startsWith('data: ')) continue; |
| try{ |
| const d=JSON.parse(line.slice(6)); |
| if(d.type==='session') sessionId=d.session_id; |
| else if(d.type==='meta'){ |
| qtype=d.query_type; |
| if(bdg){ bdg.className=`m-badge ${qtype}`; bdg.textContent=qtype==='portfolio'?'π§ Portfolio RAG':'π Web Search'; } |
| } |
| else if(d.type==='chunk'){ full+=d.content; bbl.innerHTML=md(full)+'<span class="a-cur">β</span>'; scroll(); } |
| else if(d.type==='done'){ bbl.innerHTML=md(full); scroll(); } |
| else if(d.type==='error') bbl.innerHTML=`<div class="a-err">β οΈ ${d.message}</div>`; |
| }catch(_){} |
| } |
| } |
| }catch(e){ |
| hideTyping(); |
| addMsg('bot',null,null,e.message.includes('fetch')?`β οΈ Cannot connect to <code>${API}</code>. Make sure server is running.`:`β οΈ ${e.message}`); |
| }finally{ setLoad(false); } |
| } |
| |
| function addMsg(role,text,type,err){ |
| const d=document.createElement('div'); d.className=`a-msg ${role}`; |
| const av=document.createElement('div'); av.className='m-av'; av.textContent=role==='bot'?'β¦':'π€'; |
| const wrap=document.createElement('div'); wrap.className='m-wrap'; |
| if(role==='bot'&&!err){ |
| const bdg=document.createElement('div'); |
| bdg.className=`m-badge ${type||'general'}`; |
| bdg.textContent=type==='portfolio'?'π§ Portfolio RAG':'π Web Search'; |
| wrap.appendChild(bdg); |
| } |
| const b=document.createElement('div'); b.className='m-bbl'; |
| if(err) b.innerHTML=`<div class="a-err">${err}</div>`; |
| else if(role==='bot') b.innerHTML=md(text||''); |
| else b.textContent=text; |
| wrap.appendChild(b); d.appendChild(av); d.appendChild(wrap); |
| msgs.appendChild(d); scroll(); return d; |
| } |
| |
| function showTyping(){ |
| typingEl=document.createElement('div'); typingEl.className='a-typing'; |
| typingEl.innerHTML=`<div class="m-av" style="background:linear-gradient(135deg,#6366f1,#818cf8);border-radius:8px;width:26px;height:26px;display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0;margin-top:3px">β¦</div><div class="t-dots"><div class="t-d"></div><div class="t-d"></div><div class="t-d"></div></div>`; |
| msgs.appendChild(typingEl); scroll(); |
| } |
| function hideTyping(){ if(typingEl){typingEl.remove();typingEl=null;} } |
| function setLoad(s){ loading=s; send.disabled=s; inp.disabled=s; } |
| function scroll(){ setTimeout(()=>{msgs.scrollTop=msgs.scrollHeight;},40); } |
| |
| function clearChat(){ |
| if(sessionId) fetch(`${API}/chat/${sessionId}`,{method:'DELETE'}).catch(()=>{}); |
| sessionId=null; |
| msgs.innerHTML=`<div class="a-welcome" style="animation:mi .4s ease forwards"><div class="w-icon">β¦</div><h4>Fresh start!</h4><p>Ask me anything!</p><div class="a-chips"><button class="a-chip" data-q="Who is Abhishek Kumar?">π¨βπ» About Abhishek</button><button class="a-chip" data-q="What are Abhishek's skills?">π His Skills</button><button class="a-chip" data-q="Tell me about Delhi city">π Delhi</button><button class="a-chip" data-q="What is machine learning?">π€ What is ML?</button></div></div>`; |
| } |
| |
| function md(t){ |
| return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') |
| .replace(/\*\*(.*?)\*\*/g,'<strong>$1</strong>').replace(/\*(.*?)\*/g,'<em>$1</em>') |
| .replace(/`(.*?)`/g,'<code>$1</code>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener">$1</a>') |
| .replace(/^#{1,3} (.+)$/gm,'<strong>$1</strong>').replace(/^[-β’] (.+)$/gm,'β’ $1').replace(/\n/g,'<br>'); |
| } |
| })(); |
| </script> |
| </body> |
| </html> |