@d34dman/flowdrop 0.0.60 → 0.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +6 -0
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -1
  4. package/dist/api/client.d.ts +4 -0
  5. package/dist/api/client.js +6 -1
  6. package/dist/api/enhanced-client.js +7 -6
  7. package/dist/components/App.svelte +143 -219
  8. package/dist/components/CanvasBanner.stories.svelte +25 -0
  9. package/dist/components/CanvasBanner.stories.svelte.d.ts +27 -0
  10. package/dist/components/CanvasBanner.svelte +2 -2
  11. package/dist/components/ConfigForm.svelte +37 -36
  12. package/dist/components/ConfigPanel.stories.svelte +38 -0
  13. package/dist/components/ConfigPanel.stories.svelte.d.ts +27 -0
  14. package/dist/components/ConfigPanel.svelte +2 -2
  15. package/dist/components/ConnectionLine.svelte +2 -2
  16. package/dist/components/FlowDropZone.svelte +18 -2
  17. package/dist/components/FlowDropZone.svelte.d.ts +2 -0
  18. package/dist/components/LoadingSpinner.stories.svelte +30 -0
  19. package/dist/components/LoadingSpinner.stories.svelte.d.ts +27 -0
  20. package/dist/components/Logo.stories.svelte +22 -0
  21. package/dist/components/Logo.stories.svelte.d.ts +27 -0
  22. package/dist/components/Logo.svelte +33 -13
  23. package/dist/components/Logo.svelte.d.ts +1 -1
  24. package/dist/components/MarkdownDisplay.stories.svelte +21 -0
  25. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +27 -0
  26. package/dist/components/MarkdownDisplay.svelte +4 -3
  27. package/dist/components/Navbar.stories.svelte +41 -0
  28. package/dist/components/Navbar.stories.svelte.d.ts +27 -0
  29. package/dist/components/Navbar.svelte +4 -4
  30. package/dist/components/NodeSidebar.svelte +12 -12
  31. package/dist/components/NodeStatusOverlay.stories.svelte +74 -0
  32. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +27 -0
  33. package/dist/components/PipelineStatus.svelte +11 -4
  34. package/dist/components/PortCoordinateTracker.svelte +1 -1
  35. package/dist/components/SchemaForm.stories.svelte +101 -0
  36. package/dist/components/SchemaForm.stories.svelte.d.ts +27 -0
  37. package/dist/components/SchemaForm.svelte +17 -12
  38. package/dist/components/SettingsModal.svelte +3 -3
  39. package/dist/components/SettingsPanel.svelte +23 -22
  40. package/dist/components/StatusIcon.stories.svelte +60 -0
  41. package/dist/components/StatusIcon.stories.svelte.d.ts +27 -0
  42. package/dist/components/StatusIcon.svelte +7 -0
  43. package/dist/components/StatusLabel.stories.svelte +17 -0
  44. package/dist/components/StatusLabel.stories.svelte.d.ts +27 -0
  45. package/dist/components/ThemeToggle.stories.svelte +25 -0
  46. package/dist/components/ThemeToggle.stories.svelte.d.ts +27 -0
  47. package/dist/components/ThemeToggle.svelte +8 -8
  48. package/dist/components/UniversalNode.svelte +1 -1
  49. package/dist/components/WorkflowEditor.svelte +298 -294
  50. package/dist/components/form/FormAutocomplete.svelte +20 -19
  51. package/dist/components/form/FormCheckboxGroup.stories.svelte +28 -0
  52. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +27 -0
  53. package/dist/components/form/FormField.svelte +3 -3
  54. package/dist/components/form/FormFieldLight.svelte +2 -2
  55. package/dist/components/form/FormFieldWrapper.stories.svelte +31 -0
  56. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +27 -0
  57. package/dist/components/form/FormFieldset.svelte +7 -7
  58. package/dist/components/form/FormNumberField.stories.svelte +33 -0
  59. package/dist/components/form/FormNumberField.stories.svelte.d.ts +27 -0
  60. package/dist/components/form/FormRangeField.stories.svelte +31 -0
  61. package/dist/components/form/FormRangeField.stories.svelte.d.ts +27 -0
  62. package/dist/components/form/FormSelect.stories.svelte +50 -0
  63. package/dist/components/form/FormSelect.stories.svelte.d.ts +27 -0
  64. package/dist/components/form/FormTemplateEditor.svelte +2 -1
  65. package/dist/components/form/FormTextField.stories.svelte +30 -0
  66. package/dist/components/form/FormTextField.stories.svelte.d.ts +27 -0
  67. package/dist/components/form/FormTextarea.stories.svelte +31 -0
  68. package/dist/components/form/FormTextarea.stories.svelte.d.ts +27 -0
  69. package/dist/components/form/FormToggle.stories.svelte +30 -0
  70. package/dist/components/form/FormToggle.stories.svelte.d.ts +27 -0
  71. package/dist/components/form/FormUISchemaRenderer.svelte +1 -1
  72. package/dist/components/form/types.d.ts +15 -47
  73. package/dist/components/interrupt/ChoicePrompt.stories.svelte +43 -0
  74. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +27 -0
  75. package/dist/components/interrupt/ChoicePrompt.svelte +24 -24
  76. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +49 -0
  77. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +27 -0
  78. package/dist/components/interrupt/ConfirmationPrompt.svelte +19 -19
  79. package/dist/components/interrupt/FormPrompt.svelte +15 -15
  80. package/dist/components/interrupt/InterruptBubble.svelte +202 -236
  81. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +1 -1
  82. package/dist/components/interrupt/ReviewPrompt.stories.svelte +46 -0
  83. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +27 -0
  84. package/dist/components/interrupt/ReviewPrompt.svelte +842 -0
  85. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +23 -0
  86. package/dist/components/interrupt/TextInputPrompt.stories.svelte +34 -0
  87. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +27 -0
  88. package/dist/components/interrupt/TextInputPrompt.svelte +21 -21
  89. package/dist/components/nodes/GatewayNode.stories.svelte +76 -0
  90. package/dist/components/nodes/GatewayNode.stories.svelte.d.ts +26 -0
  91. package/dist/components/nodes/GatewayNode.svelte +19 -17
  92. package/dist/components/nodes/IdeaNode.stories.svelte +48 -0
  93. package/dist/components/nodes/IdeaNode.stories.svelte.d.ts +26 -0
  94. package/dist/components/nodes/IdeaNode.svelte +10 -26
  95. package/dist/components/nodes/NotesNode.stories.svelte +69 -0
  96. package/dist/components/nodes/NotesNode.stories.svelte.d.ts +26 -0
  97. package/dist/components/nodes/NotesNode.svelte +8 -8
  98. package/dist/components/nodes/SimpleNode.stories.svelte +101 -0
  99. package/dist/components/nodes/SimpleNode.stories.svelte.d.ts +26 -0
  100. package/dist/components/nodes/SimpleNode.svelte +16 -24
  101. package/dist/components/nodes/SquareNode.stories.svelte +56 -0
  102. package/dist/components/nodes/SquareNode.stories.svelte.d.ts +26 -0
  103. package/dist/components/nodes/SquareNode.svelte +13 -21
  104. package/dist/components/nodes/TerminalNode.stories.svelte +25 -0
  105. package/dist/components/nodes/TerminalNode.stories.svelte.d.ts +26 -0
  106. package/dist/components/nodes/TerminalNode.svelte +6 -6
  107. package/dist/components/nodes/ToolNode.stories.svelte +71 -0
  108. package/dist/components/nodes/ToolNode.stories.svelte.d.ts +26 -0
  109. package/dist/components/nodes/ToolNode.svelte +7 -15
  110. package/dist/components/nodes/WorkflowNode.stories.svelte +50 -0
  111. package/dist/components/nodes/WorkflowNode.stories.svelte.d.ts +26 -0
  112. package/dist/components/nodes/WorkflowNode.svelte +13 -13
  113. package/dist/components/playground/ChatPanel.svelte +48 -48
  114. package/dist/components/playground/ExecutionLogs.svelte +23 -23
  115. package/dist/components/playground/InputCollector.svelte +24 -24
  116. package/dist/components/playground/MessageBubble.stories.svelte +49 -0
  117. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +27 -0
  118. package/dist/components/playground/MessageBubble.svelte +49 -46
  119. package/dist/components/playground/Playground.svelte +203 -172
  120. package/dist/components/playground/PlaygroundModal.svelte +5 -5
  121. package/dist/components/playground/SessionManager.svelte +26 -26
  122. package/dist/config/constants.d.ts +22 -0
  123. package/dist/config/constants.js +22 -0
  124. package/dist/config/endpoints.d.ts +19 -0
  125. package/dist/config/runtimeConfig.js +2 -1
  126. package/dist/core/index.d.ts +5 -2
  127. package/dist/core/index.js +9 -1
  128. package/dist/editor/index.d.ts +13 -9
  129. package/dist/editor/index.js +15 -11
  130. package/dist/form/code.d.ts +2 -1
  131. package/dist/form/code.js +1 -3
  132. package/dist/form/markdown.d.ts +2 -1
  133. package/dist/form/markdown.js +1 -3
  134. package/dist/helpers/workflowEditorHelper.js +13 -9
  135. package/dist/mocks/app-forms.js +1 -0
  136. package/dist/mocks/app-navigation.js +3 -1
  137. package/dist/mocks/app-stores.d.ts +4 -4
  138. package/dist/playground/index.d.ts +5 -4
  139. package/dist/playground/index.js +15 -11
  140. package/dist/playground/mount.d.ts +20 -1
  141. package/dist/playground/mount.js +24 -6
  142. package/dist/services/agentSpecExecutionService.js +2 -1
  143. package/dist/services/api.js +10 -18
  144. package/dist/services/apiVariableService.js +2 -1
  145. package/dist/services/autoSaveService.d.ts +3 -3
  146. package/dist/services/autoSaveService.js +21 -17
  147. package/dist/services/categoriesApi.js +13 -5
  148. package/dist/services/draftStorage.js +5 -4
  149. package/dist/services/dynamicSchemaService.js +4 -4
  150. package/dist/services/globalSave.d.ts +60 -11
  151. package/dist/services/globalSave.js +160 -83
  152. package/dist/services/historyService.d.ts +2 -1
  153. package/dist/services/historyService.js +7 -3
  154. package/dist/services/interruptService.js +9 -8
  155. package/dist/services/nodeExecutionService.js +14 -6
  156. package/dist/services/playgroundService.d.ts +3 -2
  157. package/dist/services/playgroundService.js +8 -7
  158. package/dist/services/portConfigApi.js +11 -7
  159. package/dist/services/toastService.d.ts +1 -1
  160. package/dist/services/toastService.js +6 -5
  161. package/dist/services/variableService.js +3 -2
  162. package/dist/settings/index.d.ts +1 -1
  163. package/dist/settings/index.js +1 -1
  164. package/dist/stores/{categoriesStore.d.ts → categoriesStore.svelte.d.ts} +3 -3
  165. package/dist/stores/{categoriesStore.js → categoriesStore.svelte.js} +15 -18
  166. package/dist/stores/editorStateMachine.svelte.d.ts +42 -0
  167. package/dist/stores/editorStateMachine.svelte.js +132 -0
  168. package/dist/stores/{historyStore.d.ts → historyStore.svelte.d.ts} +18 -15
  169. package/dist/stores/{historyStore.js → historyStore.svelte.js} +40 -21
  170. package/dist/stores/{interruptStore.d.ts → interruptStore.svelte.d.ts} +16 -15
  171. package/dist/stores/{interruptStore.js → interruptStore.svelte.js} +85 -94
  172. package/dist/stores/{playgroundStore.d.ts → playgroundStore.svelte.d.ts} +52 -34
  173. package/dist/stores/{playgroundStore.js → playgroundStore.svelte.js} +193 -100
  174. package/dist/stores/{portCoordinateStore.d.ts → portCoordinateStore.svelte.d.ts} +10 -4
  175. package/dist/stores/{portCoordinateStore.js → portCoordinateStore.svelte.js} +38 -35
  176. package/dist/stores/{settingsStore.d.ts → settingsStore.svelte.d.ts} +45 -28
  177. package/dist/stores/{settingsStore.js → settingsStore.svelte.js} +169 -128
  178. package/dist/stores/{workflowStore.d.ts → workflowStore.svelte.d.ts} +101 -65
  179. package/dist/stores/{workflowStore.js → workflowStore.svelte.js} +285 -239
  180. package/dist/stories/CanvasDecorator.svelte +50 -0
  181. package/dist/stories/CanvasDecorator.svelte.d.ts +8 -0
  182. package/dist/stories/NodeDecorator.svelte +74 -0
  183. package/dist/stories/NodeDecorator.svelte.d.ts +8 -0
  184. package/dist/stories/utils.d.ts +93 -0
  185. package/dist/stories/utils.js +122 -0
  186. package/dist/styles/base.css +114 -61
  187. package/dist/styles/toast.css +2 -2
  188. package/dist/styles/tokens.css +250 -185
  189. package/dist/svelte-app.d.ts +0 -6
  190. package/dist/svelte-app.js +13 -31
  191. package/dist/types/index.d.ts +2 -0
  192. package/dist/types/interrupt.d.ts +89 -5
  193. package/dist/types/interrupt.js +13 -1
  194. package/dist/types/playground.d.ts +42 -1
  195. package/dist/types/playground.js +38 -0
  196. package/dist/types/settings.js +1 -1
  197. package/dist/utils/colors.js +4 -4
  198. package/dist/utils/connections.js +33 -8
  199. package/dist/utils/icons.js +1 -1
  200. package/dist/utils/logger.d.ts +47 -0
  201. package/dist/utils/logger.js +72 -0
  202. package/dist/utils/nodeWrapper.js +1 -1
  203. package/dist/utils/sanitize.d.ts +19 -0
  204. package/dist/utils/sanitize.js +31 -0
  205. package/dist/utils/validation.d.ts +29 -0
  206. package/dist/utils/validation.js +39 -0
  207. package/package.json +243 -232
