@d34dman/flowdrop 0.0.39 → 0.0.41
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/README.md +1 -1
- package/dist/components/playground/ChatPanel.svelte +314 -47
- package/dist/components/playground/ChatPanel.svelte.d.ts +22 -0
- package/dist/components/playground/MessageBubble.svelte +146 -60
- package/dist/components/playground/MessageBubble.svelte.d.ts +7 -0
- package/dist/components/playground/Playground.svelte +82 -2
- package/dist/config/endpoints.js +2 -1
- package/dist/types/playground.d.ts +52 -0
- package/dist/types/playground.js +29 -0
- package/package.json +8 -3
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,28 @@
|
|
|
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;
|
|
69
|
+
/**
|
|
70
|
+
* Whether to display system messages in compact mode.
|
|
71
|
+
* When true, system messages appear as minimal inline text
|
|
72
|
+
* instead of full chat bubbles to reduce visual noise.
|
|
73
|
+
* @default true
|
|
74
|
+
*/
|
|
75
|
+
compactSystemMessages?: boolean;
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
let {
|
|
@@ -60,9 +83,26 @@
|
|
|
60
83
|
onStopExecution,
|
|
61
84
|
showLogsInline = false,
|
|
62
85
|
enableMarkdown = true,
|
|
63
|
-
onInterruptResolved
|
|
86
|
+
onInterruptResolved,
|
|
87
|
+
showChatInput = true,
|
|
88
|
+
showRunButton = true,
|
|
89
|
+
predefinedMessage = 'Run workflow',
|
|
90
|
+
compactSystemMessages = true
|
|
64
91
|
}: Props = $props();
|
|
65
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Tracks whether the Run button is enabled.
|
|
95
|
+
* Starts as true, becomes false after Run is clicked,
|
|
96
|
+
* and is re-enabled when backend sends a message with enableRun: true metadata.
|
|
97
|
+
*/
|
|
98
|
+
let runEnabled = $state(true);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Computed flag: true if both chat input and run button are hidden.
|
|
102
|
+
* In this case, we show a helpful message to the user.
|
|
103
|
+
*/
|
|
104
|
+
const noInputsAvailable = $derived(!showChatInput && !showRunButton);
|
|
105
|
+
|
|
66
106
|
/** Input field value */
|
|
67
107
|
let inputValue = $state('');
|
|
68
108
|
|
|
@@ -77,6 +117,46 @@
|
|
|
77
117
|
*/
|
|
78
118
|
const displayMessages = $derived(showLogsInline ? $messages : $chatMessages);
|
|
79
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Track previous message count for detecting new messages.
|
|
122
|
+
* We only want to auto-scroll when NEW messages are added,
|
|
123
|
+
* not when existing messages are updated.
|
|
124
|
+
*/
|
|
125
|
+
let previousMessageCount = $state(0);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if user is near the bottom of the scroll container.
|
|
129
|
+
* Used to determine if we should auto-scroll when new messages arrive.
|
|
130
|
+
* If user has scrolled up to read previous messages, we don't interrupt them.
|
|
131
|
+
*
|
|
132
|
+
* @param threshold - Pixels from bottom to consider "near bottom"
|
|
133
|
+
* @returns True if user is within threshold of the bottom
|
|
134
|
+
*/
|
|
135
|
+
function isNearBottom(threshold: number = 100): boolean {
|
|
136
|
+
if (!messagesContainer) return true;
|
|
137
|
+
const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
|
|
138
|
+
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if a form element inside the messages container has focus.
|
|
143
|
+
* When user is interacting with a form (e.g., interrupt prompt),
|
|
144
|
+
* we should not auto-scroll as it disrupts their input.
|
|
145
|
+
*/
|
|
146
|
+
function isFormFocused(): boolean {
|
|
147
|
+
if (!messagesContainer) return false;
|
|
148
|
+
const activeElement = document.activeElement;
|
|
149
|
+
if (!activeElement) return false;
|
|
150
|
+
// Check if active element is a form control inside the messages container
|
|
151
|
+
const isFormControl =
|
|
152
|
+
activeElement.tagName === 'INPUT' ||
|
|
153
|
+
activeElement.tagName === 'TEXTAREA' ||
|
|
154
|
+
activeElement.tagName === 'SELECT' ||
|
|
155
|
+
activeElement.tagName === 'BUTTON' ||
|
|
156
|
+
activeElement.getAttribute('contenteditable') === 'true';
|
|
157
|
+
return isFormControl && messagesContainer.contains(activeElement);
|
|
158
|
+
}
|
|
159
|
+
|
|
80
160
|
/**
|
|
81
161
|
* Check if a message is an interrupt request
|
|
82
162
|
*/
|
|
@@ -193,16 +273,110 @@
|
|
|
193
273
|
}
|
|
194
274
|
|
|
195
275
|
/**
|
|
196
|
-
*
|
|
276
|
+
* Handle "Run" button click when chat input is hidden.
|
|
277
|
+
* Sends the predefined message to execute the workflow.
|
|
278
|
+
* Disables the Run button after clicking until backend re-enables it.
|
|
279
|
+
*/
|
|
280
|
+
function handleRun(): void {
|
|
281
|
+
if ($isExecuting || !runEnabled) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// Disable the Run button after clicking
|
|
285
|
+
runEnabled = false;
|
|
286
|
+
onSendMessage?.(predefinedMessage);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Track processed message IDs for enableRun detection
|
|
291
|
+
* to avoid re-processing the same messages.
|
|
292
|
+
*/
|
|
293
|
+
let processedEnableRunIds = $state(new Set<string>());
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Watch for messages with enableRun: true metadata from the backend.
|
|
297
|
+
* When detected, re-enable the Run button.
|
|
197
298
|
*/
|
|
198
299
|
$effect(() => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
300
|
+
// Check all messages for enableRun flag
|
|
301
|
+
for (const message of displayMessages) {
|
|
302
|
+
// Skip if already processed
|
|
303
|
+
if (processedEnableRunIds.has(message.id)) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// Check if this message has the enableRun flag
|
|
307
|
+
if (hasEnableRunFlag(message.metadata)) {
|
|
308
|
+
// Mark as processed
|
|
309
|
+
processedEnableRunIds = new Set([...processedEnableRunIds, message.id]);
|
|
310
|
+
// Re-enable the Run button
|
|
311
|
+
runEnabled = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Reset runEnabled state when session changes.
|
|
318
|
+
* This ensures a fresh state for each session.
|
|
319
|
+
*/
|
|
320
|
+
$effect(() => {
|
|
321
|
+
const session = $currentSession;
|
|
322
|
+
if (session) {
|
|
323
|
+
// Reset to enabled state for new/changed sessions
|
|
324
|
+
runEnabled = true;
|
|
325
|
+
// Clear processed IDs for the new session
|
|
326
|
+
processedEnableRunIds = new Set();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Smart auto-scroll to bottom when NEW messages are added.
|
|
332
|
+
*
|
|
333
|
+
* Only scrolls if:
|
|
334
|
+
* 1. autoScroll prop is enabled
|
|
335
|
+
* 2. New messages were actually added (not just updates)
|
|
336
|
+
* 3. User is already near the bottom (hasn't scrolled up to read)
|
|
337
|
+
* 4. User is not interacting with a form inside the chat
|
|
338
|
+
*
|
|
339
|
+
* This prevents disruptive scrolling when:
|
|
340
|
+
* - User is reading previous messages
|
|
341
|
+
* - User is filling out an interrupt form
|
|
342
|
+
* - Messages are being updated (e.g., status changes)
|
|
343
|
+
*/
|
|
344
|
+
$effect(() => {
|
|
345
|
+
const currentCount = displayMessages.length;
|
|
346
|
+
|
|
347
|
+
// Skip if auto-scroll is disabled or no container
|
|
348
|
+
if (!autoScroll || !messagesContainer) {
|
|
349
|
+
previousMessageCount = currentCount;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check if this is a NEW message (count increased)
|
|
354
|
+
const hasNewMessage = currentCount > previousMessageCount;
|
|
355
|
+
|
|
356
|
+
// Update the tracked count
|
|
357
|
+
previousMessageCount = currentCount;
|
|
358
|
+
|
|
359
|
+
// Only scroll if there's a new message
|
|
360
|
+
if (!hasNewMessage) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Don't scroll if user has scrolled up to read previous messages
|
|
365
|
+
if (!isNearBottom()) {
|
|
366
|
+
return;
|
|
205
367
|
}
|
|
368
|
+
|
|
369
|
+
// Don't scroll if user is interacting with a form
|
|
370
|
+
if (isFormFocused()) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Safe to scroll to bottom
|
|
375
|
+
tick().then(() => {
|
|
376
|
+
if (messagesContainer) {
|
|
377
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
206
380
|
});
|
|
207
381
|
|
|
208
382
|
/**
|
|
@@ -293,8 +467,18 @@
|
|
|
293
467
|
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
294
468
|
</svg>
|
|
295
469
|
</div>
|
|
296
|
-
|
|
297
|
-
|
|
470
|
+
{#if noInputsAvailable}
|
|
471
|
+
<h2 class="chat-panel__welcome-title">View only</h2>
|
|
472
|
+
<p class="chat-panel__welcome-text">
|
|
473
|
+
This playground is in view-only mode. No inputs are available.
|
|
474
|
+
</p>
|
|
475
|
+
{:else if showChatInput}
|
|
476
|
+
<h2 class="chat-panel__welcome-title">New chat</h2>
|
|
477
|
+
<p class="chat-panel__welcome-text">Test your flow with a chat prompt</p>
|
|
478
|
+
{:else}
|
|
479
|
+
<h2 class="chat-panel__welcome-title">Ready to run</h2>
|
|
480
|
+
<p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
|
|
481
|
+
{/if}
|
|
298
482
|
</div>
|
|
299
483
|
{:else if showEmptyChat}
|
|
300
484
|
<!-- Empty Chat State (session exists but no messages) -->
|
|
@@ -324,8 +508,18 @@
|
|
|
324
508
|
<path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
|
325
509
|
</svg>
|
|
326
510
|
</div>
|
|
327
|
-
|
|
328
|
-
|
|
511
|
+
{#if noInputsAvailable}
|
|
512
|
+
<h2 class="chat-panel__welcome-title">View only</h2>
|
|
513
|
+
<p class="chat-panel__welcome-text">
|
|
514
|
+
This playground is in view-only mode. No inputs are available.
|
|
515
|
+
</p>
|
|
516
|
+
{:else if showChatInput}
|
|
517
|
+
<h2 class="chat-panel__welcome-title">New chat</h2>
|
|
518
|
+
<p class="chat-panel__welcome-text">Test your flow with a chat prompt</p>
|
|
519
|
+
{:else}
|
|
520
|
+
<h2 class="chat-panel__welcome-title">Ready to run</h2>
|
|
521
|
+
<p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
|
|
522
|
+
{/if}
|
|
329
523
|
</div>
|
|
330
524
|
{:else}
|
|
331
525
|
<!-- Messages -->
|
|
@@ -346,6 +540,7 @@
|
|
|
346
540
|
showTimestamp={showTimestamps}
|
|
347
541
|
isLast={index === displayMessages.length - 1}
|
|
348
542
|
{enableMarkdown}
|
|
543
|
+
{compactSystemMessages}
|
|
349
544
|
/>
|
|
350
545
|
{/if}
|
|
351
546
|
{/each}
|
|
@@ -365,42 +560,66 @@
|
|
|
365
560
|
|
|
366
561
|
<!-- Input Area -->
|
|
367
562
|
<div class="chat-panel__input-area">
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
class="chat-panel__input"
|
|
374
|
-
{placeholder}
|
|
375
|
-
rows="1"
|
|
376
|
-
disabled={$isExecuting}
|
|
377
|
-
onkeydown={handleKeydown}
|
|
378
|
-
oninput={handleInput}
|
|
379
|
-
></textarea>
|
|
563
|
+
{#if noInputsAvailable}
|
|
564
|
+
<!-- No inputs available - show informational message -->
|
|
565
|
+
<div class="chat-panel__no-inputs">
|
|
566
|
+
<Icon icon="mdi:information-outline" />
|
|
567
|
+
<span>View-only mode. Workflow execution is controlled externally.</span>
|
|
380
568
|
</div>
|
|
569
|
+
{:else}
|
|
570
|
+
<div
|
|
571
|
+
class="chat-panel__input-container"
|
|
572
|
+
class:chat-panel__input-container--run-only={!showChatInput}
|
|
573
|
+
>
|
|
574
|
+
{#if showChatInput}
|
|
575
|
+
<div class="chat-panel__input-wrapper">
|
|
576
|
+
<textarea
|
|
577
|
+
bind:this={inputField}
|
|
578
|
+
bind:value={inputValue}
|
|
579
|
+
class="chat-panel__input"
|
|
580
|
+
{placeholder}
|
|
581
|
+
rows="1"
|
|
582
|
+
disabled={$isExecuting}
|
|
583
|
+
onkeydown={handleKeydown}
|
|
584
|
+
oninput={handleInput}
|
|
585
|
+
></textarea>
|
|
586
|
+
</div>
|
|
587
|
+
{/if}
|
|
381
588
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
589
|
+
{#if $sessionStatus === 'running' || $isExecuting}
|
|
590
|
+
<button
|
|
591
|
+
type="button"
|
|
592
|
+
class="chat-panel__stop-btn"
|
|
593
|
+
onclick={handleStop}
|
|
594
|
+
title="Stop execution"
|
|
595
|
+
>
|
|
596
|
+
<Icon icon="mdi:stop" />
|
|
597
|
+
Stop
|
|
598
|
+
</button>
|
|
599
|
+
{:else if showChatInput}
|
|
600
|
+
<button
|
|
601
|
+
type="button"
|
|
602
|
+
class="chat-panel__send-btn"
|
|
603
|
+
onclick={handleSend}
|
|
604
|
+
disabled={!inputValue.trim()}
|
|
605
|
+
title="Send message"
|
|
606
|
+
>
|
|
607
|
+
Send
|
|
608
|
+
</button>
|
|
609
|
+
{:else if showRunButton}
|
|
610
|
+
<button
|
|
611
|
+
type="button"
|
|
612
|
+
class="chat-panel__run-btn"
|
|
613
|
+
onclick={handleRun}
|
|
614
|
+
disabled={!runEnabled}
|
|
615
|
+
title={runEnabled ? 'Run workflow' : 'Waiting for workflow to be ready...'}
|
|
616
|
+
>
|
|
617
|
+
<Icon icon="mdi:play" />
|
|
618
|
+
Run
|
|
619
|
+
</button>
|
|
620
|
+
{/if}
|
|
621
|
+
</div>
|
|
622
|
+
{/if}
|
|
404
623
|
</div>
|
|
405
624
|
</div>
|
|
406
625
|
|
|
@@ -616,6 +835,53 @@
|
|
|
616
835
|
background-color: #dc2626;
|
|
617
836
|
}
|
|
618
837
|
|
|
838
|
+
/* Run button (when chat input is hidden) */
|
|
839
|
+
.chat-panel__run-btn {
|
|
840
|
+
display: flex;
|
|
841
|
+
align-items: center;
|
|
842
|
+
gap: 0.375rem;
|
|
843
|
+
padding: 0.625rem 1.25rem;
|
|
844
|
+
border: none;
|
|
845
|
+
border-radius: 0.5rem;
|
|
846
|
+
background-color: #10b981;
|
|
847
|
+
color: #ffffff;
|
|
848
|
+
font-size: 0.875rem;
|
|
849
|
+
font-weight: 500;
|
|
850
|
+
cursor: pointer;
|
|
851
|
+
transition: all 0.15s ease;
|
|
852
|
+
flex-shrink: 0;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.chat-panel__run-btn:hover:not(:disabled) {
|
|
856
|
+
background-color: #059669;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.chat-panel__run-btn:disabled {
|
|
860
|
+
background-color: #e5e7eb;
|
|
861
|
+
color: #9ca3af;
|
|
862
|
+
cursor: not-allowed;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/* Container modifier for run-only mode (no text input) */
|
|
866
|
+
.chat-panel__input-container--run-only {
|
|
867
|
+
justify-content: flex-end;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/* No inputs available message (view-only mode) */
|
|
871
|
+
.chat-panel__no-inputs {
|
|
872
|
+
display: flex;
|
|
873
|
+
align-items: center;
|
|
874
|
+
justify-content: center;
|
|
875
|
+
gap: 0.5rem;
|
|
876
|
+
padding: 0.75rem 1rem;
|
|
877
|
+
background-color: #f3f4f6;
|
|
878
|
+
border-radius: 0.5rem;
|
|
879
|
+
color: #6b7280;
|
|
880
|
+
font-size: 0.875rem;
|
|
881
|
+
max-width: 800px;
|
|
882
|
+
margin: 0 auto;
|
|
883
|
+
}
|
|
884
|
+
|
|
619
885
|
/* Responsive */
|
|
620
886
|
@media (max-width: 640px) {
|
|
621
887
|
.chat-panel__messages {
|
|
@@ -631,7 +897,8 @@
|
|
|
631
897
|
}
|
|
632
898
|
|
|
633
899
|
.chat-panel__send-btn,
|
|
634
|
-
.chat-panel__stop-btn
|
|
900
|
+
.chat-panel__stop-btn,
|
|
901
|
+
.chat-panel__run-btn {
|
|
635
902
|
padding: 0.5rem 1rem;
|
|
636
903
|
}
|
|
637
904
|
}
|
|
@@ -18,6 +18,28 @@ 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;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to display system messages in compact mode.
|
|
38
|
+
* When true, system messages appear as minimal inline text
|
|
39
|
+
* instead of full chat bubbles to reduce visual noise.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
compactSystemMessages?: boolean;
|
|
21
43
|
}
|
|
22
44
|
declare const ChatPanel: import("svelte").Component<Props, {}, "">;
|
|
23
45
|
type ChatPanel = ReturnType<typeof ChatPanel>;
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
Renders individual messages in the playground chat interface.
|
|
5
5
|
Supports different message roles with distinct styling.
|
|
6
6
|
Supports markdown rendering for message content.
|
|
7
|
+
Supports compact mode for system messages to reduce visual noise.
|
|
7
8
|
Styled with BEM syntax.
|
|
8
9
|
-->
|
|
9
10
|
|
|
10
11
|
<script lang="ts">
|
|
11
12
|
import Icon from '@iconify/svelte';
|
|
12
13
|
import { marked } from 'marked';
|
|
13
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
PlaygroundMessage,
|
|
16
|
+
PlaygroundMessageMetadata,
|
|
17
|
+
PlaygroundMessageRole
|
|
18
|
+
} from '../../types/playground.js';
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Component props
|
|
@@ -24,9 +29,28 @@
|
|
|
24
29
|
isLast?: boolean;
|
|
25
30
|
/** Whether to render markdown content */
|
|
26
31
|
enableMarkdown?: boolean;
|
|
27
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Use compact display mode for system messages.
|
|
34
|
+
* When true, system messages are rendered as minimal inline text
|
|
35
|
+
* instead of full chat bubbles to reduce visual noise.
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
compactSystemMessages?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
message,
|
|
43
|
+
showTimestamp = true,
|
|
44
|
+
isLast = false,
|
|
45
|
+
enableMarkdown = true,
|
|
46
|
+
compactSystemMessages = true
|
|
47
|
+
}: Props = $props();
|
|
28
48
|
|
|
29
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Determine if this message should render in compact mode.
|
|
51
|
+
* Only system messages use compact mode when enabled.
|
|
52
|
+
*/
|
|
53
|
+
const useCompactMode = $derived(message.role === 'system' && compactSystemMessages);
|
|
30
54
|
|
|
31
55
|
/**
|
|
32
56
|
* Render content as markdown or plain text
|
|
@@ -60,18 +84,22 @@
|
|
|
60
84
|
* Get the display label for the message role
|
|
61
85
|
*
|
|
62
86
|
* @param role - The message role
|
|
87
|
+
* @param metadata - Optional message metadata containing userName for user messages
|
|
63
88
|
* @returns Display label
|
|
64
89
|
*/
|
|
65
|
-
function getRoleLabel(
|
|
90
|
+
function getRoleLabel(
|
|
91
|
+
role: PlaygroundMessageRole,
|
|
92
|
+
metadata?: PlaygroundMessageMetadata
|
|
93
|
+
): string {
|
|
66
94
|
switch (role) {
|
|
67
95
|
case 'user':
|
|
68
|
-
return 'You';
|
|
96
|
+
return metadata?.userName ?? 'You';
|
|
69
97
|
case 'assistant':
|
|
70
98
|
return 'Assistant';
|
|
71
99
|
case 'system':
|
|
72
100
|
return 'System';
|
|
73
101
|
case 'log':
|
|
74
|
-
return
|
|
102
|
+
return metadata?.nodeLabel ?? 'Log';
|
|
75
103
|
default:
|
|
76
104
|
return 'Message';
|
|
77
105
|
}
|
|
@@ -121,68 +149,79 @@
|
|
|
121
149
|
}
|
|
122
150
|
</script>
|
|
123
151
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
class:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
message.metadata?.level === 'warning'}
|
|
133
|
-
class:message-bubble--last={isLast}
|
|
134
|
-
>
|
|
135
|
-
<!-- Avatar / Icon -->
|
|
136
|
-
<div class="message-bubble__avatar">
|
|
137
|
-
<Icon icon={getRoleIcon(message.role)} />
|
|
152
|
+
{#if useCompactMode}
|
|
153
|
+
<!-- Compact system message: minimal inline text without bubble -->
|
|
154
|
+
<div class="system-notice" class:system-notice--last={isLast}>
|
|
155
|
+
<Icon icon="mdi:information-outline" class="system-notice__icon" />
|
|
156
|
+
<span class="system-notice__text">{message.content}</span>
|
|
157
|
+
{#if showTimestamp}
|
|
158
|
+
<span class="system-notice__timestamp">{formatTimestamp(message.timestamp)}</span>
|
|
159
|
+
{/if}
|
|
138
160
|
</div>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
{
|
|
161
|
+
{:else}
|
|
162
|
+
<div
|
|
163
|
+
class="message-bubble"
|
|
164
|
+
class:message-bubble--user={message.role === 'user'}
|
|
165
|
+
class:message-bubble--assistant={message.role === 'assistant'}
|
|
166
|
+
class:message-bubble--system={message.role === 'system'}
|
|
167
|
+
class:message-bubble--log={message.role === 'log'}
|
|
168
|
+
class:message-bubble--log-error={message.role === 'log' && message.metadata?.level === 'error'}
|
|
169
|
+
class:message-bubble--log-warning={message.role === 'log' &&
|
|
170
|
+
message.metadata?.level === 'warning'}
|
|
171
|
+
class:message-bubble--last={isLast}
|
|
172
|
+
>
|
|
173
|
+
<!-- Avatar / Icon -->
|
|
174
|
+
<div class="message-bubble__avatar">
|
|
175
|
+
<Icon icon={getRoleIcon(message.role)} />
|
|
154
176
|
</div>
|
|
155
177
|
|
|
156
|
-
<!--
|
|
157
|
-
<div class="message-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<!-- Metadata Footer -->
|
|
168
|
-
{#if message.metadata?.duration !== undefined || message.nodeId}
|
|
169
|
-
<div class="message-bubble__footer">
|
|
170
|
-
{#if message.nodeId}
|
|
171
|
-
<span class="message-bubble__node" title="Node ID: {message.nodeId}">
|
|
172
|
-
<Icon icon="mdi:graph" />
|
|
173
|
-
{message.metadata?.nodeLabel ?? message.nodeId}
|
|
178
|
+
<!-- Content -->
|
|
179
|
+
<div class="message-bubble__content">
|
|
180
|
+
<!-- Header -->
|
|
181
|
+
<div class="message-bubble__header">
|
|
182
|
+
<span class="message-bubble__role">{getRoleLabel(message.role, message.metadata)}</span>
|
|
183
|
+
{#if message.role === 'log' && message.metadata?.level}
|
|
184
|
+
<span class="message-bubble__log-level message-bubble__log-level--{message.metadata.level}">
|
|
185
|
+
<Icon icon={getLogLevelIcon()} />
|
|
186
|
+
{message.metadata.level.toUpperCase()}
|
|
174
187
|
</span>
|
|
175
188
|
{/if}
|
|
176
|
-
{#if
|
|
177
|
-
<span class="message-
|
|
178
|
-
<Icon icon="mdi:timer-outline" />
|
|
179
|
-
{formatDuration(message.metadata.duration)}
|
|
180
|
-
</span>
|
|
189
|
+
{#if showTimestamp}
|
|
190
|
+
<span class="message-bubble__timestamp">{formatTimestamp(message.timestamp)}</span>
|
|
181
191
|
{/if}
|
|
182
192
|
</div>
|
|
183
|
-
|
|
193
|
+
|
|
194
|
+
<!-- Message Text -->
|
|
195
|
+
<div class="message-bubble__text">
|
|
196
|
+
{#if enableMarkdown && message.role !== 'log'}
|
|
197
|
+
<!-- Markdown content - marked.js sanitizes content by default -->
|
|
198
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
199
|
+
{@html renderedContent}
|
|
200
|
+
{:else}
|
|
201
|
+
{message.content}
|
|
202
|
+
{/if}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- Metadata Footer -->
|
|
206
|
+
{#if message.metadata?.duration !== undefined || message.nodeId}
|
|
207
|
+
<div class="message-bubble__footer">
|
|
208
|
+
{#if message.nodeId}
|
|
209
|
+
<span class="message-bubble__node" title="Node ID: {message.nodeId}">
|
|
210
|
+
<Icon icon="mdi:graph" />
|
|
211
|
+
{message.metadata?.nodeLabel ?? message.nodeId}
|
|
212
|
+
</span>
|
|
213
|
+
{/if}
|
|
214
|
+
{#if message.metadata?.duration !== undefined}
|
|
215
|
+
<span class="message-bubble__duration" title="Execution duration">
|
|
216
|
+
<Icon icon="mdi:timer-outline" />
|
|
217
|
+
{formatDuration(message.metadata.duration)}
|
|
218
|
+
</span>
|
|
219
|
+
{/if}
|
|
220
|
+
</div>
|
|
221
|
+
{/if}
|
|
222
|
+
</div>
|
|
184
223
|
</div>
|
|
185
|
-
|
|
224
|
+
{/if}
|
|
186
225
|
|
|
187
226
|
<style>
|
|
188
227
|
.message-bubble {
|
|
@@ -542,4 +581,51 @@
|
|
|
542
581
|
font-size: 1rem;
|
|
543
582
|
}
|
|
544
583
|
}
|
|
584
|
+
|
|
585
|
+
/* ========================================
|
|
586
|
+
Compact System Notice Styles
|
|
587
|
+
Minimal inline display for system messages
|
|
588
|
+
======================================== */
|
|
589
|
+
|
|
590
|
+
.system-notice {
|
|
591
|
+
display: flex;
|
|
592
|
+
align-items: center;
|
|
593
|
+
justify-content: center;
|
|
594
|
+
gap: 0.375rem;
|
|
595
|
+
padding: 0.375rem 0.75rem;
|
|
596
|
+
margin: 0.25rem 0;
|
|
597
|
+
font-size: 0.75rem;
|
|
598
|
+
color: #9ca3af;
|
|
599
|
+
text-align: center;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.system-notice--last {
|
|
603
|
+
margin-bottom: 0.75rem;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/* Icon styling - using :global for Iconify component */
|
|
607
|
+
.system-notice :global(.system-notice__icon) {
|
|
608
|
+
flex-shrink: 0;
|
|
609
|
+
font-size: 0.875rem;
|
|
610
|
+
color: #d1d5db;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.system-notice__text {
|
|
614
|
+
color: #6b7280;
|
|
615
|
+
line-height: 1.4;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.system-notice__timestamp {
|
|
619
|
+
flex-shrink: 0;
|
|
620
|
+
font-size: 0.625rem;
|
|
621
|
+
color: #d1d5db;
|
|
622
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* Responsive: hide timestamp on small screens for compactness */
|
|
626
|
+
@media (max-width: 640px) {
|
|
627
|
+
.system-notice__timestamp {
|
|
628
|
+
display: none;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
545
631
|
</style>
|
|
@@ -11,6 +11,13 @@ interface Props {
|
|
|
11
11
|
isLast?: boolean;
|
|
12
12
|
/** Whether to render markdown content */
|
|
13
13
|
enableMarkdown?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Use compact display mode for system messages.
|
|
16
|
+
* When true, system messages are rendered as minimal inline text
|
|
17
|
+
* instead of full chat bubbles to reduce visual noise.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
compactSystemMessages?: boolean;
|
|
14
21
|
}
|
|
15
22
|
declare const MessageBubble: import("svelte").Component<Props, {}, "">;
|
|
16
23
|
type MessageBubble = ReturnType<typeof MessageBubble>;
|
|
@@ -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
|
}
|
|
@@ -74,6 +74,8 @@ export interface PlaygroundMessageMetadata {
|
|
|
74
74
|
nodeLabel?: string;
|
|
75
75
|
/** Node output data */
|
|
76
76
|
outputs?: Record<string, unknown>;
|
|
77
|
+
/** User's display name for user-role messages (from backend) */
|
|
78
|
+
userName?: string;
|
|
77
79
|
/** Allow additional properties */
|
|
78
80
|
[key: string]: unknown;
|
|
79
81
|
}
|
|
@@ -194,7 +196,57 @@ export interface PlaygroundConfig {
|
|
|
194
196
|
logDisplayMode?: 'inline' | 'collapsible';
|
|
195
197
|
/** Enable markdown rendering in messages (default: true) */
|
|
196
198
|
enableMarkdown?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Whether to show the chat text input (default: true)
|
|
201
|
+
* When false, only the "Run" button is displayed for workflow execution.
|
|
202
|
+
*/
|
|
203
|
+
showChatInput?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Whether to show the "Run" button (default: true)
|
|
206
|
+
* When false, the Run button is hidden. If both showChatInput and showRunButton
|
|
207
|
+
* are false, a helpful message is displayed to the user.
|
|
208
|
+
*/
|
|
209
|
+
showRunButton?: boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Predefined message to send when "Run" button is clicked (default: "Run workflow")
|
|
212
|
+
* Used when showChatInput is false to provide a default message for workflow execution.
|
|
213
|
+
*/
|
|
214
|
+
predefinedMessage?: string;
|
|
215
|
+
/**
|
|
216
|
+
* Automatically run the workflow once when the playground loads (default: false)
|
|
217
|
+
* When true, the workflow will execute immediately using the predefinedMessage.
|
|
218
|
+
* This is useful for scenarios where the workflow should start without user interaction.
|
|
219
|
+
* Note: Only runs once per session - subsequent runs require clicking the Run button.
|
|
220
|
+
*/
|
|
221
|
+
autoRun?: boolean;
|
|
197
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Metadata field to control Run button state from backend.
|
|
225
|
+
* When a message contains this field set to true, the Run button becomes enabled.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```typescript
|
|
229
|
+
* // Backend sends a message with this metadata to re-enable Run button:
|
|
230
|
+
* const message: PlaygroundMessage = {
|
|
231
|
+
* id: "msg-123",
|
|
232
|
+
* sessionId: "sess-456",
|
|
233
|
+
* role: "system",
|
|
234
|
+
* content: "Workflow completed. Ready for next run.",
|
|
235
|
+
* timestamp: new Date().toISOString(),
|
|
236
|
+
* metadata: {
|
|
237
|
+
* enableRun: true
|
|
238
|
+
* }
|
|
239
|
+
* };
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export declare const ENABLE_RUN_METADATA_KEY = "enableRun";
|
|
243
|
+
/**
|
|
244
|
+
* Check if a message metadata contains the enableRun flag
|
|
245
|
+
*
|
|
246
|
+
* @param metadata - The message metadata to check
|
|
247
|
+
* @returns True if the metadata signals to enable the Run button
|
|
248
|
+
*/
|
|
249
|
+
export declare function hasEnableRunFlag(metadata: PlaygroundMessageMetadata | undefined): boolean;
|
|
198
250
|
/**
|
|
199
251
|
* Display mode for the Playground component
|
|
200
252
|
*/
|
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.41",
|
|
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",
|