@flowdrop/flowdrop 1.8.0 → 1.9.0
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/dist/chat/responseParser.js +7 -0
- package/dist/commands/parser.js +12 -0
- package/dist/components/PipelineStatus.svelte +15 -8
- package/dist/components/PipelineStatus.svelte.d.ts +3 -0
- package/dist/components/chat/AIChatPanel.svelte +22 -1
- package/dist/components/playground/ChatPanel.svelte +149 -24
- package/dist/components/playground/ChatPanel.svelte.d.ts +3 -1
- package/dist/components/playground/ExecutionList.svelte +142 -0
- package/dist/components/playground/ExecutionList.svelte.d.ts +10 -0
- package/dist/components/playground/MessageBubble.svelte +221 -153
- package/dist/components/playground/PipelinePanel.svelte +372 -0
- package/dist/components/playground/PipelinePanel.svelte.d.ts +19 -0
- package/dist/components/playground/Playground.svelte +586 -103
- package/dist/components/playground/Playground.svelte.d.ts +6 -0
- package/dist/services/globalSave.d.ts +7 -0
- package/dist/services/globalSave.js +5 -1
- package/dist/stores/pipelinePanelStore.svelte.d.ts +6 -0
- package/dist/stores/pipelinePanelStore.svelte.js +24 -0
- package/dist/stores/playgroundStore.svelte.d.ts +4 -0
- package/dist/stores/playgroundStore.svelte.js +58 -0
- package/dist/svelte-app.js +25 -2
- package/dist/types/playground.d.ts +12 -0
- package/package.json +1 -1
|
@@ -19,6 +19,12 @@ interface Props {
|
|
|
19
19
|
config?: PlaygroundConfig;
|
|
20
20
|
/** Callback when playground is closed (for embedded mode) */
|
|
21
21
|
onClose?: () => void;
|
|
22
|
+
/** Callback to toggle the pipeline panel (if undefined, toggle button is hidden) */
|
|
23
|
+
onTogglePanel?: () => void;
|
|
24
|
+
/** Whether the pipeline panel is currently open (for toggle button active state) */
|
|
25
|
+
isPipelinePanelOpen?: boolean;
|
|
26
|
+
/** When provided, session switches and creation navigate to a URL instead of mutating store state */
|
|
27
|
+
onSessionNavigate?: (sessionId: string) => void;
|
|
22
28
|
}
|
|
23
29
|
declare const Playground: import("svelte").Component<Props, {}, "">;
|
|
24
30
|
type Playground = ReturnType<typeof Playground>;
|
|
@@ -34,6 +34,13 @@ export interface GlobalSaveOptions {
|
|
|
34
34
|
* Pass workflowStore's markAsSaved here when calling from App.svelte.
|
|
35
35
|
*/
|
|
36
36
|
onMarkAsSaved?: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Callback invoked after a successful save with the persisted workflow.
|
|
39
|
+
* Receives the server-returned workflow (which may have a different ID than
|
|
40
|
+
* the one sent, e.g. server-assigned integer vs client UUID).
|
|
41
|
+
* Use this to update draft storage keys or other ID-dependent state.
|
|
42
|
+
*/
|
|
43
|
+
onSaved?: (savedWorkflow: Workflow) => void;
|
|
37
44
|
}
|
|
38
45
|
/**
|
|
39
46
|
* Options accepted by globalExportWorkflow().
|
|
@@ -85,7 +85,7 @@ async function flushPendingFormChanges() {
|
|
|
85
85
|
* 7. Show toast notifications (respecting features.showToasts)
|
|
86
86
|
*/
|
|
87
87
|
export async function globalSaveWorkflow(options = {}) {
|
|
88
|
-
const { apiClient, eventHandlers, onMarkAsSaved } = options;
|
|
88
|
+
const { apiClient, eventHandlers, onMarkAsSaved, onSaved } = options;
|
|
89
89
|
const features = { ...DEFAULT_FEATURES, ...options.features };
|
|
90
90
|
// Step 1 — Flush pending form changes (single location for this logic)
|
|
91
91
|
await flushPendingFormChanges();
|
|
@@ -170,6 +170,10 @@ export async function globalSaveWorkflow(options = {}) {
|
|
|
170
170
|
// Fallback: call the store's own markAsSaved if no callback was provided
|
|
171
171
|
storeMarkAsSaved();
|
|
172
172
|
}
|
|
173
|
+
// Notify caller with the definitive saved workflow (server-assigned ID)
|
|
174
|
+
if (onSaved) {
|
|
175
|
+
onSaved(savedWorkflow);
|
|
176
|
+
}
|
|
173
177
|
// Show success toast
|
|
174
178
|
if (loadingToast)
|
|
175
179
|
dismissToast(loadingToast);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const STORAGE_KEY = 'fd-pipeline-panel-open';
|
|
2
|
+
let _isOpen = $state(false);
|
|
3
|
+
export function getPipelinePanelOpen() {
|
|
4
|
+
return _isOpen;
|
|
5
|
+
}
|
|
6
|
+
export const pipelinePanelActions = {
|
|
7
|
+
init() {
|
|
8
|
+
if (typeof localStorage !== 'undefined') {
|
|
9
|
+
_isOpen = localStorage.getItem(STORAGE_KEY) === 'true';
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
toggle() {
|
|
13
|
+
_isOpen = !_isOpen;
|
|
14
|
+
if (typeof localStorage !== 'undefined') {
|
|
15
|
+
localStorage.setItem(STORAGE_KEY, String(_isOpen));
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
setOpen(value) {
|
|
19
|
+
_isOpen = value;
|
|
20
|
+
if (typeof localStorage !== 'undefined') {
|
|
21
|
+
localStorage.setItem(STORAGE_KEY, String(value));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -75,6 +75,9 @@ export declare function getHasChatInput(): boolean;
|
|
|
75
75
|
* Get session count
|
|
76
76
|
*/
|
|
77
77
|
export declare function getSessionCount(): number;
|
|
78
|
+
export declare function getPinnedExecutionId(): string | null;
|
|
79
|
+
export declare function getLatestExecutionId(): string | null;
|
|
80
|
+
export declare function getActiveExecutionId(): string | null;
|
|
78
81
|
/**
|
|
79
82
|
* Playground store actions for modifying state
|
|
80
83
|
*/
|
|
@@ -174,6 +177,7 @@ export declare const playgroundActions: {
|
|
|
174
177
|
* @param sessionId - The session ID to switch to
|
|
175
178
|
*/
|
|
176
179
|
switchSession: (sessionId: string) => void;
|
|
180
|
+
pinExecution(executionId: string | null): void;
|
|
177
181
|
};
|
|
178
182
|
/**
|
|
179
183
|
* Create a polling callback that processes poll responses.
|
|
@@ -43,6 +43,12 @@ let _currentWorkflow = $state(null);
|
|
|
43
43
|
* Last polling timestamp for incremental message fetching
|
|
44
44
|
*/
|
|
45
45
|
let _lastPollTimestamp = $state(null);
|
|
46
|
+
/** Execution ID explicitly pinned by the user (null = follow latest) */
|
|
47
|
+
let _pinnedExecutionId = $state(null);
|
|
48
|
+
/** Latest execution ID derived from current session's executions list */
|
|
49
|
+
const _latestExecutionId = $derived(_currentSession?.executions?.at(-1)?.id ?? null);
|
|
50
|
+
/** Active execution: pinned if set, otherwise latest */
|
|
51
|
+
const _activeExecutionId = $derived(_pinnedExecutionId ?? _latestExecutionId);
|
|
46
52
|
// =========================================================================
|
|
47
53
|
// Getter Functions (for reactive access in components)
|
|
48
54
|
// =========================================================================
|
|
@@ -204,6 +210,15 @@ export function getHasChatInput() {
|
|
|
204
210
|
export function getSessionCount() {
|
|
205
211
|
return _sessions.length;
|
|
206
212
|
}
|
|
213
|
+
export function getPinnedExecutionId() {
|
|
214
|
+
return _pinnedExecutionId;
|
|
215
|
+
}
|
|
216
|
+
export function getLatestExecutionId() {
|
|
217
|
+
return _latestExecutionId;
|
|
218
|
+
}
|
|
219
|
+
export function getActiveExecutionId() {
|
|
220
|
+
return _activeExecutionId;
|
|
221
|
+
}
|
|
207
222
|
// =========================================================================
|
|
208
223
|
// Helper Functions
|
|
209
224
|
// =========================================================================
|
|
@@ -238,6 +253,32 @@ function sortMessagesChronologically(messageList) {
|
|
|
238
253
|
return a.id.localeCompare(b.id);
|
|
239
254
|
});
|
|
240
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Syncs the current session's executions list from incoming messages.
|
|
258
|
+
* When a message has a new executionId not yet tracked, adds it as a new execution entry.
|
|
259
|
+
*/
|
|
260
|
+
function syncExecutionsFromMessages(messages) {
|
|
261
|
+
if (!_currentSession)
|
|
262
|
+
return;
|
|
263
|
+
const existingIds = new Set((_currentSession.executions ?? []).map((e) => e.id));
|
|
264
|
+
const newExecutions = [];
|
|
265
|
+
for (const msg of messages) {
|
|
266
|
+
if (msg.executionId && !existingIds.has(msg.executionId)) {
|
|
267
|
+
existingIds.add(msg.executionId);
|
|
268
|
+
newExecutions.push({
|
|
269
|
+
id: msg.executionId,
|
|
270
|
+
startedAt: msg.timestamp,
|
|
271
|
+
status: 'running'
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (newExecutions.length > 0) {
|
|
276
|
+
_currentSession = {
|
|
277
|
+
..._currentSession,
|
|
278
|
+
executions: [...(_currentSession.executions ?? []), ...newExecutions]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
241
282
|
// =========================================================================
|
|
242
283
|
// Actions
|
|
243
284
|
// =========================================================================
|
|
@@ -259,6 +300,7 @@ export const playgroundActions = {
|
|
|
259
300
|
* @param session - The session to set as active
|
|
260
301
|
*/
|
|
261
302
|
setCurrentSession: (session) => {
|
|
303
|
+
_pinnedExecutionId = null;
|
|
262
304
|
_currentSession = session;
|
|
263
305
|
if (session) {
|
|
264
306
|
// Update session in the list
|
|
@@ -278,6 +320,17 @@ export const playgroundActions = {
|
|
|
278
320
|
updatedAt: new Date().toISOString()
|
|
279
321
|
};
|
|
280
322
|
}
|
|
323
|
+
// Update the latest execution status when the session reaches a terminal state.
|
|
324
|
+
// Only the last execution can be running at any time (sessions are single-pipeline),
|
|
325
|
+
// so we only need to check and update the tail entry.
|
|
326
|
+
if ((status === 'completed' || status === 'failed') && _currentSession?.executions?.length) {
|
|
327
|
+
const execs = [..._currentSession.executions];
|
|
328
|
+
const last = execs[execs.length - 1];
|
|
329
|
+
if (last.status === 'running') {
|
|
330
|
+
execs[execs.length - 1] = { ...last, status };
|
|
331
|
+
_currentSession = { ..._currentSession, executions: execs };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
281
334
|
// Also update in sessions list
|
|
282
335
|
const session = _currentSession;
|
|
283
336
|
if (session) {
|
|
@@ -354,6 +407,7 @@ export const playgroundActions = {
|
|
|
354
407
|
const uniqueNewMessages = newMessages.filter((m) => !existingIds.has(m.id));
|
|
355
408
|
// Sort the combined messages chronologically
|
|
356
409
|
_messages = sortMessagesChronologically([..._messages, ...uniqueNewMessages]);
|
|
410
|
+
syncExecutionsFromMessages(newMessages);
|
|
357
411
|
},
|
|
358
412
|
/**
|
|
359
413
|
* Clear all messages
|
|
@@ -413,12 +467,16 @@ export const playgroundActions = {
|
|
|
413
467
|
* @param sessionId - The session ID to switch to
|
|
414
468
|
*/
|
|
415
469
|
switchSession: (sessionId) => {
|
|
470
|
+
_pinnedExecutionId = null;
|
|
416
471
|
const session = _sessions.find((s) => s.id === sessionId);
|
|
417
472
|
if (session) {
|
|
418
473
|
_currentSession = session;
|
|
419
474
|
_messages = [];
|
|
420
475
|
_lastPollTimestamp = null;
|
|
421
476
|
}
|
|
477
|
+
},
|
|
478
|
+
pinExecution(executionId) {
|
|
479
|
+
_pinnedExecutionId = executionId;
|
|
422
480
|
}
|
|
423
481
|
};
|
|
424
482
|
// =========================================================================
|
package/dist/svelte-app.js
CHANGED
|
@@ -188,14 +188,37 @@ export async function mountFlowDropApp(container, options = {}) {
|
|
|
188
188
|
isDirty: () => isDirty(),
|
|
189
189
|
markAsSaved: () => {
|
|
190
190
|
markAsSaved();
|
|
191
|
-
// Also update draft manager
|
|
192
191
|
if (state.draftManager) {
|
|
192
|
+
// Migrate the draft key when the host confirms a save. New workflows start
|
|
193
|
+
// on 'flowdrop:draft:new', a key shared across all tabs. If the host has
|
|
194
|
+
// written the server-assigned ID back into the store before calling
|
|
195
|
+
// markAsSaved(), we can move to a unique per-workflow key and stop
|
|
196
|
+
// competing with other tabs that may also have unsaved new workflows.
|
|
197
|
+
// Skip when customDraftKey is set — the host manages that key explicitly.
|
|
198
|
+
if (!customDraftKey) {
|
|
199
|
+
const currentWorkflow = getWorkflowFromStore();
|
|
200
|
+
if (currentWorkflow?.id) {
|
|
201
|
+
state.draftManager.updateStorageKey(getDraftStorageKey(currentWorkflow.id));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
193
204
|
state.draftManager.markAsSaved();
|
|
194
205
|
}
|
|
195
206
|
},
|
|
196
207
|
getWorkflow: () => getWorkflowFromStore(),
|
|
197
208
|
save: async () => {
|
|
198
|
-
await globalSaveWorkflow(
|
|
209
|
+
await globalSaveWorkflow({
|
|
210
|
+
onSaved: (saved) => {
|
|
211
|
+
// globalSaveWorkflow does not write the server-assigned ID back to the
|
|
212
|
+
// workflow store, so we cannot read it from getWorkflowFromStore() here.
|
|
213
|
+
// Instead we use the savedWorkflow returned by the API directly.
|
|
214
|
+
// This migrates 'flowdrop:draft:new' to a unique per-workflow key
|
|
215
|
+
// immediately after the first save, preventing cross-tab collisions
|
|
216
|
+
// when multiple new workflows are open simultaneously.
|
|
217
|
+
if (state.draftManager && !customDraftKey && saved.id) {
|
|
218
|
+
state.draftManager.updateStorageKey(getDraftStorageKey(saved.id));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
199
222
|
},
|
|
200
223
|
export: () => {
|
|
201
224
|
globalExportWorkflow();
|
|
@@ -52,6 +52,14 @@ export type PlaygroundMessageLevel = 'info' | 'warning' | 'error' | 'debug';
|
|
|
52
52
|
* Status of a playground message
|
|
53
53
|
*/
|
|
54
54
|
export type PlaygroundMessageStatus = 'pending' | 'processing' | 'completed' | 'failed';
|
|
55
|
+
/**
|
|
56
|
+
* A single pipeline execution associated with a playground session
|
|
57
|
+
*/
|
|
58
|
+
export interface PlaygroundExecution {
|
|
59
|
+
id: string;
|
|
60
|
+
startedAt: string;
|
|
61
|
+
status: 'running' | 'completed' | 'failed';
|
|
62
|
+
}
|
|
55
63
|
/**
|
|
56
64
|
* Playground session representing a test conversation
|
|
57
65
|
*
|
|
@@ -83,6 +91,8 @@ export interface PlaygroundSession {
|
|
|
83
91
|
createdAt: string;
|
|
84
92
|
/** Last activity timestamp (ISO 8601) */
|
|
85
93
|
updatedAt: string;
|
|
94
|
+
/** Pipeline executions triggered within this session, ordered oldest-first */
|
|
95
|
+
executions?: PlaygroundExecution[];
|
|
86
96
|
/** Custom session metadata */
|
|
87
97
|
metadata?: Record<string, unknown>;
|
|
88
98
|
}
|
|
@@ -144,6 +154,8 @@ export interface PlaygroundMessage {
|
|
|
144
154
|
sequenceNumber?: number;
|
|
145
155
|
/** Parent message ID (for assistant responses linked to user messages) */
|
|
146
156
|
parentMessageId?: string;
|
|
157
|
+
/** Pipeline/execution ID that generated this message */
|
|
158
|
+
executionId?: string | null;
|
|
147
159
|
/** Associated node ID (for log/assistant messages) */
|
|
148
160
|
nodeId?: string | null;
|
|
149
161
|
/** Additional message metadata */
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.9.0",
|
|
7
7
|
"author": "Shibin Das (D34dMan)",
|
|
8
8
|
"bugs": {
|
|
9
9
|
"url": "https://github.com/flowdrop-io/flowdrop/issues"
|