@@ -15,24 +15,23 @@
15
15
  import type { EndpointConfig } from '../../config/endpoints.js';
16
16
  import type {
17
17
  PlaygroundMode,
18
- PlaygroundConfig,
19
- PlaygroundMessagesApiResponse
18
+ PlaygroundConfig
20
19
  } from '../../types/playground.js';
21
20
  import { playgroundService } from '../../services/playgroundService.js';
22
21
  import { interruptService } from '../../services/interruptService.js';
23
22
  import { setEndpointConfig } from '../../services/api.js';
24
23
  import {
25
- currentSession,
26
- sessions,
27
- messages,
28
- isExecuting,
29
- isLoading,
30
- error,
24
+ getCurrentSession,
25
+ getSessions,
26
+ getIsExecuting,
27
+ getIsLoading,
28
+ getError,
31
29
  playgroundActions,
32
- inputFields
33
- } from '../../stores/playgroundStore.js';
34
- import { interruptActions } from '../../stores/interruptStore.js';
35
- import { get } from 'svelte/store';
30
+ getInputFields,
31
+ createPollingCallback
32
+ } from '../../stores/playgroundStore.svelte.js';
33
+ import { interruptActions } from '../../stores/interruptStore.svelte.js';
34
+ import { logger } from '../../utils/logger.js';
36
35
 
