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