File size: 7,487 Bytes
d957247 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | <!-- templates/index.html -->
<!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">
<!-- messages will appear here -->
<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>
// UI behavior: single attachment rule
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; // {type: 'image'|'pdf', file: File}
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};
// disable pdf btn
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');
});
// remove attachment if user clears input or selects new
function clearAttachment(){
attachedFile = null;
imgInput.value = '';
pdfInput.value = '';
imgBtn.disabled = false;
pdfBtn.disabled = false;
imgBtn.style.opacity = '1';
pdfBtn.style.opacity = '1';
}
// helper to post JSON
async function postJSON(url, body){
const resp = await fetch(url, {
method:'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
return resp.json();
}
// helper to post multipart
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){
// nothing to do
return;
}
// show user message
if(text) renderBubble(text, 'user');
// if file attached show small indicator already added at attachment time
promptIn.value = '';
try {
let result = null;
if(attachedFile){
if(attachedFile.type === 'image'){
result = await postForm('/analyze_image', attachedFile.file, text);
// show direct answer (plain & concise)
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)');
}
// after handling, clear attachment
clearAttachment();
} else {
// normal chat
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);
}
});
// allow Enter to send
promptIn.addEventListener('keydown', (e)=>{
if(e.key === 'Enter' && !e.shiftKey){
e.preventDefault();
sendBtn.click();
}
});
</script>
</body>
</html>
|