@d34dman/flowdrop 0.0.31 → 0.0.33

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.
@@ -35,6 +35,8 @@
35
35
  onStopExecution?: () => void;
36
36
  /** Whether to show log messages inline (false = hide them) */
37
37
  showLogsInline?: boolean;
38
+ /** Whether to enable markdown rendering in messages */
39
+ enableMarkdown?: boolean;
38
40
  }
39
41
 
40
42
  let {
@@ -43,7 +45,8 @@
43
45
  placeholder = 'Type your message...',
44
46
  onSendMessage,
45
47
  onStopExecution,
46
- showLogsInline = false
48
+ showLogsInline = false,
49
+ enableMarkdown = true
47
50
  }: Props = $props();
48
51
 
49
52
  /** Input field value */
@@ -206,6 +209,7 @@
206
209
  {message}
207
210
  showTimestamp={showTimestamps}
208
211
  isLast={index === displayMessages.length - 1}
212
+ {enableMarkdown}
209
213
  />
210
214
  {/each}
211
215
 
@@ -14,6 +14,8 @@ interface Props {
14
14
  onStopExecution?: () => void;
15
15
  /** Whether to show log messages inline (false = hide them) */
16
16
  showLogsInline?: boolean;
17
+ /** Whether to enable markdown rendering in messages */
18
+ enableMarkdown?: boolean;
17
19
  }
18
20
  declare const ChatPanel: import("svelte").Component<Props, {}, "">;
19
21
  type ChatPanel = ReturnType<typeof ChatPanel>;
@@ -3,11 +3,13 @@
3
3
 
4
4
  Renders individual messages in the playground chat interface.
5
5
  Supports different message roles with distinct styling.
6
+ Supports markdown rendering for message content.
6
7
  Styled with BEM syntax.
7
8
  -->
8
9
 
9
10
  <script lang="ts">
10
11
  import Icon from '@iconify/svelte';
12
+ import { marked } from 'marked';
11
13
  import type { PlaygroundMessage, PlaygroundMessageRole } from '../../types/playground.js';
12
14
 
13
15
  /**
@@ -20,9 +22,20 @@
20
22
  showTimestamp?: boolean;
21
23
  /** Whether this is the last message (affects styling) */
22
24
  isLast?: boolean;
25
+ /** Whether to render markdown content */
26
+ enableMarkdown?: boolean;
23
27
  }
24
28
 
25
- let { message, showTimestamp = true, isLast = false }: Props = $props();
29
+ let { message, showTimestamp = true, isLast = false, enableMarkdown = true }: Props = $props();
30
+
31
+ /**
32
+ * Render content as markdown or plain text
33
+ */
34
+ const renderedContent = $derived(
35
+ enableMarkdown && message.role !== 'log'
36
+ ? marked.parse(message.content || '')
37
+ : message.content
38
+ );
26
39
 
27
40
  /**
28
41
  * Get the icon for the message role
@@ -144,7 +157,13 @@
144
157
 
145
158
  <!-- Message Text -->
146
159
  <div class="message-bubble__text">
147
- {message.content}
160
+ {#if enableMarkdown && message.role !== 'log'}
161
+ <!-- Markdown content - marked.js sanitizes content by default -->
162
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
163
+ {@html renderedContent}
164
+ {:else}
165
+ {message.content}
166
+ {/if}
148
167
  </div>
149
168
 
150
169
  <!-- Metadata Footer -->
@@ -188,32 +207,33 @@
188
207
  }
189
208
  }
190
209
 
191
- /* Role-specific styling */
210
+ /* Role-specific styling - Neutral theme */
192
211
  .message-bubble--user {
193
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
194
- color: #ffffff;
212
+ background-color: #f1f5f9;
213
+ border: 1px solid #e2e8f0;
214
+ color: #1e293b;
195
215
  margin-left: 2rem;
196
216
  flex-direction: row-reverse;
197
217
  }
198
218
 
199
219
  .message-bubble--assistant {
200
- background-color: #f8fafc;
201
- border: 1px solid #e2e8f0;
202
- color: #1e293b;
220
+ background-color: #ffffff;
221
+ border: 1px solid #e5e7eb;
222
+ color: #1f2937;
203
223
  margin-right: 2rem;
204
224
  }
