blob: 36f16407214ccab5bff84b5713acb3ba44949b5a [file] [log] [blame]
Sean McCullough86b56862025-04-18 13:04:03 -07001import { css, html, LitElement } from "lit";
2import { unsafeHTML } from "lit/directives/unsafe-html.js";
3import { customElement, property } from "lit/decorators.js";
Sean McCulloughd9f13372025-04-21 15:08:49 -07004import { AgentMessage } from "../types";
Sean McCullough86b56862025-04-18 13:04:03 -07005import { marked, MarkedOptions } from "marked";
6import "./sketch-tool-calls";
7@customElement("sketch-timeline-message")
8export class SketchTimelineMessage extends LitElement {
9 @property()
Sean McCulloughd9f13372025-04-21 15:08:49 -070010 message: AgentMessage;
Sean McCullough86b56862025-04-18 13:04:03 -070011
12 @property()
Sean McCulloughd9f13372025-04-21 15:08:49 -070013 previousMessage: AgentMessage;
Sean McCullough86b56862025-04-18 13:04:03 -070014
Sean McCullough2deac842025-04-21 18:17:57 -070015 @property()
16 open: boolean = false;
17
Sean McCullough86b56862025-04-18 13:04:03 -070018 // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
19 // Note that these styles only apply to the scope of this web component's
20 // shadow DOM node, so they won't leak out or collide with CSS declared in
21 // other components or the containing web page (...unless you want it to do that).
22 static styles = css`
23 .message {
24 position: relative;
25 margin-bottom: 5px;
26 padding-left: 30px;
27 }
28
29 .message-icon {
30 position: absolute;
31 left: 10px;
32 top: 0;
33 transform: translateX(-50%);
34 width: 16px;
35 height: 16px;
36 border-radius: 3px;
37 text-align: center;
38 line-height: 16px;
39 color: #fff;
40 font-size: 10px;
41 }
42
43 .message-content {
44 position: relative;
45 padding: 5px 10px;
46 background: #fff;
47 border-radius: 3px;
48 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
49 border-left: 3px solid transparent;
50 }
51
52 /* Copy button styles */
53 .message-text-container,
54 .tool-result-container {
55 position: relative;
56 }
57
58 .message-actions {
59 position: absolute;
60 top: 5px;
61 right: 5px;
62 z-index: 10;
63 opacity: 0;
64 transition: opacity 0.2s ease;
65 }
66
67 .message-text-container:hover .message-actions,
68 .tool-result-container:hover .message-actions {
69 opacity: 1;
70 }
71
72 .copy-button {
73 background-color: rgba(255, 255, 255, 0.9);
74 border: 1px solid #ddd;
75 border-radius: 4px;
76 color: #555;
77 cursor: pointer;
78 font-size: 12px;
79 padding: 2px 8px;
80 transition: all 0.2s ease;
81 }
82
83 .copy-button:hover {
84 background-color: #f0f0f0;
85 color: #333;
86 }
87
88 /* Removed arrow decoration for a more compact look */
89
90 .message-header {
91 display: flex;
92 flex-wrap: wrap;
93 gap: 5px;
94 margin-bottom: 3px;
95 font-size: 12px;
96 }
97
98 .message-timestamp {
99 font-size: 10px;
100 color: #888;
101 font-style: italic;
102 margin-left: 3px;
103 }
104
105 .message-usage {
106 font-size: 10px;
107 color: #888;
108 margin-left: 3px;
109 }
110
111 .conversation-id {
112 font-family: monospace;
113 font-size: 12px;
114 padding: 2px 4px;
115 background-color: #f0f0f0;
116 border-radius: 3px;
117 margin-left: auto;
118 }
119
120 .parent-info {
121 font-size: 11px;
122 opacity: 0.8;
123 }
124
125 .subconversation {
126 border-left: 2px solid transparent;
127 padding-left: 5px;
128 margin-left: 20px;
129 transition: margin-left 0.3s ease;
130 }
131
132 .message-text {
133 overflow-x: auto;
134 margin-bottom: 3px;
135 font-family: monospace;
136 padding: 3px 5px;
137 background: rgb(236, 236, 236);
138 border-radius: 6px;
139 user-select: text;
140 cursor: text;
141 -webkit-user-select: text;
142 -moz-user-select: text;
143 -ms-user-select: text;
144 font-size: 13px;
145 line-height: 1.3;
146 }
147
148 .tool-details {
149 margin-top: 3px;
150 padding-top: 3px;
151 border-top: 1px dashed #e0e0e0;
152 font-size: 12px;
153 }
154
155 .tool-name {
156 font-size: 12px;
157 font-weight: bold;
158 margin-bottom: 2px;
159 background: #f0f0f0;
160 padding: 2px 4px;
161 border-radius: 2px;
162 display: flex;
163 align-items: center;
164 gap: 3px;
165 }
166
167 .tool-input,
168 .tool-result {
169 margin-top: 2px;
170 padding: 3px 5px;
171 background: #f7f7f7;
172 border-radius: 2px;
173 font-family: monospace;
174 font-size: 12px;
175 overflow-x: auto;
176 white-space: pre;
177 line-height: 1.3;
178 user-select: text;
179 cursor: text;
180 -webkit-user-select: text;
181 -moz-user-select: text;
182 -ms-user-select: text;
183 }
184
185 .tool-result {
186 max-height: 300px;
187 overflow-y: auto;
188 }
189
190 .usage-info {
191 margin-top: 10px;
192 padding-top: 10px;
193 border-top: 1px dashed #e0e0e0;
194 font-size: 12px;
195 color: #666;
196 }
197
198 /* Custom styles for IRC-like experience */
199 .user .message-content {
200 border-left-color: #2196f3;
201 }
202
203 .agent .message-content {
204 border-left-color: #4caf50;
205 }
206
207 .tool .message-content {
208 border-left-color: #ff9800;
209 }
210
211 .error .message-content {
212 border-left-color: #f44336;
213 }
214
215 /* Make message type display bold but without the IRC-style markers */
216 .message-type {
217 font-weight: bold;
218 }
219
220 /* Commit message styling */
221 .message.commit {
222 background-color: #f0f7ff;
223 border-left: 4px solid #0366d6;
224 }
225
226 .commits-container {
227 margin-top: 10px;
228 padding: 5px;
229 }
230
231 .commits-header {
232 font-weight: bold;
233 margin-bottom: 5px;
234 color: #24292e;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000235 display: flex;
236 justify-content: space-between;
237 align-items: center;
Sean McCullough86b56862025-04-18 13:04:03 -0700238 }
239
240 .commit-boxes-row {
241 display: flex;
242 flex-wrap: wrap;
243 gap: 8px;
244 margin-top: 8px;
245 }
246
247 .commit-box {
248 border: 1px solid #d1d5da;
249 border-radius: 4px;
250 overflow: hidden;
251 background-color: #ffffff;
252 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
253 max-width: 100%;
254 display: flex;
255 flex-direction: column;
256 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700257
Sean McCullough86b56862025-04-18 13:04:03 -0700258 .commit-preview {
259 padding: 8px 12px;
Sean McCullough86b56862025-04-18 13:04:03 -0700260 font-family: monospace;
261 background-color: #f6f8fa;
262 border-bottom: 1px dashed #d1d5da;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000263 display: flex;
264 align-items: center;
265 flex-wrap: wrap;
266 gap: 4px;
Sean McCullough86b56862025-04-18 13:04:03 -0700267 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700268
Sean McCullough86b56862025-04-18 13:04:03 -0700269 .commit-preview:hover {
270 background-color: #eef2f6;
271 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700272
Sean McCullough86b56862025-04-18 13:04:03 -0700273 .commit-hash {
274 color: #0366d6;
275 font-weight: bold;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000276 cursor: pointer;
277 margin-right: 8px;
278 text-decoration: none;
279 position: relative;
280 }
281
282 .commit-hash:hover {
283 text-decoration: underline;
284 }
285
286 .commit-hash:hover::after {
287 content: "📋";
288 font-size: 10px;
289 position: absolute;
290 top: -8px;
291 right: -12px;
292 opacity: 0.7;
293 }
294
295 .branch-wrapper {
296 margin-right: 8px;
297 color: #555;
298 }
299
300 .commit-branch {
301 color: #28a745;
302 font-weight: 500;
303 cursor: pointer;
304 text-decoration: none;
305 position: relative;
306 }
307
308 .commit-branch:hover {
309 text-decoration: underline;
310 }
311
312 .commit-branch:hover::after {
313 content: "📋";
314 font-size: 10px;
315 position: absolute;
316 top: -8px;
317 right: -12px;
318 opacity: 0.7;
319 }
320
321 .commit-preview {
322 display: flex;
323 align-items: center;
324 flex-wrap: wrap;
325 gap: 4px;
Sean McCullough86b56862025-04-18 13:04:03 -0700326 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700327
Sean McCullough86b56862025-04-18 13:04:03 -0700328 .commit-details {
329 padding: 8px 12px;
330 max-height: 200px;
331 overflow-y: auto;
332 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700333
Sean McCullough86b56862025-04-18 13:04:03 -0700334 .commit-details pre {
335 margin: 0;
336 white-space: pre-wrap;
337 word-break: break-word;
338 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700339
Sean McCullough86b56862025-04-18 13:04:03 -0700340 .commit-details.is-hidden {
341 display: none;
342 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700343
Sean McCullough86b56862025-04-18 13:04:03 -0700344 .pushed-branch {
345 color: #28a745;
346 font-weight: 500;
347 margin-left: 6px;
348 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700349
Sean McCullough86b56862025-04-18 13:04:03 -0700350 .commit-diff-button {
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000351 padding: 3px 6px;
Sean McCullough86b56862025-04-18 13:04:03 -0700352 border: 1px solid #ccc;
353 border-radius: 3px;
354 background-color: #f7f7f7;
355 color: #24292e;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000356 font-size: 11px;
Sean McCullough86b56862025-04-18 13:04:03 -0700357 cursor: pointer;
358 transition: all 0.2s ease;
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000359 margin-left: auto;
Sean McCullough86b56862025-04-18 13:04:03 -0700360 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700361
Sean McCullough86b56862025-04-18 13:04:03 -0700362 .commit-diff-button:hover {
363 background-color: #e7e7e7;
364 border-color: #aaa;
365 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700366
Sean McCullough86b56862025-04-18 13:04:03 -0700367 /* Tool call cards */
368 .tool-call-cards-container {
369 display: flex;
370 flex-direction: column;
371 gap: 8px;
372 margin-top: 8px;
373 }
374
375 /* Message type styles */
376
377 .user .message-icon {
378 background-color: #2196f3;
379 }
380
381 .agent .message-icon {
382 background-color: #4caf50;
383 }
384
385 .tool .message-icon {
386 background-color: #ff9800;
387 }
388
389 .error .message-icon {
390 background-color: #f44336;
391 }
392
393 .end-of-turn {
394 margin-bottom: 15px;
395 }
396
397 .end-of-turn::after {
398 content: "End of Turn";
399 position: absolute;
400 left: 15px;
401 bottom: -10px;
402 transform: translateX(-50%);
403 font-size: 10px;
404 color: #666;
405 background: #f0f0f0;
406 padding: 1px 4px;
407 border-radius: 3px;
408 }
409
410 .markdown-content {
411 box-sizing: border-box;
412 min-width: 200px;
413 margin: 0 auto;
414 }
415
416 .markdown-content p {
417 margin-block-start: 0.5em;
418 margin-block-end: 0.5em;
419 }
420 `;
421
422 constructor() {
423 super();
424 }
425
426 // See https://lit.dev/docs/components/lifecycle/
427 connectedCallback() {
428 super.connectedCallback();
429 }
430
431 // See https://lit.dev/docs/components/lifecycle/
432 disconnectedCallback() {
433 super.disconnectedCallback();
434 }
435
436 renderMarkdown(markdownContent: string): string {
437 try {
438 // Set markdown options for proper code block highlighting and safety
439 const markedOptions: MarkedOptions = {
440 gfm: true, // GitHub Flavored Markdown
441 breaks: true, // Convert newlines to <br>
442 async: false,
443 // DOMPurify is recommended for production, but not included in this implementation
444 };
445 return marked.parse(markdownContent, markedOptions) as string;
446 } catch (error) {
447 console.error("Error rendering markdown:", error);
448 // Fallback to plain text if markdown parsing fails
449 return markdownContent;
450 }
451 }
452
453 /**
454 * Format timestamp for display
455 */
456 formatTimestamp(
457 timestamp: string | number | Date | null | undefined,
458 defaultValue: string = "",
459 ): string {
460 if (!timestamp) return defaultValue;
461 try {
462 const date = new Date(timestamp);
463 if (isNaN(date.getTime())) return defaultValue;
464
465 // Format: Mar 13, 2025 09:53:25 AM
466 return date.toLocaleString("en-US", {
467 month: "short",
468 day: "numeric",
469 year: "numeric",
470 hour: "numeric",
471 minute: "2-digit",
472 second: "2-digit",
473 hour12: true,
474 });
475 } catch (e) {
476 return defaultValue;
477 }
478 }
479
480 formatNumber(
481 num: number | null | undefined,
482 defaultValue: string = "0",
483 ): string {
484 if (num === undefined || num === null) return defaultValue;
485 try {
486 return num.toLocaleString();
487 } catch (e) {
488 return String(num);
489 }
490 }
491 formatCurrency(
492 num: number | string | null | undefined,
493 defaultValue: string = "$0.00",
494 isMessageLevel: boolean = false,
495 ): string {
496 if (num === undefined || num === null) return defaultValue;
497 try {
498 // Use 4 decimal places for message-level costs, 2 for totals
499 const decimalPlaces = isMessageLevel ? 4 : 2;
500 return `$${parseFloat(String(num)).toFixed(decimalPlaces)}`;
501 } catch (e) {
502 return defaultValue;
503 }
504 }
505
506 showCommit(commitHash: string) {
Sean McCullough71941bd2025-04-18 13:31:48 -0700507 this.dispatchEvent(
508 new CustomEvent("show-commit-diff", {
509 bubbles: true,
510 composed: true,
511 detail: { commitHash },
512 }),
513 );
Sean McCullough86b56862025-04-18 13:04:03 -0700514 }
515
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000516 copyToClipboard(text: string, event: Event) {
517 const element = event.currentTarget as HTMLElement;
518 const rect = element.getBoundingClientRect();
519
520 navigator.clipboard
521 .writeText(text)
522 .then(() => {
523 this.showFloatingMessage("Copied!", rect, "success");
524 })
525 .catch((err) => {
526 console.error("Failed to copy text: ", err);
527 this.showFloatingMessage("Failed to copy!", rect, "error");
528 });
529 }
530
531 showFloatingMessage(
532 message: string,
533 targetRect: DOMRect,
534 type: "success" | "error",
535 ) {
536 // Create floating message element
537 const floatingMsg = document.createElement("div");
538 floatingMsg.textContent = message;
539 floatingMsg.className = `floating-message ${type}`;
540
541 // Position it near the clicked element
542 // Position just above the element
543 const top = targetRect.top - 30;
544 const left = targetRect.left + targetRect.width / 2 - 40;
545
546 floatingMsg.style.position = "fixed";
547 floatingMsg.style.top = `${top}px`;
548 floatingMsg.style.left = `${left}px`;
549 floatingMsg.style.zIndex = "9999";
550
551 // Add to document body
552 document.body.appendChild(floatingMsg);
553
554 // Animate in
555 floatingMsg.style.opacity = "0";
556 floatingMsg.style.transform = "translateY(10px)";
557
558 setTimeout(() => {
559 floatingMsg.style.opacity = "1";
560 floatingMsg.style.transform = "translateY(0)";
561 }, 10);
562
563 // Remove after animation
564 setTimeout(() => {
565 floatingMsg.style.opacity = "0";
566 floatingMsg.style.transform = "translateY(-10px)";
567
568 setTimeout(() => {
569 document.body.removeChild(floatingMsg);
570 }, 300);
571 }, 1500);
572 }
573
Sean McCullough86b56862025-04-18 13:04:03 -0700574 render() {
575 return html`
576 <div
577 class="message ${this.message?.type} ${this.message?.end_of_turn
578 ? "end-of-turn"
579 : ""}"
580 >
581 ${this.previousMessage?.type != this.message?.type
582 ? html`<div class="message-icon">
583 ${this.message?.type.toUpperCase()[0]}
584 </div>`
585 : ""}
586 <div class="message-content">
587 <div class="message-header">
588 <span class="message-type">${this.message?.type}</span>
Sean McCullough71941bd2025-04-18 13:31:48 -0700589 <span class="message-timestamp"
590 >${this.formatTimestamp(this.message?.timestamp)}
591 ${this.message?.elapsed
592 ? html`(${(this.message?.elapsed / 1e9).toFixed(2)}s)`
593 : ""}</span
594 >
595 ${this.message?.usage
596 ? html` <span class="message-usage">
597 <span title="Input tokens"
598 >In: ${this.message?.usage?.input_tokens}</span
599 >
600 ${this.message?.usage?.cache_read_input_tokens > 0
601 ? html`<span title="Cache tokens"
602 >[Cache:
603 ${this.formatNumber(
604 this.message?.usage?.cache_read_input_tokens,
605 )}]</span
606 >`
607 : ""}
608 <span title="Output tokens"
609 >Out: ${this.message?.usage?.output_tokens}</span
610 >
611 <span title="Message cost"
612 >(${this.formatCurrency(
613 this.message?.usage?.cost_usd,
614 )})</span
615 >
616 </span>`
617 : ""}
Sean McCullough86b56862025-04-18 13:04:03 -0700618 </div>
619 <div class="message-text-container">
620 <div class="message-actions">
621 ${copyButton(this.message?.content)}
622 </div>
623 ${this.message?.content
624 ? html`
625 <div class="message-text markdown-content">
626 ${unsafeHTML(this.renderMarkdown(this.message?.content))}
627 </div>
628 `
629 : ""}
630 </div>
631 <sketch-tool-calls
632 .toolCalls=${this.message?.tool_calls}
Sean McCullough2deac842025-04-21 18:17:57 -0700633 .open=${this.open}
Sean McCullough86b56862025-04-18 13:04:03 -0700634 ></sketch-tool-calls>
635 ${this.message?.commits
636 ? html`
637 <div class="commits-container">
638 <div class="commits-header">
Sean McCullough71941bd2025-04-18 13:31:48 -0700639 ${this.message.commits.length} new
640 commit${this.message.commits.length > 1 ? "s" : ""} detected
Sean McCullough86b56862025-04-18 13:04:03 -0700641 </div>
642 ${this.message.commits.map((commit) => {
643 return html`
644 <div class="commit-boxes-row">
645 <div class="commit-box">
646 <div class="commit-preview">
Philip Zeyliger72682df2025-04-23 13:09:46 -0700647 <span
648 class="commit-hash"
649 title="Click to copy: ${commit.hash}"
650 @click=${(e) =>
651 this.copyToClipboard(
652 commit.hash.substring(0, 8),
653 e,
654 )}
655 >
Pokey Rule7be879f2025-04-23 15:30:15 +0100656 ${commit.hash.substring(0, 8)}
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000657 </span>
658 ${commit.pushed_branch
659 ? html`
Pokey Rule7be879f2025-04-23 15:30:15 +0100660 <span class="branch-wrapper">
661 (<span
662 class="commit-branch pushed-branch"
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000663 title="Click to copy: ${commit.pushed_branch}"
664 @click=${(e) =>
665 this.copyToClipboard(
666 commit.pushed_branch,
667 e,
668 )}
669 >${commit.pushed_branch}</span
Pokey Rule7be879f2025-04-23 15:30:15 +0100670 >)
671 </span>
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000672 `
673 : ``}
674 <span class="commit-subject"
675 >${commit.subject}</span
Sean McCullough71941bd2025-04-18 13:31:48 -0700676 >
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000677 <button
678 class="commit-diff-button"
679 @click=${() => this.showCommit(commit.hash)}
680 >
681 View Diff
682 </button>
Sean McCullough86b56862025-04-18 13:04:03 -0700683 </div>
684 <div class="commit-details is-hidden">
685 <pre>${commit.body}</pre>
686 </div>
Sean McCullough86b56862025-04-18 13:04:03 -0700687 </div>
688 </div>
689 `;
690 })}
691 </div>
692 `
693 : ""}
694 </div>
695 </div>
696 `;
697 }
698}
699
Sean McCullough71941bd2025-04-18 13:31:48 -0700700function copyButton(textToCopy: string) {
Sean McCullough86b56862025-04-18 13:04:03 -0700701 // Add click event listener to handle copying
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000702 const buttonClass = "copy-button";
703 const buttonContent = "Copy";
704 const successContent = "Copied!";
705 const failureContent = "Failed";
706
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700707 const ret = html`<button
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000708 class="${buttonClass}"
709 title="Copy to clipboard"
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700710 @click=${(e: Event) => {
711 e.stopPropagation();
712 const copyButton = e.currentTarget as HTMLButtonElement;
713 navigator.clipboard
714 .writeText(textToCopy)
715 .then(() => {
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000716 copyButton.textContent = successContent;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700717 setTimeout(() => {
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000718 copyButton.textContent = buttonContent;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700719 }, 2000);
720 })
721 .catch((err) => {
722 console.error("Failed to copy text: ", err);
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000723 copyButton.textContent = failureContent;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700724 setTimeout(() => {
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000725 copyButton.textContent = buttonContent;
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700726 }, 2000);
727 });
728 }}
729 >
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000730 ${buttonContent}
Sean McCulloughec3ad1a2025-04-18 13:55:16 -0700731 </button>`;
Sean McCullough86b56862025-04-18 13:04:03 -0700732
Sean McCullough71941bd2025-04-18 13:31:48 -0700733 return ret;
Sean McCullough86b56862025-04-18 13:04:03 -0700734}
735
Philip Zeyliger37dc4cf2025-04-23 12:58:52 +0000736// Create global styles for floating messages
737const floatingMessageStyles = document.createElement("style");
738floatingMessageStyles.textContent = `
739 .floating-message {
740 background-color: rgba(0, 0, 0, 0.8);
741 color: white;
742 padding: 5px 10px;
743 border-radius: 4px;
744 font-size: 12px;
745 font-family: system-ui, sans-serif;
746 box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
747 pointer-events: none;
748 transition: opacity 0.3s ease, transform 0.3s ease;
749 }
750
751 .floating-message.success {
752 background-color: rgba(40, 167, 69, 0.9);
753 }
754
755 .floating-message.error {
756 background-color: rgba(220, 53, 69, 0.9);
757 }
758`;
759document.head.appendChild(floatingMessageStyles);
760
Sean McCullough86b56862025-04-18 13:04:03 -0700761declare global {
762 interface HTMLElementTagNameMap {
763 "sketch-timeline-message": SketchTimelineMessage;
764 }
765}