@d34dman/flowdrop 0.0.38 → 0.0.40
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.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/components/playground/ChatPanel.svelte +232 -40
- package/dist/components/playground/ChatPanel.svelte.d.ts +15 -0
- package/dist/components/playground/Playground.svelte +82 -2
- package/dist/config/endpoints.js +2 -1
- package/dist/types/interrupt.d.ts +2 -0
- package/dist/types/interrupt.js +1 -0
- package/dist/types/playground.d.ts +50 -0
- package/dist/types/playground.js +29 -0
- package/package.json +8 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shibin Das (d34dman)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">FlowDrop</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<img src="https://img.shields.io/github/actions/workflow/status/
|
|
8
|
+
<img src="https://img.shields.io/github/actions/workflow/status/flowdrop-io/flowdrop/docker-publish.yml?style=flat-square&label=Build" alt="GitHub pages build status" />
|
|
9
9
|
<a href="https://www.npmjs.com/package/@d34dman/flowdrop"><img src="https://img.shields.io/npm/v/@d34dman/flowdrop?style=flat-square" alt="npm" /></a>
|
|
10
10
|
<img src="https://img.shields.io/npm/unpacked-size/%40d34dman%2Fflowdrop?style=flat-square" alt="NPM Unpacked Size" />
|
|
11
11
|
<img src="https://img.shields.io/npm/types/@d34dman/flowdrop?style=flat-square" alt="npm type definitions" />
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import MessageBubble from './MessageBubble.svelte';
|
|
13
13
|
import { InterruptBubble } from '../interrupt/index.js';
|
|
14
14
|
import type { PlaygroundMessage } from '../../types/playground.js';
|
|
15
|
+
import { hasEnableRunFlag } from '../../types/playground.js';
|
|
15
16
|
import {
|
|
16
17
|
isInterruptMetadata,
|
|
17
18
|
extractInterruptMetadata,
|
|
@@ -50,6 +51,21 @@
|
|
|
50
51
|
enableMarkdown?: boolean;
|
|
51
52
|
/** Callback when an interrupt is resolved (to refresh messages) */
|
|
52
53
|
onInterruptResolved?: () => void;
|
|
54
|
+
/**
|
|
55
|
+
* Whether to show the chat text input (default: true)
|
|
56
|
+
* When false, only the "Run" button is displayed.
|
|
57
|
+
*/
|
|
58
|
+
showChatInput?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Whether to show the "Run" button (default: true)
|
|
61
|
+
* When false, the Run button is hidden.
|
|
62
|
+
*/
|
|
63
|
+
showRunButton?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Predefined message to send when "Run" button is clicked
|
|
66
|
+
* Used when showChatInput is false.
|
|
67
|
+
*/
|
|
68
|
+
predefinedMessage?: string;
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
let {
|
|
@@ -60,9 +76,25 @@
|
|
|
60
76
|
onStopExecution,
|
|
61
77
|
showLogsInline = false,
|
|
62
78
|
enableMarkdown = true,
|
|
63
|
-
onInterruptResolved
|
|
79
|
+
onInterruptResolved,
|
|
80
|
+
showChatInput = true,
|
|
81
|
+
showRunButton = true,
|
|
82
|
+
predefinedMessage = 'Run workflow'
|
|
64
83
|
}: Props = $props();
|
|
65
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Tracks whether the Run button is enabled.
|
|
87
|
+
* Starts as true, becomes false after Run is clicked,
|
|
88
|
+
* and is re-enabled when backend sends a message with enableRun: true metadata.
|
|
89
|
+
*/
|
|
90
|
+
let runEnabled = $state(true);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Computed flag: true if both chat input and run button are hidden.
|
|
94
|
+
* In this case, we show a helpful message to the user.
|
|
95
|
+
*/
|
|
96
|
+
const noInputsAvailable = $derived(!showChatInput && !showRunButton);
|
|
97
|
+
|
|
66
98
|
/** Input field value */
|
|
67
99
|
let inputValue = $state('');
|
|
68
100
|
|
|
@@ -89,6 +121,10 @@
|
|
|
89
121
|
* This effect runs when messages change and adds any new interrupt messages
|
|
90
122
|
* to the interrupt store. We do this in an effect rather than during render
|
|
91
123
|
* to avoid Svelte 5's state_unsafe_mutation error.
|
|
124
|
+
*
|
|
125
|
+
* If a message has status 'completed', the interrupt is marked as resolved
|
|
126
|
+
* to show the "Confirmation Submitted" header, disabled buttons, and
|
|
127
|
+
* "Response submitted" indicator.
|
|
92
128
|
*/
|
|
93
129
|
$effect(() => {
|
|
94
130
|
// Get all messages that are interrupt requests
|
|
@@ -105,6 +141,15 @@
|
|
|
105
141
|
if (metadata) {
|
|
106
142
|
const interrupt = metadataToInterrupt(metadata, message.id, message.content);
|
|
107
143
|
interruptActions.addInterrupt(interrupt);
|
|
144
|
+
|
|
145
|
+
// If the message status is 'completed', mark the interrupt as resolved
|
|
146
|
+
// This ensures completed interrupts show proper UI state:
|
|
147
|
+
// - "Confirmation Submitted" header
|
|
148
|
+
// - Disabled buttons
|
|
149
|
+
// - "Response submitted" indicator
|
|
150
|
+
if (message.status === 'completed') {
|
|
151
|
+
interruptActions.resolveInterrupt(interrupt.id, metadata.response_value);
|
|
152
|
+
}
|
|
108
153
|
}
|
|
109
154
|
}
|
|
110
155
|
}
|
|
@@ -179,6 +224,61 @@
|
|
|
179
224
|
onStopExecution?.();
|
|
180
225
|
}
|
|
181
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Handle "Run" button click when chat input is hidden.
|
|
229
|
+
* Sends the predefined message to execute the workflow.
|
|
230
|
+
* Disables the Run button after clicking until backend re-enables it.
|
|
231
|
+
*/
|
|
232
|
+
function handleRun(): void {
|
|
233
|
+
if ($isExecuting || !runEnabled) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Disable the Run button after clicking
|
|
237
|
+
runEnabled = false;
|
|
238
|
+
onSendMessage?.(predefinedMessage);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Track processed message IDs for enableRun detection
|
|
243
|
+
* to avoid re-processing the same messages.
|
|
244
|
+
*/
|
|
245
|
+
let processedEnableRunIds = $state(new Set<string>());
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Watch for messages with enableRun: true metadata from the backend.
|
|
249
|
+
* When detected, re-enable the Run button.
|
|
250
|
+
*/
|
|
251
|
+
$effect(() => {
|
|
252
|
+
// Check all messages for enableRun flag
|
|
253
|
+
for (const message of displayMessages) {
|
|
254
|
+
// Skip if already processed
|
|
255
|
+
if (processedEnableRunIds.has(message.id)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
// Check if this message has the enableRun flag
|
|
259
|
+
if (hasEnableRunFlag(message.metadata)) {
|
|
260
|
+
// Mark as processed
|
|
261
|
+
processedEnableRunIds = new Set([...processedEnableRunIds, message.id]);
|
|
262
|
+
// Re-enable the Run button
|
|
263
|
+
runEnabled = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reset runEnabled state when session changes.
|
|
270
|
+
* This ensures a fresh state for each session.
|
|
271
|
+
*/
|
|
272
|
+
$effect(() => {
|
|
273
|
+
const session = $currentSession;
|
|
274
|
+
if (session) {
|
|
275
|
+
// Reset to enabled state for new/changed sessions
|
|
276
|
+
runEnabled = true;
|
|
277
|
+
// Clear processed IDs for the new session
|
|
278
|
+
processedEnableRunIds = new Set();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
182
282
|
/**
|
|
183
283
|
* Auto-scroll to bottom when messages change
|
|
184
284
|
*/
|
|
@@ -280,8 +380,18 @@
|
|
|
280
380
|
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
281
381
|
</svg>
|
|
282
382
|
</div>
|
|
283
|
-
|
|
284
|
-
|
|
383
|
+
{#if noInputsAvailable}
|
|
384
|
+
<h2 class="chat-panel__welcome-title">View only</h2>
|
|
385
|
+
<p class="chat-panel__welcome-text">
|
|
386
|
+
This playground is in view-only mode. No inputs are available.
|
|
387
|
+
</p>
|
|
388
|
+
{:else if showChatInput}
|
|
389
|
+
<h2 class="chat-panel__welcome-title">New chat</h2>
|
|
390
|
+
<p class="chat-panel__welcome-text">Test your flow with a chat prompt</p>
|
|
391
|
+
{:else}
|
|
392
|
+
<h2 class="chat-panel__welcome-title">Ready to run</h2>
|
|
393
|
+
<p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
|
|
394
|
+
{/if}
|
|
285
395
|
</div>
|
|
286
396
|
{:else if showEmptyChat}
|
|
287
397
|
<!-- Empty Chat State (session exists but no messages) -->
|
|
@@ -311,8 +421,18 @@
|
|
|
311
421
|
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
312
422
|
</svg>
|
|
313
423
|
</div>
|
|
314
|
-
|
|
315
|
-
|
|
424
|
+
{#if noInputsAvailable}
|
|
425
|
+
<h2 class="chat-panel__welcome-title">View only</h2>
|
|
426
|
+
<p class="chat-panel__welcome-text">
|
|
427
|
+
This playground is in view-only mode. No inputs are available.
|
|
428
|
+
</p>
|
|
429
|
+
{:else if showChatInput}
|
|
430
|
+
<h2 class="chat-panel__welcome-title">New chat</h2>
|
|
431
|
+
<p class="chat-panel__welcome-text">Test your flow with a chat prompt</p>
|
|
432
|
+
{:else}
|
|
433
|
+
<h2 class="chat-panel__welcome-title">Ready to run</h2>
|
|
434
|
+
<p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
|
|
435
|
+
{/if}
|
|
316
436
|
</div>
|
|
317
437
|
{:else}
|
|
318
438
|
<!-- Messages -->
|
|
@@ -352,42 +472,66 @@
|
|
|
352
472
|
|
|
353
473
|
<!-- Input Area -->
|
|
354
474
|
<div class="chat-panel__input-area">
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
class="chat-panel__input"
|
|
361
|
-
{placeholder}
|
|
362
|
-
rows="1"
|
|
363
|
-
disabled={$isExecuting}
|
|
364
|
-
onkeydown={handleKeydown}
|
|
365
|
-
oninput={handleInput}
|
|
366
|
-
></textarea>
|
|
475
|
+
{#if noInputsAvailable}
|
|
476
|
+
<!-- No inputs available - show informational message -->
|
|
477
|
+
<div class="chat-panel__no-inputs">
|
|
478
|
+
<Icon icon="mdi:information-outline" />
|
|
479
|
+
<span>View-only mode. Workflow execution is controlled externally.</span>
|
|
367
480
|
</div>
|
|
481
|
+
{:else}
|
|
482
|
+
<div
|
|
483
|
+
class="chat-panel__input-container"
|
|
484
|
+
class:chat-panel__input-container--run-only={!showChatInput}
|
|
485
|
+
>
|
|
486
|
+
{#if showChatInput}
|
|
487
|
+
<div class="chat-panel__input-wrapper">
|
|
488
|
+
<textarea
|
|
489
|
+
bind:this={inputField}
|
|
490
|
+
bind:value={inputValue}
|
|
491
|
+
class="chat-panel__input"
|
|
492
|
+
{placeholder}
|
|
493
|
+
rows="1"
|
|
494
|
+
disabled={$isExecuting}
|
|
495
|
+
onkeydown={handleKeydown}
|
|
496
|
+
oninput={handleInput}
|
|
497
|
+
></textarea>
|
|
498
|
+
</div>
|
|
499
|
+
{/if}
|
|
368
500
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
501
|
+
{#if $sessionStatus === 'running' || $isExecuting}
|
|
502
|
+
<button
|
|
503
|
+
type="button"
|
|
504
|
+
class="chat-panel__stop-btn"
|
|
505
|
+
onclick={handleStop}
|
|
506
|
+
title="Stop execution"
|
|
507
|
+
>
|
|
508
|
+
<Icon icon="mdi:stop" />
|
|
509
|
+
Stop
|
|
510
|
+
</button>
|
|
511
|
+
{:else if showChatInput}
|
|
512
|
+
<button
|
|
513
|
+
type="button"
|
|
514
|
+
class="chat-panel__send-btn"
|
|
515
|
+
onclick={handleSend}
|
|
516
|
+
disabled={!inputValue.trim()}
|
|
517
|
+
title="Send message"
|
|
518
|
+
>
|
|
519
|
+
Send
|
|
520
|
+
</button>
|
|
521
|
+
{:else if showRunButton}
|
|
522
|
+
<button
|
|
523
|
+
type="button"
|
|
524
|
+
class="chat-panel__run-btn"
|
|
525
|
+
onclick={handleRun}
|
|
526
|
+
disabled={!runEnabled}
|
|
527
|
+
title={runEnabled ? 'Run workflow' : 'Waiting for workflow to be ready...'}
|
|
528
|
+
>
|
|
529
|
+
<Icon icon="mdi:play" />
|
|
530
|
+
Run
|
|
531
|
+
</button>
|
|
532
|
+
{/if}
|
|
533
|
+
</div>
|
|
534
|
+
{/if}
|
|
391
535
|
</div>
|
|
392
536
|
</div>
|
|
393
537
|
|
|
@@ -603,6 +747,53 @@
|
|
|
603
747
|
background-color: #dc2626;
|
|
604
748
|
}
|
|
605
749
|
|
|
750
|
+
/* Run button (when chat input is hidden) */
|
|
751
|
+
.chat-panel__run-btn {
|
|
752
|
+
display: flex;
|
|
753
|
+
align-items: center;
|
|
754
|
+
gap: 0.375rem;
|
|
755
|
+
padding: 0.625rem 1.25rem;
|
|
756
|
+
border: none;
|
|
757
|
+
border-radius: 0.5rem;
|
|
758
|
+
background-color: #10b981;
|
|
759
|
+
color: #ffffff;
|
|
760
|
+
font-size: 0.875rem;
|
|
761
|
+
font-weight: 500;
|
|
762
|
+
cursor: pointer;
|
|
763
|
+
transition: all 0.15s ease;
|
|
764
|
+
flex-shrink: 0;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.chat-panel__run-btn:hover:not(:disabled) {
|
|
768
|
+
background-color: #059669;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.chat-panel__run-btn:disabled {
|
|
772
|
+
background-color: #e5e7eb;
|
|
773
|
+
color: #9ca3af;
|
|
774
|
+
cursor: not-allowed;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/* Container modifier for run-only mode (no text input) */
|
|
778
|
+
.chat-panel__input-container--run-only {
|
|
779
|
+
justify-content: flex-end;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/* No inputs available message (view-only mode) */
|
|
783
|
+
.chat-panel__no-inputs {
|
|
784
|
+
display: flex;
|
|
785
|
+
align-items: center;
|
|
786
|
+
justify-content: center;
|
|
787
|
+
gap: 0.5rem;
|
|
788
|
+
padding: 0.75rem 1rem;
|
|
789
|
+
background-color: #f3f4f6;
|
|
790
|
+
border-radius: 0.5rem;
|
|
791
|
+
color: #6b7280;
|
|
792
|
+
font-size: 0.875rem;
|
|
793
|
+
max-width: 800px;
|
|
794
|
+
margin: 0 auto;
|
|
795
|
+
}
|
|
796
|
+
|
|
606
797
|
/* Responsive */
|
|
607
798
|
@media (max-width: 640px) {
|
|
608
799
|
.chat-panel__messages {
|
|
@@ -618,7 +809,8 @@
|
|
|
618
809
|
}
|
|
619
810
|
|
|
620
811
|
.chat-panel__send-btn,
|
|
621
|
-
.chat-panel__stop-btn
|
|
812
|
+
.chat-panel__stop-btn,
|
|
813
|
+
.chat-panel__run-btn {
|
|
622
814
|
padding: 0.5rem 1rem;
|
|
623
815
|
}
|
|
624
816
|
}
|
|
@@ -18,6 +18,21 @@ interface Props {
|
|
|
18
18
|
enableMarkdown?: boolean;
|
|
19
19
|
/** Callback when an interrupt is resolved (to refresh messages) */
|
|
20
20
|
onInterruptResolved?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to show the chat text input (default: true)
|
|
23
|
+
* When false, only the "Run" button is displayed.
|
|
24
|
+
*/
|
|
25
|
+
showChatInput?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to show the "Run" button (default: true)
|
|
28
|
+
* When false, the Run button is hidden.
|
|
29
|
+
*/
|
|
30
|
+
showRunButton?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Predefined message to send when "Run" button is clicked
|
|
33
|
+
* Used when showChatInput is false.
|
|
34
|
+
*/
|
|
35
|
+
predefinedMessage?: string;
|
|
21
36
|
}
|
|
22
37
|
declare const ChatPanel: import("svelte").Component<Props, {}, "">;
|
|
23
38
|
type ChatPanel = ReturnType<typeof ChatPanel>;
|
|
@@ -73,8 +73,17 @@
|
|
|
73
73
|
/** Track session pending delete */
|
|
74
74
|
let pendingDeleteId = $state<string | null>(null);
|
|
75
75
|
|
|
76
|
+
/** Track if initial session has been loaded to prevent duplicate loads */
|
|
77
|
+
let initialSessionLoaded = $state(false);
|
|
78
|
+
|
|
79
|
+
/** Track the session ID that was loaded to detect prop changes */
|
|
80
|
+
let loadedInitialSessionId = $state<string | undefined>(undefined);
|
|
81
|
+
|
|
82
|
+
/** Track if auto-run has already been triggered to prevent duplicate executions */
|
|
83
|
+
let autoRunTriggered = $state(false);
|
|
84
|
+
|
|
76
85
|
/**
|
|
77
|
-
* Initialize the playground
|
|
86
|
+
* Initialize the playground on mount
|
|
78
87
|
*/
|
|
79
88
|
onMount(() => {
|
|
80
89
|
// Set endpoint config if provided
|
|
@@ -95,7 +104,15 @@
|
|
|
95
104
|
|
|
96
105
|
// Resume initial session if provided
|
|
97
106
|
if (initialSessionId) {
|
|
98
|
-
await
|
|
107
|
+
await loadInitialSession(initialSessionId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle auto-run after initialization is complete
|
|
111
|
+
if (config.autoRun && !autoRunTriggered) {
|
|
112
|
+
autoRunTriggered = true;
|
|
113
|
+
const predefinedMessage = config.predefinedMessage ?? 'Run workflow';
|
|
114
|
+
console.log('[Playground] Auto-run triggered with message:', predefinedMessage);
|
|
115
|
+
await handleSendMessage(predefinedMessage);
|
|
99
116
|
}
|
|
100
117
|
} catch (err) {
|
|
101
118
|
console.error('[Playground] Initialization error:', err);
|
|
@@ -106,6 +123,66 @@
|
|
|
106
123
|
void initializePlayground();
|
|
107
124
|
});
|
|
108
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Handle reactive changes to initialSessionId prop
|
|
128
|
+
* This allows the initial session to be set after mount
|
|
129
|
+
*/
|
|
130
|
+
$effect(() => {
|
|
131
|
+
// Skip if no initialSessionId provided
|
|
132
|
+
if (!initialSessionId) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Skip if this session was already loaded
|
|
137
|
+
if (initialSessionLoaded && loadedInitialSessionId === initialSessionId) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 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)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Load the initial session if sessions are available
|
|
148
|
+
if (sessionList.length > 0 && !initialSessionLoaded) {
|
|
149
|
+
void loadInitialSession(initialSessionId);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Load the initial session with validation and error handling
|
|
155
|
+
*
|
|
156
|
+
* @param sessionId - The session ID to load
|
|
157
|
+
*/
|
|
158
|
+
async function loadInitialSession(sessionId: string): Promise<void> {
|
|
159
|
+
// Validate session exists in loaded sessions
|
|
160
|
+
const sessionList = get(sessions);
|
|
161
|
+
const sessionExists = sessionList.some((s) => s.id === sessionId);
|
|
162
|
+
|
|
163
|
+
if (!sessionExists) {
|
|
164
|
+
console.warn(
|
|
165
|
+
`[Playground] Initial session "${sessionId}" not found in available sessions. ` +
|
|
166
|
+
`Available sessions: ${sessionList.map((s) => s.id).join(', ') || 'none'}`
|
|
167
|
+
);
|
|
168
|
+
// Don't set error - just log warning and let user pick a session
|
|
169
|
+
initialSessionLoaded = true;
|
|
170
|
+
loadedInitialSessionId = sessionId;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await loadSession(sessionId);
|
|
176
|
+
initialSessionLoaded = true;
|
|
177
|
+
loadedInitialSessionId = sessionId;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error('[Playground] Failed to load initial session:', err);
|
|
180
|
+
// Mark as attempted to prevent retry loops
|
|
181
|
+
initialSessionLoaded = true;
|
|
182
|
+
loadedInitialSessionId = sessionId;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
109
186
|
/**
|
|
110
187
|
* Cleanup on destroy
|
|
111
188
|
*/
|
|
@@ -563,6 +640,9 @@
|
|
|
563
640
|
autoScroll={config.autoScroll ?? true}
|
|
564
641
|
showLogsInline={config.logDisplayMode === 'inline'}
|
|
565
642
|
enableMarkdown={config.enableMarkdown ?? true}
|
|
643
|
+
showChatInput={config.showChatInput ?? true}
|
|
644
|
+
showRunButton={config.showRunButton ?? true}
|
|
645
|
+
predefinedMessage={config.predefinedMessage ?? 'Run workflow'}
|
|
566
646
|
onSendMessage={handleSendMessage}
|
|
567
647
|
onStopExecution={handleStopExecution}
|
|
568
648
|
onInterruptResolved={handleInterruptResolved}
|
package/dist/config/endpoints.js
CHANGED
|
@@ -71,7 +71,8 @@ export const defaultEndpointConfig = {
|
|
|
71
71
|
preferences: '/users/preferences'
|
|
72
72
|
},
|
|
73
73
|
system: {
|
|
74
|
-
|
|
74
|
+
/** Health check at root level (industry standard for K8s, Docker, load balancers) */
|
|
75
|
+
health: '/health',
|
|
75
76
|
config: '/system/config',
|
|
76
77
|
version: '/system/version'
|
|
77
78
|
}
|
|
@@ -244,6 +244,8 @@ export interface InterruptMessageMetadata {
|
|
|
244
244
|
options?: InterruptChoice[];
|
|
245
245
|
/** Default value for pre-filling */
|
|
246
246
|
default_value?: unknown;
|
|
247
|
+
/** Response value when interrupt has been resolved */
|
|
248
|
+
response_value?: unknown;
|
|
247
249
|
/** Node ID that triggered the interrupt */
|
|
248
250
|
node_id?: string;
|
|
249
251
|
/** Execution ID */
|
package/dist/types/interrupt.js
CHANGED
|
@@ -36,6 +36,7 @@ export function extractInterruptMetadata(metadata) {
|
|
|
36
36
|
schema: metadata.schema,
|
|
37
37
|
options: metadata.options,
|
|
38
38
|
default_value: metadata.default_value,
|
|
39
|
+
response_value: metadata.response_value,
|
|
39
40
|
node_id: metadata.node_id,
|
|
40
41
|
execution_id: metadata.execution_id,
|
|
41
42
|
allow_cancel: metadata.allow_cancel,
|
|
@@ -194,7 +194,57 @@ export interface PlaygroundConfig {
|
|
|
194
194
|
logDisplayMode?: 'inline' | 'collapsible';
|
|
195
195
|
/** Enable markdown rendering in messages (default: true) */
|
|
196
196
|
enableMarkdown?: boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Whether to show the chat text input (default: true)
|
|
199
|
+
* When false, only the "Run" button is displayed for workflow execution.
|
|
200
|
+
*/
|
|
201
|
+
showChatInput?: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* Whether to show the "Run" button (default: true)
|
|
204
|
+
* When false, the Run button is hidden. If both showChatInput and showRunButton
|
|
205
|
+
* are false, a helpful message is displayed to the user.
|
|
206
|
+
*/
|
|
207
|
+
showRunButton?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Predefined message to send when "Run" button is clicked (default: "Run workflow")
|
|
210
|
+
* Used when showChatInput is false to provide a default message for workflow execution.
|
|
211
|
+
*/
|
|
212
|
+
predefinedMessage?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Automatically run the workflow once when the playground loads (default: false)
|
|
215
|
+
* When true, the workflow will execute immediately using the predefinedMessage.
|
|
216
|
+
* This is useful for scenarios where the workflow should start without user interaction.
|
|
217
|
+
* Note: Only runs once per session - subsequent runs require clicking the Run button.
|
|
218
|
+
*/
|
|
219
|
+
autoRun?: boolean;
|
|
197
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Metadata field to control Run button state from backend.
|
|
223
|
+
* When a message contains this field set to true, the Run button becomes enabled.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* // Backend sends a message with this metadata to re-enable Run button:
|
|
228
|
+
* const message: PlaygroundMessage = {
|
|
229
|
+
* id: "msg-123",
|
|
230
|
+
* sessionId: "sess-456",
|
|
231
|
+
* role: "system",
|
|
232
|
+
* content: "Workflow completed. Ready for next run.",
|
|
233
|
+
* timestamp: new Date().toISOString(),
|
|
234
|
+
* metadata: {
|
|
235
|
+
* enableRun: true
|
|
236
|
+
* }
|
|
237
|
+
* };
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export declare const ENABLE_RUN_METADATA_KEY = "enableRun";
|
|
241
|
+
/**
|
|
242
|
+
* Check if a message metadata contains the enableRun flag
|
|
243
|
+
*
|
|
244
|
+
* @param metadata - The message metadata to check
|
|
245
|
+
* @returns True if the metadata signals to enable the Run button
|
|
246
|
+
*/
|
|
247
|
+
export declare function hasEnableRunFlag(metadata: PlaygroundMessageMetadata | undefined): boolean;
|
|
198
248
|
/**
|
|
199
249
|
* Display mode for the Playground component
|
|
200
250
|
*/
|
package/dist/types/playground.js
CHANGED
|
@@ -6,6 +6,35 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module types/playground
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Metadata field to control Run button state from backend.
|
|
11
|
+
* When a message contains this field set to true, the Run button becomes enabled.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Backend sends a message with this metadata to re-enable Run button:
|
|
16
|
+
* const message: PlaygroundMessage = {
|
|
17
|
+
* id: "msg-123",
|
|
18
|
+
* sessionId: "sess-456",
|
|
19
|
+
* role: "system",
|
|
20
|
+
* content: "Workflow completed. Ready for next run.",
|
|
21
|
+
* timestamp: new Date().toISOString(),
|
|
22
|
+
* metadata: {
|
|
23
|
+
* enableRun: true
|
|
24
|
+
* }
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const ENABLE_RUN_METADATA_KEY = 'enableRun';
|
|
29
|
+
/**
|
|
30
|
+
* Check if a message metadata contains the enableRun flag
|
|
31
|
+
*
|
|
32
|
+
* @param metadata - The message metadata to check
|
|
33
|
+
* @returns True if the metadata signals to enable the Run button
|
|
34
|
+
*/
|
|
35
|
+
export function hasEnableRunFlag(metadata) {
|
|
36
|
+
return metadata?.[ENABLE_RUN_METADATA_KEY] === true;
|
|
37
|
+
}
|
|
9
38
|
/**
|
|
10
39
|
* Chat input detection patterns for identifying chat nodes in workflows
|
|
11
40
|
*/
|
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.
|
|
5
|
+
"version": "0.0.40",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite dev",
|
|
8
8
|
"build": "vite build && npm run prepack",
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
"test:all": "npm run test && npm run test:e2e",
|
|
28
28
|
"format": "prettier --write .",
|
|
29
29
|
"storybook": "storybook dev -p 6006",
|
|
30
|
-
"build-storybook": "storybook build"
|
|
30
|
+
"build-storybook": "storybook build",
|
|
31
|
+
"api:lint": "redocly lint api/openapi.yaml --config api/redocly.yaml",
|
|
32
|
+
"api:bundle": "redocly bundle api/openapi.yaml -o api/bundled.yaml --config api/redocly.yaml",
|
|
33
|
+
"api:preview": "redocly preview-docs api/openapi.yaml --config api/redocly.yaml",
|
|
34
|
+
"api:docs": "redocly build-docs api/bundled.yaml -o api-docs/index.html"
|
|
31
35
|
},
|
|
32
36
|
"watch": {
|
|
33
37
|
"build": {
|
|
@@ -138,7 +142,7 @@
|
|
|
138
142
|
},
|
|
139
143
|
"repository": {
|
|
140
144
|
"type": "git",
|
|
141
|
-
"url": "git+https://github.com/
|
|
145
|
+
"url": "git+https://github.com/flowdrop-io/flowdrop.git"
|
|
142
146
|
},
|
|
143
147
|
"devDependencies": {
|
|
144
148
|
"@chromatic-com/storybook": "^4.0.1",
|
|
@@ -146,6 +150,7 @@
|
|
|
146
150
|
"@eslint/js": "^9.18.0",
|
|
147
151
|
"@iconify/svelte": "^5.0.0",
|
|
148
152
|
"@playwright/test": "^1.49.1",
|
|
153
|
+
"@redocly/cli": "^2.14.9",
|
|
149
154
|
"@storybook/addon-docs": "^9.0.15",
|
|
150
155
|
"@storybook/addon-svelte-csf": "^5.0.4",
|
|
151
156
|
"@storybook/addon-vitest": "^9.0.15",
|