205
225
 
206
226
  .message-bubble--system {
207
- background-color: #fef3c7;
208
- border: 1px solid #fcd34d;
209
- color: #92400e;
227
+ background-color: #f9fafb;
228
+ border: 1px solid #e5e7eb;
229
+ color: #6b7280;
210
230
  margin: 0 1rem;
211
231
  font-size: 0.875rem;
212
232
  }
213
233
 
214
234
  .message-bubble--log {
215
- background-color: #f1f5f9;
216
- border: 1px solid #cbd5e1;
235
+ background-color: #f8fafc;
236
+ border: 1px solid #e2e8f0;
217
237
  color: #475569;
218
238
  margin: 0 1rem;
219
239
  font-size: 0.8125rem;
@@ -249,18 +269,18 @@
249
269
  }
250
270
 
251
271
  .message-bubble--user .message-bubble__avatar {
252
- background-color: rgba(255, 255, 255, 0.2);
253
- color: #ffffff;
272
+ background-color: #e2e8f0;
273
+ color: #475569;
254
274
  }
255
275
 
256
276
  .message-bubble--assistant .message-bubble__avatar {
257
- background-color: #dbeafe;
258
- color: #2563eb;
277
+ background-color: #e5e7eb;
278
+ color: #374151;
259
279
  }
260
280
 
261
281
  .message-bubble--system .message-bubble__avatar {
262
- background-color: #fde68a;
263
- color: #92400e;
282
+ background-color: #f3f4f6;
283
+ color: #6b7280;
264
284
  }
265
285
 
