blob: 845967b4c1ec878191339029c69888566a21345e [file] [log] [blame]
philip.zeyliger26bc6592025-06-30 20:15:30 -07001/* eslint-disable @typescript-eslint/no-explicit-any */
bankseane59a2e12025-06-28 01:38:19 +00002/**
3 * Demo module for sketch-timeline component
4 */
5
6import { DemoModule } from "./demo-framework/types";
7import { demoUtils } from "./demo-fixtures/index";
8import type { AgentMessage } from "../../types";
9
10// Mock messages for demo
11function createMockMessage(props: Partial<AgentMessage> = {}): AgentMessage {
12 return {
13 idx: props.idx || 0,
14 type: props.type || "agent",
15 content: props.content || "Hello world",
16 timestamp: props.timestamp || "2023-05-15T12:00:00Z",
17 elapsed: props.elapsed || 1500000000, // 1.5 seconds in nanoseconds
18 end_of_turn: props.end_of_turn || false,
19 conversation_id: props.conversation_id || "conv123",
20 tool_calls: props.tool_calls || [],
21 commits: props.commits || undefined,
22 usage: props.usage,
23 hide_output: props.hide_output || false,
24 ...props,
25 };
26}
27
28function createMockMessages(count: number): AgentMessage[] {
29 return Array.from({ length: count }, (_, i) =>
30 createMockMessage({
31 idx: i,
32 content: `Message ${i + 1}: This is a sample message to demonstrate the timeline component.`,
33 type: i % 3 === 0 ? "user" : "agent",
34 timestamp: new Date(Date.now() - (count - i) * 60000).toISOString(),
35 tool_calls:
36 i % 4 === 0
37 ? [
38 {
39 name: "bash",
40 input: `echo "Tool call example ${i}"`,
41 tool_call_id: `call_${i}`,
philip.zeyliger26bc6592025-06-30 20:15:30 -070042 args: `{"command": "echo 'Tool call example ${i}'"}`,
bankseane59a2e12025-06-28 01:38:19 +000043 result: `Tool call example ${i}`,
44 },
45 ]
46 : [],
47 usage:
48 i % 5 === 0
49 ? {
50 input_tokens: 10 + i,
51 cache_creation_input_tokens: 0,
52 cache_read_input_tokens: 0,
53 output_tokens: 50 + i * 2,
54 cost_usd: 0.001 * (i + 1),
55 }
56 : undefined,
57 }),
58 );
59}
60
61const demo: DemoModule = {
62 title: "Timeline Demo",
63 description:
64 "Interactive timeline component for displaying conversation messages with various states",
65 imports: ["../sketch-timeline"],
66 styles: ["/dist/tailwind.css"],
67
68 setup: async (container: HTMLElement) => {
69 // Create demo sections
70 const basicSection = demoUtils.createDemoSection(
71 "Basic Timeline",
72 "Timeline with a few sample messages",
73 );
74
75 const statesSection = demoUtils.createDemoSection(
76 "Timeline States",
77 "Different loading and thinking states",
78 );
79
80 const interactiveSection = demoUtils.createDemoSection(
81 "Interactive Demo",
82 "Add messages and control timeline behavior",
83 );
84
85 // Basic timeline with sample messages
86 const basicMessages = createMockMessages(5);
87 const basicTimeline = document.createElement("sketch-timeline") as any;
88 basicTimeline.messages = basicMessages;
89 basicTimeline.style.cssText =
90 "height: 400px; border: 1px solid #e1e5e9; border-radius: 6px; margin: 10px 0;";
91
92 // Create a scroll container for the basic timeline
93 const basicScrollContainer = document.createElement("div");
94 basicScrollContainer.style.cssText = "height: 400px; overflow-y: auto;";
95 basicScrollContainer.appendChild(basicTimeline);
96 basicTimeline.scrollContainer = { value: basicScrollContainer };
97
98 basicSection.appendChild(basicScrollContainer);
99
100 // Timeline with loading state
101 const loadingTimeline = document.createElement("sketch-timeline") as any;
102 loadingTimeline.messages = [];
103 loadingTimeline.isLoadingOlderMessages = false;
104 loadingTimeline.style.cssText =
105 "height: 200px; border: 1px solid #e1e5e9; border-radius: 6px; margin: 10px 0;";
106
107 const loadingWrapper = document.createElement("div");
108 loadingWrapper.style.cssText = "margin: 15px 0;";
109
110 const loadingLabel = document.createElement("h4");
111 loadingLabel.textContent = "Loading State (No messages)";
112 loadingLabel.style.cssText =
113 "margin: 0 0 10px 0; color: #24292f; font-size: 14px; font-weight: 600;";
114
115 loadingWrapper.appendChild(loadingLabel);
116 loadingWrapper.appendChild(loadingTimeline);
117 statesSection.appendChild(loadingWrapper);
118
119 // Timeline with thinking state
120 const thinkingMessages = createMockMessages(3);
121 const thinkingTimeline = document.createElement("sketch-timeline") as any;
122 thinkingTimeline.messages = thinkingMessages;
123 thinkingTimeline.llmCalls = 2;
124 thinkingTimeline.toolCalls = ["bash", "patch"];
125 thinkingTimeline.agentState = "thinking";
126 thinkingTimeline.style.cssText =
127 "height: 300px; border: 1px solid #e1e5e9; border-radius: 6px; margin: 10px 0;";
128
129 // Set initial load complete for thinking timeline
130 setTimeout(() => {
131 (thinkingTimeline as any).isInitialLoadComplete = true;
132 thinkingTimeline.requestUpdate();
133 }, 100);
134
135 const thinkingWrapper = document.createElement("div");
136 thinkingWrapper.style.cssText = "margin: 15px 0;";
137
138 const thinkingLabel = document.createElement("h4");
139 thinkingLabel.textContent = "Thinking State (Agent is active)";
140 thinkingLabel.style.cssText =
141 "margin: 0 0 10px 0; color: #24292f; font-size: 14px; font-weight: 600;";
142
143 const thinkingScrollContainer = document.createElement("div");
144 thinkingScrollContainer.style.cssText = "height: 300px; overflow-y: auto;";
145 thinkingScrollContainer.appendChild(thinkingTimeline);
146 thinkingTimeline.scrollContainer = { value: thinkingScrollContainer };
147
148 thinkingWrapper.appendChild(thinkingLabel);
149 thinkingWrapper.appendChild(thinkingScrollContainer);
150 statesSection.appendChild(thinkingWrapper);
151
152 // Interactive timeline
153 const interactiveMessages = createMockMessages(8);
154 const interactiveTimeline = document.createElement(
155 "sketch-timeline",
156 ) as any;
157 interactiveTimeline.messages = interactiveMessages;
158 interactiveTimeline.style.cssText =
159 "height: 400px; border: 1px solid #e1e5e9; border-radius: 6px; margin: 10px 0;";
160
161 // Set initial load complete for interactive timeline
162 setTimeout(() => {
163 (interactiveTimeline as any).isInitialLoadComplete = true;
164 interactiveTimeline.requestUpdate();
165 }, 100);
166
167 const interactiveScrollContainer = document.createElement("div");
168 interactiveScrollContainer.style.cssText =
169 "height: 400px; overflow-y: auto;";
170 interactiveScrollContainer.appendChild(interactiveTimeline);
171 interactiveTimeline.scrollContainer = { value: interactiveScrollContainer };
172
173 // Control buttons for interactive demo
174 const controlsDiv = document.createElement("div");
175 controlsDiv.style.cssText =
176 "margin-top: 20px; display: flex; flex-wrap: wrap; gap: 10px;";
177
178 const addMessageButton = demoUtils.createButton("Add User Message", () => {
179 const newMessage = createMockMessage({
180 idx: interactiveMessages.length,
181 content: `New user message added at ${new Date().toLocaleTimeString()}`,
182 type: "user",
183 timestamp: new Date().toISOString(),
184 });
185 interactiveMessages.push(newMessage);
186 interactiveTimeline.messages = [...interactiveMessages];
187 });
188
189 const addAgentMessageButton = demoUtils.createButton(
190 "Add Agent Message",
191 () => {
192 const newMessage = createMockMessage({
193 idx: interactiveMessages.length,
194 content: `New agent response added at ${new Date().toLocaleTimeString()}`,
195 type: "agent",
196 timestamp: new Date().toISOString(),
197 tool_calls:
198 Math.random() > 0.5
199 ? [
200 {
201 name: "bash",
202 input: "date",
203 tool_call_id: `call_${Date.now()}`,
204 args: '{"command": "date"}',
205 result: new Date().toString(),
206 },
207 ]
208 : [],
209 });
210 interactiveMessages.push(newMessage);
211 interactiveTimeline.messages = [...interactiveMessages];
212 },
213 );
214
215 const toggleThinkingButton = demoUtils.createButton(
216 "Toggle Thinking",
217 () => {
218 if (
219 interactiveTimeline.llmCalls > 0 ||
220 interactiveTimeline.toolCalls.length > 0
221 ) {
222 interactiveTimeline.llmCalls = 0;
223 interactiveTimeline.toolCalls = [];
224 } else {
225 interactiveTimeline.llmCalls = 1;
226 interactiveTimeline.toolCalls = ["bash"];
227 }
228 },
229 );
230
231 const toggleCompactButton = demoUtils.createButton(
232 "Toggle Compact Padding",
233 () => {
234 interactiveTimeline.compactPadding =
235 !interactiveTimeline.compactPadding;
236 },
237 );
238
239 const clearMessagesButton = demoUtils.createButton("Clear Messages", () => {
240 interactiveMessages.length = 0;
241 interactiveTimeline.messages = [];
242 });
243
244 const resetDemoButton = demoUtils.createButton("Reset Demo", () => {
245 interactiveMessages.length = 0;
246 interactiveMessages.push(...createMockMessages(8));
247 interactiveTimeline.messages = [...interactiveMessages];
248 interactiveTimeline.llmCalls = 0;
249 interactiveTimeline.toolCalls = [];
250 interactiveTimeline.compactPadding = false;
251 });
252
253 controlsDiv.appendChild(addMessageButton);
254 controlsDiv.appendChild(addAgentMessageButton);
255 controlsDiv.appendChild(toggleThinkingButton);
256 controlsDiv.appendChild(toggleCompactButton);
257 controlsDiv.appendChild(clearMessagesButton);
258 controlsDiv.appendChild(resetDemoButton);
259
260 const interactiveWrapper = document.createElement("div");
261 interactiveWrapper.style.cssText =
262 "padding: 10px; border: 1px solid #e1e5e9; border-radius: 6px; background: white;";
263 interactiveWrapper.appendChild(interactiveScrollContainer);
264 interactiveWrapper.appendChild(controlsDiv);
265 interactiveSection.appendChild(interactiveWrapper);
266
267 // Set initial load complete for basic timeline
268 setTimeout(() => {
269 (basicTimeline as any).isInitialLoadComplete = true;
270 basicTimeline.requestUpdate();
271 }, 100);
272
273 // Assemble the demo
274 container.appendChild(basicSection);
275 container.appendChild(statesSection);
276 container.appendChild(interactiveSection);
277
278 // Store references for cleanup
279 (container as any).timelines = [
280 basicTimeline,
281 loadingTimeline,
282 thinkingTimeline,
283 interactiveTimeline,
284 ];
285 },
286
287 cleanup: async () => {
288 // Clean up any timers or listeners if needed
289 const container = document.getElementById("demo-container");
290 if (container && (container as any).timelines) {
291 const timelines = (container as any).timelines;
292 timelines.forEach((timeline: any) => {
293 // Reset timeline state
294 timeline.llmCalls = 0;
295 timeline.toolCalls = [];
296 timeline.messages = [];
297 });
298 delete (container as any).timelines;
299 }
300 },
301};
302
303export default demo;