ntphuc149 commited on
Commit
edf2fdf
·
verified ·
1 Parent(s): 23cbd3e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +620 -18
index.html CHANGED
@@ -1,19 +1,621 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Research Agent UI</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #0f1117;
12
+ --surface: #1a1d27;
13
+ --surface2: #22263a;
14
+ --border: #2e3348;
15
+ --accent: #5c7cfa;
16
+ --accent2: #38d9a9;
17
+ --warn: #f6a623;
18
+ --error: #ff6b6b;
19
+ --text: #e2e8f0;
20
+ --muted: #7a849e;
21
+ --tool-bg: #0d1f2d;
22
+ --result-bg: #0d2218;
23
+ --round-bg: #1c1a2e;
24
+ }
25
+
26
+ body {
27
+ font-family: 'Inter', system-ui, sans-serif;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ height: 100vh;
31
+ display: flex;
32
+ flex-direction: column;
33
+ overflow: hidden;
34
+ }
35
+
36
+ /* ── Header ── */
37
+ header {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ padding: 12px 20px;
42
+ background: var(--surface);
43
+ border-bottom: 1px solid var(--border);
44
+ flex-shrink: 0;
45
+ }
46
+ header h1 { font-size: 15px; font-weight: 600; color: var(--accent); letter-spacing: .3px; }
47
+ header span { font-size: 12px; color: var(--muted); }
48
+ #resetBtn {
49
+ background: none; border: 1px solid var(--border); color: var(--muted);
50
+ border-radius: 6px; padding: 5px 12px; font-size: 12px; cursor: pointer;
51
+ transition: border-color .15s, color .15s;
52
+ }
53
+ #resetBtn:hover { border-color: var(--error); color: var(--error); }
54
+
55
+ /* ── Main layout ── */
56
+ main {
57
+ display: flex;
58
+ flex: 1;
59
+ overflow: hidden;
60
+ }
61
+
62
+ /* ── Left: chat ── */
63
+ #chatPane {
64
+ width: 420px;
65
+ flex-shrink: 0;
66
+ display: flex;
67
+ flex-direction: column;
68
+ border-right: 1px solid var(--border);
69
+ }
70
+
71
+ #messages {
72
+ flex: 1;
73
+ overflow-y: auto;
74
+ padding: 16px;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 12px;
78
+ }
79
+
80
+ .msg {
81
+ max-width: 88%;
82
+ padding: 10px 14px;
83
+ border-radius: 12px;
84
+ font-size: 14px;
85
+ line-height: 1.55;
86
+ white-space: pre-wrap;
87
+ word-break: break-word;
88
+ }
89
+ .msg.user {
90
+ align-self: flex-end;
91
+ background: var(--accent);
92
+ color: #fff;
93
+ border-bottom-right-radius: 3px;
94
+ }
95
+ .msg.assistant {
96
+ align-self: flex-start;
97
+ background: var(--surface2);
98
+ border-bottom-left-radius: 3px;
99
+ }
100
+ .msg.system-note {
101
+ align-self: center;
102
+ background: none;
103
+ color: var(--muted);
104
+ font-size: 12px;
105
+ font-style: italic;
106
+ }
107
+ .msg.waiting {
108
+ align-self: flex-start;
109
+ background: var(--surface2);
110
+ color: var(--warn);
111
+ font-size: 13px;
112
+ border-left: 3px solid var(--warn);
113
+ }
114
+
115
+ /* typing indicator */
116
+ .typing { display: flex; gap: 5px; align-items: center; padding: 12px 16px; }
117
+ .typing span {
118
+ width: 7px; height: 7px; border-radius: 50%;
119
+ background: var(--muted); animation: bounce .9s infinite;
120
+ }
121
+ .typing span:nth-child(2) { animation-delay: .15s; }
122
+ .typing span:nth-child(3) { animation-delay: .30s; }
123
+ @keyframes bounce {
124
+ 0%,80%,100% { transform: translateY(0); }
125
+ 40% { transform: translateY(-6px); }
126
+ }
127
+
128
+ /* input row */
129
+ #inputRow {
130
+ display: flex;
131
+ gap: 8px;
132
+ padding: 12px 16px;
133
+ border-top: 1px solid var(--border);
134
+ background: var(--surface);
135
+ }
136
+ #userInput {
137
+ flex: 1;
138
+ background: var(--surface2);
139
+ border: 1px solid var(--border);
140
+ border-radius: 8px;
141
+ padding: 9px 12px;
142
+ color: var(--text);
143
+ font-size: 14px;
144
+ resize: none;
145
+ outline: none;
146
+ line-height: 1.4;
147
+ max-height: 120px;
148
+ overflow-y: auto;
149
+ }
150
+ #userInput:focus { border-color: var(--accent); }
151
+ #sendBtn {
152
+ background: var(--accent);
153
+ border: none;
154
+ border-radius: 8px;
155
+ color: #fff;
156
+ padding: 9px 16px;
157
+ font-size: 14px;
158
+ cursor: pointer;
159
+ transition: opacity .15s;
160
+ align-self: flex-end;
161
+ }
162
+ #sendBtn:disabled { opacity: .4; cursor: not-allowed; }
163
+
164
+ /* ── Right: debug panel ── */
165
+ #debugPane {
166
+ flex: 1;
167
+ display: flex;
168
+ flex-direction: column;
169
+ overflow: hidden;
170
+ }
171
+
172
+ #debugHeader {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 10px;
176
+ padding: 12px 16px;
177
+ border-bottom: 1px solid var(--border);
178
+ background: var(--surface);
179
+ flex-shrink: 0;
180
+ }
181
+ #debugHeader h2 { font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
182
+ #turnBadge {
183
+ font-size: 11px;
184
+ background: var(--surface2);
185
+ border: 1px solid var(--border);
186
+ border-radius: 4px;
187
+ padding: 2px 8px;
188
+ color: var(--muted);
189
+ }
190
+
191
+ #debugScroll {
192
+ flex: 1;
193
+ overflow-y: auto;
194
+ padding: 14px;
195
+ display: flex;
196
+ flex-direction: column;
197
+ gap: 16px;
198
+ }
199
+
200
+ /* empty state */
201
+ .empty-state {
202
+ display: flex;
203
+ flex-direction: column;
204
+ align-items: center;
205
+ justify-content: center;
206
+ height: 100%;
207
+ color: var(--muted);
208
+ font-size: 13px;
209
+ gap: 8px;
210
+ text-align: center;
211
+ }
212
+ .empty-state svg { opacity: .3; }
213
+
214
+ /* round block */
215
+ .round-block {
216
+ border: 1px solid var(--border);
217
+ border-radius: 10px;
218
+ overflow: hidden;
219
+ background: var(--round-bg);
220
+ }
221
+ .round-header {
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ padding: 8px 14px;
226
+ background: var(--surface2);
227
+ border-bottom: 1px solid var(--border);
228
+ font-size: 12px;
229
+ font-weight: 600;
230
+ color: var(--muted);
231
+ text-transform: uppercase;
232
+ letter-spacing: .4px;
233
+ }
234
+ .round-pill {
235
+ background: var(--accent);
236
+ color: #fff;
237
+ border-radius: 20px;
238
+ padding: 1px 8px;
239
+ font-size: 11px;
240
+ font-weight: 700;
241
+ }
242
+
243
+ /* assistant thinking text */
244
+ .think-block {
245
+ padding: 10px 14px;
246
+ font-size: 13px;
247
+ color: var(--muted);
248
+ font-style: italic;
249
+ border-bottom: 1px solid var(--border);
250
+ }
251
+
252
+ /* tool call card */
253
+ .tool-call-card {
254
+ margin: 10px 12px 0;
255
+ border-radius: 8px;
256
+ overflow: hidden;
257
+ border: 1px solid #1e3a5f;
258
+ }
259
+ .tool-call-header {
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 8px;
263
+ padding: 8px 12px;
264
+ background: var(--tool-bg);
265
+ cursor: pointer;
266
+ user-select: none;
267
+ }
268
+ .tool-name {
269
+ font-size: 13px;
270
+ font-weight: 600;
271
+ color: var(--accent2);
272
+ font-family: 'Fira Code', monospace;
273
+ }
274
+ .tool-arrow { font-size: 11px; color: var(--muted); margin-left: auto; transition: transform .2s; }
275
+ .tool-arrow.open { transform: rotate(90deg); }
276
+
277
+ .tool-args {
278
+ padding: 10px 12px;
279
+ background: #091826;
280
+ border-top: 1px solid #1e3a5f;
281
+ }
282
+ .tool-args pre {
283
+ font-family: 'Fira Code', 'Courier New', monospace;
284
+ font-size: 12px;
285
+ color: #93c5fd;
286
+ white-space: pre-wrap;
287
+ word-break: break-all;
288
+ }
289
+
290
+ /* tool result card */
291
+ .tool-result-card {
292
+ margin: 6px 12px 12px;
293
+ border-radius: 8px;
294
+ overflow: hidden;
295
+ border: 1px solid #1a3d2b;
296
+ }
297
+ .tool-result-header {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 8px;
301
+ padding: 7px 12px;
302
+ background: var(--result-bg);
303
+ cursor: pointer;
304
+ user-select: none;
305
+ }
306
+ .result-label { font-size: 12px; font-weight: 600; color: var(--accent2); opacity: .7; }
307
+ .result-badge-ok {
308
+ font-size: 10px; background: #1a4d38; color: var(--accent2);
309
+ border-radius: 4px; padding: 1px 6px;
310
+ }
311
+ .result-badge-err {
312
+ font-size: 10px; background: #4d1a1a; color: var(--error);
313
+ border-radius: 4px; padding: 1px 6px;
314
+ }
315
+ .result-badge-wait {
316
+ font-size: 10px; background: #4d3a1a; color: var(--warn);
317
+ border-radius: 4px; padding: 1px 6px;
318
+ }
319
+
320
+ .tool-result-body {
321
+ padding: 10px 12px;
322
+ background: #060f09;
323
+ border-top: 1px solid #1a3d2b;
324
+ }
325
+ .tool-result-body pre {
326
+ font-family: 'Fira Code', 'Courier New', monospace;
327
+ font-size: 12px;
328
+ color: #86efac;
329
+ white-space: pre-wrap;
330
+ word-break: break-all;
331
+ max-height: 260px;
332
+ overflow-y: auto;
333
+ }
334
+
335
+ /* status banner */
336
+ .status-banner {
337
+ margin: 0 0 4px;
338
+ padding: 6px 14px;
339
+ border-radius: 6px;
340
+ font-size: 12px;
341
+ font-weight: 600;
342
+ display: flex;
343
+ align-items: center;
344
+ gap: 6px;
345
+ }
346
+ .status-answered { background: #1a3d28; color: var(--accent2); }
347
+ .status-waiting { background: #3d2e0a; color: var(--warn); }
348
+ .status-error { background: #3d0a0a; color: var(--error); }
349
+ .status-max { background: #2a2a3d; color: var(--muted); }
350
+
351
+ /* scrollbar */
352
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
353
+ ::-webkit-scrollbar-track { background: transparent; }
354
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
355
+ </style>
356
+ </head>
357
+ <body>
358
+
359
+ <header>
360
+ <h1>🔬 Research Agent UI</h1>
361
+ <span id="modelLabel">openrouter · gpt-4o-mini</span>
362
+ <button id="resetBtn">↺ Reset</button>
363
+ </header>
364
+
365
+ <main>
366
+ <!-- ── Chat pane ── -->
367
+ <div id="chatPane">
368
+ <div id="messages">
369
+ <div class="msg system-note">Agent sẵn sàng. Hãy thử hỏi gì đó!</div>
370
+ </div>
371
+ <div id="inputRow">
372
+ <textarea id="userInput" rows="1" placeholder="Nhập câu hỏi… (Enter để gửi)"></textarea>
373
+ <button id="sendBtn">Gửi</button>
374
+ </div>
375
+ </div>
376
+
377
+ <!-- ── Debug pane ── -->
378
+ <div id="debugPane">
379
+ <div id="debugHeader">
380
+ <h2>Tool Execution Trace</h2>
381
+ <span id="turnBadge">–</span>
382
+ </div>
383
+ <div id="debugScroll">
384
+ <div class="empty-state">
385
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
386
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
387
+ </svg>
388
+ <span>Gửi tin nhắn để xem<br>tool calls xuất hiện tại đây</span>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </main>
393
+
394
+ <script>
395
+ // ── Backend URL ───────────────────────────────────────────────────────────────
396
+ // When hosting this file on HF Spaces (or any static host), set BACKEND_URL to
397
+ // your ngrok / tunnel URL (e.g. "https://abc123.ngrok-free.app").
398
+ // Leave as empty string "" to use the same origin (local dev).
399
+ const BACKEND_URL = "";
400
+
401
+ const messagesEl = document.getElementById('messages');
402
+ const userInput = document.getElementById('userInput');
403
+ const sendBtn = document.getElementById('sendBtn');
404
+ const debugScroll = document.getElementById('debugScroll');
405
+ const turnBadge = document.getElementById('turnBadge');
406
+ let turnCount = 0;
407
+
408
+ // ── helpers ──────────────────────────────────────────────────────────────────
409
+ function scrollBottom(el) { el.scrollTop = el.scrollHeight; }
410
+
411
+ function addMessage(text, role) {
412
+ const div = document.createElement('div');
413
+ div.className = `msg ${role}`;
414
+ div.textContent = text;
415
+ messagesEl.appendChild(div);
416
+ scrollBottom(messagesEl);
417
+ return div;
418
+ }
419
+
420
+ function showTyping() {
421
+ const d = document.createElement('div');
422
+ d.className = 'typing'; d.id = 'typingDot';
423
+ d.innerHTML = '<span></span><span></span><span></span>';
424
+ messagesEl.appendChild(d);
425
+ scrollBottom(messagesEl);
426
+ }
427
+ function hideTyping() {
428
+ document.getElementById('typingDot')?.remove();
429
+ }
430
+
431
+ function prettyJson(obj) {
432
+ return JSON.stringify(obj, null, 2);
433
+ }
434
+
435
+ function toggleBody(header, bodyId, arrowEl) {
436
+ const body = document.getElementById(bodyId);
437
+ if (!body) return;
438
+ const open = body.style.display !== 'none';
439
+ body.style.display = open ? 'none' : 'block';
440
+ if (arrowEl) arrowEl.classList.toggle('open', !open);
441
+ }
442
+
443
+ // ── render debug trace ────────────────────────────────────────────────────────
444
+ function renderDebug(data) {
445
+ debugScroll.innerHTML = '';
446
+ turnCount++;
447
+ turnBadge.textContent = `Turn ${turnCount}`;
448
+
449
+ // status banner
450
+ const banner = document.createElement('div');
451
+ const sMap = {
452
+ answered: ['status-answered', '✅ answered'],
453
+ waiting_for_user: ['status-waiting', '⏳ waiting for user'],
454
+ max_tool_rounds: ['status-max', '⚠️ max tool rounds reached'],
455
+ error: ['status-error', '❌ error'],
456
+ };
457
+ const [cls, label] = sMap[data.status] || ['status-error', data.status];
458
+ banner.className = `status-banner ${cls}`;
459
+ banner.textContent = label;
460
+ debugScroll.appendChild(banner);
461
+
462
+ // rounds
463
+ if (!data.rounds || data.rounds.length === 0) {
464
+ const note = document.createElement('div');
465
+ note.className = 'empty-state';
466
+ note.style.height = 'auto';
467
+ note.style.paddingTop = '24px';
468
+ note.textContent = 'No tool calls — agent answered directly.';
469
+ debugScroll.appendChild(note);
470
+ return;
471
+ }
472
+
473
+ data.rounds.forEach((round, ri) => {
474
+ const block = document.createElement('div');
475
+ block.className = 'round-block';
476
+
477
+ const rHeader = document.createElement('div');
478
+ rHeader.className = 'round-header';
479
+ rHeader.innerHTML = `<span class="round-pill">Round ${round.round}</span>
480
+ <span>${round.tool_calls?.length || 0} tool call(s)</span>`;
481
+ block.appendChild(rHeader);
482
+
483
+ // assistant thinking text
484
+ if (round.assistant_text) {
485
+ const think = document.createElement('div');
486
+ think.className = 'think-block';
487
+ think.textContent = `💭 ${round.assistant_text}`;
488
+ block.appendChild(think);
489
+ }
490
+
491
+ // tool calls + results
492
+ const calls = round.tool_calls || [];
493
+ const results = round.tool_results || [];
494
+
495
+ calls.forEach((call, ci) => {
496
+ const callId = `call-args-${ri}-${ci}`;
497
+ const resId = `call-res-${ri}-${ci}`;
498
+ const result = results[ci] || null;
499
+
500
+ // call card
501
+ const callCard = document.createElement('div');
502
+ callCard.className = 'tool-call-card';
503
+
504
+ const callHeader = document.createElement('div');
505
+ callHeader.className = 'tool-call-header';
506
+ const arrow = document.createElement('span');
507
+ arrow.className = 'tool-arrow open';
508
+ arrow.textContent = '▶';
509
+ callHeader.innerHTML = `
510
+ <span style="font-size:14px">🔧</span>
511
+ <span class="tool-name">${call.name}()</span>`;
512
+ callHeader.appendChild(arrow);
513
+ callHeader.addEventListener('click', () => toggleBody(callHeader, callId, arrow));
514
+ callCard.appendChild(callHeader);
515
+
516
+ const callArgs = document.createElement('div');
517
+ callArgs.className = 'tool-args'; callArgs.id = callId;
518
+ callArgs.innerHTML = `<pre>${prettyJson(call.args)}</pre>`;
519
+ callCard.appendChild(callArgs);
520
+ block.appendChild(callCard);
521
+
522
+ // result card
523
+ if (result) {
524
+ const resCard = document.createElement('div');
525
+ resCard.className = 'tool-result-card';
526
+
527
+ const resHeader = document.createElement('div');
528
+ resHeader.className = 'tool-result-header';
529
+ const resArrow = document.createElement('span');
530
+ resArrow.className = 'tool-arrow open'; resArrow.textContent = '▶';
531
+
532
+ let badge = '';
533
+ const r = result.result || {};
534
+ if (r.error) badge = `<span class="result-badge-err">error: ${r.error}</span>`;
535
+ else if (r.awaiting_user) badge = `<span class="result-badge-wait">awaiting user</span>`;
536
+ else badge = `<span class="result-badge-ok">ok</span>`;
537
+
538
+ resHeader.innerHTML = `<span class="result-label">↳ result</span>${badge}`;
539
+ resHeader.appendChild(resArrow);
540
+ resHeader.addEventListener('click', () => toggleBody(resHeader, resId, resArrow));
541
+ resCard.appendChild(resHeader);
542
+
543
+ const resBody = document.createElement('div');
544
+ resBody.className = 'tool-result-body'; resBody.id = resId;
545
+ resBody.innerHTML = `<pre>${prettyJson(result.result)}</pre>`;
546
+ resCard.appendChild(resBody);
547
+ block.appendChild(resCard);
548
+ }
549
+ });
550
+
551
+ debugScroll.appendChild(block);
552
+ });
553
+
554
+ scrollBottom(debugScroll);
555
+ }
556
+
557
+ // ── send ──────────────────────────────────────────────────────────────────────
558
+ async function sendMessage() {
559
+ const text = userInput.value.trim();
560
+ if (!text) return;
561
+
562
+ userInput.value = '';
563
+ userInput.style.height = 'auto';
564
+ sendBtn.disabled = true;
565
+
566
+ addMessage(text, 'user');
567
+ showTyping();
568
+
569
+ try {
570
+ const res = await fetch(`${BACKEND_URL}/chat`, {
571
+ method: 'POST',
572
+ headers: { 'Content-Type': 'application/json' },
573
+ body: JSON.stringify({ message: text }),
574
+ });
575
+ const data = await res.json();
576
+ hideTyping();
577
+
578
+ if (data.error) {
579
+ addMessage(`❌ ${data.error}`, 'msg system-note');
580
+ } else if (data.status === 'waiting_for_user') {
581
+ addMessage(data.assistant_text, 'waiting');
582
+ } else {
583
+ addMessage(data.assistant_text, 'assistant');
584
+ }
585
+
586
+ renderDebug(data);
587
+ } catch (e) {
588
+ hideTyping();
589
+ addMessage(`❌ Network error: ${e.message}`, 'system-note');
590
+ }
591
+
592
+ sendBtn.disabled = false;
593
+ userInput.focus();
594
+ }
595
+
596
+ // ── reset ─────────────────────────────────────────────────────────────────────
597
+ document.getElementById('resetBtn').addEventListener('click', async () => {
598
+ await fetch(`${BACKEND_URL}/reset`, { method: 'POST' });
599
+ messagesEl.innerHTML = '<div class="msg system-note">Conversation reset.</div>';
600
+ debugScroll.innerHTML = `<div class="empty-state">
601
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
602
+ <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
603
+ </svg>
604
+ <span>Gửi tin nhắn để xem<br>tool calls xuất hiện tại đây</span>
605
+ </div>`;
606
+ turnBadge.textContent = '–';
607
+ turnCount = 0;
608
+ });
609
+
610
+ // ── input auto-resize + Enter ──────────────────────────────────────────────────
611
+ userInput.addEventListener('input', () => {
612
+ userInput.style.height = 'auto';
613
+ userInput.style.height = Math.min(userInput.scrollHeight, 120) + 'px';
614
+ });
615
+ userInput.addEventListener('keydown', e => {
616
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
617
+ });
618
+ sendBtn.addEventListener('click', sendMessage);
619
+ </script>
620
+ </body>
621
  </html>