leedab 0.2.2 → 0.2.4

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.
@@ -145,7 +145,7 @@ export function createRoutes(config) {
145
145
  }
146
146
  },
147
147
  /**
148
- * GET /api/whoami — current OS user's first name
148
+ * GET /api/whoami — current OS user's first name + configured agent name
149
149
  */
150
150
  "GET /api/whoami": async (_req, res) => {
151
151
  let firstName = userInfo().username;
@@ -156,7 +156,7 @@ export function createRoutes(config) {
156
156
  firstName = fullName.split(/\s+/)[0];
157
157
  }
158
158
  catch { }
159
- json(res, { name: firstName });
159
+ json(res, { name: firstName, agent: config.agent?.name ?? "LeedAB" });
160
160
  },
161
161
  /**
162
162
  * GET /api/status — channel health status
@@ -11,7 +11,8 @@
11
11
  <style>
12
12
  .page-header { display:flex; align-items:center; justify-content:space-between; padding:0 20px; height:52px; border-bottom:1px solid var(--border); background:var(--bg); }
13
13
  .page-header-left { display:flex; align-items:center; gap:10px; }
14
- .page-header-title { font-size:13px; font-weight:700; letter-spacing:-0.01em; }
14
+ .page-header-title { display:flex; align-items:center; }
15
+ .page-header-title .logo-img { height:22px; width:auto; display:block; }
15
16
  .page-nav { display:flex; align-items:center; gap:2px; }
16
17
  .page-nav a, .page-nav button { color:var(--text-dim); text-decoration:none; display:flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:13px; font-weight:450; transition:all 0.15s; background:none; border:none; cursor:pointer; font-family:inherit; }
17
18
  .page-nav a:hover, .page-nav button:hover { color:var(--text-secondary); background:var(--surface-raised); }
@@ -56,7 +57,7 @@
56
57
 
57
58
  <div class="page-header">
58
59
  <div class="page-header-left">
59
- <a href="/" class="page-header-title" style="text-decoration:none;color:inherit">LeedAB</a>
60
+ <a href="/" class="page-header-title" aria-label="LeedAB"><img class="logo-img" alt="LeedAB"></a>
60
61
  </div>
61
62
  <div class="page-nav">
62
63
  <button class="theme-btn" onclick="toggleTheme()" title="Toggle theme">
@@ -272,6 +273,7 @@
272
273
  const saved = localStorage.getItem("leedab-theme") || "dark";
273
274
  document.documentElement.setAttribute("data-theme", saved);
274
275
  updateThemeIcon(saved);
276
+ updateLogo(saved);
275
277
  }
276
278
  function toggleTheme() {
277
279
  const current = document.documentElement.getAttribute("data-theme") || "dark";
@@ -279,6 +281,11 @@
279
281
  document.documentElement.setAttribute("data-theme", next);
280
282
  localStorage.setItem("leedab-theme", next);
281
283
  updateThemeIcon(next);
284
+ updateLogo(next);
285
+ }
286
+ function updateLogo(theme) {
287
+ const src = theme === "dark" ? "/logo-dark.png" : "/logo-light.png";
288
+ document.querySelectorAll(".logo-img").forEach(img => img.src = src);
282
289
  }
283
290
  function updateThemeIcon(theme) {
284
291
  const sun = document.getElementById("theme-icon-sun");
@@ -75,9 +75,14 @@
75
75
  }
76
76
 
77
77
  .header-title {
78
- font-size: 13px;
79
- font-weight: 700;
80
- letter-spacing: -0.01em;
78
+ display: flex;
79
+ align-items: center;
80
+ }
81
+
82
+ .header-logo {
83
+ height: 22px;
84
+ width: auto;
85
+ display: block;
81
86
  }
82
87
 
83
88
 
@@ -105,6 +110,40 @@
105
110
  background: var(--surface-raised);
106
111
  }
107
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
+
108
147
  /* ── Chat area ── */
109
148
  .chat-wrap {
110
149
  flex: 1;
@@ -355,64 +394,44 @@
355
394
  align-items: center;
356
395
  justify-content: center;
357
396
  text-align: center;
397
+ opacity: 0;
398
+ transition: opacity 0.3s ease;
399
+ }
400
+ .empty-state.ready {
401
+ opacity: 1;
358
402
  }
359
403
 
360
404
  .empty-inner {
361
405
  max-width: 480px;
362
406
  }
363
407
 