37
36
  /**
38
37
  * Component props
@@ -70,8 +69,8 @@
70
69
  /** Track session being edited for rename */
71
70
  let editingSessionId = $state<string | null>(null);
72
71
 
73
- /** Track session pending delete */
74
- let pendingDeleteId = $state<string | null>(null);
72
+ /** Track which session's dropdown menu is open */
73
+ let openMenuId = $state<string | null>(null);
75
74
 
76
75
  /** Track if initial session has been loaded to prevent duplicate loads */
77
76
  let initialSessionLoaded = $state(false);
@@ -111,11 +110,11 @@
111
110
  if (config.autoRun && !autoRunTriggered) {
112
111
  autoRunTriggered = true;
113
112
  const predefinedMessage = config.predefinedMessage ?? 'Run workflow';
114
- console.log('[Playground] Auto-run triggered with message:', predefinedMessage);
113
+ logger.debug('[Playground] Auto-run triggered with message:', predefinedMessage);
115
114
  await handleSendMessage(predefinedMessage);
116
115
  }
117
116
  } catch (err) {
118
- console.error('[Playground] Initialization error:', err);
117
+ logger.error('[Playground] Initialization error:', err);
119
118
  }
120
119
  };
121
120
 
@@ -139,8 +138,8 @@
139
138
  }
140
139
 
141
140
  // Skip if sessions haven't been loaded yet (will be handled by onMount)
