| |
| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/> |
| <title>ICIS Mini-Hub — Mobile</title> |
| <style> |
| /* Mobile-first simple UI */ |
| :root{--bg:#0f172a;--card:#0b1220;--accent:#06b6d4;--muted:#94a3b8;--text:#e6eef8} |
| *{box-sizing:border-box} |
| html,body{height:100%;margin:0;background:linear-gradient(180deg,var(--bg),#071024);font-family:Inter,system-ui,Segoe UI,Roboto,"Helvetica Neue",Arial} |
| .app{max-width:480px;margin:0 auto;height:100vh;display:flex;flex-direction:column;gap:8px;padding:12px} |
| header{display:flex;align-items:center;gap:10px} |
| .brand{color:var(--text);font-weight:700;font-size:18px} |
| .sub{color:var(--muted);font-size:12px} |
| .chat-window{flex:1;background:transparent;padding:8px;overflow:auto;display:flex;flex-direction:column;gap:8px} |
| .bubble{max-width:86%;padding:10px 12px;border-radius:12px;color:var(--text);line-height:1.3} |
| .user{align-self:flex-end;background:linear-gradient(90deg,#0ea5a4,#06b6d4);border-bottom-right-radius:4px} |
| .bot{align-self:flex-start;background:var(--card);border-bottom-left-radius:4px;color:var(--text);box-shadow:0 2px 8px rgba(2,6,23,.6)} |
| .meta{font-size:11px;color:var(--muted);margin-top:6px} |
| .composer{display:flex;gap:8px;align-items:center;padding:8px;background:transparent} |
| .input{flex:1;display:flex;gap:8px;align-items:center;background:rgba(255,255,255,0.03);padding:8px;border-radius:999px} |
| input[type="text"]{flex:1;background:transparent;border:0;color:var(--text);outline:none;padding:6px 4px;font-size:15px} |
| .icon-btn{width:40px;height:40px;border-radius:10px;border:0;background:transparent;color:var(--muted);display:flex;align-items:center;justify-content:center;cursor:pointer} |
| .icon-btn[disabled]{opacity:0.35;cursor:not-allowed} |
| .send{background:var(--accent);color:#002; padding:8px 12px;border-radius:12px;border:0;font-weight:700} |
| .small{font-size:12px;color:var(--muted)} |
| footer.small{text-align:center;padding:6px 0;color:var(--muted);font-size:12px} |
| /* simple responsive tweaks */ |
| @media(min-width:520px){.app{margin-top:30px;border-radius:12px;box-shadow:0 10px 30px rgba(2,6,23,.6);padding:16px}} |
| </style> |
| </head> |
| <body> |
| <div class="app" role="application"> |
| <header> |
| <div> |
| <div class="brand">ICIS Mini-Hub</div> |
| <div class="sub">Mobile-first chat • image/pdf single-attachment</div> |
| </div> |
| </header> |
|
|
| <main class="chat-window" id="chatWindow" aria-live="polite"> |
| |
| <div class="meta small">Function used will show with chat replies. Image/PDF produce direct concise answers.</div> |
| </main> |
|
|
| <div class="composer" aria-label="Composer"> |
| <div class="input" id="composer"> |
| <button class="icon-btn" id="imgBtn" title="Attach image">🖼️</button> |
| <button class="icon-btn" id="pdfBtn" title="Attach PDF">📄</button> |
|
|
| <input type="file" id="imgInput" accept="image/*" style="display:none"> |
| <input type="file" id="pdfInput" accept="application/pdf" style="display:none"> |
|
|
| <input type="text" id="prompt" placeholder="Type a message or add prompt..." aria-label="Message"> |
| </div> |
|
|
| <button class="send" id="sendBtn">Send</button> |
| </div> |
|
|
| <footer class="small">Only one file at a time. Attaching image disables PDF and vice versa.</footer> |
| </div> |
|
|
| <script> |
| |
| const imgBtn = document.getElementById('imgBtn'); |
| const pdfBtn = document.getElementById('pdfBtn'); |
| const imgInput = document.getElementById('imgInput'); |
| const pdfInput = document.getElementById('pdfInput'); |
| const sendBtn = document.getElementById('sendBtn'); |
| const promptIn = document.getElementById('prompt'); |
| const chatWindow = document.getElementById('chatWindow'); |
| |
| let attachedFile = null; |
| |
| function renderBubble(text, who='bot', meta='') { |
| const b = document.createElement('div'); |
| b.className = 'bubble ' + (who==='user' ? 'user' : 'bot'); |
| b.innerText = text; |
| chatWindow.appendChild(b); |
| if(meta){ |
| const m = document.createElement('div'); |
| m.className = 'meta small'; |
| m.innerText = meta; |
| chatWindow.appendChild(m); |
| } |
| chatWindow.scrollTop = chatWindow.scrollHeight; |
| } |
| |
| imgBtn.addEventListener('click', ()=> imgInput.click()); |
| pdfBtn.addEventListener('click', ()=> pdfInput.click()); |
| |
| imgInput.addEventListener('change', (e)=>{ |
| const f = e.target.files[0]; |
| if(!f) return; |
| attachedFile = {type:'image', file:f}; |
| |
| pdfBtn.disabled = true; |
| imgBtn.style.opacity = '0.9'; |
| renderBubble('[Image attached: ' + f.name + ']', 'user'); |
| }); |
| |
| pdfInput.addEventListener('change', (e)=>{ |
| const f = e.target.files[0]; |
| if(!f) return; |
| attachedFile = {type:'pdf', file:f}; |
| imgBtn.disabled = true; |
| pdfBtn.style.opacity = '0.9'; |
| renderBubble('[PDF attached: ' + f.name + ']', 'user'); |
| }); |
| |
| |
| function clearAttachment(){ |
| attachedFile = null; |
| imgInput.value = ''; |
| pdfInput.value = ''; |
| imgBtn.disabled = false; |
| pdfBtn.disabled = false; |
| imgBtn.style.opacity = '1'; |
| pdfBtn.style.opacity = '1'; |
| } |
| |
| |
| async function postJSON(url, body){ |
| const resp = await fetch(url, { |
| method:'POST', |
| headers: {'Content-Type':'application/json'}, |
| body: JSON.stringify(body) |
| }); |
| return resp.json(); |
| } |
| |
| |
| async function postForm(url, file, prompt){ |
| const fd = new FormData(); |
| fd.append('file', file); |
| fd.append('prompt', prompt || ''); |
| const resp = await fetch(url, {method:'POST', body:fd}); |
| return resp.json(); |
| } |
| |
| sendBtn.addEventListener('click', async ()=>{ |
| const text = promptIn.value.trim(); |
| if(!text && !attachedFile){ |
| |
| return; |
| } |
| |
| |
| if(text) renderBubble(text, 'user'); |
| |
| |
| promptIn.value = ''; |
| |
| try { |
| let result = null; |
| if(attachedFile){ |
| if(attachedFile.type === 'image'){ |
| result = await postForm('/analyze_image', attachedFile.file, text); |
| |
| renderBubble(result.response || result.error || 'No response', 'bot', 'Direct (image)'); |
| } else if(attachedFile.type === 'pdf'){ |
| result = await postForm('/summarize_pdf', attachedFile.file, text); |
| renderBubble(result.response || result.error || 'No response', 'bot', 'Direct (pdf)'); |
| } |
| |
| clearAttachment(); |
| } else { |
| |
| const json = await postJSON('/chat', {query: text}); |
| const func = json.function_used || 'chat'; |
| const resp = json.response || json.error || 'No response'; |
| renderBubble(resp, 'bot', 'Function: ' + func); |
| } |
| } catch (err) { |
| renderBubble('Network or server error', 'bot'); |
| console.error(err); |
| } |
| }); |
| |
| |
| promptIn.addEventListener('keydown', (e)=>{ |
| if(e.key === 'Enter' && !e.shiftKey){ |
| e.preventDefault(); |
| sendBtn.click(); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|