364
- .empty-logo {
365
- width: 48px;
366
- height: 48px;
367
- border-radius: 14px;
368
- margin: 0 auto 20px;
369
- position: relative;
370
- overflow: hidden;
371
- }
372
-
373
- .empty-logo img {
374
- width: 100%;
375
- height: 100%;
376
- object-fit: cover;
377
- }
378
-
379
- .empty-logo::after {
380
- content: '';
381
- position: absolute;
382
- inset: -8px;
383
- border-radius: 20px;
384
- background: var(--accent);
385
- opacity: 0.06;
386
- filter: blur(16px);
387
- z-index: -1;
388
- }
389
-
390
408
  .empty-state h3 {
391
- font-size: 20px;
409
+ font-size: 26px;
392
410
  font-weight: 600;
393
- margin-bottom: 6px;
411
+ margin-bottom: 4px;
394
412
  color: var(--text);
395
- letter-spacing: -0.02em;
413
+ letter-spacing: -0.03em;
396
414
  }
397
415
 
398
- .empty-state p {
399
- font-size: 14px;
416
+ .empty-subtitle {
417
+ font-size: 15px;
400
418
  color: var(--text-dim);
401
419
  line-height: 1.5;
420
+ margin: 0;
402
421
  }
403
422
 
404
423
  .suggestions {
405
424
  display: grid;
406
425
  grid-template-columns: 1fr 1fr;
407
- gap: 8px;
408
- margin-top: 24px;
426
+ gap: 12px;
427
+ margin-top: 44px;
409
428
  }
410
429
 
411
430
  .suggestion {
412
431
  display: flex;
413
432
  align-items: flex-start;
414
- gap: 10px;
415
- padding: 14px;
433
+ gap: 14px;
434
+ padding: 18px;
416
435
  background: var(--surface);
417
436
  border: 1px solid var(--border);
418
437
  border-radius: var(--radius-md);
@@ -424,6 +443,18 @@
424
443
  text-align: left;
425
444
  }
426
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
+
427
458
  .suggestion:hover {
428
459
  border-color: var(--accent);
429
460
  background: var(--accent-soft);
@@ -431,6 +462,11 @@
431
462
  transform: translateY(-1px);
432
463
  }
433
464
 
465
+ .suggestion:hover .suggestion-icon {
466
+ background: var(--accent);
467
+ color: white;
468
+ }
469
+
434
470
  .suggestion:active {
435
471
  transform: translateY(0);
436
472
  }
@@ -487,11 +523,13 @@
487
523
  border: 1px solid var(--border);
488
524
  border-radius: 18px;
489
525
  padding: 4px 4px 4px 16px;
490
- transition: border-color 0.2s;
526
+ transition: border-color 0.2s, box-shadow 0.2s;
527
+ box-shadow: 0 1px 4px rgba(0,0,0,0.04);
491
528
  }
492
529
 
493
530
  .input-wrap:focus-within {
494
531
  border-color: var(--border-hover);
532
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
495
533
  }
496
534
 
497
535
  .input-wrap textarea {
@@ -542,13 +580,14 @@
542
580
  font-size: 11px;
543
581
  color: var(--text-faint);
544
582
  margin-top: 8px;
583
+ opacity: 0.6;
545
584
  }
546
585
  </style>
547
586
  </head>
548
587
  <body>
549
588
  <div class="header">
550
589
  <div class="header-left">
551
- <a href="/" class="header-title" style="text-decoration:none;color:inherit">LeedAB</a>
590
+ <a href="/" class="header-title" aria-label="LeedAB"><img class="logo-img header-logo" alt="LeedAB"></a>
552
591
  </div>
553
592
  <div class="header-nav">
554
593
  <a href="/admin">
@@ -566,13 +605,13 @@
566
605
  </div>
567
606
  </div>
568
607
 
608
+ <div class="main-col">
569
609
  <div class="chat-wrap">
570
610
  <div class="chat-area" id="chat-area">
571
611
  <div class="empty-state" id="empty-state">
572
612
  <div class="empty-inner">
573
- <div class="empty-logo"><img class="logo-img" alt="LeedAB"></div>
574
- <h3>How can I help?</h3>
575
- <p></p>
613
+ <h3 id="empty-greeting"></h3>
614
+ <p class="empty-subtitle" id="empty-subtitle">What can I help with?</p>
576
615
  <div class="suggestions">
577
616
  <div class="suggestion" onclick="sendSuggestion('Which deliveries are delayed today and what\'s the estimated impact on SLAs?')">
578
617
  <div class="suggestion-icon">