266
286
  .message-bubble--log .message-bubble__avatar {
@@ -292,14 +312,15 @@
292
312
  .message-bubble__role {
293
313
  font-weight: 600;
294
314
  font-size: 0.8125rem;
315
+ color: #374151;
295
316
  }
296
317
 
297
318
  .message-bubble--user .message-bubble__role {
298
- color: rgba(255, 255, 255, 0.9);
319
+ color: #475569;
299
320
  }
300
321
 
301
322
  .message-bubble--assistant .message-bubble__role {
302
- color: #3b82f6;
323
+ color: #374151;
303
324
  }
304
325
 
305
326
  .message-bubble--log .message-bubble__role {
@@ -319,8 +340,8 @@
319
340
  }
320
341
 
321
342
  .message-bubble__log-level--info {
322
- background-color: #dbeafe;
323
- color: #1d4ed8;
343
+ background-color: #e0f2fe;
344
+ color: #0369a1;
324
345
  }
325
346
 
326
347
  .message-bubble__log-level--warning {
@@ -340,24 +361,152 @@
340
361
 
341
362
  .message-bubble__timestamp {
342
363
  font-size: 0.6875rem;
343
- opacity: 0.7;
364
+ color: #9ca3af;
344
365
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
345
366
  }
346
367
 
347
368
  .message-bubble--user .message-bubble__timestamp {
348
- color: rgba(255, 255, 255, 0.7);
369
+ color: #9ca3af;
349
370
  }
350
371
 
351
372
  /* Message text */
352
373
  .message-bubble__text {
353
- line-height: 1.5;
354
- white-space: pre-wrap;
374
+ line-height: 1.6;
355
375
  word-break: break-word;
356
376
  }
357
377
 
358
378
  .message-bubble--log .message-bubble__text {
359
379
  font-size: 0.8125rem;
360
380
  line-height: 1.4;
381
+ white-space: pre-wrap;
382
+ }
383
+
384
+ /* Markdown styling for message content */
385
+ .message-bubble__text :global(p) {
386
+ margin: 0 0 0.75rem 0;
387
+ }
388
+
389
+ .message-bubble__text :global(p:last-child) {
390
+ margin-bottom: 0;
391
+ }
392
+
393
+ .message-bubble__text :global(h1),
394
+ .message-bubble__text :global(h2),
395
+ .message-bubble__text :global(h3),
396
+ .message-bubble__text :global(h4),
397
+ .message-bubble__text :global(h5),
398
+ .message-bubble__text :global(h6) {
399
+ margin: 1rem 0 0.5rem 0;
400
+ font-weight: 600;
401
+ line-height: 1.3;
402
+ }
403
+
404
+ .message-bubble__text :global(h1:first-child),
405
+ .message-bubble__text :global(h2:first-child),
406
+ .message-bubble__text :global(h3:first-child),
407
+ .message-bubble__text :global(h4:first-child),
408
+ .message-bubble__text :global(h5:first-child),
409
+ .message-bubble__text :global(h6:first-child) {
410
+ margin-top: 0;
411
+ }
412
+
413
+ .message-bubble__text :global(h1) {
414
+ font-size: 1.25rem;
415
+ }
416
+
417
+ .message-bubble__text :global(h2) {
418
+ font-size: 1.125rem;
419
+ }
420
+
421
+ .message-bubble__text :global(h3) {
422
+ font-size: 1rem;
423
+ }
424
+
425
+ .message-bubble__text :global(ul),
426
+ .message-bubble__text :global(ol) {
427
+ margin: 0.5rem 0;
428
+ padding-left: 1.5rem;
429
+ }
430
+
431
+ .message-bubble__text :global(li) {
432
+ margin: 0.25rem 0;
433
+ }
434
+
435
+ .message-bubble__text :global(code) {
436
+ background-color: rgba(0, 0, 0, 0.06);
437
+ padding: 0.125rem 0.375rem;
438
+ border-radius: 0.25rem;
439
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
440
+ font-size: 0.875em;
441
+ }
442
+
443
+ .message-bubble__text :global(pre) {
444
+ background-color: #1e293b;
445
+ color: #e2e8f0;
446
+ padding: 0.75rem 1rem;
447
+ border-radius: 0.5rem;
448
+ overflow-x: auto;
449
+ margin: 0.75rem 0;
450
+ font-size: 0.8125rem;
451
+ line-height: 1.5;
452
+ }
453
+
454
+ .message-bubble__text :global(pre code) {
455
+ background-color: transparent;
456
+ padding: 0;
457
+ border-radius: 0;
458
+ color: inherit;
459
+ font-size: inherit;
460
+ }
461
+
462
+ .message-bubble__text :global(blockquote) {
463
+ border-left: 3px solid #d1d5db;
464
+ padding-left: 1rem;
465
+ margin: 0.75rem 0;
466
+ color: #6b7280;
467
+ font-style: italic;
468
+ }
469
+
470
+ .message-bubble__text :global(a) {
471
+ color: #2563eb;
472
+ text-decoration: none;
473
+ }
474
+
475
+ .message-bubble__text :global(a:hover) {
476
+ text-decoration: underline;
477
+ }
478
+
479
+ .message-bubble__text :global(hr) {
480
+ border: none;
481
+ border-top: 1px solid #e5e7eb;
482
+ margin: 1rem 0;
483
+ }
484
+
485
+ .message-bubble__text :global(table) {
486
+ border-collapse: collapse;
487
+ width: 100%;
488
+ margin: 0.75rem 0;
489
+ font-size: 0.875rem;
490
+ }
491
+
492
+ .message-bubble__text :global(th),
493
+ .message-bubble__text :global(td) {
494
+ border: 1px solid #e5e7eb;
495
+ padding: 0.5rem 0.75rem;
496
+ text-align: left;
497
+ }
498
+
499
+ .message-bubble__text :global(th) {
500
+ background-color: #f9fafb;
501
+ font-weight: 600;
502
+ }
503
+
504
+ .message-bubble__text :global(strong) {
505
+ font-weight: 600;
506
+ }
507
+
508
+ .message-bubble__text :global(em) {
509
+ font-style: italic;
361
510
  }
362
511
 
363
512
  /* Footer */
@@ -367,7 +516,7 @@
367
516
  gap: 0.75rem;
368
517
  margin-top: 0.5rem;
369
518
  font-size: 0.6875rem;
370
- opacity: 0.7;
519
+ color: #9ca3af;
371
520
  }
372
521
 
373
522
  .message-bubble--user .message-bubble__footer {
@@ -9,6 +9,8 @@ interface Props {
9
9
  showTimestamp?: boolean;
10
10
  /** Whether this is the last message (affects styling) */
11
11
  isLast?: boolean;
12
+ /** Whether to render markdown content */
13
+ enableMarkdown?: boolean;
12
14
  }
13
15
  declare const MessageBubble: import("svelte").Component<Props, {}, "">;
14
16
  type MessageBubble = ReturnType<typeof MessageBubble>;
@@ -335,15 +335,20 @@
335
335
  playgroundActions.addMessages(response.data);
336
336
  }
337
337
 
338
- // Update session status
339
- if (response.sessionStatus) {
340
- playgroundActions.updateSessionStatus(response.sessionStatus);
341
-
342
- // Stop executing if completed or failed
343
- if (response.sessionStatus === 'completed' || response.sessionStatus === 'failed') {
344
- playgroundActions.setExecuting(false);
345
- }
338
+ // Update session status
339
+ if (response.sessionStatus) {
340
+ playgroundActions.updateSessionStatus(response.sessionStatus);
341
+
342
+ // Stop executing if idle, completed, or failed
343
+ // "idle" means no processing is happening (execution finished)
344
+ if (
345
+ response.sessionStatus === 'idle' ||
346
+ response.sessionStatus === 'completed' ||
347
+ response.sessionStatus === 'failed'
348
+ ) {
349
+ playgroundActions.setExecuting(false);
346
350
  }
351
+ }
347
352
  },
348
353
  pollingInterval
349
354
  );
