blob: 8122db743bde44bc369651530254c11a4ad53b7d [file] [log] [blame]
Sean McCullough86b56862025-04-18 13:04:03 -07001import {css, html, LitElement} from 'lit';
2import {repeat} from 'lit/directives/repeat.js';
3import {customElement, property} from 'lit/decorators.js';
4import {State, TimelineMessage} from '../types';
5import './sketch-timeline-message'
6
7@customElement('sketch-timeline')
8export class SketchTimeline extends LitElement {
9 @property()
10 messages: TimelineMessage[] = [];
11
12 // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
13 // Note that these styles only apply to the scope of this web component's
14 // shadow DOM node, so they won't leak out or collide with CSS declared in
15 // other components or the containing web page (...unless you want it to do that).
16 static styles = css`
17 /* Hide views initially to prevent flash of content */
18 .timeline-container .timeline,
19 .timeline-container .diff-view,
20 .timeline-container .chart-view,
21 .timeline-container .terminal-view {
22 visibility: hidden;
23 }
24
25 /* Will be set by JavaScript once we know which view to display */
26 .timeline-container.view-initialized .timeline,
27 .timeline-container.view-initialized .diff-view,
28 .timeline-container.view-initialized .chart-view,
29 .timeline-container.view-initialized .terminal-view {
30 visibility: visible;
31 }
32
33 .timeline-container {
34 width: 100%;
35 position: relative;
36 }
37
38 /* Timeline styles that should remain unchanged */
39 .timeline {
40 position: relative;
41 margin: 10px 0;
42 scroll-behavior: smooth;
43 }
44
45 .timeline::before {
46 content: "";
47 position: absolute;
48 top: 0;
49 bottom: 0;
50 left: 15px;
51 width: 2px;
52 background: #e0e0e0;
53 border-radius: 1px;
54 }
55
56 /* Hide the timeline vertical line when there are no messages */
57 .timeline.empty::before {
58 display: none;
59 }
60 `;
61
62 constructor() {
63 super();
64
65 // Binding methods
66 this._handleShowCommitDiff = this._handleShowCommitDiff.bind(this);
67 }
68
69 /**
70 * Handle showCommitDiff event
71 */
72 private _handleShowCommitDiff(event: CustomEvent) {
73 const { commitHash } = event.detail;
74 if (commitHash) {
75 // Bubble up the event to the app shell
76 const newEvent = new CustomEvent('show-commit-diff', {
77 detail: { commitHash },
78 bubbles: true,
79 composed: true
80 });
81 this.dispatchEvent(newEvent);
82 }
83 }
84
85 // See https://lit.dev/docs/components/lifecycle/
86 connectedCallback() {
87 super.connectedCallback();
88
89 // Listen for showCommitDiff events from the renderer
90 document.addEventListener('showCommitDiff', this._handleShowCommitDiff as EventListener);
91 }
92
93 // See https://lit.dev/docs/components/lifecycle/
94 disconnectedCallback() {
95 super.disconnectedCallback();
96
97 // Remove event listeners
98 document.removeEventListener('showCommitDiff', this._handleShowCommitDiff as EventListener);
99 }
100
101 messageKey(message: TimelineMessage): string {
102 // If the message has tool calls, and any of the tool_calls get a response, we need to
103 // re-render that message.
104 const toolCallResponses = message.tool_calls?.filter((tc)=>tc.result_message).map((tc)=>tc.tool_call_id).join('-');
105 return `message-${message.idx}-${toolCallResponses}`;
106 }
107
108 render() {
109 return html`
110 <div class="timeline-container">
111 ${repeat(this.messages, this.messageKey, (message, index) => {
112 let previousMessage: TimelineMessage;
113 if (index > 0) {
114 previousMessage = this.messages[index-1];
115 }
116 return html`<sketch-timeline-message .message=${message} .previousMessage=${previousMessage}></sketch-timeline-message>`;
117 })}
118 </div>
119 `;
120 }
121}
122
123declare global {
124 interface HTMLElementTagNameMap {
125 "sketch-timeline": SketchTimeline;
126 }
127}