blob: 8c1e9a30f982cc6ab53cd144d8a456378df7786d [file] [log] [blame]
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001import { css, html, LitElement, render } from "lit";
Sean McCullough86b56862025-04-18 13:04:03 -07002import { unsafeHTML } from "lit/directives/unsafe-html.js";
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00003import { customElement, property, state } from "lit/decorators.js";
Sean McCulloughd9f13372025-04-21 15:08:49 -07004import { AgentMessage } from "../types";
Sean McCullough8d93e362025-04-27 23:32:18 +00005import { marked, MarkedOptions, Renderer, Tokens } from "marked";
6import mermaid from "mermaid";
Sean McCullough86b56862025-04-18 13:04:03 -07007import "./sketch-tool-calls";
8@customElement("sketch-timeline-message")
9export class SketchTimelineMessage extends LitElement {
10 @property()
Sean McCulloughd9f13372025-04-21 15:08:49 -070011 message: AgentMessage;
Sean McCullough86b56862025-04-18 13:04:03 -070012
13 @property()
Sean McCulloughd9f13372025-04-21 15:08:49 -070014 previousMessage: AgentMessage;
Sean McCullough86b56862025-04-18 13:04:03 -070015
Sean McCullough2deac842025-04-21 18:17:57 -070016 @property()
17 open: boolean = false;
18
Philip Zeyligerb8a8f352025-06-02 07:39:37 -070019 @property()
20 firstMessageIndex: number = 0;
21
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000022 @state()
23 showInfo: boolean = false;
24
Sean McCullough86b56862025-04-18 13:04:03 -070025 // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
26 // Note that these styles only apply to the scope of this web component's
27 // shadow DOM node, so they won't leak out or collide with CSS declared in
28 // other components or the containing web page (...unless you want it to do that).
29 static styles = css`
30 .message {
31 position: relative;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000032 margin-bottom: 6px;
33 display: flex;
34 flex-direction: column;
35 width: 100%;
Sean McCullough86b56862025-04-18 13:04:03 -070036 }
37
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000038 .message-container {
39 display: flex;
40 position: relative;
41 width: 100%;
42 }
43
44 .message-metadata-left {
45 flex: 0 0 80px;
46 padding: 3px 5px;
47 text-align: right;
48 font-size: 11px;
49 color: #777;
50 align-self: flex-start;
51 }
52
53 .message-metadata-right {
54 flex: 0 0 80px;
55 padding: 3px 5px;
56 text-align: left;
57 font-size: 11px;
58 color: #777;
59 align-self: flex-start;
60 }
61
62 .message-bubble-container {
63 flex: 1;
64 display: flex;
65 max-width: calc(100% - 160px);
Philip Zeyligere31d2a92025-05-11 15:22:35 -070066 overflow: hidden;
67 text-overflow: ellipsis;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000068 }
69
70 .user .message-bubble-container {
71 justify-content: flex-end;
72 }
73
74 .agent .message-bubble-container,
75 .tool .message-bubble-container,
76 .error .message-bubble-container {
77 justify-content: flex-start;
Sean McCullough86b56862025-04-18 13:04:03 -070078 }
79
80 .message-content {
81 position: relative;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000082 padding: 6px 10px;
83 border-radius: 12px;
84 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
Philip Zeyligere31d2a92025-05-11 15:22:35 -070085 max-width: 100%;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000086 width: fit-content;
87 min-width: min-content;
Philip Zeyligere31d2a92025-05-11 15:22:35 -070088 overflow-wrap: break-word;
89 word-break: break-word;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000090 }
91
92 /* User message styling */
93 .user .message-content {
94 background-color: #2196f3;
95 color: white;
96 border-bottom-right-radius: 5px;
97 }
98
99 /* Agent message styling */
100 .agent .message-content,
101 .tool .message-content,
102 .error .message-content {
103 background-color: #f1f1f1;
104 color: black;
105 border-bottom-left-radius: 5px;
Sean McCullough86b56862025-04-18 13:04:03 -0700106 }
107
108 /* Copy button styles */
109 .message-text-container,
110 .tool-result-container {
111 position: relative;
112 }
113
114 .message-actions {
115 position: absolute;
116 top: 5px;
117 right: 5px;
118 z-index: 10;
119 opacity: 0;
120 transition: opacity 0.2s ease;
121 }
122
123 .message-text-container:hover .message-actions,
124 .tool-result-container:hover .message-actions {
125 opacity: 1;
126 }
127
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000128 .message-actions {
Sean McCullough86b56862025-04-18 13:04:03 -0700129 display: flex;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000130 gap: 6px;
131 }
132
133 .copy-icon,
134 .info-icon {
135 background-color: transparent;
136 border: none;
137 color: rgba(0, 0, 0, 0.6);
138 cursor: pointer;
139 padding: 3px;
140 border-radius: 50%;
141 display: flex;
142 align-items: center;
143 justify-content: center;
144 width: 24px;
145 height: 24px;
146 transition: all 0.15s ease;
147 }
148
149 .user .copy-icon,
150 .user .info-icon {
151 color: rgba(255, 255, 255, 0.8);
152 }
153
154 .copy-icon:hover,
155 .info-icon:hover {
156 background-color: rgba(0, 0, 0, 0.08);
157 }
158
159 .user .copy-icon:hover,
160 .user .info-icon:hover {
161 background-color: rgba(255, 255, 255, 0.15);
162 }
163
164 /* Message metadata styling */
165 .message-type {
166 font-weight: bold;
167 font-size: 11px;
Sean McCullough86b56862025-04-18 13:04:03 -0700168 }
169
170 .message-timestamp {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000171 display: block;
Sean McCullough86b56862025-04-18 13:04:03 -0700172 font-size: 10px;
173 color: #888;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000174 margin-top: 2px;
175 }
176
177 .message-duration {
178 display: block;
179 font-size: 10px;
180 color: #888;
181 margin-top: 2px;
Sean McCullough86b56862025-04-18 13:04:03 -0700182 }
183
184 .message-usage {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000185 display: block;
Sean McCullough86b56862025-04-18 13:04:03 -0700186 font-size: 10px;
187 color: #888;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000188 margin-top: 3px;
Sean McCullough86b56862025-04-18 13:04:03 -0700189 }
190
191 .conversation-id {
192 font-family: monospace;
193 font-size: 12px;
194 padding: 2px 4px;
Sean McCullough86b56862025-04-18 13:04:03 -0700195 margin-left: auto;
196 }
197
198 .parent-info {
199 font-size: 11px;
200 opacity: 0.8;
201 }
202
203 .subconversation {
204 border-left: 2px solid transparent;
205 padding-left: 5px;
206 margin-left: 20px;
207 transition: margin-left 0.3s ease;
208 }
209
210 .message-text {
211 overflow-x: auto;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000212 margin-bottom: 0;
213 font-family: sans-serif;
214 padding: 2px 0;
Sean McCullough86b56862025-04-18 13:04:03 -0700215 user-select: text;
216 cursor: text;
217 -webkit-user-select: text;
218 -moz-user-select: text;
219 -ms-user-select: text;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000220 font-size: 14px;
221 line-height: 1.35;
222 text-align: left;
223 }
224
225 /* Style for code blocks within messages */
226 .message-text pre,
227 .message-text code {
228 font-family: monospace;
229 background: rgba(0, 0, 0, 0.05);
230 border-radius: 4px;
231 padding: 2px 4px;
232 overflow-x: auto;
233 max-width: 100%;
234 white-space: pre-wrap; /* Allow wrapping for very long lines */
235 word-break: break-all; /* Break words at any character */
236 box-sizing: border-box; /* Include padding in width calculation */
237 }
238
Pokey Rulea10f1512025-05-15 13:53:26 +0000239 /* Code block container styles */
240 .code-block-container {
241 position: relative;
242 margin: 8px 0;
243 border-radius: 6px;
244 overflow: hidden;
245 background: rgba(0, 0, 0, 0.05);
246 }
247
248 .user .code-block-container {
249 background: rgba(255, 255, 255, 0.2);
250 }
251
252 .code-block-header {
253 display: flex;
254 justify-content: space-between;
255 align-items: center;
256 padding: 4px 8px;
257 background: rgba(0, 0, 0, 0.1);
258 font-size: 12px;
259 }
260
261 .user .code-block-header {
262 background: rgba(255, 255, 255, 0.2);
263 color: white;
264 }
265
266 .code-language {
267 font-family: monospace;
268 font-size: 11px;
269 font-weight: 500;
270 }
271
272 .code-copy-button {
273 background: transparent;
274 border: none;
275 color: inherit;
276 cursor: pointer;
277 padding: 2px;
278 border-radius: 3px;
279 display: flex;
280 align-items: center;
281 justify-content: center;
282 opacity: 0.7;
283 transition: all 0.15s ease;
284 }
285
286 .code-copy-button:hover {
287 opacity: 1;
288 background: rgba(0, 0, 0, 0.1);
289 }
290
291 .user .code-copy-button:hover {
292 background: rgba(255, 255, 255, 0.2);
293 }
294
295 .code-block-container pre {
296 margin: 0;
297 padding: 8px;
298 background: transparent;
299 }
300
301 .code-block-container code {
302 background: transparent;
303 padding: 0;
304 display: block;
305 width: 100%;
306 }
307
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000308 .user .message-text pre,
309 .user .message-text code {
310 background: rgba(255, 255, 255, 0.2);
311 color: white;
Sean McCullough86b56862025-04-18 13:04:03 -0700312 }
313
314 .tool-details {
315 margin-top: 3px;
316 padding-top: 3px;
317 border-top: 1px dashed #e0e0e0;
318 font-size: 12px;
319 }
320
321 .tool-name {
322 font-size: 12px;
323 font-weight: bold;
324 margin-bottom: 2px;
325 background: #f0f0f0;
326 padding: 2px 4px;
327 border-radius: 2px;
328 display: flex;
329 align-items: center;
330 gap: 3px;
331 }
332
333 .tool-input,
334 .tool-result {
335 margin-top: 2px;
336 padding: 3px 5px;
337 background: #f7f7f7;
338 border-radius: 2px;
339 font-family: monospace;
340 font-size: 12px;
341 overflow-x: auto;
342 white-space: pre;
343 line-height: 1.3;
344 user-select: text;
345 cursor: text;
346 -webkit-user-select: text;
347 -moz-user-select: text;
348 -ms-user-select: text;
349 }
350
351 .tool-result {
352 max-height: 300px;
353 overflow-y: auto;
354 }
355
356 .usage-info {
357 margin-top: 10px;
358 padding-top: 10px;
359 border-top: 1px dashed #e0e0e0;
360 font-size: 12px;
361 color: #666;
362 }
363
364 /* Custom styles for IRC-like experience */
365 .user .message-content {
366 border-left-color: #2196f3;
367 }
368
369 .agent .message-content {
370 border-left-color: #4caf50;
371 }
372
373 .tool .message-content {
374 border-left-color: #ff9800;
375 }
376
377 .error .message-content {
378 border-left-color: #f44336;
379 }
380
Philip Zeyligerb8a8f352025-06-02 07:39:37 -0700381 /* Compact message styling - distinct visual separation */
382 .compact {
383 background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
384 border: 2px solid #fd7e14;
385 border-radius: 12px;
386 margin: 20px 0;
387 padding: 0;
388 }
389
390 .compact .message-content {
391 border-left: 4px solid #fd7e14;
392 background: rgba(253, 126, 20, 0.05);
393 font-weight: 500;
394 }
395
396 .compact .message-text {
397 color: #8b4513;
398 font-size: 13px;
399 line-height: 1.4;
400 }
401
402 .compact::before {
403 content: "📚 CONVERSATION EPOCH";
404 display: block;
405 text-align: center;
406 font-size: 11px;
407 font-weight: bold;
408 color: #8b4513;
409 background: #fd7e14;
410 color: white;
411 padding: 4px 8px;
412 margin: 0;
413 border-radius: 8px 8px 0 0;
414 letter-spacing: 1px;
415 }
416
417 /* Pre-compaction messages get a subtle diagonal stripe background */
418 .pre-compaction {
419 background: repeating-linear-gradient(
420 45deg,
421 #ffffff,
422 #ffffff 10px,
423 #f8f8f8 10px,
424 #f8f8f8 20px
425 );
426 opacity: 0.85;
427 border-left: 3px solid #ddd;
428 }
429
430 .pre-compaction .message-content {
431 background: rgba(255, 255, 255, 0.7);
432 backdrop-filter: blur(1px);
433 }
434
Sean McCullough86b56862025-04-18 13:04:03 -0700435 /* Make message type display bold but without the IRC-style markers */
436 .message-type {
437 font-weight: bold;
438 }
439
440 /* Commit message styling */
Sean McCullough86b56862025-04-18 13:04:03 -0700441 .commits-container {
442 margin-top: 10px;
Sean McCullough86b56862025-04-18 13:04:03 -0700443 }
444
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000445 .commit-notification {
446 background-color: #e8f5e9;
447 color: #2e7d32;
448 font-weight: 500;
449 font-size: 12px;
450 padding: 6px 10px;
451 border-radius: 10px;
452 margin-bottom: 8px;
453 text-align: center;
454 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
Sean McCullough86b56862025-04-18 13:04:03 -0700455 }
456
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000457 .commit-card {
458 background-color: #f5f5f5;
459 border-radius: 8px;
Sean McCullough86b56862025-04-18 13:04:03 -0700460 overflow: hidden;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000461 margin-bottom: 6px;
462 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
463 padding: 6px 8px;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000464 display: flex;
465 align-items: center;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000466 gap: 8px;
Sean McCullough86b56862025-04-18 13:04:03 -0700467 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700468
Sean McCullough86b56862025-04-18 13:04:03 -0700469 .commit-hash {
470 color: #0366d6;
471 font-weight: bold;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000472 font-family: monospace;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000473 cursor: pointer;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000474 text-decoration: none;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000475 background-color: rgba(3, 102, 214, 0.08);
476 padding: 2px 5px;
477 border-radius: 4px;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000478 }
479
480 .commit-hash:hover {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000481 background-color: rgba(3, 102, 214, 0.15);
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000482 }
483
484 .commit-branch {
485 color: #28a745;
486 font-weight: 500;
487 cursor: pointer;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000488 font-family: monospace;
489 background-color: rgba(40, 167, 69, 0.08);
490 padding: 2px 5px;
491 border-radius: 4px;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000492 }
493
494 .commit-branch:hover {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000495 background-color: rgba(40, 167, 69, 0.15);
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000496 }
497
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000498 .commit-subject {
499 font-size: 13px;
500 color: #333;
501 flex-grow: 1;
502 overflow: hidden;
503 text-overflow: ellipsis;
504 white-space: nowrap;
Sean McCullough86b56862025-04-18 13:04:03 -0700505 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700506
Sean McCullough86b56862025-04-18 13:04:03 -0700507 .commit-diff-button {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000508 padding: 3px 8px;
509 border: none;
510 border-radius: 4px;
511 background-color: #0366d6;
512 color: white;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000513 font-size: 11px;
Sean McCullough86b56862025-04-18 13:04:03 -0700514 cursor: pointer;
515 transition: all 0.2s ease;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000516 display: block;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000517 margin-left: auto;
Sean McCullough86b56862025-04-18 13:04:03 -0700518 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700519
Sean McCullough86b56862025-04-18 13:04:03 -0700520 .commit-diff-button:hover {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000521 background-color: #0256b4;
Sean McCullough86b56862025-04-18 13:04:03 -0700522 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700523
Sean McCullough86b56862025-04-18 13:04:03 -0700524 /* Tool call cards */
525 .tool-call-cards-container {
526 display: flex;
527 flex-direction: column;
528 gap: 8px;
529 margin-top: 8px;
530 }
531
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000532 /* Error message specific styling */
533 .error .message-content {
534 background-color: #ffebee;
535 border-left: 3px solid #f44336;
Sean McCullough86b56862025-04-18 13:04:03 -0700536 }
537
538 .end-of-turn {
539 margin-bottom: 15px;
540 }
541
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000542 .end-of-turn-indicator {
543 display: block;
544 font-size: 11px;
545 color: #777;
546 padding: 2px 0;
547 margin-top: 8px;
548 text-align: right;
549 font-style: italic;
550 }
551
552 .user .end-of-turn-indicator {
553 color: rgba(255, 255, 255, 0.7);
554 }
555
556 /* Message info panel styling */
557 .message-info-panel {
558 margin-top: 8px;
559 padding: 8px;
560 background-color: rgba(0, 0, 0, 0.03);
561 border-radius: 6px;
562 font-size: 12px;
563 transition: all 0.2s ease;
564 border-left: 2px solid rgba(0, 0, 0, 0.1);
565 }
566
567 .user .message-info-panel {
568 background-color: rgba(255, 255, 255, 0.15);
569 border-left: 2px solid rgba(255, 255, 255, 0.2);
570 }
571
572 .info-row {
573 margin-bottom: 3px;
574 display: flex;
575 }
576
577 .info-label {
578 font-weight: bold;
579 margin-right: 5px;
580 min-width: 60px;
581 }
582
583 .info-value {
584 flex: 1;
585 }
586
587 .conversation-id {
588 font-family: monospace;
Sean McCullough86b56862025-04-18 13:04:03 -0700589 font-size: 10px;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000590 word-break: break-all;
Sean McCullough86b56862025-04-18 13:04:03 -0700591 }
592
593 .markdown-content {
594 box-sizing: border-box;
595 min-width: 200px;
596 margin: 0 auto;
597 }
598
599 .markdown-content p {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000600 margin-block-start: 0.3em;
601 margin-block-end: 0.3em;
602 }
603
604 .markdown-content p:first-child {
605 margin-block-start: 0;
606 }
607
608 .markdown-content p:last-child {
609 margin-block-end: 0;
610 }
611
612 /* Styling for markdown elements */
613 .markdown-content a {
614 color: inherit;
615 text-decoration: underline;
616 }
617
618 .user .markdown-content a {
619 color: #fff;
620 text-decoration: underline;
621 }
622
623 .markdown-content ul,
624 .markdown-content ol {
625 padding-left: 1.5em;
626 margin: 0.5em 0;
627 }
628
629 .markdown-content blockquote {
630 border-left: 3px solid rgba(0, 0, 0, 0.2);
631 padding-left: 1em;
632 margin-left: 0.5em;
633 font-style: italic;
634 }
635
636 .user .markdown-content blockquote {
637 border-left: 3px solid rgba(255, 255, 255, 0.4);
Sean McCullough86b56862025-04-18 13:04:03 -0700638 }
Autoformatterdded2d62025-04-28 00:27:21 +0000639
Sean McCullough8d93e362025-04-27 23:32:18 +0000640 /* Mermaid diagram styling */
641 .mermaid-container {
642 margin: 1em 0;
643 padding: 0.5em;
644 background-color: #f8f8f8;
645 border-radius: 4px;
646 overflow-x: auto;
647 }
Autoformatterdded2d62025-04-28 00:27:21 +0000648
Sean McCullough8d93e362025-04-27 23:32:18 +0000649 .mermaid {
650 text-align: center;
651 }
Sean McCullough86b56862025-04-18 13:04:03 -0700652 `;
653
Sean McCullough8d93e362025-04-27 23:32:18 +0000654 // Track mermaid diagrams that need rendering
655 private mermaidDiagrams = new Map();
656
Sean McCullough86b56862025-04-18 13:04:03 -0700657 constructor() {
658 super();
Sean McCullough8d93e362025-04-27 23:32:18 +0000659 // Initialize mermaid with specific config
660 mermaid.initialize({
661 startOnLoad: false,
Sean McCulloughf98d7302025-04-27 17:44:06 -0700662 suppressErrorRendering: true,
Autoformatterdded2d62025-04-28 00:27:21 +0000663 theme: "default",
664 securityLevel: "loose", // Allows more flexibility but be careful with user-generated content
665 fontFamily: "monospace",
Sean McCullough8d93e362025-04-27 23:32:18 +0000666 });
Sean McCullough86b56862025-04-18 13:04:03 -0700667 }
668
669 // See https://lit.dev/docs/components/lifecycle/
670 connectedCallback() {
671 super.connectedCallback();
672 }
Autoformatterdded2d62025-04-28 00:27:21 +0000673
Sean McCullough8d93e362025-04-27 23:32:18 +0000674 // After the component is updated and rendered, render any mermaid diagrams
675 updated(changedProperties: Map<string, unknown>) {
676 super.updated(changedProperties);
677 this.renderMermaidDiagrams();
Pokey Rulea10f1512025-05-15 13:53:26 +0000678 this.setupCodeBlockCopyButtons();
Sean McCullough8d93e362025-04-27 23:32:18 +0000679 }
Autoformatterdded2d62025-04-28 00:27:21 +0000680
Sean McCullough8d93e362025-04-27 23:32:18 +0000681 // Render mermaid diagrams after the component is updated
682 renderMermaidDiagrams() {
683 // Add a small delay to ensure the DOM is fully rendered
684 setTimeout(() => {
685 // Find all mermaid containers in our shadow root
Autoformatterdded2d62025-04-28 00:27:21 +0000686 const containers = this.shadowRoot?.querySelectorAll(".mermaid");
Sean McCullough8d93e362025-04-27 23:32:18 +0000687 if (!containers || containers.length === 0) return;
Autoformatterdded2d62025-04-28 00:27:21 +0000688
Sean McCullough8d93e362025-04-27 23:32:18 +0000689 // Process each mermaid diagram
Autoformatterdded2d62025-04-28 00:27:21 +0000690 containers.forEach((container) => {
Sean McCullough8d93e362025-04-27 23:32:18 +0000691 const id = container.id;
Autoformatterdded2d62025-04-28 00:27:21 +0000692 const code = container.textContent || "";
Sean McCullough8d93e362025-04-27 23:32:18 +0000693 if (!code || !id) return; // Use return for forEach instead of continue
Autoformatterdded2d62025-04-28 00:27:21 +0000694
Sean McCullough8d93e362025-04-27 23:32:18 +0000695 try {
696 // Clear any previous content
697 container.innerHTML = code;
Autoformatterdded2d62025-04-28 00:27:21 +0000698
Sean McCullough8d93e362025-04-27 23:32:18 +0000699 // Render the mermaid diagram using promise
Autoformatterdded2d62025-04-28 00:27:21 +0000700 mermaid
701 .render(`${id}-svg`, code)
Sean McCullough8d93e362025-04-27 23:32:18 +0000702 .then(({ svg }) => {
703 container.innerHTML = svg;
704 })
Autoformatterdded2d62025-04-28 00:27:21 +0000705 .catch((err) => {
706 console.error("Error rendering mermaid diagram:", err);
Sean McCullough8d93e362025-04-27 23:32:18 +0000707 // Show the original code as fallback
708 container.innerHTML = `<pre>${code}</pre>`;
709 });
710 } catch (err) {
Autoformatterdded2d62025-04-28 00:27:21 +0000711 console.error("Error processing mermaid diagram:", err);
Sean McCullough8d93e362025-04-27 23:32:18 +0000712 // Show the original code as fallback
713 container.innerHTML = `<pre>${code}</pre>`;
714 }
715 });
716 }, 100); // Small delay to ensure DOM is ready
717 }
Sean McCullough86b56862025-04-18 13:04:03 -0700718
Pokey Rulea10f1512025-05-15 13:53:26 +0000719 // Setup code block copy buttons after component is updated
720 setupCodeBlockCopyButtons() {
721 setTimeout(() => {
722 // Find all copy buttons in code blocks
723 const copyButtons =
724 this.shadowRoot?.querySelectorAll(".code-copy-button");
725 if (!copyButtons || copyButtons.length === 0) return;
726
727 // Add click event listener to each button
728 copyButtons.forEach((button) => {
729 button.addEventListener("click", (e) => {
730 e.stopPropagation();
731 const codeId = (button as HTMLElement).dataset.codeId;
732 if (!codeId) return;
733
734 const codeElement = this.shadowRoot?.querySelector(`#${codeId}`);
735 if (!codeElement) return;
736
737 const codeText = codeElement.textContent || "";
738 const buttonRect = button.getBoundingClientRect();
739
740 // Copy code to clipboard
741 navigator.clipboard
742 .writeText(codeText)
743 .then(() => {
744 // Show success indicator
745 const originalHTML = button.innerHTML;
746 button.innerHTML = `
747 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
748 <path d="M20 6L9 17l-5-5"></path>
749 </svg>
750 `;
751
752 // Display floating message
753 this.showFloatingMessage("Copied!", buttonRect, "success");
754
755 // Reset button after delay
756 setTimeout(() => {
757 button.innerHTML = originalHTML;
758 }, 2000);
759 })
760 .catch((err) => {
761 console.error("Failed to copy code:", err);
762 this.showFloatingMessage("Failed to copy!", buttonRect, "error");
763 });
764 });
765 });
766 }, 100); // Small delay to ensure DOM is ready
767 }
768
Sean McCullough86b56862025-04-18 13:04:03 -0700769 // See https://lit.dev/docs/components/lifecycle/
770 disconnectedCallback() {
771 super.disconnectedCallback();
772 }
773
774 renderMarkdown(markdownContent: string): string {
775 try {
Sean McCullough8d93e362025-04-27 23:32:18 +0000776 // Create a custom renderer
777 const renderer = new Renderer();
778 const originalCodeRenderer = renderer.code.bind(renderer);
Autoformatterdded2d62025-04-28 00:27:21 +0000779
Pokey Rulea10f1512025-05-15 13:53:26 +0000780 // Override the code renderer to handle mermaid diagrams and add copy buttons
Autoformatterdded2d62025-04-28 00:27:21 +0000781 renderer.code = function ({ text, lang, escaped }: Tokens.Code): string {
782 if (lang === "mermaid") {
Sean McCullough8d93e362025-04-27 23:32:18 +0000783 // Generate a unique ID for this diagram
784 const id = `mermaid-diagram-${Math.random().toString(36).substring(2, 10)}`;
Autoformatterdded2d62025-04-28 00:27:21 +0000785
Sean McCullough8d93e362025-04-27 23:32:18 +0000786 // Just create the container and mermaid div - we'll render it in the updated() lifecycle method
787 return `<div class="mermaid-container">
788 <div class="mermaid" id="${id}">${text}</div>
789 </div>`;
790 }
Pokey Rulea10f1512025-05-15 13:53:26 +0000791
792 // For regular code blocks, add a copy button
793 const id = `code-block-${Math.random().toString(36).substring(2, 10)}`;
794 const langClass = lang ? ` class="language-${lang}"` : "";
795
796 return `<div class="code-block-container">
797 <div class="code-block-header">
798 ${lang ? `<span class="code-language">${lang}</span>` : ""}
799 <button class="code-copy-button" title="Copy code" data-code-id="${id}">
800 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
801 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
802 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
803 </svg>
804 </button>
805 </div>
806 <pre><code id="${id}"${langClass}>${text}</code></pre>
807 </div>`;
Sean McCullough8d93e362025-04-27 23:32:18 +0000808 };
Autoformatterdded2d62025-04-28 00:27:21 +0000809
Sean McCullough86b56862025-04-18 13:04:03 -0700810 // Set markdown options for proper code block highlighting and safety
811 const markedOptions: MarkedOptions = {
812 gfm: true, // GitHub Flavored Markdown
813 breaks: true, // Convert newlines to <br>
814 async: false,
Autoformatterdded2d62025-04-28 00:27:21 +0000815 renderer: renderer,
Sean McCullough86b56862025-04-18 13:04:03 -0700816 // DOMPurify is recommended for production, but not included in this implementation
817 };
818 return marked.parse(markdownContent, markedOptions) as string;
819 } catch (error) {
820 console.error("Error rendering markdown:", error);
821 // Fallback to plain text if markdown parsing fails
822 return markdownContent;
823 }
824 }
825
826 /**
827 * Format timestamp for display
828 */
829 formatTimestamp(
830 timestamp: string | number | Date | null | undefined,
831 defaultValue: string = "",
832 ): string {
833 if (!timestamp) return defaultValue;
834 try {
835 const date = new Date(timestamp);
836 if (isNaN(date.getTime())) return defaultValue;
837
838 // Format: Mar 13, 2025 09:53:25 AM
839 return date.toLocaleString("en-US", {
840 month: "short",
841 day: "numeric",
842 year: "numeric",
843 hour: "numeric",
844 minute: "2-digit",
845 second: "2-digit",
846 hour12: true,
847 });
848 } catch (e) {
849 return defaultValue;
850 }
851 }
852
853 formatNumber(
854 num: number | null | undefined,
855 defaultValue: string = "0",
856 ): string {
857 if (num === undefined || num === null) return defaultValue;
858 try {
859 return num.toLocaleString();
860 } catch (e) {
861 return String(num);
862 }
863 }
864 formatCurrency(
865 num: number | string | null | undefined,
866 defaultValue: string = "$0.00",
867 isMessageLevel: boolean = false,
868 ): string {
869 if (num === undefined || num === null) return defaultValue;
870 try {
871 // Use 4 decimal places for message-level costs, 2 for totals
872 const decimalPlaces = isMessageLevel ? 4 : 2;
873 return `$${parseFloat(String(num)).toFixed(decimalPlaces)}`;
874 } catch (e) {
875 return defaultValue;
876 }
877 }
878
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000879 // Format duration from nanoseconds to a human-readable string
880 _formatDuration(nanoseconds: number | null | undefined): string {
881 if (!nanoseconds) return "0s";
882
883 const seconds = nanoseconds / 1e9;
884
885 if (seconds < 60) {
886 return `${seconds.toFixed(1)}s`;
887 } else if (seconds < 3600) {
888 const minutes = Math.floor(seconds / 60);
889 const remainingSeconds = seconds % 60;
890 return `${minutes}min ${remainingSeconds.toFixed(0)}s`;
891 } else {
892 const hours = Math.floor(seconds / 3600);
893 const remainingSeconds = seconds % 3600;
894 const minutes = Math.floor(remainingSeconds / 60);
895 return `${hours}h ${minutes}min`;
896 }
897 }
898
Sean McCullough86b56862025-04-18 13:04:03 -0700899 showCommit(commitHash: string) {
Sean McCullough71941bd2025-04-18 13:31:48 -0700900 this.dispatchEvent(
901 new CustomEvent("show-commit-diff", {
902 bubbles: true,
903 composed: true,
904 detail: { commitHash },
905 }),
906 );
Sean McCullough86b56862025-04-18 13:04:03 -0700907 }
908
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000909 _toggleInfo(e: Event) {
910 e.stopPropagation();
911 this.showInfo = !this.showInfo;
912 }
913
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000914 copyToClipboard(text: string, event: Event) {
915 const element = event.currentTarget as HTMLElement;
916 const rect = element.getBoundingClientRect();
917
918 navigator.clipboard
919 .writeText(text)
920 .then(() => {
921 this.showFloatingMessage("Copied!", rect, "success");
922 })
923 .catch((err) => {
924 console.error("Failed to copy text: ", err);
925 this.showFloatingMessage("Failed to copy!", rect, "error");
926 });
927 }
928
929 showFloatingMessage(
930 message: string,
931 targetRect: DOMRect,
932 type: "success" | "error",
933 ) {
934 // Create floating message element
935 const floatingMsg = document.createElement("div");
936 floatingMsg.textContent = message;
937 floatingMsg.className = `floating-message ${type}`;
938
939 // Position it near the clicked element
940 // Position just above the element
941 const top = targetRect.top - 30;
942 const left = targetRect.left + targetRect.width / 2 - 40;
943
944 floatingMsg.style.position = "fixed";
945 floatingMsg.style.top = `${top}px`;
946 floatingMsg.style.left = `${left}px`;
947 floatingMsg.style.zIndex = "9999";
948
949 // Add to document body
950 document.body.appendChild(floatingMsg);
951
952 // Animate in
953 floatingMsg.style.opacity = "0";
954 floatingMsg.style.transform = "translateY(10px)";
955
956 setTimeout(() => {
957 floatingMsg.style.opacity = "1";
958 floatingMsg.style.transform = "translateY(0)";
959 }, 10);
960
961 // Remove after animation
962 setTimeout(() => {
963 floatingMsg.style.opacity = "0";
964 floatingMsg.style.transform = "translateY(-10px)";
965
966 setTimeout(() => {
967 document.body.removeChild(floatingMsg);
968 }, 300);
969 }, 1500);
970 }
971
Sean McCullough86b56862025-04-18 13:04:03 -0700972 render() {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000973 // Calculate if this is an end of turn message with no parent conversation ID
974 const isEndOfTurn =
975 this.message?.end_of_turn && !this.message?.parent_conversation_id;
976
Philip Zeyligerb8a8f352025-06-02 07:39:37 -0700977 const isPreCompaction =
978 this.message?.idx !== undefined &&
979 this.message.idx < this.firstMessageIndex;
980
Sean McCullough86b56862025-04-18 13:04:03 -0700981 return html`
982 <div
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000983 class="message ${this.message?.type} ${isEndOfTurn
Sean McCullough86b56862025-04-18 13:04:03 -0700984 ? "end-of-turn"
Philip Zeyligerb8a8f352025-06-02 07:39:37 -0700985 : ""} ${isPreCompaction ? "pre-compaction" : ""}"
Sean McCullough86b56862025-04-18 13:04:03 -0700986 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000987 <div class="message-container">
988 <!-- Left area (empty for simplicity) -->
989 <div class="message-metadata-left"></div>
990
991 <!-- Message bubble -->
992 <div class="message-bubble-container">
993 <div class="message-content">
994 <div class="message-text-container">
995 <div class="message-actions">
996 ${copyButton(this.message?.content)}
997 <button
998 class="info-icon"
999 title="Show message details"
1000 @click=${this._toggleInfo}
Sean McCullough71941bd2025-04-18 13:31:48 -07001001 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001002 <svg
1003 xmlns="http://www.w3.org/2000/svg"
1004 width="16"
1005 height="16"
1006 viewBox="0 0 24 24"
1007 fill="none"
1008 stroke="currentColor"
1009 stroke-width="2"
1010 stroke-linecap="round"
1011 stroke-linejoin="round"
1012 >
1013 <circle cx="12" cy="12" r="10"></circle>
1014 <line x1="12" y1="16" x2="12" y2="12"></line>
1015 <line x1="12" y1="8" x2="12.01" y2="8"></line>
1016 </svg>
1017 </button>
1018 </div>
1019 ${this.message?.content
1020 ? html`
1021 <div class="message-text markdown-content">
1022 ${unsafeHTML(
1023 this.renderMarkdown(this.message?.content),
1024 )}
1025 </div>
1026 `
1027 : ""}
1028
1029 <!-- End of turn indicator inside the bubble -->
1030 ${isEndOfTurn && this.message?.elapsed
1031 ? html`
1032 <div class="end-of-turn-indicator">
1033 end of turn
1034 (${this._formatDuration(this.message?.elapsed)})
1035 </div>
1036 `
1037 : ""}
1038
1039 <!-- Info panel that can be toggled -->
1040 ${this.showInfo
1041 ? html`
1042 <div class="message-info-panel">
1043 <div class="info-row">
1044 <span class="info-label">Type:</span>
1045 <span class="info-value">${this.message?.type}</span>
1046 </div>
1047 <div class="info-row">
1048 <span class="info-label">Time:</span>
1049 <span class="info-value"
1050 >${this.formatTimestamp(
1051 this.message?.timestamp,
1052 "",
1053 )}</span
1054 >
1055 </div>
1056 ${this.message?.elapsed
1057 ? html`
1058 <div class="info-row">
1059 <span class="info-label">Duration:</span>
1060 <span class="info-value"
1061 >${this._formatDuration(
1062 this.message?.elapsed,
1063 )}</span
1064 >
1065 </div>
1066 `
1067 : ""}
1068 ${this.message?.usage
1069 ? html`
1070 <div class="info-row">
1071 <span class="info-label">Tokens:</span>
1072 <span class="info-value">
1073 ${this.message?.usage
1074 ? html`
1075 <div>
1076 Input:
1077 ${this.formatNumber(
1078 this.message?.usage?.input_tokens ||
1079 0,
1080 )}
1081 </div>
1082 ${this.message?.usage
1083 ?.cache_creation_input_tokens
1084 ? html`
1085 <div>
1086 Cache creation:
1087 ${this.formatNumber(
1088 this.message?.usage
1089 ?.cache_creation_input_tokens,
1090 )}
1091 </div>
1092 `
1093 : ""}
1094 ${this.message?.usage
1095 ?.cache_read_input_tokens
1096 ? html`
1097 <div>
1098 Cache read:
1099 ${this.formatNumber(
1100 this.message?.usage
1101 ?.cache_read_input_tokens,
1102 )}
1103 </div>
1104 `
1105 : ""}
1106 <div>
1107 Output:
1108 ${this.formatNumber(
1109 this.message?.usage?.output_tokens,
1110 )}
1111 </div>
1112 <div>
1113 Cost:
1114 ${this.formatCurrency(
1115 this.message?.usage?.cost_usd,
1116 )}
1117 </div>
1118 `
1119 : "N/A"}
1120 </span>
1121 </div>
1122 `
1123 : ""}
1124 ${this.message?.conversation_id
1125 ? html`
1126 <div class="info-row">
1127 <span class="info-label">Conversation ID:</span>
1128 <span class="info-value conversation-id"
1129 >${this.message?.conversation_id}</span
1130 >
1131 </div>
1132 `
1133 : ""}
1134 </div>
1135 `
1136 : ""}
1137 </div>
1138
1139 <!-- Tool calls - only shown for agent messages -->
1140 ${this.message?.type === "agent"
1141 ? html`
1142 <sketch-tool-calls
1143 .toolCalls=${this.message?.tool_calls}
1144 .open=${this.open}
1145 ></sketch-tool-calls>
1146 `
1147 : ""}
1148
1149 <!-- Commits section (redesigned as bubbles) -->
1150 ${this.message?.commits
1151 ? html`
1152 <div class="commits-container">
1153 <div class="commit-notification">
1154 ${this.message.commits.length} new
1155 commit${this.message.commits.length > 1 ? "s" : ""}
1156 detected
1157 </div>
1158 ${this.message.commits.map((commit) => {
1159 return html`
1160 <div class="commit-card">
Philip Zeyliger72682df2025-04-23 13:09:46 -07001161 <span
1162 class="commit-hash"
1163 title="Click to copy: ${commit.hash}"
1164 @click=${(e) =>
1165 this.copyToClipboard(
1166 commit.hash.substring(0, 8),
1167 e,
1168 )}
1169 >
Pokey Rule7be879f2025-04-23 15:30:15 +01001170 ${commit.hash.substring(0, 8)}
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001171 </span>
1172 ${commit.pushed_branch
1173 ? html`
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001174 <span
1175 class="commit-branch pushed-branch"
1176 title="Click to copy: ${commit.pushed_branch}"
1177 @click=${(e) =>
1178 this.copyToClipboard(
1179 commit.pushed_branch,
1180 e,
1181 )}
1182 >${commit.pushed_branch}</span
1183 >
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001184 `
1185 : ``}
1186 <span class="commit-subject"
1187 >${commit.subject}</span
Sean McCullough71941bd2025-04-18 13:31:48 -07001188 >
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001189 <button
1190 class="commit-diff-button"
1191 @click=${() => this.showCommit(commit.hash)}
1192 >
1193 View Diff
1194 </button>
Sean McCullough86b56862025-04-18 13:04:03 -07001195 </div>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001196 `;
1197 })}
1198 </div>
1199 `
1200 : ""}
1201 </div>
1202 </div>
1203
1204 <!-- Right side (empty for consistency) -->
1205 <div class="message-metadata-right"></div>
Sean McCullough86b56862025-04-18 13:04:03 -07001206 </div>
1207 </div>
1208 `;
1209 }
1210}
1211
Sean McCullough71941bd2025-04-18 13:31:48 -07001212function copyButton(textToCopy: string) {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001213 // Use an icon of overlapping rectangles for copy
1214 const buttonClass = "copy-icon";
1215
1216 // SVG for copy icon (two overlapping rectangles)
1217 const copyIcon = html`<svg
1218 xmlns="http://www.w3.org/2000/svg"
1219 width="16"
1220 height="16"
1221 viewBox="0 0 24 24"
1222 fill="none"
1223 stroke="currentColor"
1224 stroke-width="2"
1225 stroke-linecap="round"
1226 stroke-linejoin="round"
1227 >
1228 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
1229 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
1230 </svg>`;
1231
1232 // SVG for success check mark
1233 const successIcon = html`<svg
1234 xmlns="http://www.w3.org/2000/svg"
1235 width="16"
1236 height="16"
1237 viewBox="0 0 24 24"
1238 fill="none"
1239 stroke="currentColor"
1240 stroke-width="2"
1241 stroke-linecap="round"
1242 stroke-linejoin="round"
1243 >
1244 <path d="M20 6L9 17l-5-5"></path>
1245 </svg>`;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001246
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001247 const ret = html`<button
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001248 class="${buttonClass}"
1249 title="Copy to clipboard"
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001250 @click=${(e: Event) => {
1251 e.stopPropagation();
1252 const copyButton = e.currentTarget as HTMLButtonElement;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001253 const originalInnerHTML = copyButton.innerHTML;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001254 navigator.clipboard
1255 .writeText(textToCopy)
1256 .then(() => {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001257 copyButton.innerHTML = "";
1258 const successElement = document.createElement("div");
1259 copyButton.appendChild(successElement);
1260 render(successIcon, successElement);
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001261 setTimeout(() => {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001262 copyButton.innerHTML = originalInnerHTML;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001263 }, 2000);
1264 })
1265 .catch((err) => {
1266 console.error("Failed to copy text: ", err);
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001267 setTimeout(() => {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001268 copyButton.innerHTML = originalInnerHTML;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001269 }, 2000);
1270 });
1271 }}
1272 >
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001273 ${copyIcon}
Sean McCulloughec3ad1a2025-04-18 13:55:16 -07001274 </button>`;
Sean McCullough86b56862025-04-18 13:04:03 -07001275
Sean McCullough71941bd2025-04-18 13:31:48 -07001276 return ret;
Sean McCullough86b56862025-04-18 13:04:03 -07001277}
1278
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001279// Create global styles for floating messages
1280const floatingMessageStyles = document.createElement("style");
1281floatingMessageStyles.textContent = `
1282 .floating-message {
1283 background-color: rgba(0, 0, 0, 0.8);
1284 color: white;
1285 padding: 5px 10px;
1286 border-radius: 4px;
1287 font-size: 12px;
1288 font-family: system-ui, sans-serif;
1289 box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
1290 pointer-events: none;
1291 transition: opacity 0.3s ease, transform 0.3s ease;
1292 }
Josh Bleecher Snyder4d544932025-05-07 13:33:53 +00001293
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001294 .floating-message.success {
1295 background-color: rgba(40, 167, 69, 0.9);
1296 }
Josh Bleecher Snyder4d544932025-05-07 13:33:53 +00001297
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001298 .floating-message.error {
1299 background-color: rgba(220, 53, 69, 0.9);
1300 }
Philip Zeyligere31d2a92025-05-11 15:22:35 -07001301
1302 /* Style for code, pre elements, and tool components to ensure proper wrapping/truncation */
1303 pre, code, sketch-tool-calls, sketch-tool-card, sketch-tool-card-bash {
1304 white-space: nowrap;
1305 overflow: hidden;
1306 text-overflow: ellipsis;
1307 max-width: 100%;
1308 }
1309
1310 /* Special rule for the message content container */
1311 .message-content {
1312 max-width: 100% !important;
1313 overflow: hidden !important;
1314 }
1315
1316 /* Ensure tool call containers don't overflow */
1317 ::slotted(sketch-tool-calls) {
1318 max-width: 100%;
1319 width: 100%;
1320 overflow-wrap: break-word;
1321 word-break: break-word;
1322 }
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +00001323`;
1324document.head.appendChild(floatingMessageStyles);
1325
Sean McCullough86b56862025-04-18 13:04:03 -07001326declare global {
1327 interface HTMLElementTagNameMap {
1328 "sketch-timeline-message": SketchTimelineMessage;
1329 }
1330}