142
- const sessionList = get(sessions);
143
- if (sessionList.length === 0 && get(isLoading)) {
141
+ const sessionList = getSessions();
142
+ if (sessionList.length === 0 && getIsLoading()) {
144
143
  return;
145
144
  }
146
145
 
@@ -157,11 +156,11 @@
157
156
  */
158
157
  async function loadInitialSession(sessionId: string): Promise<void> {
159
158
  // Validate session exists in loaded sessions
160
- const sessionList = get(sessions);
159
+ const sessionList = getSessions();
161
160
  const sessionExists = sessionList.some((s) => s.id === sessionId);
162
161
 
163
162
  if (!sessionExists) {
164
- console.warn(
163
+ logger.warn(
165
164
  `[Playground] Initial session "${sessionId}" not found in available sessions. ` +
166
165
  `Available sessions: ${sessionList.map((s) => s.id).join(', ') || 'none'}`
167
166
  );
@@ -176,7 +175,7 @@
176
175
  initialSessionLoaded = true;
177
176
  loadedInitialSessionId = sessionId;
178
177
  } catch (err) {
179
- console.error('[Playground] Failed to load initial session:', err);
178
+ logger.error('[Playground] Failed to load initial session:', err);
180
179
  // Mark as attempted to prevent retry loops
181
180
  initialSessionLoaded = true;
182
181
  loadedInitialSessionId = sessionId;
@@ -193,6 +192,20 @@
193
192
  interruptActions.reset();
194
193
  });
195
194
 
195
+ /**
196
+ * Close dropdown menu when clicking outside
197
+ */
198
+ $effect(() => {
199
+ if (!openMenuId) return;
200
+
201
+ function onDocumentClick() {
202
+ openMenuId = null;
203
+ }
204
+
205
+ document.addEventListener('click', onDocumentClick);
206
+ return () => document.removeEventListener('click', onDocumentClick);
207
+ });
208
+
196
209
  /**
197
210
  * Load sessions for the workflow
198
211
  */
@@ -206,7 +219,7 @@
206
219
  } catch (err) {
207
220
  const errorMessage = err instanceof Error ? err.message : 'Failed to load sessions';
208
221
  playgroundActions.setError(errorMessage);
209
- console.error('Failed to load sessions:', err);
222
+ logger.error('Failed to load sessions:', err);
210
223
  } finally {
211
224
  playgroundActions.setLoading(false);
212
225
  }
@@ -235,7 +248,7 @@
235
248
  } catch (err) {
236
249
  const errorMessage = err instanceof Error ? err.message : 'Failed to load session';
237
250
  playgroundActions.setError(errorMessage);
238
- console.error('Failed to load session:', err);
251
+ logger.error('Failed to load session:', err);
239
252
  } finally {
240
253
  playgroundActions.setLoading(false);
241
254
  }
@@ -249,7 +262,7 @@
249
262
  playgroundActions.setError(null);
250
263
 
251
264
  try {
252
- const sessionName = `Session ${get(sessions).length + 1}`;
265
+ const sessionName = `Session ${getSessions().length + 1}`;
253
266
  const session = await playgroundService.createSession(workflowId, sessionName);
254
267
  playgroundActions.addSession(session);
255
268
  playgroundActions.setCurrentSession(session);
@@ -257,7 +270,7 @@
257
270
  } catch (err) {
258
271
  const errorMessage = err instanceof Error ? err.message : 'Failed to create session';
259
272
  playgroundActions.setError(errorMessage);
260
- console.error('Failed to create session:', err);
273
+ logger.error('Failed to create session:', err);
261
274
  } finally {
262
275
  playgroundActions.setLoading(false);
263
276
  }
@@ -267,7 +280,7 @@
267
280
  * Select a session
268
281
  */
269
282
  async function handleSelectSession(sessionId: string): Promise<void> {
270
- const currentSessionId = get(currentSession)?.id;
283
+ const currentSessionId = getCurrentSession()?.id;
271
284
  if (currentSessionId === sessionId) {
272
285
  return;
273
286
  }
@@ -287,34 +300,31 @@
287
300
  playgroundActions.removeSession(sessionId);
288
301
 
289
302
  // If we deleted the current session, clear it
290
- if (get(currentSession)?.id === sessionId) {
303
+ if (getCurrentSession()?.id === sessionId) {
291
304
  playgroundService.stopPolling();
292
305
  }
293
- pendingDeleteId = null;
294
306
  } catch (err) {
295
307
  const errorMessage = err instanceof Error ? err.message : 'Failed to delete session';
296
308
  playgroundActions.setError(errorMessage);
297
- console.error('Failed to delete session:', err);
309
+ logger.error('Failed to delete session:', err);
298
310
  }
299
311
  }
300
312
 
301
313
  /**
302
- * Handle delete click - show confirmation or execute
314
+ * Toggle session dropdown menu
303
315
  */
