leedab 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,936 +0,0 @@
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.0">
6
- <link rel="icon" type="image/png" href="/favicon.png">
7
- <title>LeedAB</title>
8
- <style>
9
- * { margin: 0; padding: 0; box-sizing: border-box; }
10
-
11
- :root {
12
- --bg: #0a0a0b;
13
- --surface: #111113;
14
- --surface-raised: #18181b;
15
- --border: #232328;
16
- --border-hover: #333338;
17
- --text: #e4e4e7;
18
- --text-secondary: #a1a1aa;
19
- --text-dim: #71717a;
20
- --text-faint: #52525b;
21
- --accent: #AE5630;
22
- --accent-hover: #c4633a;
23
- --accent-soft: rgba(174, 86, 48, 0.08);
24
- --accent-medium: rgba(174, 86, 48, 0.15);
25
- --green: #22c55e;
26
- --radius: 16px;
27
- --radius-md: 12px;
28
- --radius-sm: 8px;
29
- }
30
-
31
- [data-theme="light"] {
32
- --bg: #f8f8fa;
33
- --surface: #ffffff;
34
- --surface-raised: #f0f0f3;
35
- --border: #d4d4d8;
36
- --border-hover: #a1a1aa;
37
- --text: #18181b;
38
- --text-secondary: #3f3f46;
39
- --text-dim: #52525b;
40
- --text-faint: #71717a;
41
- --accent: #9a4528;
42
- --accent-hover: #AE5630;
43
- --accent-soft: rgba(174, 86, 48, 0.06);
44
- --accent-medium: rgba(174, 86, 48, 0.12);
45
- --green: #16a34a;
46
- }
47
-
48
- body {
49
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", system-ui, sans-serif;
50
- background: var(--bg);
51
- color: var(--text);
52
- height: 100vh;
53
- display: flex;
54
- flex-direction: column;
55
- -webkit-font-smoothing: antialiased;
56
- -moz-osx-font-smoothing: grayscale;
57
- }
58
-
59
- /* ── Header ── */
60
- .header {
61
- flex-shrink: 0;
62
- display: flex;
63
- align-items: center;
64
- justify-content: space-between;
65
- padding: 0 20px;
66
- height: 52px;
67
- border-bottom: 1px solid var(--border);
68
- background: var(--bg);
69
- }
70
-
71
- .header-left {
72
- display: flex;
73
- align-items: center;
74
- gap: 10px;
75
- }
76
-
77
- .header-title {
78
- display: flex;
79
- align-items: center;
80
- }
81
-
82
- .header-logo {
83
- height: 22px;
84
- width: auto;
85
- display: block;
86
- }
87
-
88
-
89
- .header-nav {
90
- display: flex;
91
- align-items: center;
92
- gap: 2px;
93
- }
94
-
95
- .header-nav a {
96
- color: var(--text-dim);
97
- text-decoration: none;
98
- display: flex;
99
- align-items: center;
100
- gap: 5px;
101
- padding: 6px 12px;
102
- border-radius: var(--radius-sm);
103
- font-size: 13px;
104
- font-weight: 450;
105
- transition: all 0.15s;
106
- }
107
-
108
- .header-nav a:hover {
109
- color: var(--text-secondary);
110
- background: var(--surface-raised);
111
- }
112
-
113
- /* ── Main column below header ── */
114
- .main-col {
115
- flex: 1;
116
- display: flex;
117
- flex-direction: column;
118
- min-height: 0;
119
- }
120
-
121
- /* Empty state: center the chat content + input bar together */
122
- body.empty-mode .main-col {
123
- justify-content: center;
124
- padding-top: 5vh;
125
- }
126
- body.empty-mode .chat-wrap {
127
- flex: 0 0 auto;
128
- overflow: visible;
129
- }
130
- body.empty-mode .chat-wrap::before,
131
- body.empty-mode .chat-wrap::after {
132
- display: none;
133
- }
134
- body.empty-mode .chat-area {
135
- height: auto;
136
- overflow: visible;
137
- padding: 0 24px;
138
- }
139
- body.empty-mode .empty-state {
140
- flex: 0 0 auto;
141
- padding: 0;
142
- }
143
- body.empty-mode .input-bar {
144
- padding-top: 36px;
145
- }
146
-
147
- /* ── Chat area ── */
148
- .chat-wrap {
149
- flex: 1;
150
- overflow: hidden;
151
- position: relative;
152
- }
153
-
154
- .chat-wrap::before,
155
- .chat-wrap::after {
156
- content: '';
157
- position: absolute;
158
- left: 0;
159
- right: 0;
160
- height: 32px;
161
- pointer-events: none;
162
- z-index: 2;
163
- }
164
-
165
- .chat-wrap::before {
166
- top: 0;
167
- background: linear-gradient(var(--bg), transparent);
168
- }
169
-
170
- .chat-wrap::after {
171
- bottom: 0;
172
- background: linear-gradient(transparent, var(--bg));
173
- }
174
-
175
- .chat-area {
176
- height: 100%;
177
- overflow-y: auto;
178
- padding: 32px 24px;
179
- display: flex;
180
- flex-direction: column;
181
- gap: 24px;
182
- max-width: 720px;
183
- width: 100%;
184
- margin: 0 auto;
185
- }
186
-
187
- .chat-area::-webkit-scrollbar { width: 4px; }
188
- .chat-area::-webkit-scrollbar-track { background: transparent; }
189
- .chat-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
190
- .chat-area::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
191
-
192
- /* ── Messages ── */
193
- .msg-row {
194
- display: flex;
195
- gap: 12px;
196
- }
197
-
198
- .msg-row.user {
199
- flex-direction: row-reverse;
200
- }
201
-
202
- .msg-avatar {
203
- width: 28px;
204
- height: 28px;
205
- border-radius: var(--radius-sm);
206
- flex-shrink: 0;
207
- display: flex;
208
- align-items: center;
209
- justify-content: center;
210
- font-size: 13px;
211
- font-weight: 600;
212
- margin-top: 2px;
213
- }
214
-
215
- .msg-avatar.agent {
216
- background: none;
217
- padding: 0;
218
- overflow: hidden;
219
- }
220
-
221
- .msg-avatar.agent img {
222
- width: 100%;
223
- height: 100%;
224
- object-fit: cover;
225
- }
226
-
227
- .msg-avatar.user {
228
- background: var(--surface-raised);
229
- color: var(--text-dim);
230
- }
231
-
232
- .msg-content {
233
- max-width: 80%;
234
- display: flex;
235
- flex-direction: column;
236
- gap: 4px;
237
- }
238
-
239
- .msg-meta {
240
- display: flex;
241
- align-items: center;
242
- gap: 8px;
243
- padding: 0 2px;
244
- }
245
-
246
- .msg-row.user .msg-meta {
247
- flex-direction: row-reverse;
248
- }
249
-
250
- .msg-name {
251
- font-size: 12px;
252
- font-weight: 550;
253
- color: var(--text-secondary);
254
- }
255
-
256
- .msg-time {
257
- font-size: 11px;
258
- color: var(--text-faint);
259
- }
260
-
261
- .msg-bubble {
262
- padding: 10px 14px;
263
- font-size: 14px;
264
- line-height: 1.6;
265
- word-wrap: break-word;
266
- white-space: pre-wrap;
267
- }
268
-
269
- .msg-bubble.user {
270
- background: var(--accent-soft);
271
- border: 1px solid var(--accent-medium);
272
- color: var(--text);
273
- border-radius: var(--radius) var(--radius) 4px var(--radius);
274
- }
275
-
276
- .msg-bubble.agent {
277
- background: var(--surface);
278
- border: 1px solid var(--border);
279
- color: var(--text);
280
- border-radius: var(--radius) var(--radius) var(--radius) 4px;
281
- }
282
-
283
- /* Code blocks in messages */
284
- .msg-bubble code {
285
- background: var(--surface-raised);
286
- padding: 1px 5px;
287
- border-radius: 4px;
288
- font-size: 13px;
289
- font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
290
- }
291
-
292
- .msg-bubble pre {
293
- background: var(--surface-raised);
294
- border: 1px solid var(--border);
295
- border-radius: var(--radius-sm);
296
- padding: 12px 14px;
297
- overflow-x: auto;
298
- margin: 8px 0;
299
- }
300
-
301
- .msg-bubble pre code {
302
- background: none;
303
- padding: 0;
304
- font-size: 13px;
305
- line-height: 1.5;
306
- }
307
-
308
- .msg-bubble.agent p { margin-bottom: 8px; }
309
- .msg-bubble.agent p:last-child { margin-bottom: 0; }
310
- .msg-bubble.agent h2, .msg-bubble.agent h3, .msg-bubble.agent h4 { margin: 12px 0 6px; font-weight: 600; }
311
- .msg-bubble.agent h2 { font-size: 16px; }
312
- .msg-bubble.agent h3 { font-size: 15px; }
313
- .msg-bubble.agent h4 { font-size: 14px; }
314
- .msg-bubble.agent ul { padding-left: 18px; margin: 6px 0; }
315
- .msg-bubble.agent li { margin-bottom: 4px; }
316
- .msg-bubble.agent a { color: var(--accent); text-decoration: none; }
317
- .msg-bubble.agent a:hover { text-decoration: underline; }
318
- .msg-bubble.agent strong { font-weight: 600; }
319
- .msg-bubble.agent em { font-style: italic; }
320
- .msg-bubble.agent blockquote { border-left: 3px solid var(--accent); padding-left: 10px; color: var(--text-dim); margin: 6px 0; }
321
- .msg-bubble.agent table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 13px; }
322
- .msg-bubble.agent th, .msg-bubble.agent td { text-align: left; padding: 6px 10px; border: 1px solid var(--border); }
323
- .msg-bubble.agent th { background: var(--surface-raised); font-weight: 600; color: var(--text-secondary); font-size: 12px; }
324
- .msg-bubble.agent td { color: var(--text); }
325
-
326
- /* Thinking */
327
- .thinking-row {
328
- display: flex;
329
- gap: 12px;
330
- }
331
-
332
- .thinking-bubble {
333
- display: flex;
334
- align-items: center;
335
- gap: 8px;
336
- padding: 10px 14px;
337
- background: var(--surface);
338
- border: 1px solid var(--border);
339
- border-radius: var(--radius);
340
- color: var(--text-dim);
341
- font-size: 13px;
342
- }
343
-
344
- /* Thoughts (collapsible, shown before final answer) */
345
- .thoughts {
346
- margin-bottom: 8px;
347
- border: 1px solid var(--border);
348
- border-radius: var(--radius);
349
- background: var(--surface);
350
- font-size: 13px;
351
- }
352
- .thoughts > summary {
353
- cursor: pointer;
354
- list-style: none;
355
- padding: 8px 12px;
356
- color: var(--text-dim);
357
- display: flex;
358
- align-items: center;
359
- gap: 6px;
360
- user-select: none;
361
- }
362
- .thoughts > summary::-webkit-details-marker { display: none; }
363
- .thoughts > summary::before {
364
- content: "▸";
365
- font-size: 10px;
366
- transition: transform 0.15s;
367
- }
368
- .thoughts[open] > summary::before { transform: rotate(90deg); }
369
- .thoughts .thought {
370
- padding: 8px 12px;
371
- border-top: 1px solid var(--border);
372
- color: var(--text-secondary);
373
- white-space: pre-wrap;
374
- line-height: 1.5;
375
- }
376
-
377
- .thinking-spinner {
378
- width: 14px;
379
- height: 14px;
380
- border: 2px solid var(--border);
381
- border-top-color: var(--accent);
382
- border-radius: 50%;
383
- animation: spin 0.8s linear infinite;
384
- }
385
-
386
- @keyframes spin {
387
- to { transform: rotate(360deg); }
388
- }
389
-
390
- /* ── Empty state ── */
391
- .empty-state {
392
- flex: 1;
393
- display: flex;
394
- align-items: center;
395
- justify-content: center;
396
- text-align: center;
397
- opacity: 0;
398
- transition: opacity 0.3s ease;
399
- }
400
- .empty-state.ready {
401
- opacity: 1;
402
- }
403
-
404
- .empty-inner {
405
- max-width: 480px;
406
- }
407
-
408
- .empty-state h3 {
409
- font-size: 26px;
410
- font-weight: 600;
411
- margin-bottom: 4px;
412
- color: var(--text);
413
- letter-spacing: -0.03em;
414
- }
415
-
416
- .empty-subtitle {
417
- font-size: 15px;
418
- color: var(--text-dim);
419
- line-height: 1.5;
420
- margin: 0;
421
- }
422
-
423
- .suggestions {
424
- display: grid;
425
- grid-template-columns: 1fr 1fr;
426
- gap: 12px;
427
- margin-top: 44px;
428
- }
429
-
430
- .suggestion {
431
- display: flex;
432
- align-items: flex-start;
433
- gap: 14px;
434
- padding: 18px;
435
- background: var(--surface);
436
- border: 1px solid var(--border);
437
- border-radius: var(--radius-md);
438
- color: var(--text-secondary);
439
- font-size: 13px;
440
- line-height: 1.4;
441
- cursor: pointer;
442
- transition: all 0.15s;
443
- text-align: left;
444
- }
445
-
446
- .suggestion-icon {
447
- flex-shrink: 0;
448
- width: 36px;
449
- height: 36px;
450
- border-radius: 10px;
451
- background: var(--accent-soft);
452
- display: flex;
453
- align-items: center;
454
- justify-content: center;
455
- color: var(--accent);
456
- }
457
-
458
- .suggestion:hover {
459
- border-color: var(--accent);
460
- background: var(--accent-soft);
461
- color: var(--text);
462
- transform: translateY(-1px);
463
- }
464
-
465
- .suggestion:hover .suggestion-icon {
466
- background: var(--accent);
467
- color: white;
468
- }
469
-
470
- .suggestion:active {
471
- transform: translateY(0);
472
- }
473
-
474
- .suggestion-icon {
475
- width: 32px;
476
- height: 32px;
477
- border-radius: var(--radius-sm);
478
- background: var(--surface-raised);
479
- display: flex;
480
- align-items: center;
481
- justify-content: center;
482
- flex-shrink: 0;
483
- color: var(--text-dim);
484
- transition: all 0.15s;
485
- }
486
-
487
- .suggestion:hover .suggestion-icon {
488
- background: var(--accent-medium);
489
- color: var(--accent);
490
- }
491
-
492
- .suggestion-text {
493
- display: flex;
494
- flex-direction: column;
495
- gap: 2px;
496
- }
497
-
498
- .suggestion-title {
499
- font-weight: 500;
500
- color: var(--text);
501
- font-size: 13px;
502
- }
503
-
504
- .suggestion-desc {
505
- font-size: 12px;
506
- color: var(--text-dim);
507
- }
508
-
509
- /* ── Input bar ── */
510
- .input-bar {
511
- flex-shrink: 0;
512
- padding: 12px 24px 20px;
513
- max-width: 720px;
514
- width: 100%;
515
- margin: 0 auto;
516
- }
517
-
518
- .input-wrap {
519
- display: flex;
520
- align-items: flex-end;
521
- gap: 8px;
522
- background: var(--surface);
523
- border: 1px solid var(--border);
524
- border-radius: 18px;
525
- padding: 4px 4px 4px 16px;
526
- transition: border-color 0.2s, box-shadow 0.2s;
527
- box-shadow: 0 1px 4px rgba(0,0,0,0.04);
528
- }
529
-
530
- .input-wrap:focus-within {
531
- border-color: var(--border-hover);
532
- box-shadow: 0 2px 12px rgba(0,0,0,0.08);
533
- }
534
-
535
- .input-wrap textarea {
536
- flex: 1;
537
- background: transparent;
538
- border: none;
539
- color: var(--text);
540
- font-size: 14px;
541
- font-family: inherit;
542
- line-height: 1.5;
543
- resize: none;
544
- outline: none;
545
- min-height: 22px;
546
- max-height: 120px;
547
- padding: 9px 0;
548
- }
549
-
550
- .input-wrap textarea::placeholder {
551
- color: var(--text-faint);
552
- }
553
-
554
- .send-btn {
555
- width: 36px;
556
- height: 36px;
557
- border-radius: 10px;
558
- background: var(--accent);
559
- border: none;
560
- cursor: pointer;
561
- display: flex;
562
- align-items: center;
563
- justify-content: center;
564
- flex-shrink: 0;
565
- transition: all 0.15s;
566
- }
567
-
568
- .send-btn:hover { background: var(--accent-hover); }
569
- .send-btn:disabled { opacity: 0.25; cursor: not-allowed; }
570
- .send-btn:active:not(:disabled) { transform: scale(0.95); }
571
-
572
- .send-btn svg {
573
- width: 16px;
574
- height: 16px;
575
- color: white;
576
- }
577
-
578
- .disclaimer {
579
- text-align: center;
580
- font-size: 11px;
581
- color: var(--text-faint);
582
- margin-top: 8px;
583
- opacity: 0.6;
584
- }
585
- </style>
586
- </head>
587
- <body>
588
- <div class="header">
589
- <div class="header-left">
590
- <a href="/" class="header-title" aria-label="LeedAB"><img class="logo-img header-logo" alt="LeedAB"></a>
591
- </div>
592
- <div class="header-nav">
593
- <a href="/admin">
594
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
595
- <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
596
- </svg>
597
- Admin
598
- </a>
599
- <a href="/sessions.html">
600
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
601
- <circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
602
- </svg>
603
- Sessions
604
- </a>
605
- </div>
606
- </div>
607
-
608
- <div class="main-col">
609
- <div class="chat-wrap">
610
- <div class="chat-area" id="chat-area">
611
- <div class="empty-state" id="empty-state">
612
- <div class="empty-inner">
613
- <h3 id="empty-greeting"></h3>
614
- <p class="empty-subtitle" id="empty-subtitle">What can I help with?</p>
615
- <div class="suggestions">
616
- <div class="suggestion" onclick="sendSuggestion('Which deliveries are delayed today and what\'s the estimated impact on SLAs?')">
617
- <div class="suggestion-icon">
618
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
619
- </div>
620
- <div class="suggestion-text">
621
- <div class="suggestion-title">Delivery delays</div>
622
- <div class="suggestion-desc">Flag late shipments and SLA risk</div>
623
- </div>
624
- </div>
625
- <div class="suggestion" onclick="sendSuggestion('Reconcile yesterday\'s invoices against purchase orders and flag mismatches')">
626
- <div class="suggestion-icon">
627
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
628
- </div>
629
- <div class="suggestion-text">
630
- <div class="suggestion-title">Invoice reconciliation</div>
631
- <div class="suggestion-desc">Match invoices to POs, flag mismatches</div>
632
- </div>
633
- </div>
634
- <div class="suggestion" onclick="sendSuggestion('Draft a client-ready summary of this week\'s fulfillment performance')">
635
- <div class="suggestion-icon">
636
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
637
- </div>
638
- <div class="suggestion-text">
639
- <div class="suggestion-title">Weekly ops report</div>
640
- <div class="suggestion-desc">Fulfillment summary ready for clients</div>
641
- </div>
642
- </div>
643
- <div class="suggestion" onclick="sendSuggestion('Check which vendors have expiring contracts in the next 30 days')">
644
- <div class="suggestion-icon">
645
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
646
- </div>
647
- <div class="suggestion-text">
648
- <div class="suggestion-title">Expiring contracts</div>
649
- <div class="suggestion-desc">Vendor renewals due in 30 days</div>
650
- </div>
651
- </div>
652
- </div>
653
- </div>
654
- </div>
655
- </div>
656
- </div>
657
-
658
- <div class="input-bar">
659
- <div class="input-wrap">
660
- <textarea id="msg-input" placeholder="Message LeedAB..." rows="1" autofocus></textarea>
661
- <button class="send-btn" id="send-btn" onclick="sendMessage()" disabled>
662
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
663
- <path d="m5 12 14 0M12 5l7 7-7 7"/>
664
- </svg>
665
- </button>
666
- </div>
667
- <div class="disclaimer">LeedAB is AI. Verify important information.</div>
668
- </div>
669
- </div>
670
-
671
- <script>
672
- // Theme
673
- function getTheme() { return document.documentElement.getAttribute("data-theme") || "dark"; }
674
- function logoSrc() { return getTheme() === "dark" ? "/logo-dark.png" : "/logo-light.png"; }
675
- function updateLogos() {
676
- document.querySelectorAll(".logo-img").forEach(img => img.src = logoSrc());
677
- }
678
- (function() {
679
- const saved = localStorage.getItem("leedab-theme") || "dark";
680
- document.documentElement.setAttribute("data-theme", saved);
681
- })();
682
-
683
- updateLogos();
684
- const chatArea = document.getElementById("chat-area");
685
- const emptyState = document.getElementById("empty-state");
686
- const input = document.getElementById("msg-input");
687
- const sendBtn = document.getElementById("send-btn");
688
-
689
- // Empty-mode centers the welcome + input together; drop it as soon as
690
- // the conversation starts so the input pins back to the bottom.
691
- if (emptyState) document.body.classList.add("empty-mode");
692
- function exitEmptyMode() {
693
- document.body.classList.remove("empty-mode");
694
- if (emptyState) emptyState.remove();
695
- }
696
-
697
- // Session: "console" default, or from URL for viewing history
698
- const params = new URLSearchParams(window.location.search);
699
- let sessionId = params.get("session") || "console";
700
-
701
- let userName = "You";
702
- let userInitial = "Y";
703
- let agentName = "LeedAB";
704
- fetch("/api/whoami").then(r => r.json()).then(d => {
705
- if (d.name) {
706
- userName = d.name;
707
- userInitial = d.name.charAt(0).toUpperCase();
708
- }
709
- if (d.agent) {
710
- agentName = d.agent;
711
- input.placeholder = `Message ${agentName}...`;
712
- const disc = document.querySelector(".disclaimer");
713
- if (disc) disc.textContent = `${agentName} is AI. Verify important information.`;
714
- document.title = agentName;
715
- }
716
- // Time-aware greeting
717
- const greet = document.getElementById("empty-greeting");
718
- if (greet) {
719
- const h = new Date().getHours();
720
- const tod = h < 12 ? "Good morning" : h < 17 ? "Good afternoon" : "Good evening";
721
- greet.textContent = d.name ? `${tod}, ${d.name}.` : tod + ".";
722
- }
723
- if (emptyState) emptyState.classList.add("ready");
724
- }).catch(() => {
725
- const greet = document.getElementById("empty-greeting");
726
- if (greet) greet.textContent = "How can I help?";
727
- if (emptyState) emptyState.classList.add("ready");
728
- });
729
-
730
- input.addEventListener("input", () => {
731
- input.style.height = "auto";
732
- input.style.height = Math.min(input.scrollHeight, 120) + "px";
733
- sendBtn.disabled = !input.value.trim();
734
- });
735
-
736
- input.addEventListener("keydown", (e) => {
737
- if (e.key === "Enter" && !e.shiftKey) {
738
- e.preventDefault();
739
- if (input.value.trim()) sendMessage();
740
- }
741
- });
742
-
743
- // Load history if opening an existing session
744
- if (params.get("session")) {
745
- loadHistory(sessionId);
746
- }
747
-
748
- function sendSuggestion(text) {
749
- input.value = text;
750
- sendBtn.disabled = false;
751
- sendMessage();
752
- }
753
-
754
- async function sendMessage() {
755
- const text = input.value.trim();
756
- if (!text) return;
757
-
758
- exitEmptyMode();
759
-
760
- appendMessage(text, "user");
761
- input.value = "";
762
- input.style.height = "auto";
763
- sendBtn.disabled = true;
764
-
765
- const thinkingEl = appendThinking();
766
-
767
- try {
768
- const res = await fetch("/api/chat", {
769
- method: "POST",
770
- headers: { "Content-Type": "application/json" },
771
- body: JSON.stringify({ message: text, session: sessionId }),
772
- });
773
-
774
- const data = await res.json();
775
- thinkingEl.remove();
776
-
777
- if (data.error) {
778
- appendMessage("Something went wrong. Is the gateway running?", "agent");
779
- } else {
780
- appendMessage(data.reply, "agent", data.thoughts);
781
- }
782
- } catch (err) {
783
- thinkingEl.remove();
784
- appendMessage("Can't reach the agent. Make sure the gateway is running.", "agent");
785
- }
786
-
787
- sendBtn.disabled = !input.value.trim();
788
- input.focus();
789
- }
790
-
791
- async function loadHistory(sid) {
792
- try {
793
- const res = await fetch(`/api/chat/history?session=${encodeURIComponent(sid)}`);
794
- const messages = await res.json();
795
- if (messages.length) {
796
- exitEmptyMode();
797
- for (const m of messages) {
798
- const type = m.role === "user" ? "user" : "agent";
799
- appendMessage(m.text || m.content || "", type, m.thoughts);
800
- }
801
- }
802
- } catch {}
803
- }
804
-
805
- function now() {
806
- return new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
807
- }
808
-
809
- function appendMessage(text, type, thoughts) {
810
- const row = document.createElement("div");
811
- row.className = `msg-row ${type}`;
812
-
813
- const avatar = document.createElement("div");
814
- avatar.className = `msg-avatar ${type}`;
815
- if (type === "agent") {
816
- const img = document.createElement("img");
817
- img.className = "logo-img";
818
- img.src = logoSrc();
819
- img.alt = "LeedAB";
820
- avatar.appendChild(img);
821
- } else {
822
- avatar.textContent = userInitial;
823
- }
824
-
825
- const content = document.createElement("div");
826
- content.className = "msg-content";
827
-
828
- const meta = document.createElement("div");
829
- meta.className = "msg-meta";
830
- meta.innerHTML = `<span class="msg-name">${type === "agent" ? agentName : userName}</span><span class="msg-time">${now()}</span>`;
831
-
832
- content.appendChild(meta);
833
-
834
- if (type === "agent" && Array.isArray(thoughts) && thoughts.length) {
835
- const details = document.createElement("details");
836
- details.className = "thoughts";
837
- const summary = document.createElement("summary");
838
- summary.textContent = `Thought for a moment · ${thoughts.length} step${thoughts.length === 1 ? "" : "s"}`;
839
- details.appendChild(summary);
840
- for (const t of thoughts) {
841
- const div = document.createElement("div");
842
- div.className = "thought";
843
- div.textContent = t;
844
- details.appendChild(div);
845
- }
846
- content.appendChild(details);
847
- }
848
-
849
- const bubble = document.createElement("div");
850
- bubble.className = `msg-bubble ${type}`;
851
- if (type === "agent") {
852
- bubble.innerHTML = renderMarkdown(text);
853
- } else {
854
- bubble.textContent = text;
855
- }
856
-
857
- content.appendChild(bubble);
858
- row.appendChild(avatar);
859
- row.appendChild(content);
860
- chatArea.appendChild(row);
861
- chatArea.scrollTop = chatArea.scrollHeight;
862
- return row;
863
- }
864
-
865
- function renderMarkdown(text) {
866
- if (!text) return "";
867
- // Escape HTML first
868
- let h = text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
869
- // Code blocks
870
- h = h.replace(/```(\w*)\n([\s\S]*?)```/g, (_,lang,code) => `<pre><code>${code.trim()}</code></pre>`);
871
- // Tables (must run before inline formatting)
872
- h = h.replace(/(\n|^)(\|[^\n]+\|\n\|[\s\-:|]+\|\n(?:\|[^\n]+\|\n?)+)/g, function(_, prefix, block) {
873
- const rows = block.trim().split("\n");
874
- const parseRow = r => r.replace(/^\|/, "").replace(/\|$/, "").split("|").map(c => c.trim());
875
- const headers = parseRow(rows[0]);
876
- const body = rows.slice(2).map(parseRow);
877
- let t = "<table><thead><tr>" + headers.map(h => `<th>${h}</th>`).join("") + "</tr></thead><tbody>";
878
- for (const cells of body) {
879
- t += "<tr>" + cells.map(c => `<td>${c}</td>`).join("") + "</tr>";
880
- }
881
- t += "</tbody></table>";
882
- return prefix + t;
883
- });
884
- // Inline code
885
- h = h.replace(/`([^`]+)`/g, "<code>$1</code>");
886
- // Bold
887
- h = h.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
888
- // Italic
889
- h = h.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>");
890
- // Headers
891
- h = h.replace(/^### (.+)$/gm, "<h4>$1</h4>");
892
- h = h.replace(/^## (.+)$/gm, "<h3>$1</h3>");
893
- h = h.replace(/^# (.+)$/gm, "<h2>$1</h2>");
894
- // Unordered lists
895
- h = h.replace(/^- (.+)$/gm, "<li>$1</li>");
896
- // Ordered lists
897
- h = h.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
898
- // Wrap consecutive <li> in <ul>
899
- h = h.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>");
900
- // Links
901
- h = h.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
902
- // Paragraphs
903
- h = h.replace(/\n\n+/g, "</p><p>");
904
- h = "<p>" + h + "</p>";
905
- // Line breaks
906
- h = h.replace(/([^>])\n([^<])/g, "$1<br>$2");
907
- // Clean
908
- h = h.replace(/<p>\s*<\/p>/g, "");
909
- return h;
910
- }
911
-
912
- function appendThinking() {
913
- const row = document.createElement("div");
914
- row.className = "thinking-row";
915
-
916
- const avatar = document.createElement("div");
917
- avatar.className = "msg-avatar agent";
918
- const thinkImg = document.createElement("img");
919
- thinkImg.className = "logo-img";
920
- thinkImg.src = logoSrc();
921
- thinkImg.alt = "LeedAB";
922
- avatar.appendChild(thinkImg);
923
-
924
- const bubble = document.createElement("div");
925
- bubble.className = "thinking-bubble";
926
- bubble.innerHTML = '<div class="thinking-spinner"></div>Thinking...';
927
-
928
- row.appendChild(avatar);
929
- row.appendChild(bubble);
930
- chatArea.appendChild(row);
931
- chatArea.scrollTop = chatArea.scrollHeight;
932
- return row;
933
- }
934
- </script>
935
- </body>
936
- </html>