triflix commited on
Commit
dcdb630
·
verified ·
1 Parent(s): 472a1d8

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +231 -0
templates/index.html ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/>
6
+ <title>ICIS Mini-Hub — Mobile</title>
7
+ <style>
8
+ :root{--bg:#071026;--card:#071827;--accent:#06b6d4;--muted:#94a3b8;--text:#e6eef8}
9
+ *{box-sizing:border-box}
10
+ html,body{height:100%;margin:0;background:linear-gradient(180deg,var(--bg),#00101a);font-family:Inter,system-ui,Roboto,Arial}
11
+ .app{max-width:480px;margin:0 auto;height:100vh;display:flex;flex-direction:column;padding:12px;gap:8px}
12
+ header{display:flex;align-items:center;gap:10px}
13
+ .brand{color:var(--text);font-weight:700;font-size:18px}
14
+ .sub{color:var(--muted);font-size:12px}
15
+ .chat-window{flex:1;background:transparent;padding:8px;overflow:auto;display:flex;flex-direction:column;gap:8px}
16
+ .bubble{max-width:86%;padding:10px 12px;border-radius:12px;color:var(--text);line-height:1.3;word-wrap:break-word;white-space:pre-wrap}
17
+ .user{align-self:flex-end;background:linear-gradient(90deg,#0ea5a4,#06b6d4);border-bottom-right-radius:4px}
18
+ .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)}
19
+ .meta{font-size:11px;color:var(--muted);margin-top:4px}
20
+ .composer{display:flex;gap:8px;align-items:center;padding:8px;background:transparent}
21
+ .input{flex:1;display:flex;gap:8px;align-items:center;background:rgba(255,255,255,0.02);padding:8px;border-radius:999px}
22
+ input[type="text"]{flex:1;background:transparent;border:0;color:var(--text);outline:none;padding:6px 4px;font-size:15px}
23
+ .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}
24
+ .icon-btn[disabled]{opacity:0.35;cursor:not-allowed}
25
+ .send{background:var(--accent);color:#002; padding:8px 12px;border-radius:12px;border:0;font-weight:700}
26
+ .attachment-preview{display:flex;align-items:center;gap:8px;padding:8px;background:rgba(255,255,255,0.02);border-radius:8px}
27
+ .remove-btn{background:transparent;border:1px solid rgba(255,255,255,0.04);padding:6px 8px;border-radius:8px;color:var(--muted);cursor:pointer}
28
+ footer.small{text-align:center;padding:6px 0;color:var(--muted);font-size:12px}
29
+ @media(min-width:520px){.app{margin-top:30px;border-radius:12px;box-shadow:0 10px 30px rgba(2,6,23,.6);padding:16px}}
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <div class="app" role="application">
34
+ <header>
35
+ <div>
36
+ <div class="brand">ICIS Mini-Hub</div>
37
+ <div class="sub">Attach first (image/pdf) → add prompt → Send</div>
38
+ </div>
39
+ </header>
40
+
41
+ <main class="chat-window" id="chatWindow" aria-live="polite">
42
+ <div class="meta small">Tip: Only one file at a time. Attach and optionally remove before sending.</div>
43
+ </main>
44
+
45
+ <div id="attachArea" class="attachment-area">
46
+ <!-- visible only when file attached -->
47
+ </div>
48
+
49
+ <div class="composer" aria-label="Composer">
50
+ <div class="input" id="composer">
51
+ <button class="icon-btn" id="imgBtn" title="Attach image">🖼️</button>
52
+ <button class="icon-btn" id="pdfBtn" title="Attach PDF">📄</button>
53
+
54
+ <input type="file" id="imgInput" accept="image/*" style="display:none">
55
+ <input type="file" id="pdfInput" accept="application/pdf" style="display:none">
56
+
57
+ <input type="text" id="prompt" placeholder="Type a message or add prompt..." aria-label="Message">
58
+ </div>
59
+
60
+ <button class="send" id="sendBtn">Send</button>
61
+ </div>
62
+
63
+ <footer class="small">Function used will be shown with chat replies; image/pdf give direct concise answers.</footer>
64
+ </div>
65
+
66
+ <script>
67
+ // Strict attach-first client behavior:
68
+ const imgBtn = document.getElementById('imgBtn');
69
+ const pdfBtn = document.getElementById('pdfBtn');
70
+ const imgInput = document.getElementById('imgInput');
71
+ const pdfInput = document.getElementById('pdfInput');
72
+ const sendBtn = document.getElementById('sendBtn');
73
+ const promptIn = document.getElementById('prompt');
74
+ const chatWindow = document.getElementById('chatWindow');
75
+ const attachArea = document.getElementById('attachArea');
76
+
77
+ let attachedFile = null; // {type:'image'|'pdf', file:File, previewUrl?:string}
78
+
79
+ function renderBubble(text, who='bot', meta='') {
80
+ const b = document.createElement('div');
81
+ b.className = 'bubble ' + (who==='user' ? 'user' : 'bot');
82
+ b.innerText = text;
83
+ chatWindow.appendChild(b);
84
+ if(meta){
85
+ const m = document.createElement('div');
86
+ m.className = 'meta small';
87
+ m.innerText = meta;
88
+ chatWindow.appendChild(m);
89
+ }
90
+ chatWindow.scrollTop = chatWindow.scrollHeight;
91
+ }
92
+
93
+ function showAttachmentPreview() {
94
+ attachArea.innerHTML = '';
95
+ if(!attachedFile) return;
96
+ const box = document.createElement('div');
97
+ box.className = 'attachment-preview';
98
+ if(attachedFile.type === 'image') {
99
+ const img = document.createElement('img');
100
+ img.src = attachedFile.previewUrl;
101
+ img.style.maxWidth = '72px';
102
+ img.style.maxHeight = '72px';
103
+ img.style.borderRadius = '8px';
104
+ box.appendChild(img);
105
+ } else {
106
+ const ico = document.createElement('div');
107
+ ico.innerText = '📄';
108
+ ico.style.fontSize = '28px';
109
+ box.appendChild(ico);
110
+ }
111
+ const info = document.createElement('div');
112
+ info.style.flex = '1';
113
+ info.innerHTML = `<div style="font-weight:600;color:#fff">${attachedFile.file.name}</div>
114
+ <div style="font-size:12px;color:#9aa9b6">${(attachedFile.file.size/1024|0)} KB • ${attachedFile.type.toUpperCase()}</div>`;
115
+ box.appendChild(info);
116
+ const remove = document.createElement('button');
117
+ remove.className = 'remove-btn';
118
+ remove.innerText = 'Remove';
119
+ remove.onclick = ()=> {
120
+ clearAttachment();
121
+ };
122
+ box.appendChild(remove);
123
+ attachArea.appendChild(box);
124
+ }
125
+
126
+ imgBtn.addEventListener('click', ()=> imgInput.click());
127
+ pdfBtn.addEventListener('click', ()=> pdfInput.click());
128
+
129
+ // IMPORTANT: file selection only queues attachment; DO NOT send here.
130
+ imgInput.addEventListener('change', (e)=>{
131
+ const f = e.target.files[0];
132
+ if(!f) return;
133
+ attachedFile = {type:'image', file:f};
134
+ // create preview
135
+ attachedFile.previewUrl = URL.createObjectURL(f);
136
+ pdfBtn.disabled = true;
137
+ imgBtn.disabled = false;
138
+ showAttachmentPreview();
139
+ // do not send automatically
140
+ });
141
+
142
+ pdfInput.addEventListener('change', (e)=>{
143
+ const f = e.target.files[0];
144
+ if(!f) return;
145
+ attachedFile = {type:'pdf', file:f};
146
+ pdfBtn.disabled = false;
147
+ imgBtn.disabled = true;
148
+ showAttachmentPreview();
149
+ // do not send automatically
150
+ });
151
+
152
+ function clearAttachment(){
153
+ if(attachedFile && attachedFile.previewUrl){
154
+ URL.revokeObjectURL(attachedFile.previewUrl);
155
+ }
156
+ attachedFile = null;
157
+ imgInput.value = '';
158
+ pdfInput.value = '';
159
+ imgBtn.disabled = false;
160
+ pdfBtn.disabled = false;
161
+ attachArea.innerHTML = '';
162
+ }
163
+
164
+ async function postJSON(url, body){
165
+ const resp = await fetch(url, {
166
+ method:'POST',
167
+ headers: {'Content-Type':'application/json'},
168
+ body: JSON.stringify(body)
169
+ });
170
+ return resp.json();
171
+ }
172
+
173
+ async function postForm(url, file, prompt){
174
+ const fd = new FormData();
175
+ fd.append('file', file);
176
+ fd.append('prompt', prompt || '');
177
+ const resp = await fetch(url, {method:'POST', body:fd});
178
+ return resp.json();
179
+ }
180
+
181
+ sendBtn.addEventListener('click', async ()=>{
182
+ const text = promptIn.value.trim();
183
+ // If nothing to send (no text and no attachment) do nothing
184
+ if(!text && !attachedFile) return;
185
+
186
+ // Show user text
187
+ if(text) renderBubble(text, 'user');
188
+
189
+ try {
190
+ let result = null;
191
+ if(attachedFile){
192
+ // Send file + prompt only on explicit Send
193
+ if(attachedFile.type === 'image'){
194
+ result = await postForm('/analyze_image', attachedFile.file, text);
195
+ if(result.error) renderBubble(result.error, 'bot');
196
+ else renderBubble(result.response || 'No response', 'bot', 'Direct (image)');
197
+ } else {
198
+ result = await postForm('/summarize_pdf', attachedFile.file, text);
199
+ if(result.error) renderBubble(result.error, 'bot');
200
+ else renderBubble(result.response || 'No response', 'bot', 'Direct (pdf)');
201
+ }
202
+ // clear attachment after send (user wanted it sent)
203
+ clearAttachment();
204
+ } else {
205
+ // normal chat
206
+ const json = await postJSON('/chat', {query: text});
207
+ if(json.error) renderBubble(json.error, 'bot');
208
+ else {
209
+ const func = json.function_used || 'chat';
210
+ const resp = json.response || 'No response';
211
+ renderBubble(resp, 'bot', 'Function: ' + func);
212
+ }
213
+ }
214
+ } catch (err) {
215
+ console.error(err);
216
+ renderBubble('Network or server error', 'bot');
217
+ }
218
+
219
+ promptIn.value = '';
220
+ });
221
+
222
+ // Enter to send
223
+ promptIn.addEventListener('keydown', (e)=>{
224
+ if(e.key === 'Enter' && !e.shiftKey){
225
+ e.preventDefault();
226
+ sendBtn.click();
227
+ }
228
+ });
229
+ </script>
230
+ </body>
231
+ </html>