@@ -627,6 +666,7 @@
627
666
  </div>
628
667
  <div class="disclaimer">LeedAB is AI. Verify important information.</div>
629
668
  </div>
669
+ </div>
630
670
 
631
671
  <script>
632
672
  // Theme
@@ -646,18 +686,46 @@
646
686
  const input = document.getElementById("msg-input");
647
687
  const sendBtn = document.getElementById("send-btn");
648
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
+ exitEmptyMode();
695
+ }
696
+
649
697
  // Session: "console" default, or from URL for viewing history
650
698
  const params = new URLSearchParams(window.location.search);
651
699
  let sessionId = params.get("session") || "console";
652
700
 
653
701
  let userName = "You";
654
702
  let userInitial = "Y";
703
+ let agentName = "LeedAB";
655
704
  fetch("/api/whoami").then(r => r.json()).then(d => {
656
705
  if (d.name) {
657
706
  userName = d.name;
658
707
  userInitial = d.name.charAt(0).toUpperCase();
659
708
  }
660
- }).catch(() => {});
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
+ });
661
729
 
662
730
  input.addEventListener("input", () => {
663
731
  input.style.height = "auto";
@@ -687,7 +755,7 @@
687
755
  const text = input.value.trim();
688
756
  if (!text) return;
689
757
 
690
- if (emptyState) emptyState.remove();
758
+ exitEmptyMode();
691
759
 
692
760
  appendMessage(text, "user");
693
761
  input.value = "";
@@ -725,7 +793,7 @@
725
793
  const res = await fetch(`/api/chat/history?session=${encodeURIComponent(sid)}`);
726
794
  const messages = await res.json();
727
795
  if (messages.length) {
728
- if (emptyState) emptyState.remove();
796
+ exitEmptyMode();
729
797
  for (const m of messages) {
730
798
  const type = m.role === "user" ? "user" : "agent";
731
799
  appendMessage(m.text || m.content || "", type, m.thoughts);
@@ -759,7 +827,7 @@
759
827
 
760
828
  const meta = document.createElement("div");
761
829
  meta.className = "msg-meta";
762
- meta.innerHTML = `<span class="msg-name">${type === "agent" ? "LeedAB" : userName}</span><span class="msg-time">${now()}</span>`;
830
+ meta.innerHTML = `<span class="msg-name">${type === "agent" ? agentName : userName}</span><span class="msg-time">${now()}</span>`;
763
831
 
764
832
  content.appendChild(meta);
765
833
 
@@ -11,7 +11,8 @@
11
11
  <style>
12
12
  .page-header { display:flex; align-items:center; justify-content:space-between; padding:0 20px; height:52px; border-bottom:1px solid var(--border); background:var(--bg); }
13
13
  .page-header-left { display:flex; align-items:center; gap:10px; }
14
- .page-header-title { font-size:13px; font-weight:700; letter-spacing:-0.01em; }
14
+ .page-header-title { display:flex; align-items:center; }
15
+ .page-header-title .logo-img { height:22px; width:auto; display:block; }
15
16
  .page-nav { display:flex; align-items:center; gap:2px; }
16
17
  .page-nav a, .page-nav button { color:var(--text-dim); text-decoration:none; display:flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:13px; font-weight:450; transition:all 0.15s; background:none; border:none; cursor:pointer; font-family:inherit; }
17
18
  .page-nav a:hover, .page-nav button:hover { color:var(--text-secondary); background:var(--surface-raised); }
@@ -20,7 +21,7 @@
20
21
 
21
22
  <div class="page-header">
22
23
  <div class="page-header-left">
23
- <a href="/" class="page-header-title" style="text-decoration:none;color:inherit">LeedAB</a>
24
+ <a href="/" class="page-header-title" aria-label="LeedAB"><img class="logo-img" alt="LeedAB"></a>
24
25
  </div>
25
26
  <div class="page-nav">
26
27
  </div>
@@ -102,6 +103,8 @@
102
103
  function initTheme() {
103
104
  const saved = localStorage.getItem("leedab-theme") || "dark";
104
105
  document.documentElement.setAttribute("data-theme", saved);
106
+ const src = saved === "dark" ? "/logo-dark.png" : "/logo-light.png";
107
+ document.querySelectorAll(".logo-img").forEach(img => img.src = src);
105
108
  }
106
109
  initTheme();
107
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leedab",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "LeedAB — Your enterprise AI agent. Local-first, private by default.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "LeedAB <hello@leedab.com>",