@@ -383,6 +388,7 @@
383
388
  class="playground"
384
389
  class:playground--embedded={mode === 'embedded'}
385
390
  class:playground--standalone={mode === 'standalone'}
391
+ class:playground--modal={mode === 'modal'}
386
392
  >
387
393
  <div class="playground__container">
388
394
  <!-- Sidebar -->
@@ -393,14 +399,18 @@
393
399
  <Icon icon="mdi:play-circle-outline" />
394
400
  <span>Playground</span>
395
401
  </div>
396
- {#if mode === 'embedded' && onClose}
402
+ {#if (mode === 'embedded' || mode === 'modal') && onClose}
397
403
  <button
398
404
  type="button"
399
405
  class="playground__sidebar-close"
400
406
  onclick={onClose}
401
407
  title="Close playground"
402
408
  >
403
- <Icon icon="mdi:dock-right" />
409
+ {#if mode === 'modal'}
410
+ <Icon icon="mdi:close" />
411
+ {:else}
412
+ <Icon icon="mdi:dock-right" />
413
+ {/if}
404
414
  </button>
405
415
  {/if}
406
416
  </div>
@@ -508,6 +518,7 @@
508
518
  showTimestamps={config.showTimestamps ?? true}
509
519
  autoScroll={config.autoScroll ?? true}
510
520
  showLogsInline={config.logDisplayMode === 'inline'}
521
+ enableMarkdown={config.enableMarkdown ?? true}
511
522
  onSendMessage={handleSendMessage}
512
523
  onStopExecution={handleStopExecution}
513
524
  />
@@ -536,6 +547,11 @@
536
547
  height: 100vh;
537
548
  }
538
549
 
550
+ .playground--modal {
551
+ height: 100%;
552
+ width: 100%;
553
+ }
554
+
539
555
  /* Container */
540
556
  .playground__container {
541
557
  display: flex;
@@ -0,0 +1,220 @@
1
+ <!--
2
+ PlaygroundModal Component
3
+
4
+ Modal wrapper for the Playground component.
5
+ Provides a centered modal dialog with backdrop, similar to Langflow's implementation.
6
+ Supports closing via backdrop click, Escape key, or close button.
7
+ -->
8
+
9
+ <script lang="ts">
10
+ import Icon from "@iconify/svelte";
11
+ import Playground from "./Playground.svelte";
12
+ import type { Workflow } from "../../types/index.js";
13
+ import type { EndpointConfig } from "../../config/endpoints.js";
14
+ import type { PlaygroundConfig } from "../../types/playground.js";
15
+
16
+ /**
17
+ * Component props
18
+ */
19
+ interface Props {
20
+ /** Whether the modal is open */
21
+ isOpen: boolean;
22
+ /** Target workflow ID */
23
+ workflowId: string;
24
+ /** Pre-loaded workflow (optional, will be fetched if not provided) */
25
+ workflow?: Workflow;
26
+ /** Resume a specific session */
27
+ initialSessionId?: string;
28
+ /** API endpoint configuration */
29
+ endpointConfig?: EndpointConfig;
30
+ /** Playground configuration options */
31
+ config?: PlaygroundConfig;
32
+ /** Callback when modal is closed */
33
+ onClose: () => void;
34
+ }
35
+
36
+ let {
37
+ isOpen,
38
+ workflowId,
39
+ workflow,
40
+ initialSessionId,
41
+ endpointConfig,
42
+ config = {},
43
+ onClose
44
+ }: Props = $props();
45
+
46
+ /**
47
+ * Close modal on Escape key
48
+ */
49
+ function handleKeydown(event: KeyboardEvent): void {
50
+ if (event.key === "Escape") {
51
+ onClose();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Close modal when clicking outside (on backdrop)
57
+ */
58
+ function handleBackdropClick(event: MouseEvent): void {
59
+ if (event.target === event.currentTarget) {
60
+ onClose();
61
+ }
62
+ }
63
+ </script>
64
+
65
+ {#if isOpen}
66
+ <!-- Modal Backdrop -->
67
+ <div
68
+ class="playground-modal-backdrop"
69
+ onclick={handleBackdropClick}
70
+ onkeydown={handleKeydown}
71
+ role="dialog"
72
+ aria-modal="true"
73
+ aria-labelledby="playground-modal-title"
74
+ tabindex="-1"
75
+ >
76
+ <!-- Modal Container -->
77
+ <div class="playground-modal" onclick={(e) => e.stopPropagation()}>
78
+ <!-- Modal Header -->
79
+ <div class="playground-modal__header">
80
+ <div class="playground-modal__title" id="playground-modal-title">
81
+ <Icon icon="mdi:play-circle-outline" />
82
+ <span>Playground</span>
83
+ </div>
84
+ <button
85
+ type="button"
86
+ class="playground-modal__close-btn"
87
+ onclick={onClose}
88
+ aria-label="Close playground modal"
89
+ >
90
+ <Icon icon="mdi:close" />
91
+ </button>
92
+ </div>
93
+
94
+ <!-- Modal Content -->
95
+ <div class="playground-modal__content">
96
+ <Playground
97
+ {workflowId}
98
+ {workflow}
99
+ mode="modal"
100
+ {initialSessionId}
101
+ {endpointConfig}
102
+ {config}
103
+ {onClose}
104
+ />
105
+ </div>
106
+ </div>
107
+ </div>
108
+ {/if}
109
+
110
+ <style>
111
+ .playground-modal-backdrop {
112
+ position: fixed;
113
+ top: 0;
114
+ left: 0;
115
+ right: 0;
116
+ bottom: 0;
117
+ background-color: rgba(0, 0, 0, 0.5);
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ z-index: 1100;
122
+ padding: 1rem;
123
+ }
124
+
125
+ .playground-modal {
126
+ background: white;
127
+ border-radius: 0.75rem;
128
+ box-shadow:
129
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
130
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
131
+ width: 100%;
132
+ max-width: 90vw;
133
+ min-width: 800px;
134
+ max-height: 90vh;
135
+ display: flex;
136
+ flex-direction: column;
137
+ overflow: hidden;
138
+ }
139
+
140
+ .playground-modal__header {
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: space-between;
144
+ padding: 1rem 1.25rem;
145
+ border-bottom: 1px solid #e5e7eb;
146
+ background-color: #fafbfc;
147
+ flex-shrink: 0;
148
+ }
149
+
150
+ .playground-modal__title {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 0.5rem;
154
+ font-size: 1rem;
155
+ font-weight: 600;
156
+ color: #1f2937;
157
+ margin: 0;
158
+ }
159
+
160
+ .playground-modal__close-btn {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ width: 2rem;
165
+ height: 2rem;
166
+ border: none;
167
+ background: transparent;
168
+ border-radius: 0.375rem;
169
+ color: #6b7280;
170
+ cursor: pointer;
171
+ transition: all 0.2s;
172
+ }
173
+
174
+ .playground-modal__close-btn:hover {
175
+ background-color: #f3f4f6;
176
+ color: #374151;
177
+ }
178
+
179
+ .playground-modal__content {
180
+ flex: 1;
181
+ min-height: 0;
182
+ display: flex;
183
+ flex-direction: column;
184
+ overflow: hidden;
185
+ padding: 0;
186
+ }
187
+
188
+ /* Responsive adjustments */
189
+ @media (max-width: 1024px) {
190
+ .playground-modal {
191
+ max-width: 95vw;
192
+ min-width: 600px;
193
+ }
194
+ }
195
+
196
+ @media (max-width: 768px) {
197
+ .playground-modal {
198
+ max-width: 100%;
199
+ min-width: auto;
200
+ max-height: 100vh;
201
+ border-radius: 0;
202
+ margin: 0;
203
+ }
204
+
205
+ .playground-modal-backdrop {
206
+ padding: 0;
207
+ }
208
+
209
+ .playground-modal__header {
210
+ padding: 0.875rem 1rem;
211
+ }
212
+ }
213
+
214
+ @media (max-width: 640px) {
215
+ .playground-modal {
216
+ max-width: 100%;
217
+ max-height: 100vh;
218
+ }
219
+ }
220
+ </style>
@@ -0,0 +1,25 @@
1
+ import type { Workflow } from "../../types/index.js";
2
+ import type { EndpointConfig } from "../../config/endpoints.js";
3
+ import type { PlaygroundConfig } from "../../types/playground.js";
4
+ /**
5
+ * Component props
6
+ */
7
+ interface Props {
8
+ /** Whether the modal is open */
9
+ isOpen: boolean;
10
+ /** Target workflow ID */
11
+ workflowId: string;
12
+ /** Pre-loaded workflow (optional, will be fetched if not provided) */
13
+ workflow?: Workflow;
14
+ /** Resume a specific session */
15
+ initialSessionId?: string;
16
+ /** API endpoint configuration */
17
+ endpointConfig?: EndpointConfig;
18
+ /** Playground configuration options */
19
+ config?: PlaygroundConfig;
20
+ /** Callback when modal is closed */
21
+ onClose: () => void;
22
+ }
23
+ declare const PlaygroundModal: import("svelte").Component<Props, {}, "">;
24
+ type PlaygroundModal = ReturnType<typeof PlaygroundModal>;
25
+ export default PlaygroundModal;
@@ -54,6 +54,7 @@ export { default as PipelineStatus } from '../components/PipelineStatus.svelte';
54
54
  export { default as Navbar } from '../components/Navbar.svelte';
55
55
  export { default as Logo } from '../components/Logo.svelte';
56
56
  export { default as Playground } from '../components/playground/Playground.svelte';
57
+ export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
57
58
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
58
59
  export { default as SessionManager } from '../components/playground/SessionManager.svelte';
59
60
  export { default as InputCollector } from '../components/playground/InputCollector.svelte';
@@ -69,6 +69,7 @@ export { default as Navbar } from '../components/Navbar.svelte';
69
69
  export { default as Logo } from '../components/Logo.svelte';
70
70
  // Playground Components
71
71
  export { default as Playground } from '../components/playground/Playground.svelte';
72
+ export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
72
73
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
73
74
  export { default as SessionManager } from '../components/playground/SessionManager.svelte';
74
75
  export { default as InputCollector } from '../components/playground/InputCollector.svelte';
@@ -77,8 +77,41 @@
77
77
  * />
78
78
  * {/if}
79
79
  * ```
80
+ *
81
+ * @example In Svelte (Modal mode):
82
+ * ```svelte
83
+ * <script>
84
+ * import { PlaygroundModal } from "@d34dman/flowdrop/playground";
85
+ * let showPlayground = false;
86
+ * </script>
87
+ *
88
+ * <PlaygroundModal
89
+ * isOpen={showPlayground}
90
+ * workflowId="wf-123"
91
+ * workflow={myWorkflow}
92
+ * onClose={() => showPlayground = false}
93
+ * />
94
+ * ```
95
+ *
96
+ * @example Using mountPlayground with modal mode:
97
+ * ```typescript
98
+ * import { mountPlayground, createEndpointConfig } from "@d34dman/flowdrop/playground";
99
+ *
100
+ * const app = await mountPlayground(
101
+ * document.getElementById("playground-container"),
102
+ * {
103
+ * workflowId: "wf-123",
104
+ * endpointConfig: createEndpointConfig("/api/flowdrop"),
105
+ * mode: "modal",
106
+ * onClose: () => {
107
+ * app.destroy();
108
+ * }
109
+ * }
110
+ * );
111
+ * ```
80
112
  */
81
113
  export { default as Playground } from '../components/playground/Playground.svelte';
114
+ export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
82
115
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
83
116
  export { default as SessionManager } from '../components/playground/SessionManager.svelte';
84
117
  export { default as InputCollector } from '../components/playground/InputCollector.svelte';
@@ -77,11 +77,44 @@
77
77
  * />
78
78
  * {/if}
79
79
  * ```
80
+ *
81
+ * @example In Svelte (Modal mode):
82
+ * ```svelte
83
+ * <script>
84
+ * import { PlaygroundModal } from "@d34dman/flowdrop/playground";
85
+ * let showPlayground = false;
86
+ * </script>
87
+ *
88
+ * <PlaygroundModal
89
+ * isOpen={showPlayground}
90
+ * workflowId="wf-123"
91
+ * workflow={myWorkflow}
92
+ * onClose={() => showPlayground = false}
93
+ * />
94
+ * ```
95
+ *
96
+ * @example Using mountPlayground with modal mode:
97
+ * ```typescript
98
+ * import { mountPlayground, createEndpointConfig } from "@d34dman/flowdrop/playground";
99
+ *
100
+ * const app = await mountPlayground(
101
+ * document.getElementById("playground-container"),
102
+ * {
103
+ * workflowId: "wf-123",
104
+ * endpointConfig: createEndpointConfig("/api/flowdrop"),
105
+ * mode: "modal",
106
+ * onClose: () => {
107
+ * app.destroy();
108
+ * }
109
+ * }
110
+ * );
111
+ * ```
80
112
  */
81
113
  // ============================================================================
82
114
  // Playground Components
83
115
  // ============================================================================
84
116
  export { default as Playground } from '../components/playground/Playground.svelte';
117
+ export { default as PlaygroundModal } from '../components/playground/PlaygroundModal.svelte';
85
118
  export { default as ChatPanel } from '../components/playground/ChatPanel.svelte';
86
119
  export { default as SessionManager } from '../components/playground/SessionManager.svelte';
87
120
  export { default as InputCollector } from '../components/playground/InputCollector.svelte';
@@ -64,6 +64,7 @@ export interface PlaygroundMountOptions {
64
64
  * Display mode
65
65
  * - "standalone": Full-page playground experience
66
66
  * - "embedded": Panel mode for embedding alongside other content
67
+ * - "modal": Modal dialog mode with backdrop
67
68
  * @default "standalone"
68
69
  */
69
70
  mode?: PlaygroundMode;
@@ -92,7 +93,7 @@ export interface PlaygroundMountOptions {
92
93
  */
93
94
  width?: string;
94
95
  /**
95
- * Callback when playground is closed (for embedded mode)
96
+ * Callback when playground is closed (required for embedded and modal modes)
96
97
  */
97
98
  onClose?: () => void;
98
99
  }
@@ -45,6 +45,7 @@
45
45
  */
46
46
  import { mount, unmount } from "svelte";
47
47
  import Playground from "../components/playground/Playground.svelte";
48
+ import PlaygroundModal from "../components/playground/PlaygroundModal.svelte";
48
49
  import { setEndpointConfig } from "../services/api.js";
49
50
  import { playgroundService } from "../services/playgroundService.js";
50
51
  import { currentSession, sessions, messages, playgroundActions } from "../stores/playgroundStore.js";
@@ -87,6 +88,10 @@ export async function mountPlayground(container, options) {
87
88
  if (!container) {
88
89
  throw new Error("container element is required for mountPlayground()");
89
90
  }
91
+ // Validate onClose for modal mode
92
+ if (mode === "modal" && !onClose) {
93
+ throw new Error("onClose callback is required for modal mode");
94
+ }
90
95
  // Set endpoint configuration if provided
91
96
  let finalEndpointConfig;
92
97
  if (endpointConfig) {
@@ -102,26 +107,52 @@ export async function mountPlayground(container, options) {
102
107
  };
103
108
  setEndpointConfig(finalEndpointConfig);
104
109
  }
105
- // Apply container styling
106
- container.style.height = height;
107
- container.style.width = width;
108
- // Mount the Svelte Playground component
109
- const svelteApp = mount(Playground, {
110
- target: container,
111
- props: {
112
- workflowId,
113
- workflow,
114
- mode,
115
- initialSessionId,
116
- endpointConfig: finalEndpointConfig,
117
- config,
118
- onClose
119
- }
110
+ // Handle modal mode differently
111
+ // For modal mode, PlaygroundModal creates its own backdrop, so we mount directly to body
112
+ // For other modes, use the provided container
113
+ let targetContainer = container;
114
+ if (mode === "modal") {
115
+ // For modal mode, create a container in the body
116
+ // PlaygroundModal will handle the backdrop itself
117
+ targetContainer = document.body;
118
+ }
119
+ else {
120
+ // Apply container styling for non-modal modes
121
+ container.style.height = height;
122
+ container.style.width = width;
123
+ }
124
+ // Mount the appropriate component
125
+ const svelteApp = mount(mode === "modal" ? PlaygroundModal : Playground, {
126
+ target: targetContainer,
127
+ props: mode === "modal"
128
+ ? {
129
+ isOpen: true,
130
+ workflowId,
131
+ workflow,
132
+ initialSessionId,
133
+ endpointConfig: finalEndpointConfig,
134
+ config,
135
+ onClose: () => {
136
+ if (onClose) {
137
+ onClose();
138
+ }
139
+ }
140
+ }
141
+ : {
142
+ workflowId,
143
+ workflow,
144
+ mode,
145
+ initialSessionId,
146
+ endpointConfig: finalEndpointConfig,
147
+ config,
148
+ onClose
149
+ }
120
150
  });
121
151
  // Store state for cleanup
122
152
  const state = {
123
153
  svelteApp,
124
- container,
154
+ container: targetContainer,
155
+ originalContainer: mode === "modal" ? container : undefined,
125
156
  workflowId
126
157
  };
127
158
  // Create the mounted playground interface
@@ -263,8 +263,11 @@ export class PlaygroundService {
263
263
  this.currentBackoff = interval;
264
264
  // Call the callback with new messages
265
265
  callback(response);
266
- // Stop polling if session is completed or failed
267
- if (response.sessionStatus === 'completed' || response.sessionStatus === 'failed') {
266
+ // Stop polling if session is idle, completed, or failed
267
+ // "idle" means no processing is happening (execution finished)
268
+ if (response.sessionStatus === 'idle' ||
269
+ response.sessionStatus === 'completed' ||
270
+ response.sessionStatus === 'failed') {
268
271
  this.stopPolling();
269
272
  return;
270
273
  }
@@ -178,11 +178,13 @@ export interface PlaygroundConfig {
178
178
  showTimestamps?: boolean;
179
179
  /** Show log messages inline or in collapsible section (default: "collapsible") */
180
180
  logDisplayMode?: 'inline' | 'collapsible';
181
+ /** Enable markdown rendering in messages (default: true) */
182
+ enableMarkdown?: boolean;
181
183
  }
182
184
  /**
183
185
  * Display mode for the Playground component
184
186
  */
185
- export type PlaygroundMode = 'embedded' | 'standalone';
187
+ export type PlaygroundMode = 'embedded' | 'standalone' | 'modal';
186
188
  /**
187
189
  * Chat input detection patterns for identifying chat nodes in workflows
188
190
  */
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@d34dman/flowdrop",
3
3
  "license": "MIT",
4
4
  "private": false,
5
- "version": "0.0.31",
5
+ "version": "0.0.33",
6
6
  "scripts": {
7
7
  "dev": "vite dev",
8
8
  "build": "vite build && npm run prepack",