304
- function handleDeleteClick(event: Event, sessionId: string): void {
316
+ function handleMenuToggle(event: Event, sessionId: string): void {
305
317
  event.stopPropagation();
306
- if (pendingDeleteId === sessionId) {
307
- // Confirm deletion
308
- void handleDeleteSession(sessionId);
309
- } else {
310
- pendingDeleteId = sessionId;
311
- // Auto-reset after 3 seconds
312
- setTimeout(() => {
313
- if (pendingDeleteId === sessionId) {
314
- pendingDeleteId = null;
315
- }
316
- }, 3000);
317
- }
318
+ openMenuId = openMenuId === sessionId ? null : sessionId;
319
+ }
320
+
321
+ /**
322
+ * Handle delete from dropdown menu
323
+ */
324
+ function handleMenuDelete(event: Event, sessionId: string): void {
325
+ event.stopPropagation();
326
+ openMenuId = null;
327
+ void handleDeleteSession(sessionId);
318
328
  }
319
329
 
320
330
  /**
@@ -326,7 +336,7 @@
326
336
  playgroundActions.setCurrentSession(null);
327
337
  playgroundActions.clearMessages();
328
338
  // Clear interrupts for this session
329
- const sessionId = get(currentSession)?.id;
339
+ const sessionId = getCurrentSession()?.id;
330
340
  if (sessionId) {
331
341
  interruptActions.clearSessionInterrupts(sessionId);
332
342
  }
@@ -336,17 +346,17 @@
336
346
  * Send a message
337
347
  */
338
348
  async function handleSendMessage(content: string): Promise<void> {
339
- const session = get(currentSession);
349
+ const session = getCurrentSession();
340
350
  if (!session) {
341
351
  // Create a session first if none exists
342
352
  await handleCreateSession();
343
- const newSession = get(currentSession);
353
+ const newSession = getCurrentSession();
344
354
  if (!newSession) {
345
355
  return;
346
356
  }
347
357
  }
348
358
 
349
- const sessionId = get(currentSession)?.id;
359
+ const sessionId = getCurrentSession()?.id;
350
360
  if (!sessionId) {
351
361
  return;
352
362
  }
@@ -357,7 +367,7 @@
357
367
  try {
358
368
  // Prepare inputs from the input collector
359
369
  const inputs: Record<string, unknown> = {};
360
- const fields = get(inputFields);
370
+ const fields = getInputFields();
361
371
 
362
372
  fields.forEach((field) => {
363
373
  const key = `${field.nodeId}:${field.fieldId}`;
@@ -383,7 +393,7 @@
383
393
  const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
384
394
  playgroundActions.setError(errorMessage);
385
395
  playgroundActions.setExecuting(false);
386
- console.error('Failed to send message:', err);
396
+ logger.error('Failed to send message:', err);
387
397
  }
388
398
  }
389
399
 
@@ -391,7 +401,7 @@
391
401
  * Stop execution
392
402
  */
393
403
  async function handleStopExecution(): Promise<void> {
394
- const sessionId = get(currentSession)?.id;
404
+ const sessionId = getCurrentSession()?.id;
395
405
  if (!sessionId) {
396
406
  return;
397
407
  }
@@ -404,10 +414,13 @@
404
414
  } catch (err) {
405
415
  const errorMessage = err instanceof Error ? err.message : 'Failed to stop execution';
406
416
  playgroundActions.setError(errorMessage);
407
- console.error('Failed to stop execution:', err);
417
+ logger.error('Failed to stop execution:', err);
408
418
  }
409
419
  }
410
420
 
421
+ /** Shared polling callback created from config lifecycle hooks */
422
+ const pollingCallback = createPollingCallback(config.isTerminalStatus);
423
+
411
424
  /**
412
425
  * Start polling for messages
413
426
  */
@@ -416,28 +429,9 @@
416
429
 
417
430
  playgroundService.startPolling(
418
431
  sessionId,
419
- (response: PlaygroundMessagesApiResponse) => {
420
- // Add new messages
421
- if (response.data && response.data.length > 0) {
422
- playgroundActions.addMessages(response.data);
423
- }
424
-
425
- // Update session status
426
- if (response.sessionStatus) {
427
- playgroundActions.updateSessionStatus(response.sessionStatus);
428
-
429
- // Stop executing if idle, completed, or failed
430
- // "idle" means no processing is happening (execution finished)
431
- if (
432
- response.sessionStatus === 'idle' ||
433
- response.sessionStatus === 'completed' ||
434
- response.sessionStatus === 'failed'
435
- ) {
436
- playgroundActions.setExecuting(false);
437
- }
438
- }
439
- },
440
- pollingInterval
432
+ pollingCallback,
433
+ pollingInterval,
434
+ config.shouldStopPolling
441
435
  );
442
436
  }
443
437
 
@@ -446,32 +440,14 @@
446
440
  * Called after interrupt resolution when polling has stopped
447
441
  */
448
442
  async function handleInterruptResolved(): Promise<void> {
449
- const sessionId = get(currentSession)?.id;
443
+ const sessionId = getCurrentSession()?.id;
450
444
  if (!sessionId) return;
451
445
 
452
446
  try {
453
447
  const response = await playgroundService.getMessages(sessionId);
454
-
455
- // Add new messages (deduplicates automatically)
456
- if (response.data && response.data.length > 0) {
457
- playgroundActions.addMessages(response.data);
458
- }
459
-
460
- // Update session status
461
- if (response.sessionStatus) {
462
- playgroundActions.updateSessionStatus(response.sessionStatus);
463
-
464
- // Update executing state based on session status
465
- if (
466
- response.sessionStatus === 'idle' ||
467
- response.sessionStatus === 'completed' ||
468
- response.sessionStatus === 'failed'
469
- ) {
470
- playgroundActions.setExecuting(false);
471
- }
472
- }
448
+ pollingCallback(response);
473
449
  } catch (err) {
474
- console.error('[Playground] Failed to refresh messages after interrupt:', err);
450
+ logger.error('[Playground] Failed to refresh messages after interrupt:', err);
475
451
  }
476
452
  }
477
453
 
@@ -515,7 +491,10 @@
515
491
  <div class="playground__container">
516
492
  <!-- Sidebar (conditionally rendered based on config.showSidebar) -->
517
493
  {#if config.showSidebar !== false}
518
- <aside class="playground__sidebar">
494
+ <aside
495
+ class="playground__sidebar"
496
+ style={config.sidebarWidth ? `--fd-playground-sidebar-width: ${config.sidebarWidth}` : ''}
497
+ >
519
498
  <!-- Sidebar Header -->
520
499
  <div class="playground__sidebar-header">
521
500
  <div class="playground__sidebar-title">
@@ -543,7 +522,7 @@
543
522
  type="button"
544
523
  class="playground__new-session-btn"
545
524
  onclick={handleCreateSession}
546
- disabled={$isLoading}
525
+ disabled={getIsLoading()}
547
526
  title="Start a new session"
548
527
  >
549
528
  <Icon icon="mdi:plus" />
@@ -552,19 +531,19 @@
552
531
 
553
532
  <!-- Sessions List - click a session to load it -->
554
533
  <div class="playground__sessions-wrap">
555
- {#if $sessions.length > 0}
534
+ {#if getSessions().length > 0}
556
535
  <p class="playground__sessions-hint">Click a session to load it</p>
557
536
  {/if}
558
537
  <div class="playground__sessions">
559
- {#if $sessions.length === 0 && !$isLoading}
538
+ {#if getSessions().length === 0 && !getIsLoading()}
560
539
  <div class="playground__sessions-empty">
561
540
  <span>No sessions yet</span>
562
541
  </div>
563
542
  {:else}
564
- {#each $sessions as session (session.id)}
543
+ {#each getSessions() as session (session.id)}
565
544
  <div
566
545
  class="playground__session"
567
- class:playground__session--active={$currentSession?.id === session.id}
546
+ class:playground__session--active={getCurrentSession()?.id === session.id}
568
547
  role="button"
569
548
  tabindex="0"
570
549
  title="Click to load this session"
@@ -575,21 +554,29 @@
575
554
  <span class="playground__session-name" title={session.name}>
576
555
  {session.name}
577
556
  </span>
578
- <button
579
- type="button"
580
- class="playground__session-menu"
581
- class:playground__session-menu--delete={pendingDeleteId === session.id}
582
- onclick={(e) => handleDeleteClick(e, session.id)}
583
- title={pendingDeleteId === session.id
584
- ? 'Click to confirm delete'
585
- : 'Delete session'}
586
- >
587
- {#if pendingDeleteId === session.id}
588
- <Icon icon="mdi:check" />
589
- {:else}
590
- <Icon icon="mdi:dots-horizontal" />
557
+ <div class="playground__session-actions">
558
+ <button
559
+ type="button"
560
+ class="playground__session-menu"
561
+ class:playground__session-menu--open={openMenuId === session.id}
562
+ onclick={(e) => handleMenuToggle(e, session.id)}
563
+ title="Session options"
564
+ >
565
+ <Icon icon="mdi:dots-vertical" />
566
+ </button>
567
+ {#if openMenuId === session.id}
568
+ <div class="playground__session-dropdown">
569
+ <button
570
+ type="button"
571
+ class="playground__session-dropdown-item playground__session-dropdown-item--danger"
572
+ onclick={(e) => handleMenuDelete(e, session.id)}
573
+ >
574
+ <Icon icon="mdi:delete-outline" />
575
+ <span>Delete</span>
576
+ </button>
577
+ </div>
591
578
  {/if}
592
- </button>
579
+ </div>
593
580
  </div>
594
581
  {/each}
595
582
  {/if}
@@ -602,9 +589,9 @@
602
589
  <!-- Main Content -->
603
590
  <main class="playground__main">
604
591
  <!-- Session Header (conditionally rendered based on config.showSessionHeader) -->
605
- {#if $currentSession && config.showSessionHeader !== false}
592
+ {#if getCurrentSession() && config.showSessionHeader !== false}
606
593
  <header class="playground__header">
607
- <h2 class="playground__header-title">{$currentSession.name}</h2>
594
+ <h2 class="playground__header-title">{getCurrentSession()?.name}</h2>
608
595
  <button
609
596
  type="button"
610
597
  class="playground__header-close"
@@ -617,10 +604,10 @@
617
604
  {/if}
618
605
 
619
606
  <!-- Error Banner -->
620
- {#if $error}
607
+ {#if getError()}
621
608
  <div class="playground__error">
622
609
  <Icon icon="mdi:alert-circle" />
623
- <span>{$error}</span>
610
+ <span>{getError()}</span>
624
611
  <button
625
612
  type="button"
626
613
  class="playground__error-dismiss"
@@ -633,7 +620,7 @@
633
620
 
634
621
  <!-- Chat Content -->
635
622
  <div class="playground__content">
636
- {#if $isLoading && !$currentSession}
623
+ {#if getIsLoading() && !getCurrentSession()}
637
624
  <div class="playground__loading">
638
625
  <Icon icon="mdi:loading" class="playground__loading-icon" />
639
626
  <span>Loading...</span>
@@ -702,7 +689,7 @@
702
689
 
703
690
  /* Sidebar */
704
691
  .playground__sidebar {
705
- width: 220px;
692
+ width: var(--fd-playground-sidebar-width);
706
693
  background-color: var(--fd-background);
707
694
  border-right: 1px solid var(--fd-border);
708
695
  display: flex;
@@ -714,8 +701,8 @@
714
701
  display: flex;
715
702
  align-items: center;
716
703
  justify-content: space-between;
717
- height: 3.25rem;
718
- padding: 0 1rem;
704
+ height: var(--fd-playground-header-height);
705
+ padding: 0 var(--fd-space-xl);
719
706
  border-bottom: 1px solid var(--fd-border);
720
707
  box-sizing: border-box;
721
708
  flex-shrink: 0;
@@ -724,8 +711,8 @@
724
711
  .playground__sidebar-title {
725
712
  display: flex;
726
713
  align-items: center;
727
- gap: 0.5rem;
728
- font-size: 0.9375rem;
714
+ gap: var(--fd-space-xs);
715
+ font-size: var(--fd-text-md);
729
716
  font-weight: 600;
730
717
  line-height: 1.25;
731
718
  color: var(--fd-foreground);
@@ -735,14 +722,14 @@
735
722
  display: flex;
736
723
  align-items: center;
737
724
  justify-content: center;
738
- width: 1.75rem;
739
- height: 1.75rem;
725
+ width: var(--fd-playground-icon-btn-size);
726
+ height: var(--fd-playground-icon-btn-size);
740
727
  border: none;
741
- border-radius: 0.375rem;
728
+ border-radius: var(--fd-radius-md);
742
729
  background: transparent;
743
730
  color: var(--fd-muted-foreground);
744
731
  cursor: pointer;
745
- transition: all 0.15s ease;
732
+ transition: all var(--fd-transition-fast);
746
733
  }
747
734
 
748
735
  .playground__sidebar-close:hover {
@@ -756,7 +743,7 @@
756
743
  display: flex;
757
744
  flex-direction: column;
758
745
  min-height: 0;
759
- padding: 0.75rem 0.5rem 0;
746
+ padding: var(--fd-space-md) var(--fd-space-xs) 0;
760
747
  }
761
748
 
762
749
  /* New Session – neutral full-width button with icon */
@@ -764,19 +751,19 @@
764
751
  display: flex;
765
752
  align-items: center;
766
753
  justify-content: center;
767
- gap: 0.5rem;
754
+ gap: var(--fd-space-xs);
768
755
  width: 100%;
769
- padding: 0.625rem 1rem;
756
+ padding: var(--fd-space-sm) var(--fd-space-xl);
770
757
  border: 1px solid var(--fd-border);
771
758
  border-radius: var(--fd-radius-md);
772
759
  background-color: var(--fd-background);
773
760
  color: var(--fd-foreground);
774
- font-size: 0.875rem;
761
+ font-size: var(--fd-text-sm);
775
762
  font-weight: 500;
776
763
  cursor: pointer;
777
764
  transition:
778
- background-color 0.15s ease,
779
- border-color 0.15s ease,
765
+ background-color var(--fd-transition-fast),
766
+ border-color var(--fd-transition-fast),
780
767
  transform 0.1s ease;
781
768
  box-sizing: border-box;
782
769
  }
@@ -812,23 +799,23 @@
812
799
  }
813
800
 
814
801
  .playground__sessions-hint {
815
- font-size: 0.6875rem;
802
+ font-size: var(--fd-text-2xs);
816
803
  color: var(--fd-muted-foreground);
817
- margin: 0.75rem 0 0.375rem 0.75rem;
804
+ margin: var(--fd-space-md) 0 var(--fd-space-2xs) var(--fd-space-md);
818
805
  line-height: 1.3;
819
806
  }
820
807
 
821
808
  .playground__sessions {
822
809
  flex: 1;
823
810
  overflow-y: auto;
824
- padding: 0 0.5rem 1rem;
811
+ padding: 0 var(--fd-space-xs) var(--fd-space-xl);
825
812
  min-height: 0;
826
813
  }
827
814
 
828
815
  .playground__sessions-empty {
829
- padding: 1rem;
816
+ padding: var(--fd-space-xl);
830
817
  text-align: center;
831
- font-size: 0.8125rem;
818
+ font-size: var(--fd-text-xsm);
832
819
  color: var(--fd-muted-foreground);
833
820
  }
834
821
 
@@ -837,14 +824,14 @@
837
824
  display: flex;
838
825
  align-items: center;
839
826
  justify-content: space-between;
840
- padding: 0.625rem 0.75rem;
841
- margin-bottom: 0.25rem;
827
+ padding: var(--fd-space-sm) var(--fd-space-md);
828
+ margin-bottom: var(--fd-space-3xs);
842
829
  border-radius: var(--fd-radius-md);
843
830
  border-left: 3px solid transparent;
844
831
  cursor: pointer;
845
832
  transition:
846
- background-color 0.15s ease,
847
- border-left-color 0.15s ease;
833
+ background-color var(--fd-transition-fast),
834
+ border-left-color var(--fd-transition-fast);
848
835
  }
849
836
 
850
837
  .playground__session:hover {
@@ -864,7 +851,7 @@
864
851
 
865
852
  .playground__session-name {
866
853
  flex: 1;
867
- font-size: 0.875rem;
854
+ font-size: var(--fd-text-sm);
868
855
  color: var(--fd-foreground);
869
856
  white-space: nowrap;
870
857
  overflow: hidden;
@@ -880,15 +867,15 @@
880
867
  display: flex;
881
868
  align-items: center;
882
869
  justify-content: center;
883
- width: 1.5rem;
884
- height: 1.5rem;
870
+ width: var(--fd-space-3xl);
871
+ height: var(--fd-space-3xl);
885
872
  border: none;
886
- border-radius: 0.25rem;
873
+ border-radius: var(--fd-radius-sm);
887
874
  background: transparent;
888
875
  color: var(--fd-muted-foreground);
889
876
  cursor: pointer;
890
877
  opacity: 0;
891
- transition: all 0.15s ease;
878
+ transition: all var(--fd-transition-fast);
892
879
  }
893
880
 
894
881
  .playground__session:hover .playground__session-menu {
@@ -896,19 +883,63 @@
896
883
  }
897
884
 
898
885
  .playground__session-menu:hover {
899
- background-color: var(--fd-error-muted);
900
- color: var(--fd-error);
886
+ background-color: var(--fd-muted);
887
+ color: var(--fd-foreground);
901
888
  }
902
889
 
903
- .playground__session-menu--delete {
890
+ .playground__session-menu--open {
904
891
  opacity: 1;
905
- background-color: var(--fd-success-muted);
906
- color: var(--fd-success);
892
+ background-color: var(--fd-muted);
893
+ color: var(--fd-foreground);
894
+ }
895
+
896
+ .playground__session-actions {
897
+ position: relative;
898
+ display: flex;
899
+ align-items: center;
900
+ flex-shrink: 0;
907
901
  }
908
902
 
909
- .playground__session-menu--delete:hover {
910
- background-color: var(--fd-success-muted);
911
- color: var(--fd-success-hover);
903
+ .playground__session-dropdown {
904
+ position: absolute;
905
+ top: 100%;
906
+ right: 0;
907
+ z-index: 50;
908
+ min-width: 140px;
909
+ padding: var(--fd-space-xs);
910
+ background-color: var(--fd-background);
911
+ border: 1px solid var(--fd-border);
912
+ border-radius: var(--fd-radius-md);
913
+ box-shadow: var(--fd-shadow-lg);
914
+ }
915
+
916
+ .playground__session-dropdown-item {
917
+ display: flex;
918
+ align-items: center;
919
+ gap: var(--fd-space-sm);
920
+ width: 100%;
921
+ padding: var(--fd-space-sm) var(--fd-space-md);
922
+ border: none;
923
+ border-radius: var(--fd-radius-sm);
924
+ background: transparent;
925
+ color: var(--fd-foreground);
926
+ font-size: var(--fd-text-sm);
927
+ cursor: pointer;
928
+ transition: all var(--fd-transition-fast);
929
+ white-space: nowrap;
930
+ }
931
+
932
+ .playground__session-dropdown-item:hover {
933
+ background-color: var(--fd-muted);
934
+ }
935
+
936
+ .playground__session-dropdown-item--danger {
937
+ color: var(--fd-error);
938
+ }
939
+
940
+ .playground__session-dropdown-item--danger:hover {
941
+ background-color: var(--fd-error-muted);
942
+ color: var(--fd-error);
912
943
  }
913
944
 
914
945
  /* Main Content */
@@ -927,8 +958,8 @@
927
958
  display: flex;
928
959
  align-items: center;
929
960
  justify-content: space-between;
930
- height: 3.25rem;
931
- padding: 0 1.25rem;
961
+ height: var(--fd-playground-header-height);
962
+ padding: 0 var(--fd-space-2xl);
932
963
  border-bottom: 1px solid var(--fd-border);
933
964
  background-color: var(--fd-background);
934
965
  box-sizing: border-box;
@@ -936,7 +967,7 @@
936
967
  }
937
968
 
938
969
  .playground__header-title {
939
- font-size: 0.9375rem;
970
+ font-size: var(--fd-text-md);
940
971
  font-weight: 600;
941
972
  line-height: 1.25;
942
973
  color: var(--fd-foreground);
@@ -947,14 +978,14 @@
947
978
  display: flex;
948
979
  align-items: center;
949
980
  justify-content: center;
950
- width: 1.75rem;
951
- height: 1.75rem;
981
+ width: var(--fd-playground-icon-btn-size);
982
+ height: var(--fd-playground-icon-btn-size);
952
983
  border: none;
953
- border-radius: 0.375rem;
984
+ border-radius: var(--fd-radius-md);
954
985
  background: transparent;
955
986
  color: var(--fd-muted-foreground);
956
987
  cursor: pointer;
957
- transition: all 0.15s ease;
988
+ transition: all var(--fd-transition-fast);
958
989
  }
959
990
 
960
991
  .playground__header-close:hover {
@@ -966,12 +997,12 @@
966
997
  .playground__error {
967
998
  display: flex;
968
999
  align-items: center;
969
- gap: 0.5rem;
970
- padding: 0.75rem 1rem;
1000
+ gap: var(--fd-space-xs);
1001
+ padding: var(--fd-space-md) var(--fd-space-xl);
971
1002
  background-color: var(--fd-error-muted);
972
1003
  border-bottom: 1px solid var(--fd-error);
973
1004
  color: var(--fd-error);
974
- font-size: 0.875rem;
1005
+ font-size: var(--fd-text-sm);
975
1006
  }
976
1007
 
977
1008
  .playground__error-dismiss {
@@ -979,14 +1010,14 @@
979
1010
  display: flex;
980
1011
  align-items: center;
981
1012
  justify-content: center;
982
- width: 1.5rem;
983
- height: 1.5rem;
1013
+ width: var(--fd-space-3xl);
1014
+ height: var(--fd-space-3xl);
984
1015
  border: none;
985
- border-radius: 0.25rem;
1016
+ border-radius: var(--fd-radius-sm);
986
1017
  background: transparent;
987
1018
  color: var(--fd-error);
988
1019
  cursor: pointer;
989
- transition: background-color 0.15s ease;
1020
+ transition: background-color var(--fd-transition-fast);
990
1021
  }
991
1022
 
992
1023
  .playground__error-dismiss:hover {
@@ -1008,12 +1039,12 @@
1008
1039
  align-items: center;
1009
1040
  justify-content: center;
1010
1041
  flex: 1;
1011
- gap: 1rem;
1042
+ gap: var(--fd-space-xl);
1012
1043
  color: var(--fd-muted-foreground);
1013
1044
  }
1014
1045
 
1015
1046
  :global(.playground__loading-icon) {
1016
- font-size: 2rem;
1047
+ font-size: var(--fd-text-2xl);
1017
1048
  animation: spin 1s linear infinite;
1018
1049
  }
1019
1050