msg redesign wip

frdel committed Jul 1, 2025 at 16:38 UTC e15b959eee71275a8727bb8ea3c44963f33a6493
8 files changed +811 -619
prompts/default/fw.msg_truncated.md
+3 -1
@@ -1 +1,3 @@
1 -<< {{length}} CHARACTERS REMOVED TO SAVE SPACE >>
\ No newline at end of file
1 +<<
2 +{{length}} CHARACTERS REMOVED TO SAVE SPACE
3 +>>
\ No newline at end of file
python/helpers/strings.py
+11 -1
@@ -119,4 +119,14 @@ def dict_to_text(d: dict) -> str:
119 parts.append(f"{value}")
120 parts.append("") # Add empty line between entries
121
122 - return "\n".join(parts).rstrip() # rstrip to remove trailing newline
\ No newline at end of file
122 + return "\n".join(parts).rstrip() # rstrip to remove trailing newline
123 +
124 +def truncate_text(text: str, length: int, at_end: bool = True, replacement: str = "...") -> str:
125 + orig_length = len(text)
126 + if orig_length <= length:
127 + return text
128 + if at_end:
129 + return text[:length] + replacement
130 + else:
131 + return replacement + text[-length:]
132 +
\ No newline at end of file
python/tools/code_execution_tool.py
+52 -10
@@ -8,7 +8,8 @@ from python.helpers.print_style import PrintStyle
8 from python.helpers.shell_local import LocalInteractiveSession
9 from python.helpers.shell_ssh import SSHInteractiveSession
10 from python.helpers.docker import DockerContainerManager
11 -from python.helpers.messages import truncate_text
11 +from python.helpers.strings import truncate_text as truncate_text_string
12 +from python.helpers.messages import truncate_text as truncate_text_agent
13 import re
14
15
@@ -63,11 +64,17 @@ class CodeExecution(Tool):
64 def get_log_object(self):
65 return self.agent.context.log.log(
66 type="code_exe",
66 - heading=f"{self.agent.agent_name}: Using tool '{self.name}'",
67 + heading=self.get_heading(),
68 content="",
69 kvps=self.args,
70 )
71
72 + def get_heading(self, text: str = ""):
73 + if not text:
74 + text = f"{self.name} - {self.args['runtime']}"
75 + text = truncate_text_string(text, 60)
76 + return f"icon://terminal {text}"
77 +
78 async def after_execution(self, response, **kwargs):
79 self.agent.hist_add_tool_result(self.name, response.message)
80
@@ -229,10 +236,9 @@ class CodeExecution(Tool):
236 if partial_output:
237 PrintStyle(font_color="#85C1E9").stream(partial_output)
238 # full_output += partial_output # Append new output
232 - truncated_output = truncate_text(
233 - agent=self.agent, output=full_output, threshold=10000
234 - )
235 - self.log.update(content=truncated_output)
239 + truncated_output = self.fix_full_output(full_output)
240 + heading = self.get_heading_from_output(truncated_output, 0)
241 + self.log.update(content=truncated_output, heading=heading)
242 last_output_time = now
243 got_output = True
244
@@ -240,12 +246,18 @@ class CodeExecution(Tool):
246 last_lines = (
247 truncated_output.splitlines()[-3:] if truncated_output else []
248 )
243 - for line in last_lines:
249 + last_lines.reverse()
250 + for idx, line in enumerate(last_lines):
251 for pat in prompt_patterns:
252 if pat.search(line.strip()):
253 PrintStyle.info(
254 "Detected shell prompt, returning output early."
255 )
256 + last_lines.reverse()
257 + heading = self.get_heading_from_output(
258 + "\n".join(last_lines), idx + 1, True
259 + )
260 + self.log.update(heading=heading)
261 return truncated_output
262
263 # Check for max execution time
@@ -257,7 +269,8 @@ class CodeExecution(Tool):
269 if truncated_output:
270 response = truncated_output + "\n\n" + response
271 PrintStyle.warning(sysinfo)
260 - self.log.update(content=response)
272 + heading = self.get_heading_from_output(truncated_output, 0)
273 + self.log.update(content=response, heading=heading)
274 return response
275
276 # Waiting for first output
@@ -280,7 +293,8 @@ class CodeExecution(Tool):
293 if truncated_output:
294 response = truncated_output + "\n\n" + response
295 PrintStyle.warning(sysinfo)
283 - self.log.update(content=response)
296 + heading = self.get_heading_from_output(truncated_output, 0)
297 + self.log.update(content=response, heading=heading)
298 return response
299
300 # potential dialog detection
@@ -305,7 +319,10 @@ class CodeExecution(Tool):
319 if truncated_output:
320 response = truncated_output + "\n\n" + response
321 PrintStyle.warning(sysinfo)
308 - self.log.update(content=response)
322 + heading = self.get_heading_from_output(
323 + truncated_output, 0
324 + )
325 + self.log.update(content=response, heading=heading)
326 return response
327
328 async def reset_terminal(self, session=0, reason: str | None = None):
@@ -326,3 +343,28 @@ class CodeExecution(Tool):
343 )
344 self.log.update(content=response)
345 return response
346 +
347 + def get_heading_from_output(self, output: str, skip_lines=0, done=False):
348 + done_icon = " icon://done_all" if done else ""
349 +
350 + if not output:
351 + return self.get_heading() + done_icon
352 +
353 + # find last non-empty line with skip
354 + lines = output.splitlines()
355 + # Start from len(lines) - skip_lines - 1 down to 0
356 + for i in range(len(lines) - skip_lines - 1, -1, -1):
357 + line = lines[i].strip()
358 + if not line:
359 + continue
360 + return self.get_heading(line) + done_icon
361 +
362 + return self.get_heading() + done_icon
363 +
364 + def fix_full_output(self, output: str):
365 + # remove any single byte \xXX escapes
366 + output = re.sub(r'(?<!\\)\\x[0-9A-Fa-f]{2}', '', output)
367 + # Strip every line of output before truncation
368 + output = "\n".join(line.strip() for line in output.splitlines())
369 + output = truncate_text_agent(agent=self.agent, output=output, threshold=10000)
370 + return output
webui/css/messages.css new
+656
@@ -0,0 +1,656 @@
1 +/* Chat History */
2 +#chat-history {
3 + display: -webkit-flex;
4 + display: flex;
5 + flex-direction: column;
6 + flex-grow: 1;
7 + width: 100%;
8 + overflow-y: scroll;
9 + overflow-x: hidden;
10 + scroll-behavior: auto !important; /* avoid infinite scrolling! */
11 + padding: var(--spacing-md) var(--spacing-sm) 0;
12 + -webkit-transition: all 0.3s ease;
13 + transition: all 0.3s ease;
14 + scrollbar-width: thin;
15 + scrollbar-color: #555 transparent;
16 + }
17 +
18 + #chat-history > *:first-child {
19 + margin-top: 4.4em;
20 + }
21 +
22 + /* Scrollbar styling for Firefox */
23 + #chat-history::-webkit-scrollbar {
24 + width: 5px;
25 + }
26 +
27 + #chat-history::-webkit-scrollbar-track {
28 + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
29 + border-radius: 3px;
30 + }
31 +
32 + #chat-history::-webkit-scrollbar-thumb {
33 + border-radius: 3px;
34 + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
35 + background-color: #555;
36 + -webkit-transition: background-color var(--transition-speed) ease-in-out;
37 + transition: background-color var(--transition-speed) ease-in-out;
38 + }
39 +
40 + #chat-history::-webkit-scrollbar-thumb:hover {
41 + background-color: #666;
42 + }
43 +
44 + #chat-history::-webkit-scrollbar-thumb:active {
45 + background-color: #888;
46 + }
47 +
48 +
49 +/* Message Styles */
50 +
51 +
52 + .user-container {
53 + align-self: flex-end;
54 + /* margin: var(--spacing-sm) var(--spacing-md); */
55 + /* margin-bottom: var(--spacing-lg); */
56 + /* margin-top: var(--spacing-sm); */
57 + display: flex;
58 + justify-content: flex-end;
59 + width: 100%;
60 + }
61 +
62 + .ai-container {
63 + align-self: flex-start;
64 + }
65 +
66 + .center-container {
67 + align-self: center;
68 + max-width: 80%;
69 + margin: 0;
70 + }
71 +
72 + .center-container .message {
73 + margin-bottom: var(--spacing-sm);
74 + }
75 +
76 + .message-user {
77 + background-color: #4a4a4a;
78 + /* border-bottom-right-radius: var(--spacing-xxs); */
79 + min-width: 195px;
80 + text-align: end;
81 + }
82 +
83 + .message-user > div {
84 + padding-top: var(--spacing-xs);
85 + font-family: "Roboto Mono", monospace;
86 + font-optical-sizing: auto;
87 + -webkit-font-optical-sizing: auto;
88 + font-size: var(--font-size-small);
89 + }
90 +
91 + .message-ai {
92 + /* border-bottom-left-radius: var(--spacing-xxs); */
93 + }
94 +
95 + .message-center {
96 + align-self: center;
97 + /* border-bottom-left-radius: unset; */
98 + }
99 +
100 + .message-followup {
101 + /* margin-left: var(--spacing-lg); */
102 + /* margin-bottom: var(--spacing-lg); */
103 + }
104 +
105 + .message-followup .message {
106 + border-radius: 1.125em; /* 18px */
107 + /* border-top-left-radius: var(--spacing-xxs); */
108 + }
109 +
110 + .message-followup + .message-followup {
111 + margin-bottom: 0;
112 + }
113 +
114 + /* Update message types for dark mode */
115 + .message-default,
116 + .message-agent,
117 + .message-agent-response,
118 + .message-agent-delegation,
119 + .message-tool,
120 + .message-code-exe,
121 + .message-browser,
122 + .message-info,
123 + .message-util,
124 + .message-warning,
125 + .message-error {
126 + color: #e0e0e0;
127 + }
128 +
129 + .message-default {
130 + background-color: #1a242f;
131 + }
132 +
133 + .message-agent {
134 + background-color: #34506b;
135 + }
136 +
137 + .message-agent-response {
138 + min-width: 255px;
139 + background-color: #1f3c1e;
140 + }
141 +
142 + .message-agent-delegation {
143 + background-color: #12685e;
144 + }
145 +
146 + .message-tool {
147 + background-color: #2a4170;
148 + }
149 +
150 + .message-code-exe {
151 + background-color: #4b3a69;
152 + }
153 +
154 + .message-code-exe .message-body {
155 + min-height: 5em;
156 + width: 100%;
157 + background-color: var(--color-panel);
158 + border-radius: 0.5em;
159 + margin-top: 0.5em;
160 + padding: 0.3em;
161 + }
162 +
163 + .light-mode .message-code-exe .message-body {
164 + border: 1px solid var(--color-border);
165 + }
166 +
167 + .message-browser {
168 + background-color: #4b3a69;
169 + }
170 +
171 + .message-info {
172 + background-color: var(--color-panel);
173 + }
174 +
175 + .message-util {
176 + background-color: #23211a;
177 + display: none;
178 + }
179 +
180 + .message-warning {
181 + background-color: #bc8036;
182 + }
183 +
184 + .message-error {
185 + background-color: #af2222;
186 + }
187 +
188 + /* Agent and AI Info */
189 + .agent-start {
190 + color: var(--color-text);
191 + font-size: var(--font-size-small);
192 + margin-bottom: var(--spacing-xs);
193 + opacity: 0.7;
194 + }
195 +
196 + .msg-kvps {
197 + font-size: 0.9em;
198 + margin: 0.5rem 0 0.55rem 0;
199 + border-collapse: collapse;
200 + width: 100%;
201 + }
202 +
203 + .kvps-val pre {
204 + white-space: pre-wrap; /* keep \n, collapse no spaces, allow wrapping */
205 + word-break: break-word; /* optional – forces really long “words” to break */
206 + }
207 +
208 + .msg-kvps th,
209 + .msg-kvps td {
210 + align-content: center;
211 + padding: 0.25rem;
212 + padding-left: 0;
213 + text-align: left;
214 + }
215 +
216 + .msg-kvps th {
217 + color: var(--color-primary);
218 + width: 40%;
219 + }
220 +
221 + .msg-kvps tr {
222 + border-bottom: 1px solid rgba(255, 255, 255, 0.15);
223 + }
224 +
225 + .msg-heading {
226 + margin: 0;
227 + position: relative;
228 + display: block;
229 + white-space: nowrap;
230 + }
231 +
232 + .msg-heading h4 {
233 + margin: 0;
234 + /* width: calc(100% - 4em); */
235 + margin-right: 4em;
236 + overflow-y: hidden;
237 + text-overflow: ellipsis;
238 + }
239 +
240 + /* Message Actions */
241 + .message-actions {
242 + color: var(--color-text);
243 + font-size: var(--font-size-small);
244 + margin-top: var(--spacing-xs);
245 + }
246 +
247 + .message-action {
248 + cursor: pointer;
249 + opacity: 0.7;
250 + -webkit-transition: opacity var(--transition-speed) ease-in-out;
251 + transition: opacity var(--transition-speed) ease-in-out;
252 + }
253 +
254 + .message-action:hover {
255 + opacity: 1;
256 + }
257 +
258 +
259 +/* Copy button styles */
260 +.copy-button {
261 + position: absolute;
262 + right: 0;
263 + top: var(--spacing-sm);
264 + background: none;
265 + border: none;
266 + padding: var(--spacing-xs) var(--spacing-sm);
267 + padding-right: 0;
268 + cursor: pointer;
269 + text-decoration: underline;
270 + text-wrap: nowrap;
271 + opacity: 0;
272 + -webkit-transition: opacity var(--transition-speed) ease-in-out;
273 + transition: opacity var(--transition-speed) ease-in-out;
274 + color: inherit;
275 + font-size: 12px;
276 + font-family: "Rubik", Arial, Helvetica, sans-serif;
277 + }
278 +
279 + .copy-button:hover {
280 + opacity: 0.8 !important;
281 + }
282 +
283 + .msg-content:hover .copy-button,
284 + .kvps-row:hover .copy-button,
285 + .message-text:hover .copy-button {
286 + opacity: 0.6;
287 + }
288 +
289 + .copy-button.copied {
290 + font-family: "Rubik", Arial, Helvetica, sans-serif !important;
291 + opacity: 1 !important;
292 + }
293 +
294 + .msg-thoughts .copy-button {
295 + top: -12px !important;
296 + }
297 +
298 + .message-user .copy-button {
299 + top: -15px !important;
300 + left: -13px !important;
301 + right: 99% !important;
302 + }
303 +
304 + .message-agent-response .copy-button {
305 + top: -22px !important;
306 + right: 0 !important;
307 + padding-right: 0px !important;
308 + }
309 +
310 + .message-info .copy-button {
311 + top: -22px !important;
312 + right: 0 !important;
313 + padding-right: 0px !important;
314 + }
315 +
316 + .message-tool .copy-button {
317 + top: -12px !important;
318 + right: 0 !important;
319 + }
320 +
321 + .msg-output .copy-button {
322 + top: -6px !important;
323 + right: 0 !important;
324 + }
325 +
326 + /* Make message containers relative for absolute positioning of copy buttons */
327 + .msg-content,
328 + .kvps-row,
329 + .message-text {
330 + position: relative;
331 + }
332 +
333 + .message-text pre {
334 + margin: 0;
335 + }
336 +
337 +/* Utility Classes */
338 +.kvps-key {
339 + font-weight: 500;
340 + font-size: var(--font-size-small);
341 + }
342 +
343 + .kvps-val {
344 + margin: 0.65rem 0 0.65rem 0.4rem;
345 + white-space: pre-wrap;
346 + }
347 +
348 + .kvps-img {
349 + width: 8em;
350 + height: 8em;
351 + object-fit: cover;
352 + object-position: top left;
353 + border-radius: 10%;
354 + border: 1px solid rgba(255, 255, 255, 0.15);
355 + }
356 +
357 + .image-viewer-img {
358 + width: 100%;
359 + }
360 +
361 + .msg-json {
362 + display: none;
363 + }
364 +
365 + .msg-thoughts {
366 + display: auto;
367 + }
368 +
369 + .msg-thoughts .kvps-val {
370 + max-height: 20em;
371 + overflow: auto;
372 + }
373 +
374 + .msg-content {
375 + margin-bottom: 0;
376 + }
377 +
378 + .message-temp {
379 + display: none;
380 + }
381 +
382 + .message-temp:not([style*="display: none"]):last-of-type {
383 + display: block; /* or any style you want for visibility */
384 + }
385 +
386 +/* Math (KaTeX) */
387 +.katex {
388 + line-height: 1.2 !important;
389 + font-size: 1.1em;
390 + }
391 +
392 +/* Media Queries */
393 +@media (max-width: 640px) {
394 + /* New styles for mobile messages */
395 +
396 + .message-followup {
397 + /* margin-left: var(--spacing-md); */
398 + margin-bottom: var(--spacing-md);
399 + }
400 +
401 + .msg-kvps {
402 + display: flex;
403 + flex-direction: column;
404 + border-collapse: separate;
405 + border-spacing: 0 0.5rem;
406 + }
407 +
408 + .msg-kvps tr {
409 + display: flex;
410 + flex-direction: column;
411 + margin-top: 0.3rem;
412 + padding-bottom: 0;
413 + }
414 +
415 + .msg-kvps th,
416 + .msg-kvps td {
417 + display: block;
418 + width: 100%;
419 + text-align: left;
420 + border-bottom: none;
421 + padding: 0.25rem 0;
422 + padding-left: 0 !important;
423 + }
424 +
425 + .msg-kvps th {
426 + color: var(--color-primary);
427 + margin-bottom: 0.25rem;
428 + }
429 +
430 + .kvps-val {
431 + margin: 0 0 0.4rem 0;
432 + white-space: pre-wrap;
433 + word-break: break-word;
434 + overflow-wrap: anywhere;
435 + }
436 + }
437 +
438 +.light-mode .msg-kvps tr {
439 + border-bottom: 1px solid rgb(192 192 192 / 50%);
440 + }
441 +
442 + .light-mode .message-default {
443 + background-color: #f3f3f3;
444 + color: #1a242f;
445 + }
446 +
447 + .light-mode .message-agent {
448 + background-color: #f3f3f3;
449 + color: #356ca3;
450 + }
451 +
452 + .light-mode .message-agent-response {
453 + background-color: #f3f3f3;
454 + color: #188216;
455 + }
456 +
457 + .light-mode .message-agent-delegation {
458 + background-color: #f3f3f3;
459 + color: #12685e;
460 + }
461 +
462 + .light-mode .message-tool {
463 + background-color: #f3f3f3;
464 + color: #1c3c88;
465 + }
466 +
467 + .light-mode .message-code-exe {
468 + background-color: #f3f3f3;
469 + color: #6c43b0;
470 + }
471 +
472 + .light-mode .message-browser {
473 + background-color: #ffffff;
474 + color: #6c43b0;
475 + }
476 +
477 + .light-mode .message-info {
478 + background-color: #f3f3f3;
479 + color: #3f3f3f;
480 + }
481 +
482 + .light-mode .message-util {
483 + background-color: #f3f3f3;
484 + color: #5b5540;
485 + }
486 +
487 + .light-mode .message-warning {
488 + background-color: #f3f3f3;
489 + color: #8f4800;
490 + }
491 +
492 + .light-mode .message-error {
493 + background-color: #f3f3f3;
494 + color: #8f1010;
495 + }
496 +
497 + .light-mode .message-user {
498 + background-color: #f3f3f3;
499 + color: #4e4e4e;
500 + }
501 +
502 +/* Markdown in messages */
503 +.msg-content {
504 + font-size: var(--font-size-small);
505 + }
506 +
507 + .message-agent-response .msg-content {
508 + font-size: var(--font-size-smaller);
509 + }
510 +
511 + .msg-content h1 {
512 + font-size: 1.25em;
513 + font-weight: 800;
514 + margin-bottom: 0.2em;
515 + }
516 +
517 + .msg-content h2 {
518 + font-size: 1.2em;
519 + font-weight: 700;
520 + margin-bottom: 0.2em;
521 + }
522 +
523 + .msg-content h3 {
524 + font-size: 1.15em;
525 + font-weight: 600;
526 + margin-bottom: 0.2em;
527 + }
528 +
529 + .msg-content h4 {
530 + font-size: 1.1em;
531 + font-weight: 500;
532 + margin-bottom: 0.2em;
533 + }
534 +
535 + .msg-content h5 {
536 + font-size: 1.05em;
537 + font-weight: 500;
538 + margin-bottom: 0.2em;
539 + }
540 +
541 + .msg-content h6 {
542 + font-size: 1em;
543 + font-weight: 500;
544 + margin-bottom: 0.2em;
545 + }
546 +
547 + .msg-content table {
548 + width: 100%;
549 + border-collapse: collapse;
550 + margin: 1em 0;
551 + font-size: 0.98em;
552 + background: transparent;
553 + border-radius: 8px;
554 + overflow: hidden;
555 + }
556 +
557 + .msg-content th,
558 + .msg-content td {
559 + padding: 0.55em 1em;
560 + border: 1px solid rgba(142, 142, 142, 0.1);
561 + text-align: left;
562 + background: transparent;
563 + }
564 +
565 + .msg-content th {
566 + font-weight: 600;
567 + background: rgba(142, 142, 142, 0.1);
568 + }
569 +
570 + .msg-content tr:nth-child(even) {
571 + background: rgba(142, 142, 142, 0.1);
572 + }
573 +
574 + .msg-content tr:nth-child(odd) {
575 + background: transparent;
576 + }
577 +
578 + .msg-content table {
579 + box-shadow: none;
580 + }
581 +
582 + .msg-content pre:has(code) {
583 + padding: 0.5em;
584 + border: 1px solid rgba(142, 142, 142, 0.1);
585 + border-radius: 0.3em;
586 + }
587 +
588 + .msg-min-max-btns {
589 + opacity: 40%;
590 + position: absolute;
591 + top: -0.2em;
592 + right: -0.2em;
593 + display: flex;
594 + gap: 0.3em;
595 + z-index: 1;
596 + }
597 +
598 +.message-group {
599 + margin-top: 1em;
600 + margin-bottom: 1em;
601 + display: inline-grid;
602 + grid-template-columns: minmax(0, max-content);
603 + grid-auto-rows: auto;
604 + max-width: 100%;
605 + width: fit-content;
606 + }
607 +
608 + .message-group > * {
609 + grid-column: 1;
610 + } /* both children sit in the same column */
611 +
612 + .message-group-right {
613 + justify-content: end;
614 + }
615 +
616 + .message-group-mid {
617 + justify-content: center;
618 + }
619 +
620 + /* 1. FIRST child’s .message – clear ONLY bottom corners */
621 + .message-group > *:first-child:not(:last-child) > .message {
622 + border-bottom-left-radius: var(--spacing-xxs);;
623 + border-bottom-right-radius: var(--spacing-xxs);;
624 + }
625 +
626 + /* 2. MIDDLE children’s .message – clear ALL corners */
627 + .message-group > *:not(:first-child):not(:last-child) > .message {
628 + border-radius: var(--spacing-xxs);
629 + }
630 +
631 + /* 3. LAST child’s .message – clear ONLY top corners */
632 + .message-group > *:last-child:not(:first-child) > .message {
633 + border-top-left-radius: var(--spacing-xxs);;
634 + border-top-right-radius: var(--spacing-xxs);;
635 + }
636 +
637 + .message-container {
638 + animation: fadeIn 0.5s;
639 + -webkit-animation: fadeIn 0.5s;
640 + margin-bottom: var(--spacing-sm);
641 + width: 100%;
642 + max-width: 100%;
643 + }
644 +
645 + .message {
646 + /* background-color: var(--color-message-bg); */
647 + border-radius: var(--border-radius);
648 + padding: 0.9rem var(--spacing-sm) 0.7rem var(--spacing-sm);
649 + overflow-x: auto;
650 + width: auto;
651 + max-width: 100%;
652 + box-sizing: border-box;
653 + display: block;
654 + word-break: break-word;
655 + overflow-wrap: anywhere;
656 + }
\ No newline at end of file
webui/index.css
+8 -576
@@ -48,6 +48,7 @@
48 --color-input-focus: var(--color-input-focus-dark);
49
50 /* Spacing variables */
51 + --spacing-xxs: 0.15rem;
52 --spacing-xs: 0.3125rem;
53 --spacing-sm: 0.625rem;
54 --spacing-md: 1.25rem;
@@ -90,6 +91,10 @@ body {
91 -webkit-overscroll-behavior: none;
92 }
93
94 +p:last-child {
95 + margin-bottom: 0;
96 +}
97 +
98 body,
99 #left-panel,
100 #chat-input,
@@ -441,51 +446,6 @@ pre {
446 font-size: 0.75rem;
447 }
448
444 -/* Chat History */
445 -#chat-history {
446 - display: -webkit-flex;
447 - display: flex;
448 - flex-direction: column;
449 - flex-grow: 1;
450 - overflow-y: scroll;
451 - overflow-x: hidden;
452 - scroll-behavior: auto !important; /* avoid infinite scrolling! */
453 - padding: var(--spacing-md) var(--spacing-md) 0;
454 - -webkit-transition: all 0.3s ease;
455 - transition: all 0.3s ease;
456 - scrollbar-width: thin;
457 - scrollbar-color: #555 transparent;
458 -}
459 -
460 -#chat-history > *:first-child {
461 - margin-top: 4.4em;
462 -}
463 -
464 -/* Scrollbar styling for Firefox */
465 -#chat-history::-webkit-scrollbar {
466 - width: 5px;
467 -}
468 -
469 -#chat-history::-webkit-scrollbar-track {
470 - box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
471 - border-radius: 3px;
472 -}
473 -
474 -#chat-history::-webkit-scrollbar-thumb {
475 - border-radius: 3px;
476 - box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
477 - background-color: #555;
478 - -webkit-transition: background-color var(--transition-speed) ease-in-out;
479 - transition: background-color var(--transition-speed) ease-in-out;
480 -}
481 -
482 -#chat-history::-webkit-scrollbar-thumb:hover {
483 - background-color: #666;
484 -}
485 -
486 -#chat-history::-webkit-scrollbar-thumb:active {
487 - background-color: #888;
488 -}
449
450 /* Logo Container */
451 #logo-container {
@@ -569,220 +529,7 @@ pre {
529 margin-left: 4.6rem;
530 }
531
572 -/* Message Styles */
573 -.message-container {
574 - animation: fadeIn 0.5s;
575 - -webkit-animation: fadeIn 0.5s;
576 - margin-bottom: var(--spacing-sm);
577 - width: 100%;
578 - max-width: 100%;
579 -}
580 -
581 -.message {
582 - background-color: var(--color-message-bg);
583 - border-radius: var(--border-radius);
584 - padding: 0.9rem var(--spacing-md) 0.7rem var(--spacing-md);
585 - overflow-x: auto;
586 - width: fit-content;
587 - max-width: 100%;
588 - box-sizing: border-box;
589 - display: block;
590 -}
591 -
592 -.user-container {
593 - align-self: flex-end;
594 - margin: var(--spacing-sm) var(--spacing-md);
595 - display: flex;
596 - justify-content: flex-end;
597 - width: 100%;
598 -}
599 -
600 -.ai-container {
601 - align-self: flex-start;
602 -}
603 -
604 -.center-container {
605 - align-self: center;
606 - max-width: 80%;
607 - margin: 0;
608 -}
609 -
610 -.center-container .message {
611 - margin-bottom: var(--spacing-sm);
612 -}
613 -
614 -.message-user {
615 - background-color: #4a4a4a;
616 - border-bottom-right-radius: var(--spacing-xs);
617 - min-width: 195px;
618 - text-align: end;
619 -}
620 -
621 -.message-user > div {
622 - padding-top: var(--spacing-xs);
623 - font-family: "Roboto Mono", monospace;
624 - font-optical-sizing: auto;
625 - -webkit-font-optical-sizing: auto;
626 - font-size: var(--font-size-small);
627 -}
628 -
629 -.message-ai {
630 - border-bottom-left-radius: var(--spacing-xs);
631 -}
632 -
633 -.message-center {
634 - align-self: center;
635 - border-bottom-left-radius: unset;
636 -}
637 -
638 -.message-followup {
639 - margin-left: var(--spacing-lg);
640 - margin-bottom: var(--spacing-lg);
641 -}
642 -
643 -.message-followup .message {
644 - border-radius: 1.125em; /* 18px */
645 - border-top-left-radius: 0.3125em; /* 5px */
646 -}
647 -
648 -.message-followup + .message-followup {
649 - margin-bottom: 0;
650 -}
651 -
652 -/* Update message types for dark mode */
653 -.message-default,
654 -.message-agent,
655 -.message-agent-response,
656 -.message-agent-delegation,
657 -.message-tool,
658 -.message-code-exe,
659 -.message-browser,
660 -.message-info,
661 -.message-util,
662 -.message-warning,
663 -.message-error {
664 - color: #e0e0e0;
665 -}
666 -
667 -.message-default {
668 - background-color: #1a242f;
669 -}
670 -
671 -.message-agent {
672 - background-color: #34506b;
673 -}
674 -
675 -.message-agent-response {
676 - min-width: 255px;
677 - background-color: #1f3c1e;
678 -}
679 -
680 -.message-agent-delegation {
681 - background-color: #12685e;
682 -}
683 -
684 -.message-tool {
685 - background-color: #2a4170;
686 -}
687 -
688 -.message-code-exe {
689 - background-color: #4b3a69;
690 -}
691 -
692 -.message-code-exe .message-body {
693 - min-height: 5em;
694 - background-color: var(--color-panel);
695 - border-radius: 0.5em;
696 - margin: 0.3em;
697 - padding: 0.3em;
698 -}
699 -
700 -.light-mode .message-code-exe .message-body {
701 - border: 1px solid var(--color-border);
702 -}
703 -
704 -.message-browser {
705 - background-color: #4b3a69;
706 -}
707 -
708 -.message-info {
709 - background-color: var(--color-panel);
710 -}
711 -
712 -.message-util {
713 - background-color: #23211a;
714 - display: none;
715 -}
716 -
717 -.message-warning {
718 - background-color: #bc8036;
719 -}
720 -
721 -.message-error {
722 - background-color: #af2222;
723 -}
724 -
725 -/* Agent and AI Info */
726 -.agent-start {
727 - color: var(--color-text);
728 - font-size: var(--font-size-small);
729 - margin-bottom: var(--spacing-xs);
730 - opacity: 0.7;
731 -}
732 -
733 -.msg-kvps {
734 - font-size: 0.9em;
735 - margin: 0.5rem 0 0.55rem 0;
736 - border-collapse: collapse;
737 - width: 100%;
738 -}
739 -
740 -.kvps-val pre {
741 - white-space: pre-wrap; /* keep \n, collapse no spaces, allow wrapping */
742 - word-break: break-word; /* optional – forces really long “words” to break */
743 -}
744 -
745 -.msg-kvps th,
746 -.msg-kvps td {
747 - align-content: center;
748 - padding: 0.25rem;
749 - padding-left: 0;
750 - text-align: left;
751 -}
532
753 -.msg-kvps th {
754 - color: var(--color-primary);
755 - width: 40%;
756 -}
757 -
758 -.msg-kvps tr {
759 - border-bottom: 1px solid rgba(255, 255, 255, 0.15);
760 -}
761 -
762 -.msg-heading {
763 - margin: 0;
764 - padding-right: 5.5em; /* space for buttons */
765 - position: relative;
766 - display: block;
767 -}
768 -
769 -/* Message Actions */
770 -.message-actions {
771 - color: var(--color-text);
772 - font-size: var(--font-size-small);
773 - margin-top: var(--spacing-xs);
774 -}
775 -
776 -.message-action {
777 - cursor: pointer;
778 - opacity: 0.7;
779 - -webkit-transition: opacity var(--transition-speed) ease-in-out;
780 - transition: opacity var(--transition-speed) ease-in-out;
781 -}
782 -
783 -.message-action:hover {
784 - opacity: 1;
785 -}
533
534 /* Input Section */
535 #input-section {
@@ -1486,128 +1233,9 @@ input:checked + .slider:before {
1233 /* Already defined above */
1234 }
1235
1489 -/* Copy button styles */
1490 -.copy-button {
1491 - position: absolute;
1492 - right: 0;
1493 - top: var(--spacing-sm);
1494 - background: none;
1495 - border: none;
1496 - padding: var(--spacing-xs) var(--spacing-sm);
1497 - padding-right: 0;
1498 - cursor: pointer;
1499 - text-decoration: underline;
1500 - text-wrap: nowrap;
1501 - opacity: 0;
1502 - -webkit-transition: opacity var(--transition-speed) ease-in-out;
1503 - transition: opacity var(--transition-speed) ease-in-out;
1504 - color: inherit;
1505 - font-size: 12px;
1506 - font-family: "Rubik", Arial, Helvetica, sans-serif;
1507 -}
1508 -
1509 -.copy-button:hover {
1510 - opacity: 0.8 !important;
1511 -}
1512 -
1513 -.msg-content:hover .copy-button,
1514 -.kvps-row:hover .copy-button,
1515 -.message-text:hover .copy-button {
1516 - opacity: 0.6;
1517 -}
1518 -
1519 -.copy-button.copied {
1520 - font-family: "Rubik", Arial, Helvetica, sans-serif !important;
1521 - opacity: 1 !important;
1522 -}
1523 -
1524 -.msg-thoughts .copy-button {
1525 - top: -12px !important;
1526 -}
1527 -
1528 -.message-user .copy-button {
1529 - top: -15px !important;
1530 - left: -13px !important;
1531 - right: 99% !important;
1532 -}
1533 -
1534 -.message-agent-response .copy-button {
1535 - top: -22px !important;
1536 - right: 0 !important;
1537 - padding-right: 0px !important;
1538 -}
1539 -
1540 -.message-info .copy-button {
1541 - top: -22px !important;
1542 - right: 0 !important;
1543 - padding-right: 0px !important;
1544 -}
1545 -
1546 -.message-tool .copy-button {
1547 - top: -12px !important;
1548 - right: 0 !important;
1549 -}
1550 -
1551 -.msg-output .copy-button {
1552 - top: -6px !important;
1553 - right: 0 !important;
1554 -}
1555 -
1556 -/* Make message containers relative for absolute positioning of copy buttons */
1557 -.msg-content,
1558 -.kvps-row,
1559 -.message-text {
1560 - position: relative;
1561 -}
1562 -
1563 -/* Utility Classes */
1564 -.kvps-key {
1565 - font-weight: 500;
1566 - font-size: var(--font-size-small);
1567 -}
1568 -
1569 -.kvps-val {
1570 - margin: 0.65rem 0 0.65rem 0.4rem;
1571 - white-space: pre-wrap;
1572 -}
1573 -
1574 -.kvps-img {
1575 - width: 8em;
1576 - height: 8em;
1577 - object-fit: cover;
1578 - object-position: top left;
1579 - border-radius: 10%;
1580 - border: 1px solid rgba(255, 255, 255, 0.15);
1581 -}
1582 -
1583 -.image-viewer-img {
1584 - width: 100%;
1585 -}
1586 -
1587 -.msg-json {
1588 - display: none;
1589 -}
1590 -
1591 -.msg-thoughts {
1592 - display: auto;
1593 -}
1594 -
1595 -.msg-thoughts .kvps-val {
1596 - max-height: 20em;
1597 - overflow: auto;
1598 -}
1236
1600 -.msg-content {
1601 - margin-bottom: 0;
1602 -}
1237
1604 -.message-temp {
1605 - display: none;
1606 -}
1238
1608 -.message-temp:not([style*="display: none"]):last-of-type {
1609 - display: block; /* or any style you want for visibility */
1610 -}
1239
1240 .status-icon {
1241 display: flex;
@@ -1643,11 +1271,7 @@ input:checked + .slider:before {
1271 font-weight: bold;
1272 }
1273
1646 -/* Math (KaTeX) */
1647 -.katex {
1648 - line-height: 1.2 !important;
1649 - font-size: 1.1em;
1650 -}
1274 +
1275
1276 /* Animations */
1277 @keyframes fadeIn {
@@ -1717,48 +1341,6 @@ input:checked + .slider:before {
1341 display: none;
1342 }
1343
1720 - /* New styles for mobile messages */
1721 -
1722 - .message-followup {
1723 - margin-left: var(--spacing-md);
1724 - margin-bottom: var(--spacing-md);
1725 - }
1726 -
1727 - .msg-kvps {
1728 - display: flex;
1729 - flex-direction: column;
1730 - border-collapse: separate;
1731 - border-spacing: 0 0.5rem;
1732 - }
1733 -
1734 - .msg-kvps tr {
1735 - display: flex;
1736 - flex-direction: column;
1737 - margin-top: 0.3rem;
1738 - padding-bottom: 0;
1739 - }
1740 -
1741 - .msg-kvps th,
1742 - .msg-kvps td {
1743 - display: block;
1744 - width: 100%;
1745 - text-align: left;
1746 - border-bottom: none;
1747 - padding: 0.25rem 0;
1748 - padding-left: 0 !important;
1749 - }
1750 -
1751 - .msg-kvps th {
1752 - color: var(--color-primary);
1753 - margin-bottom: 0.25rem;
1754 - }
1755 -
1756 - .kvps-val {
1757 - margin: 0 0 0.4rem 0;
1758 - white-space: pre-wrap;
1759 - word-break: break-word;
1760 - overflow-wrap: anywhere;
1761 - }
1344 }
1345
1346 @media (max-width: 640px) {
@@ -1962,69 +1544,7 @@ a:active {
1544 --color-input-focus: var(--color-input-focus-light);
1545 }
1546
1965 -.light-mode .msg-kvps tr {
1966 - border-bottom: 1px solid rgb(192 192 192 / 50%);
1967 -}
1968 -
1969 -.light-mode .message-default {
1970 - background-color: #f3f3f3;
1971 - color: #1a242f;
1972 -}
1973 -
1974 -.light-mode .message-agent {
1975 - background-color: #f3f3f3;
1976 - color: #356ca3;
1977 -}
1978 -
1979 -.light-mode .message-agent-response {
1980 - background-color: #f3f3f3;
1981 - color: #188216;
1982 -}
1983 -
1984 -.light-mode .message-agent-delegation {
1985 - background-color: #f3f3f3;
1986 - color: #12685e;
1987 -}
1547
1989 -.light-mode .message-tool {
1990 - background-color: #f3f3f3;
1991 - color: #1c3c88;
1992 -}
1993 -
1994 -.light-mode .message-code-exe {
1995 - background-color: #f3f3f3;
1996 - color: #6c43b0;
1997 -}
1998 -
1999 -.light-mode .message-browser {
2000 - background-color: #ffffff;
2001 - color: #6c43b0;
2002 -}
2003 -
2004 -.light-mode .message-info {
2005 - background-color: #f3f3f3;
2006 - color: #3f3f3f;
2007 -}
2008 -
2009 -.light-mode .message-util {
2010 - background-color: #f3f3f3;
2011 - color: #5b5540;
2012 -}
2013 -
2014 -.light-mode .message-warning {
2015 - background-color: #f3f3f3;
2016 - color: #8f4800;
2017 -}
2018 -
2019 -.light-mode .message-error {
2020 - background-color: #f3f3f3;
2021 - color: #8f1010;
2022 -}
2023 -
2024 -.light-mode .message-user {
2025 - background-color: #f3f3f3;
2026 - color: #4e4e4e;
2027 -}
1548
1549 .light-mode .connected {
1550 color: #4caf50;
@@ -3019,97 +2539,9 @@ nav ul li a img {
2539 text-align: center;
2540 }
2541
3022 -/* Markdown in messages */
3023 -.msg-content {
3024 - font-size: var(--font-size-small);
3025 -}
3026 -
3027 -.message-agent-response .msg-content {
3028 - font-size: var(--font-size-smaller);
3029 -}
3030 -
3031 -.msg-content h1 {
3032 - font-size: 1.25em;
3033 - font-weight: 800;
3034 - margin-bottom: 0.2em;
3035 -}
3036 -
3037 -.msg-content h2 {
3038 - font-size: 1.2em;
3039 - font-weight: 700;
3040 - margin-bottom: 0.2em;
3041 -}
3042 -
3043 -.msg-content h3 {
3044 - font-size: 1.15em;
3045 - font-weight: 600;
3046 - margin-bottom: 0.2em;
3047 -}
3048 -
3049 -.msg-content h4 {
3050 - font-size: 1.1em;
3051 - font-weight: 500;
3052 - margin-bottom: 0.2em;
3053 -}
3054 -
3055 -.msg-content h5 {
3056 - font-size: 1.05em;
3057 - font-weight: 500;
3058 - margin-bottom: 0.2em;
3059 -}
3060 -
3061 -.msg-content h6 {
3062 - font-size: 1em;
3063 - font-weight: 500;
3064 - margin-bottom: 0.2em;
3065 -}
2542
3067 -.msg-content table {
3068 - width: 100%;
3069 - border-collapse: collapse;
3070 - margin: 1em 0;
3071 - font-size: 0.98em;
3072 - background: transparent;
3073 - border-radius: 8px;
3074 - overflow: hidden;
3075 -}
2543
3077 -.msg-content th,
3078 -.msg-content td {
3079 - padding: 0.55em 1em;
3080 - border: 1px solid rgba(142, 142, 142, 0.1);
3081 - text-align: left;
3082 - background: transparent;
3083 -}
3084 -
3085 -.msg-content th {
3086 - font-weight: 600;
3087 - background: rgba(142, 142, 142, 0.1);
3088 -}
3089 -
3090 -.msg-content tr:nth-child(even) {
3091 - background: rgba(142, 142, 142, 0.1);
3092 -}
3093 -
3094 -.msg-content tr:nth-child(odd) {
3095 - background: transparent;
3096 -}
3097 -
3098 -.msg-content table {
3099 - box-shadow: none;
3100 -}
3101 -
3102 -.msg-content pre:has(code) {
3103 - padding: 0.5em;
3104 - border: 1px solid rgba(142, 142, 142, 0.1);
3105 - border-radius: 0.3em;
2544 +.icon {
2545 + vertical-align: middle;
2546 }
2547
3108 -.msg-min-max-btns {
3109 - position: absolute;
3110 - top: -0.2em;
3111 - right: -0.5em;
3112 - display: flex;
3113 - gap: 0.3em;
3114 - z-index: 1;
3115 -}
webui/index.html
+1
@@ -7,6 +7,7 @@
7 <title>Agent Zero</title>
8 <link rel="icon" type="image/svg+xml" href="public/favicon.svg">
9 <link rel="stylesheet" href="index.css">
10 + <link rel="stylesheet" href="css/messages.css">
11 <link rel="stylesheet" href="css/toast.css">
12 <link rel="stylesheet" href="css/settings.css">
13 <link rel="stylesheet" href="css/file_browser.css">
webui/index.js
+2 -27
@@ -235,34 +235,9 @@ updateUserTime();
235 setInterval(updateUserTime, 1000);
236
237 function setMessage(id, type, heading, content, temp, kvps = null) {
238 - // Search for the existing message container by id
239 - let messageContainer = document.getElementById(`message-${id}`);
240 -
241 - if (messageContainer) {
242 - // Don't re-render user messages
243 - if (type === "user") {
244 - return; // Skip re-rendering
245 - }
246 - // For other types, update the message
247 - messageContainer.innerHTML = "";
248 - } else {
249 - // Create a new container if not found
250 - const sender = type === "user" ? "user" : "ai";
251 - messageContainer = document.createElement("div");
252 - messageContainer.id = `message-${id}`;
253 - messageContainer.classList.add("message-container", `${sender}-container`);
254 - if (temp) messageContainer.classList.add("message-temp");
255 - }
256 -
257 - const handler = msgs.getHandler(type);
258 - handler(messageContainer, id, type, heading, content, temp, kvps);
259 -
260 - // If the container was found, it was already in the DOM, no need to append again
261 - if (!document.getElementById(`message-${id}`)) {
262 - chatHistory.appendChild(messageContainer);
263 - }
264 -
238 + const result = msgs.setMessage(id, type, heading, content, temp, kvps);
239 if (autoScroll) chatHistory.scrollTop = chatHistory.scrollHeight;
240 + return result;
241 }
242
243 window.loadKnowledge = async function () {
webui/js/messages.js
+78 -4
@@ -3,6 +3,71 @@ import { openImageModal } from "./image_modal.js";
3 import { marked } from "../vendor/marked/marked.esm.js";
4 import { store as messageResizeStore } from "/components/messages/resize/message-resize-store.js";
5
6 +const chatHistory = document.getElementById("chat-history");
7 +
8 +let messageGroup = null;
9 +
10 +export function setMessage(id, type, heading, content, temp, kvps = null) {
11 + // Search for the existing message container by id
12 + let messageContainer = document.getElementById(`message-${id}`);
13 +
14 + if (messageContainer) {
15 + // Don't re-render user messages
16 + if (type === "user") {
17 + return; // Skip re-rendering
18 + }
19 + // For other types, update the message
20 + messageContainer.innerHTML = "";
21 + } else {
22 + // Create a new container if not found
23 + const sender = type === "user" ? "user" : "ai";
24 + messageContainer = document.createElement("div");
25 + messageContainer.id = `message-${id}`;
26 + messageContainer.classList.add("message-container", `${sender}-container`);
27 + // if (temp) messageContainer.classList.add("message-temp");
28 + }
29 +
30 + const handler = getHandler(type);
31 + handler(messageContainer, id, type, heading, content, temp, kvps);
32 +
33 + // If the container was found, it was already in the DOM, no need to append again
34 + if (!document.getElementById(`message-${id}`)) {
35 + // message type visual grouping
36 + const groupTypeMap = {
37 + user: "right",
38 + info: "mid",
39 + warning: "mid",
40 + error: "mid",
41 + rate_limit: "mid",
42 + util: "mid",
43 + hint: "mid",
44 + // anything else is "left"
45 + };
46 +
47 + //force new group on these types
48 + const groupStart = {
49 + agent: true,
50 + // anything else is false
51 + };
52 +
53 + const groupType = groupTypeMap[type] || "left";
54 +
55 + if (
56 + !messageGroup || // no group yet exists
57 + groupStart[type] || // message type forces new group
58 + groupType != messageGroup.getAttribute("data-group-type") // message type changes group
59 + ) {
60 + messageGroup = document.createElement("div");
61 + messageGroup.id = `message-group-${id}`;
62 + messageGroup.classList.add(`message-group`, `message-group-${groupType}`);
63 + messageGroup.setAttribute("data-group-type", groupType);
64 + }
65 +
66 + messageGroup.appendChild(messageContainer);
67 + chatHistory.appendChild(messageGroup);
68 + }
69 +}
70 +
71 function createCopyButton() {
72 const button = document.createElement("button");
73 button.className = "copy-button";
@@ -94,9 +159,11 @@ export function _drawMessage(
159 messageDiv.classList.add("message", mainClass, ...messageClasses);
160
161 if (heading) {
97 - const headingElement = document.createElement("h4");
162 + const headingElement = document.createElement("div");
163 headingElement.classList.add("msg-heading");
99 - headingElement.textContent = heading;
164 + const headingH4 = document.createElement("h4");
165 + headingH4.innerHTML = convertIcons(escapeHTML(heading));
166 + headingElement.appendChild(headingH4);
167 messageDiv.appendChild(headingElement);
168
169 if (resizeBtns) {
@@ -301,8 +368,8 @@ export function drawMessageUser(
368 textDiv.classList.add("message-text");
369
370 // Create a span for the content
304 - const spanElement = document.createElement("span");
305 - spanElement.innerHTML = convertHTML(content);
371 + const spanElement = document.createElement("pre");
372 + spanElement.innerHTML = escapeHTML(content);
373 textDiv.appendChild(spanElement);
374
375 // Add click handler
@@ -699,6 +766,13 @@ function convertImgFilePaths(str) {
766 return str.replace("img://", "/image_get?path=");
767 }
768
769 +function convertIcons(str) {
770 + return str.replace(
771 + /icon:\/\/([a-zA-Z0-9_]+)/g,
772 + '<span class="icon material-symbols-outlined">$1</span>'
773 + );
774 +}
775 +
776 function escapeHTML(str) {
777 const escapeChars = {
778 "&": "&amp;",