daedalus-cli 3.73.0 → 3.75.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.
@@ -106,8 +106,84 @@ function renderMarkdown(text) {
106
106
  return tempDiv.innerHTML;
107
107
  }
108
108
 
109
- function addChatMessage(role, text, roleBadge = null, imageBase64 = null, timestamp = null) {
110
- if (!chatMessages) return;
109
+ function attachCliFooter(msgEl, modelName, toolCount, durationMs, tokenCount) {
110
+ if (!msgEl) return;
111
+ if (msgEl.querySelector('.msg-cli-footer')) return;
112
+
113
+ const footer = document.createElement('div');
114
+ footer.className = 'msg-cli-footer';
115
+
116
+ const left = document.createElement('div');
117
+ left.className = 'msg-cli-footer-left';
118
+
119
+ const intel = document.createElement('span');
120
+ intel.className = 'cli-pill cli-pill-intel';
121
+ intel.innerHTML = '<span class="cli-dot">●</span> intelligence';
122
+ left.appendChild(intel);
123
+
124
+ const divider1 = document.createElement('span');
125
+ divider1.className = 'msg-cli-footer-divider';
126
+ divider1.textContent = '·';
127
+ left.appendChild(divider1);
128
+
129
+ const modelPill = document.createElement('span');
130
+ modelPill.className = 'cli-pill cli-pill-model';
131
+ modelPill.textContent = modelName || 'auto';
132
+ left.appendChild(modelPill);
133
+
134
+ if (toolCount && toolCount > 0) {
135
+ const dividerTools = document.createElement('span');
136
+ dividerTools.className = 'msg-cli-footer-divider';
137
+ dividerTools.textContent = '·';
138
+ left.appendChild(dividerTools);
139
+
140
+ const toolsPill = document.createElement('span');
141
+ toolsPill.className = 'cli-pill cli-pill-tools';
142
+ toolsPill.textContent = `${toolCount} tool(s)`;
143
+ left.appendChild(toolsPill);
144
+ }
145
+
146
+ const durationSec = durationMs ? (durationMs / 1000).toFixed(1) : null;
147
+ if (durationSec) {
148
+ const dividerTime = document.createElement('span');
149
+ dividerTime.className = 'msg-cli-footer-divider';
150
+ dividerTime.textContent = '·';
151
+ left.appendChild(dividerTime);
152
+
153
+ const timePill = document.createElement('span');
154
+ timePill.className = 'cli-pill cli-pill-time';
155
+ timePill.textContent = `${durationSec}s`;
156
+ left.appendChild(timePill);
157
+ }
158
+
159
+ if (tokenCount && tokenCount > 0 && durationMs && durationMs > 0) {
160
+ const tokPerSec = (tokenCount / (durationMs / 1000)).toFixed(1);
161
+ const dividerSpeed = document.createElement('span');
162
+ dividerSpeed.className = 'msg-cli-footer-divider';
163
+ dividerSpeed.textContent = '·';
164
+ left.appendChild(dividerSpeed);
165
+
166
+ const speedPill = document.createElement('span');
167
+ speedPill.className = 'cli-pill cli-pill-speed';
168
+ speedPill.textContent = `${tokPerSec} tok/s`;
169
+ left.appendChild(speedPill);
170
+ }
171
+
172
+ footer.appendChild(left);
173
+ msgEl.appendChild(footer);
174
+ }
175
+
176
+ let userProfileName = 'YOU';
177
+ let activeAssistantBody = null;
178
+ let activeAssistantMsgEl = null;
179
+ let thinkingEl = null;
180
+ let currentToolTreeEl = null;
181
+ let currentTurnExecutedTools = [];
182
+ let currentTurnTokenCount = 0;
183
+ let turnStartTime = null;
184
+
185
+ function addChatMessage(role, text, roleBadge = null, imageBase64 = null, timestamp = null, meta = null) {
186
+ if (!chatMessages) return null;
111
187
  const msgEl = document.createElement('div');
112
188
  msgEl.className = `chat-msg ${role}`;
113
189
 
@@ -116,7 +192,7 @@ function addChatMessage(role, text, roleBadge = null, imageBase64 = null, timest
116
192
 
117
193
  const sender = document.createElement('span');
118
194
  sender.className = 'sender';
119
- sender.textContent = role === 'user' ? 'YOU' : 'DAEDALUS';
195
+ sender.textContent = role === 'user' ? userProfileName : 'DAEDALUS';
120
196
  header.appendChild(sender);
121
197
 
122
198
  if (roleBadge) {
@@ -174,93 +250,143 @@ function addChatMessage(role, text, roleBadge = null, imageBase64 = null, timest
174
250
 
175
251
  msgEl.appendChild(header);
176
252
  msgEl.appendChild(body);
253
+
254
+ if (role === 'assistant' && meta) {
255
+ attachCliFooter(msgEl, meta.model, meta.toolCount, meta.durationMs, meta.tokenCount);
256
+ }
257
+
177
258
  chatMessages.appendChild(msgEl);
178
259
  chatMessages.scrollTop = chatMessages.scrollHeight;
260
+
261
+ if (role === 'assistant') {
262
+ activeAssistantMsgEl = msgEl;
263
+ }
179
264
  return body;
180
265
  }
181
266
 
182
- let activeAssistantBody = null;
183
- let thinkingEl = null;
184
- let currentRunningToolEl = null;
185
-
186
267
  function renderToolStart(toolName) {
187
268
  if (!chatMessages) return null;
188
- const accordion = document.createElement('div');
189
- accordion.className = 'tool-accordion running';
190
-
191
- const header = document.createElement('div');
192
- header.className = 'tool-accordion-header';
193
-
194
- const left = document.createElement('div');
195
- left.className = 'tool-accordion-left';
196
-
197
- const icon = document.createElement('span');
198
- icon.className = 'tool-accordion-icon';
199
- icon.textContent = '⚡';
200
-
201
- const name = document.createElement('span');
202
- name.className = 'tool-accordion-name';
203
- name.textContent = toolName;
204
-
205
- left.appendChild(icon);
206
- left.appendChild(name);
207
-
208
- const right = document.createElement('div');
209
- right.style.display = 'flex';
210
- right.style.alignItems = 'center';
211
-
212
- const status = document.createElement('span');
213
- status.className = 'tool-accordion-status running';
214
- status.textContent = 'RUNNING';
215
-
216
- const chevron = document.createElement('span');
217
- chevron.className = 'tool-accordion-chevron';
218
- chevron.textContent = '▶';
219
-
220
- right.appendChild(status);
221
- right.appendChild(chevron);
222
-
223
- header.appendChild(left);
224
- header.appendChild(right);
225
-
226
- const body = document.createElement('div');
227
- body.className = 'tool-accordion-body hidden';
228
- body.textContent = `Executing tool: ${toolName}...`;
229
-
230
- header.addEventListener('click', () => {
231
- accordion.classList.toggle('open');
232
- body.classList.toggle('hidden');
233
- });
234
-
235
- accordion.appendChild(header);
236
- accordion.appendChild(body);
237
- chatMessages.appendChild(accordion);
238
- chatMessages.scrollTop = chatMessages.scrollHeight;
239
- currentRunningToolEl = accordion;
240
- return accordion;
269
+
270
+ if (!currentTurnExecutedTools.includes(toolName)) {
271
+ currentTurnExecutedTools.push(toolName);
272
+ }
273
+
274
+ if (!currentToolTreeEl) {
275
+ const tree = document.createElement('div');
276
+ tree.className = 'tool-execution-tree running';
277
+
278
+ const header = document.createElement('div');
279
+ header.className = 'tool-tree-header';
280
+
281
+ const left = document.createElement('div');
282
+ left.className = 'tool-tree-left';
283
+
284
+ const rail = document.createElement('span');
285
+ rail.className = 'tool-tree-rail';
286
+ rail.textContent = '┊';
287
+
288
+ const icon = document.createElement('span');
289
+ icon.className = 'tool-tree-icon';
290
+ icon.textContent = '';
291
+
292
+ const label = document.createElement('span');
293
+ label.className = 'tool-tree-label';
294
+ label.textContent = `Running: ${toolName}...`;
295
+
296
+ left.appendChild(rail);
297
+ left.appendChild(icon);
298
+ left.appendChild(label);
299
+
300
+ const right = document.createElement('div');
301
+ right.className = 'tool-tree-right';
302
+
303
+ const badge = document.createElement('span');
304
+ badge.className = 'tool-tree-badge running';
305
+ badge.textContent = 'RUNNING';
306
+
307
+ const count = document.createElement('span');
308
+ count.className = 'tool-tree-count';
309
+ count.textContent = '1 tool';
310
+
311
+ const chevron = document.createElement('span');
312
+ chevron.className = 'tool-tree-chevron';
313
+ chevron.textContent = '';
314
+
315
+ right.appendChild(badge);
316
+ right.appendChild(count);
317
+ right.appendChild(chevron);
318
+
319
+ header.appendChild(left);
320
+ header.appendChild(right);
321
+
322
+ const details = document.createElement('div');
323
+ details.className = 'tool-tree-details hidden';
324
+
325
+ header.addEventListener('click', () => {
326
+ tree.classList.toggle('open');
327
+ details.classList.toggle('hidden');
328
+ });
329
+
330
+ tree.appendChild(header);
331
+ tree.appendChild(details);
332
+ chatMessages.appendChild(tree);
333
+ chatMessages.scrollTop = chatMessages.scrollHeight;
334
+
335
+ currentToolTreeEl = tree;
336
+ }
337
+
338
+ const label = currentToolTreeEl.querySelector('.tool-tree-label');
339
+ if (label) label.textContent = `Running: ${toolName}...`;
340
+
341
+ const count = currentToolTreeEl.querySelector('.tool-tree-count');
342
+ if (count) count.textContent = `${currentTurnExecutedTools.length} tool(s)`;
343
+
344
+ return currentToolTreeEl;
241
345
  }
242
346
 
243
347
  function renderToolResult(toolName, resultSummary) {
244
- let accordion = currentRunningToolEl;
245
- if (!accordion) {
246
- accordion = renderToolStart(toolName);
348
+ if (!currentToolTreeEl) {
349
+ renderToolStart(toolName);
247
350
  }
248
- if (accordion) {
249
- accordion.classList.remove('running');
250
- accordion.classList.add('completed');
251
-
252
- const status = accordion.querySelector('.tool-accordion-status');
253
- if (status) {
254
- status.className = 'tool-accordion-status completed';
255
- status.textContent = 'COMPLETED';
351
+
352
+ if (currentToolTreeEl) {
353
+ const details = currentToolTreeEl.querySelector('.tool-tree-details');
354
+ if (details) {
355
+ const item = document.createElement('div');
356
+ item.className = 'tool-tree-item';
357
+
358
+ const rail = document.createElement('span');
359
+ rail.className = 'tool-tree-item-rail';
360
+ rail.textContent = '┊ └─';
361
+
362
+ const name = document.createElement('span');
363
+ name.className = 'tool-tree-item-name';
364
+ name.textContent = toolName;
365
+
366
+ const summary = document.createElement('span');
367
+ summary.className = 'tool-tree-item-summary';
368
+ summary.textContent = resultSummary ? `(${resultSummary})` : '✔ completed';
369
+
370
+ item.appendChild(rail);
371
+ item.appendChild(name);
372
+ item.appendChild(summary);
373
+ details.appendChild(item);
256
374
  }
257
-
258
- const body = accordion.querySelector('.tool-accordion-body');
259
- if (body) {
260
- body.textContent = resultSummary || `Tool ${toolName} execution finished.`;
375
+
376
+ const label = currentToolTreeEl.querySelector('.tool-tree-label');
377
+ if (label) {
378
+ label.textContent = `Executed tools: ${currentTurnExecutedTools.join(', ')}`;
379
+ }
380
+
381
+ const badge = currentToolTreeEl.querySelector('.tool-tree-badge');
382
+ if (badge) {
383
+ badge.className = 'tool-tree-badge completed';
384
+ badge.textContent = '✔ DONE';
261
385
  }
386
+
387
+ currentToolTreeEl.classList.remove('running');
388
+ currentToolTreeEl.classList.add('completed');
262
389
  }
263
- currentRunningToolEl = null;
264
390
  }
265
391
 
266
392
  function showThinkingSpinner() {
@@ -483,8 +609,14 @@ if (chatForm && chatInput) {
483
609
  chatInput.style.height = 'auto';
484
610
  if (chatStatusBadge) chatStatusBadge.textContent = 'THINKING...';
485
611
 
486
- addChatMessage('user', effectiveText || 'Attached image', null, imageBase64);
612
+ turnStartTime = Date.now();
613
+ currentTurnExecutedTools = [];
614
+ currentTurnTokenCount = 0;
615
+ currentToolTreeEl = null;
487
616
  activeAssistantBody = null;
617
+ activeAssistantMsgEl = null;
618
+
619
+ addChatMessage('user', effectiveText || 'Attached image', null, imageBase64);
488
620
  showThinkingSpinner();
489
621
 
490
622
  try {
@@ -709,7 +841,14 @@ async function loadChatHistory() {
709
841
  if (validHistory.length > 0) {
710
842
  chatMessages.innerHTML = '';
711
843
  validHistory.forEach(item => {
712
- addChatMessage(item.role === 'assistant' ? 'assistant' : 'user', item.text, null, null, item.timestamp);
844
+ addChatMessage(
845
+ item.role === 'assistant' ? 'assistant' : 'user',
846
+ item.text,
847
+ null,
848
+ null,
849
+ item.timestamp,
850
+ item.role === 'assistant' ? { model: 'daedalus' } : null
851
+ );
713
852
  });
714
853
  }
715
854
  }
@@ -748,8 +887,12 @@ function connectSSE() {
748
887
  // already added locally or by another client
749
888
  } else {
750
889
  removeThinkingSpinner();
890
+ currentTurnTokenCount += (data.text || '').split(/\s+/).filter(Boolean).length;
751
891
  if (!activeAssistantBody) {
752
892
  activeAssistantBody = addChatMessage('assistant', data.text || '', 'STREAM', null, data.timestamp);
893
+ if (activeAssistantBody) {
894
+ activeAssistantMsgEl = activeAssistantBody.closest('.chat-msg');
895
+ }
753
896
  } else if (data.text) {
754
897
  const raw = (activeAssistantBody.dataset.raw || '') + data.text;
755
898
  activeAssistantBody.dataset.raw = raw;
@@ -781,7 +924,14 @@ function connectSSE() {
781
924
  if (activeAssistantBody && activeAssistantBody.dataset.raw) {
782
925
  activeAssistantBody.innerHTML = renderMarkdown(activeAssistantBody.dataset.raw);
783
926
  }
927
+ if (activeAssistantMsgEl) {
928
+ const duration = data.durationMs || (turnStartTime ? Date.now() - turnStartTime : 0);
929
+ const activeModelName = data.model || (modelLabel ? modelLabel.textContent.replace('MODEL: ', '').toLowerCase() : 'auto');
930
+ attachCliFooter(activeAssistantMsgEl, activeModelName, currentTurnExecutedTools.length, duration, currentTurnTokenCount);
931
+ }
784
932
  activeAssistantBody = null;
933
+ activeAssistantMsgEl = null;
934
+ currentToolTreeEl = null;
785
935
  if (chatStatusBadge) chatStatusBadge.textContent = 'READY';
786
936
  addLog('Chat completion finished.');
787
937
  return;
@@ -1067,8 +1217,23 @@ if (modelModal) {
1067
1217
  });
1068
1218
  }
1069
1219
 
1220
+ async function loadUserProfile() {
1221
+ try {
1222
+ const res = await fetch('/api/profile');
1223
+ if (!res.ok) return;
1224
+ const data = await res.json();
1225
+ if (data.name && data.name.trim()) {
1226
+ userProfileName = data.name.trim().toUpperCase();
1227
+ document.querySelectorAll('.chat-msg.user .sender').forEach(el => {
1228
+ el.textContent = userProfileName;
1229
+ });
1230
+ }
1231
+ } catch {}
1232
+ }
1233
+
1070
1234
  connectSSE();
1071
1235
  loadModels();
1236
+ loadUserProfile();
1072
1237
 
1073
1238
  setInterval(() => {
1074
1239
  if (lastUpdateEl) {