| philip.zeyliger | 26bc659 | 2025-06-30 20:15:30 -0700 | [diff] [blame] | 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ |
| banksean | 659b983 | 2025-06-27 00:50:41 +0000 | [diff] [blame] | 2 | /** |
| 3 | * Demo module for sketch-call-status component |
| 4 | */ |
| 5 | |
| 6 | import { DemoModule } from "./demo-framework/types"; |
| 7 | import { |
| 8 | demoUtils, |
| 9 | idleCallStatus, |
| 10 | workingCallStatus, |
| 11 | heavyWorkingCallStatus, |
| 12 | disconnectedCallStatus, |
| 13 | workingDisconnectedCallStatus, |
| 14 | } from "./demo-fixtures/index"; |
| 15 | import type { CallStatusState } from "./demo-fixtures/index"; |
| 16 | |
| 17 | const demo: DemoModule = { |
| 18 | title: "Call Status Demo", |
| 19 | description: |
| 20 | "Display current LLM and tool call status with visual indicators", |
| 21 | imports: ["../sketch-call-status"], |
| 22 | styles: ["/dist/tailwind.css"], |
| 23 | |
| 24 | setup: async (container: HTMLElement) => { |
| 25 | // Create demo sections |
| 26 | const statusVariationsSection = demoUtils.createDemoSection( |
| 27 | "Status Variations", |
| 28 | "Different states of the call status component", |
| 29 | ); |
| 30 | |
| 31 | const interactiveSection = demoUtils.createDemoSection( |
| 32 | "Interactive Demo", |
| 33 | "Dynamically change call status to see real-time updates", |
| 34 | ); |
| 35 | |
| 36 | // Helper function to create status component with state |
| 37 | const createStatusComponent = ( |
| 38 | id: string, |
| 39 | state: CallStatusState, |
| 40 | label: string, |
| 41 | ) => { |
| 42 | const wrapper = document.createElement("div"); |
| 43 | wrapper.style.cssText = |
| 44 | "margin: 15px 0; padding: 10px; border: 1px solid #e1e5e9; border-radius: 6px; background: white;"; |
| 45 | |
| 46 | const labelEl = document.createElement("h4"); |
| 47 | labelEl.textContent = label; |
| 48 | labelEl.style.cssText = |
| 49 | "margin: 0 0 10px 0; color: #24292f; font-size: 14px; font-weight: 600;"; |
| 50 | |
| 51 | const statusComponent = document.createElement( |
| 52 | "sketch-call-status", |
| 53 | ) as any; |
| 54 | statusComponent.id = id; |
| 55 | statusComponent.llmCalls = state.llmCalls; |
| 56 | statusComponent.toolCalls = state.toolCalls; |
| 57 | statusComponent.agentState = state.agentState; |
| 58 | statusComponent.isIdle = state.isIdle; |
| 59 | statusComponent.isDisconnected = state.isDisconnected; |
| 60 | |
| 61 | wrapper.appendChild(labelEl); |
| 62 | wrapper.appendChild(statusComponent); |
| 63 | return wrapper; |
| 64 | }; |
| 65 | |
| 66 | // Create status variations |
| 67 | const idleStatus = createStatusComponent( |
| 68 | "idle-status", |
| 69 | idleCallStatus, |
| 70 | "Idle State - No active calls", |
| 71 | ); |
| 72 | |
| 73 | const workingStatus = createStatusComponent( |
| 74 | "working-status", |
| 75 | workingCallStatus, |
| 76 | "Working State - LLM and tool calls active", |
| 77 | ); |
| 78 | |
| 79 | const heavyWorkingStatus = createStatusComponent( |
| 80 | "heavy-working-status", |
| 81 | heavyWorkingCallStatus, |
| 82 | "Heavy Working State - Multiple calls active", |
| 83 | ); |
| 84 | |
| 85 | const disconnectedStatus = createStatusComponent( |
| 86 | "disconnected-status", |
| 87 | disconnectedCallStatus, |
| 88 | "Disconnected State - No connection", |
| 89 | ); |
| 90 | |
| 91 | const workingDisconnectedStatus = createStatusComponent( |
| 92 | "working-disconnected-status", |
| 93 | workingDisconnectedCallStatus, |
| 94 | "Working but Disconnected - Calls active but no connection", |
| 95 | ); |
| 96 | |
| 97 | // Interactive demo component |
| 98 | const interactiveStatus = document.createElement( |
| 99 | "sketch-call-status", |
| 100 | ) as any; |
| 101 | interactiveStatus.id = "interactive-status"; |
| 102 | interactiveStatus.llmCalls = 0; |
| 103 | interactiveStatus.toolCalls = []; |
| 104 | interactiveStatus.agentState = null; |
| 105 | interactiveStatus.isIdle = true; |
| 106 | interactiveStatus.isDisconnected = false; |
| 107 | |
| 108 | // Control buttons for interactive demo |
| 109 | const controlsDiv = document.createElement("div"); |
| 110 | controlsDiv.style.cssText = |
| 111 | "margin-top: 20px; display: flex; flex-wrap: wrap; gap: 10px;"; |
| 112 | |
| 113 | const addLLMCallButton = demoUtils.createButton("Add LLM Call", () => { |
| 114 | interactiveStatus.llmCalls = interactiveStatus.llmCalls + 1; |
| 115 | interactiveStatus.isIdle = false; |
| 116 | }); |
| 117 | |
| 118 | const removeLLMCallButton = demoUtils.createButton( |
| 119 | "Remove LLM Call", |
| 120 | () => { |
| 121 | interactiveStatus.llmCalls = Math.max( |
| 122 | 0, |
| 123 | interactiveStatus.llmCalls - 1, |
| 124 | ); |
| 125 | if ( |
| 126 | interactiveStatus.llmCalls === 0 && |
| 127 | interactiveStatus.toolCalls.length === 0 |
| 128 | ) { |
| 129 | interactiveStatus.isIdle = true; |
| 130 | } |
| 131 | }, |
| 132 | ); |
| 133 | |
| 134 | const addToolCallButton = demoUtils.createButton("Add Tool Call", () => { |
| 135 | const toolNames = [ |
| 136 | "bash", |
| 137 | "patch", |
| 138 | "think", |
| 139 | "keyword_search", |
| 140 | "browser_navigate", |
| 141 | "codereview", |
| 142 | ]; |
| 143 | const randomTool = |
| 144 | toolNames[Math.floor(Math.random() * toolNames.length)]; |
| 145 | const currentTools = Array.isArray(interactiveStatus.toolCalls) |
| 146 | ? [...interactiveStatus.toolCalls] |
| 147 | : []; |
| 148 | if (!currentTools.includes(randomTool)) { |
| 149 | currentTools.push(randomTool); |
| 150 | interactiveStatus.toolCalls = currentTools; |
| 151 | interactiveStatus.isIdle = false; |
| 152 | } |
| 153 | }); |
| 154 | |
| 155 | const removeToolCallButton = demoUtils.createButton( |
| 156 | "Remove Tool Call", |
| 157 | () => { |
| 158 | const currentTools = Array.isArray(interactiveStatus.toolCalls) |
| 159 | ? [...interactiveStatus.toolCalls] |
| 160 | : []; |
| 161 | if (currentTools.length > 0) { |
| 162 | currentTools.pop(); |
| 163 | interactiveStatus.toolCalls = currentTools; |
| 164 | if (interactiveStatus.llmCalls === 0 && currentTools.length === 0) { |
| 165 | interactiveStatus.isIdle = true; |
| 166 | } |
| 167 | } |
| 168 | }, |
| 169 | ); |
| 170 | |
| 171 | const toggleConnectionButton = demoUtils.createButton( |
| 172 | "Toggle Connection", |
| 173 | () => { |
| 174 | interactiveStatus.isDisconnected = !interactiveStatus.isDisconnected; |
| 175 | }, |
| 176 | ); |
| 177 | |
| 178 | const setAgentStateButton = demoUtils.createButton( |
| 179 | "Change Agent State", |
| 180 | () => { |
| 181 | const states = [ |
| 182 | null, |
| 183 | "analyzing code", |
| 184 | "refactoring components", |
| 185 | "running tests", |
| 186 | "reviewing changes", |
| 187 | "generating documentation", |
| 188 | ]; |
| 189 | const currentIndex = states.indexOf(interactiveStatus.agentState); |
| 190 | const nextIndex = (currentIndex + 1) % states.length; |
| 191 | interactiveStatus.agentState = states[nextIndex]; |
| 192 | }, |
| 193 | ); |
| 194 | |
| 195 | const resetButton = demoUtils.createButton("Reset to Idle", () => { |
| 196 | interactiveStatus.llmCalls = 0; |
| 197 | interactiveStatus.toolCalls = []; |
| 198 | interactiveStatus.agentState = null; |
| 199 | interactiveStatus.isIdle = true; |
| 200 | interactiveStatus.isDisconnected = false; |
| 201 | }); |
| 202 | |
| 203 | controlsDiv.appendChild(addLLMCallButton); |
| 204 | controlsDiv.appendChild(removeLLMCallButton); |
| 205 | controlsDiv.appendChild(addToolCallButton); |
| 206 | controlsDiv.appendChild(removeToolCallButton); |
| 207 | controlsDiv.appendChild(toggleConnectionButton); |
| 208 | controlsDiv.appendChild(setAgentStateButton); |
| 209 | controlsDiv.appendChild(resetButton); |
| 210 | |
| 211 | // Assemble the demo |
| 212 | statusVariationsSection.appendChild(idleStatus); |
| 213 | statusVariationsSection.appendChild(workingStatus); |
| 214 | statusVariationsSection.appendChild(heavyWorkingStatus); |
| 215 | statusVariationsSection.appendChild(disconnectedStatus); |
| 216 | statusVariationsSection.appendChild(workingDisconnectedStatus); |
| 217 | |
| 218 | const interactiveWrapper = document.createElement("div"); |
| 219 | interactiveWrapper.style.cssText = |
| 220 | "padding: 10px; border: 1px solid #e1e5e9; border-radius: 6px; background: white;"; |
| 221 | interactiveWrapper.appendChild(interactiveStatus); |
| 222 | interactiveWrapper.appendChild(controlsDiv); |
| 223 | interactiveSection.appendChild(interactiveWrapper); |
| 224 | |
| 225 | container.appendChild(statusVariationsSection); |
| 226 | container.appendChild(interactiveSection); |
| 227 | |
| 228 | // Add some simulation of real activity |
| 229 | const simulationInterval = setInterval(() => { |
| 230 | const statusComponents = [ |
| 231 | document.getElementById("working-status") as any, |
| 232 | document.getElementById("heavy-working-status") as any, |
| 233 | ].filter(Boolean); |
| 234 | |
| 235 | statusComponents.forEach((statusEl) => { |
| 236 | if (statusEl && Math.random() > 0.8) { |
| 237 | // 20% chance to update |
| 238 | // Simulate some activity by slightly changing the number of calls |
| 239 | const variation = Math.floor(Math.random() * 3) - 1; // -1, 0, or 1 |
| 240 | statusEl.llmCalls = Math.max(0, statusEl.llmCalls + variation); |
| 241 | } |
| 242 | }); |
| 243 | }, 2000); |
| 244 | |
| 245 | // Store interval for cleanup |
| 246 | (container as any).demoInterval = simulationInterval; |
| 247 | }, |
| 248 | |
| 249 | cleanup: async () => { |
| 250 | // Clear any intervals |
| 251 | const container = document.getElementById("demo-container"); |
| 252 | if (container && (container as any).demoInterval) { |
| 253 | clearInterval((container as any).demoInterval); |
| 254 | delete (container as any).demoInterval; |
| 255 | } |
| 256 | }, |
| 257 | }; |
| 258 | |
| 259 | export default demo; |