leedab 0.1.0

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.
Files changed (71) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +85 -0
  3. package/bin/leedab.js +626 -0
  4. package/dist/analytics.d.ts +20 -0
  5. package/dist/analytics.js +57 -0
  6. package/dist/audit.d.ts +15 -0
  7. package/dist/audit.js +46 -0
  8. package/dist/brand.d.ts +9 -0
  9. package/dist/brand.js +57 -0
  10. package/dist/channels/index.d.ts +5 -0
  11. package/dist/channels/index.js +47 -0
  12. package/dist/config/index.d.ts +10 -0
  13. package/dist/config/index.js +49 -0
  14. package/dist/config/schema.d.ts +58 -0
  15. package/dist/config/schema.js +21 -0
  16. package/dist/dashboard/routes.d.ts +5 -0
  17. package/dist/dashboard/routes.js +410 -0
  18. package/dist/dashboard/server.d.ts +2 -0
  19. package/dist/dashboard/server.js +80 -0
  20. package/dist/dashboard/static/app.js +351 -0
  21. package/dist/dashboard/static/console.html +252 -0
  22. package/dist/dashboard/static/favicon.png +0 -0
  23. package/dist/dashboard/static/index.html +815 -0
  24. package/dist/dashboard/static/logo-dark.png +0 -0
  25. package/dist/dashboard/static/logo-light.png +0 -0
  26. package/dist/dashboard/static/sessions.html +182 -0
  27. package/dist/dashboard/static/settings.html +274 -0
  28. package/dist/dashboard/static/style.css +493 -0
  29. package/dist/dashboard/static/team.html +215 -0
  30. package/dist/gateway.d.ts +8 -0
  31. package/dist/gateway.js +213 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.js +5 -0
  34. package/dist/license.d.ts +27 -0
  35. package/dist/license.js +92 -0
  36. package/dist/memory/index.d.ts +9 -0
  37. package/dist/memory/index.js +41 -0
  38. package/dist/onboard/index.d.ts +4 -0
  39. package/dist/onboard/index.js +263 -0
  40. package/dist/onboard/oauth-server.d.ts +13 -0
  41. package/dist/onboard/oauth-server.js +73 -0
  42. package/dist/onboard/steps/google.d.ts +12 -0
  43. package/dist/onboard/steps/google.js +178 -0
  44. package/dist/onboard/steps/provider.d.ts +10 -0
  45. package/dist/onboard/steps/provider.js +292 -0
  46. package/dist/onboard/steps/teams.d.ts +5 -0
  47. package/dist/onboard/steps/teams.js +51 -0
  48. package/dist/onboard/steps/telegram.d.ts +6 -0
  49. package/dist/onboard/steps/telegram.js +88 -0
  50. package/dist/onboard/steps/welcome.d.ts +1 -0
  51. package/dist/onboard/steps/welcome.js +10 -0
  52. package/dist/onboard/steps/whatsapp.d.ts +2 -0
  53. package/dist/onboard/steps/whatsapp.js +76 -0
  54. package/dist/openclaw.d.ts +9 -0
  55. package/dist/openclaw.js +20 -0
  56. package/dist/team.d.ts +13 -0
  57. package/dist/team.js +49 -0
  58. package/dist/templates/verticals/supply-chain/HEARTBEAT.md +12 -0
  59. package/dist/templates/verticals/supply-chain/SOUL.md +49 -0
  60. package/dist/templates/verticals/supply-chain/WORKFLOWS.md +148 -0
  61. package/dist/templates/verticals/supply-chain/vault-template.json +18 -0
  62. package/dist/templates/workspace/AGENTS.md +181 -0
  63. package/dist/templates/workspace/BOOTSTRAP.md +32 -0
  64. package/dist/templates/workspace/HEARTBEAT.md +9 -0
  65. package/dist/templates/workspace/IDENTITY.md +14 -0
  66. package/dist/templates/workspace/SOUL.md +32 -0
  67. package/dist/templates/workspace/TOOLS.md +40 -0
  68. package/dist/templates/workspace/USER.md +26 -0
  69. package/dist/vault.d.ts +24 -0
  70. package/dist/vault.js +123 -0
  71. package/package.json +58 -0
