leedab 0.2.0 → 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.
@@ -65,7 +65,8 @@ export function createRoutes(config) {
65
65
  result.text ??
66
66
  result.content ??
67
67
  stdout.trim();
68
- json(res, { reply, session: session ?? "console" });
68
+ const thoughts = await readLatestThoughts(stateDir, session ?? "console");
69
+ json(res, { reply, thoughts, session: session ?? "console" });
69
70
  }
70
71
  catch {
71
72
  json(res, {
@@ -88,6 +89,7 @@ export function createRoutes(config) {
88
89
  const jsonlPath = resolve(sessionsDir, `${session}.jsonl`);
89
90
  const raw = await readFile(jsonlPath, "utf-8");
90
91
  const messages = [];
92
+ let pendingThoughts = [];
91
93
  for (const line of raw.split("\n")) {
92
94
  if (!line.trim())
93
95
  continue;
@@ -99,11 +101,16 @@ export function createRoutes(config) {
99
101
  if (!msg || (msg.role !== "user" && msg.role !== "assistant"))
100
102
  continue;
101
103
  let text = "";
104
+ const theseThoughts = [];
102
105
  if (Array.isArray(msg.content)) {
103
- text = msg.content
104
- .filter((b) => b.type === "text")
105
- .map((b) => b.text)
106
- .join("\n");
106
+ for (const b of msg.content) {
107
+ if (b?.type === "text" && typeof b.text === "string") {
108
+ text += (text ? "\n" : "") + b.text;
109
+ }
110
+ else if (b?.type === "thinking" && typeof b.thinking === "string") {
111
+ theseThoughts.push(b.thinking);
112
+ }
113
+ }
107
114
  }
108
115
  else if (typeof msg.content === "string") {
109
116
  text = msg.content;
@@ -111,12 +118,22 @@ export function createRoutes(config) {
111
118
  // Strip all OpenClaw "(untrusted metadata)" blocks and timestamp prefix
112
119
  text = text.replace(/^(\w[\w\s]*\(untrusted metadata\):\n```json\n[\s\S]*?\n```\n\n)+/, "");
113
120
  text = text.replace(/^\[[\w]{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2} \w+\] /, "");
114
- if (text) {
115
- messages.push({
116
- role: msg.role,
117
- text,
118
- timestamp: entry.timestamp,
119
- });
121
+ if (msg.role === "assistant") {
122
+ // Accumulate thoughts across tool-use turns until we see text
123
+ pendingThoughts.push(...theseThoughts);
124
+ if (text) {
125
+ messages.push({
126
+ role: msg.role,
127
+ text,
128
+ thoughts: pendingThoughts.length ? pendingThoughts : undefined,
129
+ timestamp: entry.timestamp,
130
+ });
131
+ pendingThoughts = [];
132
+ }
133
+ }
134
+ else if (text) {
135
+ messages.push({ role: msg.role, text, timestamp: entry.timestamp });
136
+ pendingThoughts = [];
120
137
  }
121
138
  }
122
139
  catch { }
@@ -128,7 +145,7 @@ export function createRoutes(config) {
128
145
  }
129
146
  },
130
147
  /**
131
- * GET /api/whoami — current OS user's first name
148
+ * GET /api/whoami — current OS user's first name + configured agent name
132
149
  */
133
150
  "GET /api/whoami": async (_req, res) => {
134
151
  let firstName = userInfo().username;
@@ -139,7 +156,7 @@ export function createRoutes(config) {
139
156
  firstName = fullName.split(/\s+/)[0];
140
157
  }
141
158
  catch { }
142
- json(res, { name: firstName });
159
+ json(res, { name: firstName, agent: config.agent?.name ?? "LeedAB" });
143
160
  },
144
161
  /**
145
162
  * GET /api/status — channel health status
@@ -703,6 +720,49 @@ function parseSessionFirstMessage(text) {
703
720
  userText = userText.replace(/\nUntrusted context \(metadata[\s\S]*$/, "");
704
721
  return { senderName, cleanText: userText.trim() };
705
722
  }
723
+ /**
724
+ * Walk the session JSONL backwards and collect `thinking` block content from
725
+ * the most recent assistant turn(s) since the last user message. Returns an
726
+ * ordered list of thought strings (oldest → newest).
727
+ */
728
+ async function readLatestThoughts(stateDir, session) {
729
+ try {
730
+ const jsonlPath = resolve(stateDir, "agents", "main", "sessions", `${session}.jsonl`);
731
+ const raw = await readFile(jsonlPath, "utf-8");
732
+ const lines = raw.split("\n").filter((l) => l.trim());
733
+ const thoughts = [];
734
+ for (let i = lines.length - 1; i >= 0; i--) {
735
+ let entry;
736
+ try {
737
+ entry = JSON.parse(lines[i]);
738
+ }
739
+ catch {
740
+ continue;
741
+ }
742
+ if (entry.type !== "message")
743
+ continue;
744
+ const msg = entry.message;
745
+ if (!msg)
746
+ continue;
747
+ // Stop once we hit the user message that triggered this turn.
748
+ if (msg.role === "user")
749
+ break;
750
+ if (msg.role !== "assistant")
751
+ continue;
752
+ if (!Array.isArray(msg.content))
753
+ continue;
754
+ for (const block of msg.content) {
755
+ if (block?.type === "thinking" && typeof block.thinking === "string") {
756
+ thoughts.unshift(block.thinking);
757
+ }
758
+ }
759
+ }
760
+ return thoughts;
761
+ }
762
+ catch {
763
+ return [];
764
+ }
765
+ }
706
766
  function json(res, data, status = 200) {
707
767
  res.writeHead(status, { "Content-Type": "application/json" });
708
768
  res.end(JSON.stringify(data));
@@ -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:14px; font-weight:600; 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: 14px;
79
- font-weight: 600;
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;
@@ -302,6 +341,39 @@
302
341
  font-size: 13px;
303
342
  }
304
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
+
305
377
  .thinking-spinner {
306
378
  width: 14px;
307
379
  height: 14px;
@@ -322,64 +394,44 @@
322
394
  align-items: center;
323
395
  justify-content: center;
324
396
  text-align: center;
397
+ opacity: 0;
398
+ transition: opacity 0.3s ease;
399
+ }
400
+ .empty-state.ready {
401
+ opacity: 1;
325
402
  }
326
403
 
327
404
  .empty-inner {
328
405
  max-width: 480px;
329
406
  }
330
407
 
331
- .empty-logo {
332
- width: 48px;
333
- height: 48px;
334
- border-radius: 14px;
335
- margin: 0 auto 20px;
336
- position: relative;
337
- overflow: hidden;
338
- }
339
-
340
- .empty-logo img {
341
- width: 100%;
342
- height: 100%;
343
- object-fit: cover;
344
- }
345
-
346
- .empty-logo::after {
347
- content: '';
348
- position: absolute;
349
- inset: -8px;
350
- border-radius: 20px;
351
- background: var(--accent);
352
- opacity: 0.06;
353
- filter: blur(16px);
354
- z-index: -1;
355
- }
356
-
357
408
  .empty-state h3 {
358
- font-size: 20px;
409
+ font-size: 26px;
359
410
  font-weight: 600;
360
- margin-bottom: 6px;
411
+ margin-bottom: 4px;
361
412
  color: var(--text);
362
- letter-spacing: -0.02em;
413
+ letter-spacing: -0.03em;
363
414
  }
364
415
 
365
- .empty-state p {
366
- font-size: 14px;
416
+ .empty-subtitle {
417
+ font-size: 15px;
367
418
  color: var(--text-dim);
368
419
  line-height: 1.5;
420
+ margin: 0;
369
421
  }
370
422
 
371
423
  .suggestions {
372
424
  display: grid;
373
425
  grid-template-columns: 1fr 1fr;
374
- gap: 8px;
375
- margin-top: 24px;
426
+ gap: 12px;
427
+ margin-top: 44px;
376
428
  }
377
429
 
378
430
  .suggestion {
379
431
  display: flex;
380
432
  align-items: flex-start;
381
- gap: 10px;
382
- padding: 14px;
433
+ gap: 14px;
434
+ padding: 18px;
383
435
  background: var(--surface);
384
436
  border: 1px solid var(--border);
385
437
  border-radius: var(--radius-md);
@@ -391,6 +443,18 @@
391
443
  text-align: left;
392
444
  }
393
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
+
394
458
  .suggestion:hover {
395
459
  border-color: var(--accent);
396
460
  background: var(--accent-soft);
@@ -398,6 +462,11 @@
398
462
  transform: translateY(-1px);
399
463
  }
400
464
 
465
+ .suggestion:hover .suggestion-icon {
466
+ background: var(--accent);
467
+ color: white;
468
+ }
469
+
401
470
  .suggestion:active {
402
471
  transform: translateY(0);
403
472
  }
@@ -454,11 +523,13 @@
454
523
  border: 1px solid var(--border);
455
524
  border-radius: 18px;
456
525
  padding: 4px 4px 4px 16px;
457
- 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);
458
528
  }
459
529
 
460
530
  .input-wrap:focus-within {
461
531
  border-color: var(--border-hover);
532
+ box-shadow: 0 2px 12px rgba(0,0,0,0.08);
462
533
  }
463
534
 
464
535
  .input-wrap textarea {
@@ -509,13 +580,14 @@
509
580
  font-size: 11px;
510
581
  color: var(--text-faint);
511
582
  margin-top: 8px;
583
+ opacity: 0.6;
512
584
  }
513
585
  </style>
514
586
  </head>
515
587
  <body>
516
588
  <div class="header">
517
589
  <div class="header-left">
518
- <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>
519
591
  </div>
520
592
  <div class="header-nav">
521
593
  <a href="/admin">
@@ -533,13 +605,13 @@
533
605
  </div>
534
606
  </div>
535
607
 
608
+ <div class="main-col">
536
609
  <div class="chat-wrap">
537
610
  <div class="chat-area" id="chat-area">
538
611
  <div class="empty-state" id="empty-state">
539
612
  <div class="empty-inner">
540
- <div class="empty-logo"><img class="logo-img" alt="LeedAB"></div>
541
- <h3>How can I help?</h3>
542
- <p></p>
613
+ <h3 id="empty-greeting"></h3>
614
+ <p class="empty-subtitle" id="empty-subtitle">What can I help with?</p>
543
615
  <div class="suggestions">
544
616
  <div class="suggestion" onclick="sendSuggestion('Which deliveries are delayed today and what\'s the estimated impact on SLAs?')">
545
617
  <div class="suggestion-icon">
@@ -594,6 +666,7 @@
594
666
  </div>
595
667
  <div class="disclaimer">LeedAB is AI. Verify important information.</div>
596
668
  </div>
669
+ </div>
597
670
 
598
671
  <script>
599
672
  // Theme
@@ -613,18 +686,46 @@
613
686
  const input = document.getElementById("msg-input");
614
687
  const sendBtn = document.getElementById("send-btn");
615
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
+
616
697
  // Session: "console" default, or from URL for viewing history
617
698
  const params = new URLSearchParams(window.location.search);
618
699
  let sessionId = params.get("session") || "console";
619
700
 
620
701
  let userName = "You";
621
702
  let userInitial = "Y";
703
+ let agentName = "LeedAB";
622
704
  fetch("/api/whoami").then(r => r.json()).then(d => {
623
705
  if (d.name) {
624
706
  userName = d.name;
625
707
  userInitial = d.name.charAt(0).toUpperCase();
626
708
  }
627
- }).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
+ });
628
729
 
629
730
  input.addEventListener("input", () => {
630
731
  input.style.height = "auto";
@@ -654,7 +755,7 @@
654
755
  const text = input.value.trim();
655
756
  if (!text) return;
656
757
 
657
- if (emptyState) emptyState.remove();
758
+ exitEmptyMode();
658
759
 
659
760
  appendMessage(text, "user");
660
761
  input.value = "";
@@ -676,7 +777,7 @@
676
777
  if (data.error) {
677
778
  appendMessage("Something went wrong. Is the gateway running?", "agent");
678
779
  } else {
679
- appendMessage(data.reply, "agent");
780
+ appendMessage(data.reply, "agent", data.thoughts);
680
781
  }
681
782
  } catch (err) {
682
783
  thinkingEl.remove();
@@ -692,10 +793,10 @@
692
793
  const res = await fetch(`/api/chat/history?session=${encodeURIComponent(sid)}`);
693
794
  const messages = await res.json();
694
795
  if (messages.length) {
695
- if (emptyState) emptyState.remove();
796
+ exitEmptyMode();
696
797
  for (const m of messages) {
697
798
  const type = m.role === "user" ? "user" : "agent";
698
- appendMessage(m.text || m.content || "", type);
799
+ appendMessage(m.text || m.content || "", type, m.thoughts);
699
800
  }
700
801
  }
701
802
  } catch {}
@@ -705,7 +806,7 @@
705
806
  return new Date().toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
706
807
  }
707
808
 
708
- function appendMessage(text, type) {
809
+ function appendMessage(text, type, thoughts) {
709
810
  const row = document.createElement("div");
710
811
  row.className = `msg-row ${type}`;
711
812
 
@@ -726,7 +827,24 @@
726
827
 
727
828
  const meta = document.createElement("div");
728
829
  meta.className = "msg-meta";
729
- 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>`;
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
+ }
730
848
 
731
849
  const bubble = document.createElement("div");
732
850
  bubble.className = `msg-bubble ${type}`;
@@ -736,7 +854,6 @@
736
854
  bubble.textContent = text;
737
855
  }
738
856
 
739
- content.appendChild(meta);
740
857
  content.appendChild(bubble);
741
858
  row.appendChild(avatar);
742
859
  row.appendChild(content);
@@ -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:14px; font-weight:600; 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,13 +21,9 @@
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
- <button class="theme-btn" onclick="toggleTheme()" title="Toggle theme">
27
- <svg id="theme-icon-sun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
28
- <svg id="theme-icon-moon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
29
- </button>
30
27
  </div>
31
28
  </div>
32
29
 
@@ -106,22 +103,8 @@
106
103
  function initTheme() {
107
104
  const saved = localStorage.getItem("leedab-theme") || "dark";
108
105
  document.documentElement.setAttribute("data-theme", saved);
109
- updateThemeIcon(saved);
110
- }
111
- function toggleTheme() {
112
- const current = document.documentElement.getAttribute("data-theme") || "dark";
113
- const next = current === "dark" ? "light" : "dark";
114
- document.documentElement.setAttribute("data-theme", next);
115
- localStorage.setItem("leedab-theme", next);
116
- updateThemeIcon(next);
117
- }
118
- function updateThemeIcon(theme) {
119
- const sun = document.getElementById("theme-icon-sun");
120
- const moon = document.getElementById("theme-icon-moon");
121
- if (sun && moon) {
122
- sun.style.display = theme === "dark" ? "block" : "none";
123
- moon.style.display = theme === "light" ? "block" : "none";
124
- }
106
+ const src = saved === "dark" ? "/logo-dark.png" : "/logo-light.png";
107
+ document.querySelectorAll(".logo-img").forEach(img => img.src = src);
125
108
  }
126
109
  initTheme();
127
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leedab",
3
- "version": "0.2.0",
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>",