@@ -0,0 +1,815 @@
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
+ font-size: 14px;
79
+ font-weight: 600;
80
+ letter-spacing: -0.01em;
81
+ }
82
+
83
+ .header-badge {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 5px;
87
+ padding: 3px 8px;
88
+ background: rgba(34, 197, 94, 0.1);
89
+ border-radius: 20px;
90
+ font-size: 11px;
91
+ color: var(--green);
92
+ font-weight: 500;
93
+ }
94
+
95
+ .header-badge-dot {
96
+ width: 6px;
97
+ height: 6px;
98
+ border-radius: 50%;
99
+ background: var(--green);
100
+ }
101
+
102
+ .header-nav {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 2px;
106
+ }
107
+
108
+ .header-nav a {
109
+ color: var(--text-dim);
110
+ text-decoration: none;
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 5px;
114
+ padding: 6px 12px;
115
+ border-radius: var(--radius-sm);
116
+ font-size: 13px;
117
+ font-weight: 450;
118
+ transition: all 0.15s;
119
+ }
120
+
121
+ .header-nav a:hover {
122
+ color: var(--text-secondary);
123
+ background: var(--surface-raised);
124
+ }
125
+
126
+ /* ── Chat area ── */
127
+ .chat-wrap {
128
+ flex: 1;
129
+ overflow: hidden;
130
+ position: relative;
131
+ }
132
+
133
+ .chat-wrap::before,
134
+ .chat-wrap::after {
135
+ content: '';
136
+ position: absolute;
137
+ left: 0;
138
+ right: 0;
139
+ height: 32px;
140
+ pointer-events: none;
141
+ z-index: 2;
142
+ }
143
+
144
+ .chat-wrap::before {
145
+ top: 0;
146
+ background: linear-gradient(var(--bg), transparent);
147
+ }
148
+
149
+ .chat-wrap::after {
150
+ bottom: 0;
151
+ background: linear-gradient(transparent, var(--bg));
152
+ }
153
+
154
+ .chat-area {
155
+ height: 100%;
156
+ overflow-y: auto;
157
+ padding: 32px 24px;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 24px;
161
+ max-width: 720px;
162
+ width: 100%;
163
+ margin: 0 auto;
164
+ }
165
+
166
+ .chat-area::-webkit-scrollbar { width: 4px; }
167
+ .chat-area::-webkit-scrollbar-track { background: transparent; }
168
+ .chat-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
169
+ .chat-area::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
170
+
171
+ /* ── Messages ── */
172
+ .msg-row {
173
+ display: flex;
174
+ gap: 12px;
175
+ }
176
+
177
+ .msg-row.user {
178
+ flex-direction: row-reverse;
179
+ }
180
+
181
+ .msg-avatar {
182
+ width: 28px;
183
+ height: 28px;
184
+ border-radius: var(--radius-sm);
185
+ flex-shrink: 0;
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ font-size: 13px;
190
+ font-weight: 600;
191
+ margin-top: 2px;
192
+ }
193
+
194
+ .msg-avatar.agent {
195
+ background: none;
196
+ padding: 0;
197
+ overflow: hidden;
198
+ }
199
+
200
+ .msg-avatar.agent img {
201
+ width: 100%;
202
+ height: 100%;
203
+ object-fit: cover;
204
+ }
205
+
206
+ .msg-avatar.user {
207
+ background: var(--surface-raised);
208
+ color: var(--text-dim);
209
+ }
210
+
211
+ .msg-content {
212
+ max-width: 80%;
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 4px;
216
+ }
217
+
218
+ .msg-meta {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ padding: 0 2px;
223
+ }
224
+
225
+ .msg-row.user .msg-meta {
226
+ flex-direction: row-reverse;
227
+ }
228
+
229
+ .msg-name {
230
+ font-size: 12px;
231
+ font-weight: 550;
232
+ color: var(--text-secondary);
233
+ }
234
+
235
+ .msg-time {
236
+ font-size: 11px;
237
+ color: var(--text-faint);
238
+ }
239
+
240
+ .msg-bubble {
241
+ padding: 10px 14px;
242
+ font-size: 14px;
243
+ line-height: 1.6;
244
+ word-wrap: break-word;
245
+ white-space: pre-wrap;
246
+ }
247
+
248
+ .msg-bubble.user {
249
+ background: var(--accent-soft);
250
+ border: 1px solid var(--accent-medium);
251
+ color: var(--text);
252
+ border-radius: var(--radius) var(--radius) 4px var(--radius);
253
+ }
254
+
255
+ .msg-bubble.agent {
256
+ background: var(--surface);
257
+ border: 1px solid var(--border);
258
+ color: var(--text);
259
+ border-radius: var(--radius) var(--radius) var(--radius) 4px;
260
+ }
261
+
262
+ /* Code blocks in messages */
263
+ .msg-bubble code {
264
+ background: var(--surface-raised);
265
+ padding: 1px 5px;
266
+ border-radius: 4px;
267
+ font-size: 13px;
268
+ font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
269
+ }
270
+
271
+ .msg-bubble pre {
272
+ background: var(--surface-raised);
273
+ border: 1px solid var(--border);
274
+ border-radius: var(--radius-sm);
275
+ padding: 12px 14px;
276
+ overflow-x: auto;
277
+ margin: 8px 0;
278
+ }
279
+
280
+ .msg-bubble pre code {
281
+ background: none;
282
+ padding: 0;
283
+ font-size: 13px;
284
+ line-height: 1.5;
285
+ }
286
+
287
+ .msg-bubble.agent p { margin-bottom: 8px; }
288
+ .msg-bubble.agent p:last-child { margin-bottom: 0; }
289
+ .msg-bubble.agent h2, .msg-bubble.agent h3, .msg-bubble.agent h4 { margin: 12px 0 6px; font-weight: 600; }
290
+ .msg-bubble.agent h2 { font-size: 16px; }
291
+ .msg-bubble.agent h3 { font-size: 15px; }
292
+ .msg-bubble.agent h4 { font-size: 14px; }
293
+ .msg-bubble.agent ul { padding-left: 18px; margin: 6px 0; }
294
+ .msg-bubble.agent li { margin-bottom: 4px; }
295
+ .msg-bubble.agent a { color: var(--accent); text-decoration: none; }
296
+ .msg-bubble.agent a:hover { text-decoration: underline; }
297
+ .msg-bubble.agent strong { font-weight: 600; }
298
+ .msg-bubble.agent em { font-style: italic; }
299
+ .msg-bubble.agent blockquote { border-left: 3px solid var(--accent); padding-left: 10px; color: var(--text-dim); margin: 6px 0; }
300
+
301
+ /* Thinking */
302
+ .thinking-row {
303
+ display: flex;
304
+ gap: 12px;
305
+ }
306
+
307
+ .thinking-bubble {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 8px;
311
+ padding: 10px 14px;
312
+ background: var(--surface);
313
+ border: 1px solid var(--border);
314
+ border-radius: var(--radius);
315
+ color: var(--text-dim);
316
+ font-size: 13px;
317
+ }
318
+
319
+ .thinking-spinner {
320
+ width: 14px;
321
+ height: 14px;
322
+ border: 2px solid var(--border);
323
+ border-top-color: var(--accent);
324
+ border-radius: 50%;
325
+ animation: spin 0.8s linear infinite;
326
+ }
327
+
328
+ @keyframes spin {
329
+ to { transform: rotate(360deg); }
330
+ }
331
+
332
+ /* ── Empty state ── */
333
+ .empty-state {
334
+ flex: 1;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ text-align: center;
339
+ }
340
+
341
+ .empty-inner {
342
+ max-width: 480px;
343
+ }
344
+
345
+ .empty-logo {
346
+ width: 48px;
347
+ height: 48px;
348
+ border-radius: 14px;
349
+ margin: 0 auto 20px;
350
+ position: relative;
351
+ overflow: hidden;
352
+ }
353
+
354
+ .empty-logo img {
355
+ width: 100%;
356
+ height: 100%;
357
+ object-fit: cover;
358
+ }
359
+
360
+ .empty-logo::after {
361
+ content: '';
362
+ position: absolute;
363
+ inset: -8px;
364
+ border-radius: 20px;
365
+ background: var(--accent);
366
+ opacity: 0.06;
367
+ filter: blur(16px);
368
+ z-index: -1;
369
+ }
370
+
371
+ .empty-state h3 {
372
+ font-size: 20px;
373
+ font-weight: 600;
374
+ margin-bottom: 6px;
375
+ color: var(--text);
376
+ letter-spacing: -0.02em;
377
+ }
378
+
379
+ .empty-state p {
380
+ font-size: 14px;
381
+ color: var(--text-dim);
382
+ line-height: 1.5;
383
+ }
384
+
385
+ .suggestions {
386
+ display: grid;
387
+ grid-template-columns: 1fr 1fr;
388
+ gap: 8px;
389
+ margin-top: 24px;
390
+ }
391
+
392
+ .suggestion {
393
+ display: flex;
394
+ align-items: flex-start;
395
+ gap: 10px;
396
+ padding: 14px;
397
+ background: var(--surface);
398
+ border: 1px solid var(--border);
399
+ border-radius: var(--radius-md);
400
+ color: var(--text-secondary);
401
+ font-size: 13px;
402
+ line-height: 1.4;
403
+ cursor: pointer;
404
+ transition: all 0.15s;
405
+ text-align: left;
406
+ }
407
+
408
+ .suggestion:hover {
409
+ border-color: var(--accent);
410
+ background: var(--accent-soft);
411
+ color: var(--text);
412
+ transform: translateY(-1px);
413
+ }
414
+
415
+ .suggestion:active {
416
+ transform: translateY(0);
417
+ }
418
+
419
+ .suggestion-icon {
420
+ width: 32px;
421
+ height: 32px;
422
+ border-radius: var(--radius-sm);
423
+ background: var(--surface-raised);
424
+ display: flex;
425
+ align-items: center;
426
+ justify-content: center;
427
+ flex-shrink: 0;
428
+ color: var(--text-dim);
429
+ transition: all 0.15s;
430
+ }
431
+
432
+ .suggestion:hover .suggestion-icon {
433
+ background: var(--accent-medium);
434
+ color: var(--accent);
435
+ }
436
+
437
+ .suggestion-text {
438
+ display: flex;
439
+ flex-direction: column;
440
+ gap: 2px;
441
+ }
442
+
443
+ .suggestion-title {
444
+ font-weight: 500;
445
+ color: var(--text);
446
+ font-size: 13px;
447
+ }
448
+
449
+ .suggestion-desc {
450
+ font-size: 12px;
451
+ color: var(--text-dim);
452
+ }
453
+
454
+ /* ── Input bar ── */
455
+ .input-bar {
456
+ flex-shrink: 0;
457
+ padding: 12px 24px 20px;
458
+ max-width: 720px;
459
+ width: 100%;
460
+ margin: 0 auto;
461
+ }
462
+
463
+ .input-wrap {
464
+ display: flex;
465
+ align-items: flex-end;
466
+ gap: 8px;
467
+ background: var(--surface);
468
+ border: 1px solid var(--border);
469
+ border-radius: 18px;
470
+ padding: 4px 4px 4px 16px;
471
+ transition: border-color 0.2s;
472
+ }
473
+
474
+ .input-wrap:focus-within {
475
+ border-color: var(--border-hover);
476
+ }
477
+
478
+ .input-wrap textarea {
479
+ flex: 1;
480
+ background: transparent;
481
+ border: none;
482
+ color: var(--text);
483
+ font-size: 14px;
484
+ font-family: inherit;
485
+ line-height: 1.5;
486
+ resize: none;
487
+ outline: none;
488
+ min-height: 22px;
489
+ max-height: 120px;
490
+ padding: 9px 0;
491
+ }
492
+
493
+ .input-wrap textarea::placeholder {
494
+ color: var(--text-faint);
495
+ }
496
+
497
+ .send-btn {
498
+ width: 36px;
499
+ height: 36px;
500
+ border-radius: 10px;
501
+ background: var(--accent);
502
+ border: none;
503
+ cursor: pointer;
504
+ display: flex;
505
+ align-items: center;
506
+ justify-content: center;
507
+ flex-shrink: 0;
508
+ transition: all 0.15s;
509
+ }
510
+
511
+ .send-btn:hover { background: var(--accent-hover); }
512
+ .send-btn:disabled { opacity: 0.25; cursor: not-allowed; }
513
+ .send-btn:active:not(:disabled) { transform: scale(0.95); }
514
+
515
+ .send-btn svg {
516
+ width: 16px;
517
+ height: 16px;
518
+ color: white;
519
+ }
520
+
521
+ .disclaimer {
522
+ text-align: center;
523
+ font-size: 11px;
524
+ color: var(--text-faint);
525
+ margin-top: 8px;
526
+ }
527
+ </style>
528
+ </head>
529
+ <body>
530
+ <div class="header">
531
+ <div class="header-left">
532
+ <span class="header-title">LeedAB</span>
533
+ <div class="header-badge">
534
+ <div class="header-badge-dot"></div>
535
+ On
536
+ </div>
537
+ </div>
538
+ <div class="header-nav">
539
+ <a href="/console.html">
540
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
541
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
542
+ </svg>
543
+ Console
544
+ </a>
545
+ <a href="/sessions.html">
546
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
547
+ <circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
548
+ </svg>
549
+ Sessions
550
+ </a>
551
+ </div>
552
+ </div>
553
+
554
+ <div class="chat-wrap">
555
+ <div class="chat-area" id="chat-area">
556
+ <div class="empty-state" id="empty-state">
557
+ <div class="empty-inner">
558
+ <div class="empty-logo"><img class="logo-img" alt="LeedAB"></div>
559
+ <h3>How can I help?</h3>
560
+ <p></p>
561
+ <div class="suggestions">
562
+ <div class="suggestion" onclick="sendSuggestion('Which deliveries are delayed today and what\'s the estimated impact on SLAs?')">
563
+ <div class="suggestion-icon">
564
+ <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>
565
+ </div>
566
+ <div class="suggestion-text">
567
+ <div class="suggestion-title">Delivery delays</div>
568
+ <div class="suggestion-desc">Flag late shipments and SLA risk</div>
569
+ </div>
570
+ </div>
571
+ <div class="suggestion" onclick="sendSuggestion('Reconcile yesterday\'s invoices against purchase orders and flag mismatches')">
572
+ <div class="suggestion-icon">
573
+ <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>
574
+ </div>
575
+ <div class="suggestion-text">
576
+ <div class="suggestion-title">Invoice reconciliation</div>
577
+ <div class="suggestion-desc">Match invoices to POs, flag mismatches</div>
578
+ </div>
579
+ </div>
580
+ <div class="suggestion" onclick="sendSuggestion('Draft a client-ready summary of this week\'s fulfillment performance')">
581
+ <div class="suggestion-icon">
582
+ <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>
583
+ </div>
584
+ <div class="suggestion-text">
585
+ <div class="suggestion-title">Weekly ops report</div>
586
+ <div class="suggestion-desc">Fulfillment summary ready for clients</div>
587
+ </div>
588
+ </div>
589
+ <div class="suggestion" onclick="sendSuggestion('Check which vendors have expiring contracts in the next 30 days')">
590
+ <div class="suggestion-icon">
591
+ <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>
592
+ </div>
593
+ <div class="suggestion-text">
594
+ <div class="suggestion-title">Expiring contracts</div>
595
+ <div class="suggestion-desc">Vendor renewals due in 30 days</div>
596
+ </div>
597
+ </div>
598
+ </div>
599
+ </div>
600
+ </div>
601
+ </div>
602
+ </div>
603
+
604
+ <div class="input-bar">
605
+ <div class="input-wrap">
606
+ <textarea id="msg-input" placeholder="Message LeedAB..." rows="1" autofocus></textarea>
607
+ <button class="send-btn" id="send-btn" onclick="sendMessage()" disabled>
608
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
609
+ <path d="m5 12 14 0M12 5l7 7-7 7"/>
610
+ </svg>
611
+ </button>
612
+ </div>
613
+ <div class="disclaimer">LeedAB is AI. Verify important information.</div>
614
+ </div>
615
+
616
+ <script>
617
+ // Theme
618
+ function getTheme() { return document.documentElement.getAttribute("data-theme") || "dark"; }
619
+ function logoSrc() { return getTheme() === "dark" ? "/logo-dark.png" : "/logo-light.png"; }
620
+ function updateLogos() {
621
+ document.querySelectorAll(".logo-img").forEach(img => img.src = logoSrc());
622
+ }
623
+ (function() {
624
+ const saved = localStorage.getItem("leedab-theme") || "dark";
625
+ document.documentElement.setAttribute("data-theme", saved);
626
+ })();
627
+
628
+ updateLogos();
629
+ const chatArea = document.getElementById("chat-area");
630
+ const emptyState = document.getElementById("empty-state");
631
+ const input = document.getElementById("msg-input");
632
+ const sendBtn = document.getElementById("send-btn");
633
+
634
+ // Session: "console" default, or from URL for viewing history
635
+ const params = new URLSearchParams(window.location.search);
636
+ let sessionId = params.get("session") || "console";
637
+
638
+ input.addEventListener("input", () => {
639
+ input.style.height = "auto";
640
+ input.style.height = Math.min(input.scrollHeight, 120) + "px";
641
+ sendBtn.disabled = !input.value.trim();
642
+ });
643
+
644
+ input.addEventListener("keydown", (e) => {
645
+ if (e.key === "Enter" && !e.shiftKey) {
646
+ e.preventDefault();
647
+ if (input.value.trim()) sendMessage();
648
+ }
649
+ });
650
+
651
+ // Load history if opening an existing session
652
+ if (params.get("session")) {
653
+ loadHistory(sessionId);
654
+ }
655
+
656
+ function sendSuggestion(text) {
657
+ input.value = text;
658
+ sendBtn.disabled = false;
659
+ sendMessage();
660
+ }
661
+
662
+ async function sendMessage() {
663
+ const text = input.value.trim();
664
+ if (!text) return;
665
+
666
+ if (emptyState) emptyState.remove();
667
+
668
+ appendMessage(text, "user");
669
+ input.value = "";
670
+ input.style.height = "auto";
671
+ sendBtn.disabled = true;
672
+
673
+ const thinkingEl = appendThinking();
674
+
675
+ try {
676
+ const res = await fetch("/api/chat", {
677
+ method: "POST",
678
+ headers: { "Content-Type": "application/json" },
679
+ body: JSON.stringify({ message: text, session: sessionId }),
680
+ });
681
+
682
+ const data = await res.json();
683
+ thinkingEl.remove();
684
+
685
+ if (data.error) {
686
+ appendMessage("Something went wrong. Is the gateway running?", "agent");
687
+ } else {
688
+ appendMessage(data.reply, "agent");
689
+ }
690
+ } catch (err) {
691
+ thinkingEl.remove();
692
+ appendMessage("Can't reach the agent. Make sure the gateway is running.", "agent");
693
+ }
694
+
695
+ sendBtn.disabled = !input.value.trim();
696
+ input.focus();
697
+ }
698
+
699
+ async function loadHistory(sid) {
700
+ try {
701
+ const res = await fetch(`/api/chat/history?session=${encodeURIComponent(sid)}`);
702
+ const messages = await res.json();
703
+ if (messages.length) {
704
+ if (emptyState) emptyState.remove();
705
+ for (const m of messages) {
706
+ const type = m.role === "user" ? "user" : "agent";
707
+ appendMessage(m.text || m.content || "", type);
708
+ }
709
+ }
710
+ } catch {}
711
+ }
712
+
713
+ function now() {
714
+ return new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
715
+ }
716
+
717
+ function appendMessage(text, type) {
718
+ const row = document.createElement("div");
719
+ row.className = `msg-row ${type}`;
720
+
721
+ const avatar = document.createElement("div");
722
+ avatar.className = `msg-avatar ${type}`;
723
+ if (type === "agent") {
724
+ const img = document.createElement("img");
725
+ img.className = "logo-img";
726
+ img.src = logoSrc();
727
+ img.alt = "LeedAB";
728
+ avatar.appendChild(img);
729
+ } else {
730
+ avatar.textContent = "A";
731
+ }
732
+
733
+ const content = document.createElement("div");
734
+ content.className = "msg-content";
735
+
736
+ const meta = document.createElement("div");
737
+ meta.className = "msg-meta";
738
+ meta.innerHTML = `<span class="msg-name">${type === "agent" ? "LeedAB" : "Admin"}</span><span class="msg-time">${now()}</span>`;
739
+
740
+ const bubble = document.createElement("div");
741
+ bubble.className = `msg-bubble ${type}`;
742
+ if (type === "agent") {
743
+ bubble.innerHTML = renderMarkdown(text);
744
+ } else {
745
+ bubble.textContent = text;
746
+ }
747
+
748
+ content.appendChild(meta);
749
+ content.appendChild(bubble);
750
+ row.appendChild(avatar);
751
+ row.appendChild(content);
752
+ chatArea.appendChild(row);
753
+ chatArea.scrollTop = chatArea.scrollHeight;
754
+ return row;
755
+ }
756
+
757
+ function renderMarkdown(text) {
758
+ if (!text) return "";
759
+ // Escape HTML first
760
+ let h = text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
761
+ // Code blocks
762
+ h = h.replace(/```(\w*)\n([\s\S]*?)```/g, (_,lang,code) => `<pre><code>${code.trim()}</code></pre>`);
763
+ // Inline code
764
+ h = h.replace(/`([^`]+)`/g, "<code>$1</code>");
765
+ // Bold
766
+ h = h.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
767
+ // Italic
768
+ h = h.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>");
769
+ // Headers
770
+ h = h.replace(/^### (.+)$/gm, "<h4>$1</h4>");
771
+ h = h.replace(/^## (.+)$/gm, "<h3>$1</h3>");
772
+ h = h.replace(/^# (.+)$/gm, "<h2>$1</h2>");
773
+ // Unordered lists
774
+ h = h.replace(/^- (.+)$/gm, "<li>$1</li>");
775
+ // Ordered lists
776
+ h = h.replace(/^\d+\. (.+)$/gm, "<li>$1</li>");
777
+ // Wrap consecutive <li> in <ul>
778
+ h = h.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>");
779
+ // Links
780
+ h = h.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
781
+ // Paragraphs
782
+ h = h.replace(/\n\n+/g, "</p><p>");
783
+ h = "<p>" + h + "</p>";
784
+ // Line breaks
785
+ h = h.replace(/([^>])\n([^<])/g, "$1<br>$2");
786
+ // Clean
787
+ h = h.replace(/<p>\s*<\/p>/g, "");
788
+ return h;
789
+ }
790
+
791
+ function appendThinking() {
792
+ const row = document.createElement("div");
793
+ row.className = "thinking-row";
794
+
795
+ const avatar = document.createElement("div");
796
+ avatar.className = "msg-avatar agent";
797
+ const thinkImg = document.createElement("img");
798
+ thinkImg.className = "logo-img";
799
+ thinkImg.src = logoSrc();
800
+ thinkImg.alt = "LeedAB";
801
+ avatar.appendChild(thinkImg);
802
+
803
+ const bubble = document.createElement("div");
804
+ bubble.className = "thinking-bubble";
805
+ bubble.innerHTML = '<div class="thinking-spinner"></div>Thinking...';
806
+
807
+ row.appendChild(avatar);
808
+ row.appendChild(bubble);
809
+ chatArea.appendChild(row);
810
+ chatArea.scrollTop = chatArea.scrollHeight;
811
+ return row;
812
+ }
813
+ </script>
814
+ </body>
815
+ </html>