@d34dman/flowdrop 0.0.60 → 0.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -1
- package/dist/api/client.d.ts +4 -0
- package/dist/api/client.js +6 -1
- package/dist/api/enhanced-client.js +7 -6
- package/dist/components/App.svelte +143 -219
- package/dist/components/CanvasBanner.stories.svelte +25 -0
- package/dist/components/CanvasBanner.stories.svelte.d.ts +27 -0
- package/dist/components/CanvasBanner.svelte +2 -2
- package/dist/components/ConfigForm.svelte +37 -36
- package/dist/components/ConfigPanel.stories.svelte +38 -0
- package/dist/components/ConfigPanel.stories.svelte.d.ts +27 -0
- package/dist/components/ConfigPanel.svelte +2 -2
- package/dist/components/ConnectionLine.svelte +2 -2
- package/dist/components/FlowDropZone.svelte +18 -2
- package/dist/components/FlowDropZone.svelte.d.ts +2 -0
- package/dist/components/LoadingSpinner.stories.svelte +30 -0
- package/dist/components/LoadingSpinner.stories.svelte.d.ts +27 -0
- package/dist/components/Logo.stories.svelte +22 -0
- package/dist/components/Logo.stories.svelte.d.ts +27 -0
- package/dist/components/Logo.svelte +33 -13
- package/dist/components/Logo.svelte.d.ts +1 -1
- package/dist/components/MarkdownDisplay.stories.svelte +21 -0
- package/dist/components/MarkdownDisplay.stories.svelte.d.ts +27 -0
- package/dist/components/MarkdownDisplay.svelte +4 -3
- package/dist/components/Navbar.stories.svelte +41 -0
- package/dist/components/Navbar.stories.svelte.d.ts +27 -0
- package/dist/components/Navbar.svelte +4 -4
- package/dist/components/NodeSidebar.svelte +12 -12
- package/dist/components/NodeStatusOverlay.stories.svelte +74 -0
- package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +27 -0
- package/dist/components/PipelineStatus.svelte +11 -4
- package/dist/components/PortCoordinateTracker.svelte +1 -1
- package/dist/components/SchemaForm.stories.svelte +101 -0
- package/dist/components/SchemaForm.stories.svelte.d.ts +27 -0
- package/dist/components/SchemaForm.svelte +17 -12
- package/dist/components/SettingsModal.svelte +3 -3
- package/dist/components/SettingsPanel.svelte +23 -22
- package/dist/components/StatusIcon.stories.svelte +60 -0
- package/dist/components/StatusIcon.stories.svelte.d.ts +27 -0
- package/dist/components/StatusIcon.svelte +7 -0
- package/dist/components/StatusLabel.stories.svelte +17 -0
- package/dist/components/StatusLabel.stories.svelte.d.ts +27 -0
- package/dist/components/ThemeToggle.stories.svelte +25 -0
- package/dist/components/ThemeToggle.stories.svelte.d.ts +27 -0
- package/dist/components/ThemeToggle.svelte +8 -8
- package/dist/components/UniversalNode.svelte +1 -1
- package/dist/components/WorkflowEditor.svelte +298 -294
- package/dist/components/form/FormAutocomplete.svelte +20 -19
- package/dist/components/form/FormCheckboxGroup.stories.svelte +28 -0
- package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormField.svelte +3 -3
- package/dist/components/form/FormFieldLight.svelte +2 -2
- package/dist/components/form/FormFieldWrapper.stories.svelte +31 -0
- package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormFieldset.svelte +7 -7
- package/dist/components/form/FormNumberField.stories.svelte +33 -0
- package/dist/components/form/FormNumberField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormRangeField.stories.svelte +31 -0
- package/dist/components/form/FormRangeField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormSelect.stories.svelte +50 -0
- package/dist/components/form/FormSelect.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormTemplateEditor.svelte +2 -1
- package/dist/components/form/FormTextField.stories.svelte +30 -0
- package/dist/components/form/FormTextField.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormTextarea.stories.svelte +31 -0
- package/dist/components/form/FormTextarea.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormToggle.stories.svelte +30 -0
- package/dist/components/form/FormToggle.stories.svelte.d.ts +27 -0
- package/dist/components/form/FormUISchemaRenderer.svelte +1 -1
- package/dist/components/form/types.d.ts +15 -47
- package/dist/components/interrupt/ChoicePrompt.stories.svelte +43 -0
- package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ChoicePrompt.svelte +24 -24
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +49 -0
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ConfirmationPrompt.svelte +19 -19
- package/dist/components/interrupt/FormPrompt.svelte +15 -15
- package/dist/components/interrupt/InterruptBubble.svelte +202 -236
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +1 -1
- package/dist/components/interrupt/ReviewPrompt.stories.svelte +46 -0
- package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/ReviewPrompt.svelte +842 -0
- package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +23 -0
- package/dist/components/interrupt/TextInputPrompt.stories.svelte +34 -0
- package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +27 -0
- package/dist/components/interrupt/TextInputPrompt.svelte +21 -21
- package/dist/components/nodes/GatewayNode.stories.svelte +76 -0
- package/dist/components/nodes/GatewayNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/GatewayNode.svelte +19 -17
- package/dist/components/nodes/IdeaNode.stories.svelte +48 -0
- package/dist/components/nodes/IdeaNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/IdeaNode.svelte +10 -26
- package/dist/components/nodes/NotesNode.stories.svelte +69 -0
- package/dist/components/nodes/NotesNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/NotesNode.svelte +8 -8
- package/dist/components/nodes/SimpleNode.stories.svelte +101 -0
- package/dist/components/nodes/SimpleNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/SimpleNode.svelte +16 -24
- package/dist/components/nodes/SquareNode.stories.svelte +56 -0
- package/dist/components/nodes/SquareNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/SquareNode.svelte +13 -21
- package/dist/components/nodes/TerminalNode.stories.svelte +25 -0
- package/dist/components/nodes/TerminalNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/TerminalNode.svelte +6 -6
- package/dist/components/nodes/ToolNode.stories.svelte +71 -0
- package/dist/components/nodes/ToolNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/ToolNode.svelte +7 -15
- package/dist/components/nodes/WorkflowNode.stories.svelte +50 -0
- package/dist/components/nodes/WorkflowNode.stories.svelte.d.ts +26 -0
- package/dist/components/nodes/WorkflowNode.svelte +13 -13
- package/dist/components/playground/ChatPanel.svelte +48 -48
- package/dist/components/playground/ExecutionLogs.svelte +23 -23
- package/dist/components/playground/InputCollector.svelte +24 -24
- package/dist/components/playground/MessageBubble.stories.svelte +49 -0
- package/dist/components/playground/MessageBubble.stories.svelte.d.ts +27 -0
- package/dist/components/playground/MessageBubble.svelte +49 -46
- package/dist/components/playground/Playground.svelte +203 -172
- package/dist/components/playground/PlaygroundModal.svelte +5 -5
- package/dist/components/playground/SessionManager.svelte +26 -26
- package/dist/config/constants.d.ts +22 -0
- package/dist/config/constants.js +22 -0
- package/dist/config/endpoints.d.ts +19 -0
- package/dist/config/runtimeConfig.js +2 -1
- package/dist/core/index.d.ts +5 -2
- package/dist/core/index.js +9 -1
- package/dist/editor/index.d.ts +13 -9
- package/dist/editor/index.js +15 -11
- package/dist/form/code.d.ts +2 -1
- package/dist/form/code.js +1 -3
- package/dist/form/markdown.d.ts +2 -1
- package/dist/form/markdown.js +1 -3
- package/dist/helpers/workflowEditorHelper.js +13 -9
- package/dist/mocks/app-forms.js +1 -0
- package/dist/mocks/app-navigation.js +3 -1
- package/dist/mocks/app-stores.d.ts +4 -4
- package/dist/playground/index.d.ts +5 -4
- package/dist/playground/index.js +15 -11
- package/dist/playground/mount.d.ts +20 -1
- package/dist/playground/mount.js +24 -6
- package/dist/services/agentSpecExecutionService.js +2 -1
- package/dist/services/api.js +10 -18
- package/dist/services/apiVariableService.js +2 -1
- package/dist/services/autoSaveService.d.ts +3 -3
- package/dist/services/autoSaveService.js +21 -17
- package/dist/services/categoriesApi.js +13 -5
- package/dist/services/draftStorage.js +5 -4
- package/dist/services/dynamicSchemaService.js +4 -4
- package/dist/services/globalSave.d.ts +60 -11
- package/dist/services/globalSave.js +160 -83
- package/dist/services/historyService.d.ts +2 -1
- package/dist/services/historyService.js +7 -3
- package/dist/services/interruptService.js +9 -8
- package/dist/services/nodeExecutionService.js +14 -6
- package/dist/services/playgroundService.d.ts +3 -2
- package/dist/services/playgroundService.js +8 -7
- package/dist/services/portConfigApi.js +11 -7
- package/dist/services/toastService.d.ts +1 -1
- package/dist/services/toastService.js +6 -5
- package/dist/services/variableService.js +3 -2
- package/dist/settings/index.d.ts +1 -1
- package/dist/settings/index.js +1 -1
- package/dist/stores/{categoriesStore.d.ts → categoriesStore.svelte.d.ts} +3 -3
- package/dist/stores/{categoriesStore.js → categoriesStore.svelte.js} +15 -18
- package/dist/stores/editorStateMachine.svelte.d.ts +42 -0
- package/dist/stores/editorStateMachine.svelte.js +132 -0
- package/dist/stores/{historyStore.d.ts → historyStore.svelte.d.ts} +18 -15
- package/dist/stores/{historyStore.js → historyStore.svelte.js} +40 -21
- package/dist/stores/{interruptStore.d.ts → interruptStore.svelte.d.ts} +16 -15
- package/dist/stores/{interruptStore.js → interruptStore.svelte.js} +85 -94
- package/dist/stores/{playgroundStore.d.ts → playgroundStore.svelte.d.ts} +52 -34
- package/dist/stores/{playgroundStore.js → playgroundStore.svelte.js} +193 -100
- package/dist/stores/{portCoordinateStore.d.ts → portCoordinateStore.svelte.d.ts} +10 -4
- package/dist/stores/{portCoordinateStore.js → portCoordinateStore.svelte.js} +38 -35
- package/dist/stores/{settingsStore.d.ts → settingsStore.svelte.d.ts} +45 -28
- package/dist/stores/{settingsStore.js → settingsStore.svelte.js} +169 -128
- package/dist/stores/{workflowStore.d.ts → workflowStore.svelte.d.ts} +101 -65
- package/dist/stores/{workflowStore.js → workflowStore.svelte.js} +285 -239
- package/dist/stories/CanvasDecorator.svelte +50 -0
- package/dist/stories/CanvasDecorator.svelte.d.ts +8 -0
- package/dist/stories/NodeDecorator.svelte +74 -0
- package/dist/stories/NodeDecorator.svelte.d.ts +8 -0
- package/dist/stories/utils.d.ts +93 -0
- package/dist/stories/utils.js +122 -0
- package/dist/styles/base.css +114 -61
- package/dist/styles/toast.css +2 -2
- package/dist/styles/tokens.css +250 -185
- package/dist/svelte-app.d.ts +0 -6
- package/dist/svelte-app.js +13 -31
- package/dist/types/index.d.ts +2 -0
- package/dist/types/interrupt.d.ts +89 -5
- package/dist/types/interrupt.js +13 -1
- package/dist/types/playground.d.ts +42 -1
- package/dist/types/playground.js +38 -0
- package/dist/types/settings.js +1 -1
- package/dist/utils/colors.js +4 -4
- package/dist/utils/connections.js +33 -8
- package/dist/utils/icons.js +1 -1
- package/dist/utils/logger.d.ts +47 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/nodeWrapper.js +1 -1
- package/dist/utils/sanitize.d.ts +19 -0
- package/dist/utils/sanitize.js +31 -0
- package/dist/utils/validation.d.ts +29 -0
- package/dist/utils/validation.js +39 -0
- package/package.json +243 -232
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import { onMount
|
|
8
|
+
import { onMount } from 'svelte';
|
|
9
9
|
import MainLayout from './layouts/MainLayout.svelte';
|
|
10
10
|
import WorkflowEditor from './WorkflowEditor.svelte';
|
|
11
11
|
import NodeSidebar from './NodeSidebar.svelte';
|
|
@@ -28,18 +28,21 @@
|
|
|
28
28
|
import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
|
|
29
29
|
import { mergeFeatures } from '../types/events.js';
|
|
30
30
|
import {
|
|
31
|
-
|
|
31
|
+
getWorkflowStore,
|
|
32
32
|
workflowActions,
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
getWorkflowName,
|
|
34
|
+
getWorkflowFormat,
|
|
35
35
|
markAsSaved
|
|
36
|
-
} from '../stores/workflowStore.js';
|
|
36
|
+
} from '../stores/workflowStore.svelte.js';
|
|
37
|
+
import { globalSaveWorkflow, globalExportWorkflow } from '../services/globalSave.js';
|
|
37
38
|
import { apiToasts, dismissToast } from '../services/toastService.js';
|
|
38
39
|
import { initAutoSave } from '../services/autoSaveService.js';
|
|
39
|
-
import {
|
|
40
|
+
import { getUiSettings } from '../stores/settingsStore.svelte.js';
|
|
40
41
|
import { initializePortCompatibility } from '../utils/connections.js';
|
|
41
42
|
import { DEFAULT_PORT_CONFIG } from '../config/defaultPortConfig.js';
|
|
42
43
|
import { workflowFormatRegistry } from '../registry/workflowFormatRegistry.js';
|
|
44
|
+
import { logger } from '../utils/logger.js';
|
|
45
|
+
import { validateWorkflowData } from '../utils/validation.js';
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
48
|
* Configuration props for runtime customization
|
|
@@ -120,10 +123,11 @@
|
|
|
120
123
|
return navbarTitle;
|
|
121
124
|
}
|
|
122
125
|
// Default workflow title logic
|
|
123
|
-
|
|
126
|
+
const wfName = getWorkflowName();
|
|
127
|
+
if (!wfName || wfName === 'Untitled Workflow') {
|
|
124
128
|
return 'Workflow / New Workflow';
|
|
125
129
|
}
|
|
126
|
-
return `Workflow / ${
|
|
130
|
+
return `Workflow / ${wfName}`;
|
|
127
131
|
});
|
|
128
132
|
|
|
129
133
|
let nodes = $state<NodeMetadata[]>([]);
|
|
@@ -175,15 +179,16 @@
|
|
|
175
179
|
|
|
176
180
|
// Workflow configuration values
|
|
177
181
|
let workflowConfigValues = $derived({
|
|
178
|
-
name:
|
|
179
|
-
description:
|
|
180
|
-
format:
|
|
182
|
+
name: getWorkflowName() || '',
|
|
183
|
+
description: getWorkflowStore()?.description || '',
|
|
184
|
+
format: getWorkflowStore()?.metadata?.format || 'flowdrop'
|
|
181
185
|
});
|
|
182
186
|
|
|
183
187
|
// Get the current node from the workflow store
|
|
184
188
|
let selectedNodeForConfig = $derived(() => {
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
const wf = getWorkflowStore();
|
|
190
|
+
if (!selectedNodeId || !wf) return null;
|
|
191
|
+
return wf.nodes.find((node) => node.id === selectedNodeId) || null;
|
|
187
192
|
});
|
|
188
193
|
|
|
189
194
|
// WorkflowEditor reference for save functionality
|
|
@@ -380,11 +385,13 @@
|
|
|
380
385
|
/**
|
|
381
386
|
* Handle workflow configuration save
|
|
382
387
|
*/
|
|
383
|
-
async function handleWorkflowSave(config:
|
|
388
|
+
async function handleWorkflowSave(config: Record<string, unknown>): Promise<void> {
|
|
384
389
|
// Update the workflow store
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
390
|
+
if (getWorkflowStore()) {
|
|
391
|
+
workflowActions.batchUpdate({
|
|
392
|
+
name: config.name as string | undefined,
|
|
393
|
+
description: config.description as string | undefined
|
|
394
|
+
});
|
|
388
395
|
}
|
|
389
396
|
|
|
390
397
|
// Close the sidebar
|
|
@@ -394,209 +401,101 @@
|
|
|
394
401
|
try {
|
|
395
402
|
await saveWorkflow();
|
|
396
403
|
} catch (error) {
|
|
397
|
-
|
|
404
|
+
logger.error('Failed to save workflow to backend:', error);
|
|
398
405
|
// Note: We don't throw the error here to avoid breaking the UI flow
|
|
399
406
|
// The user can still manually save via the main Save button if needed
|
|
400
407
|
}
|
|
401
408
|
}
|
|
402
409
|
|
|
403
410
|
/**
|
|
404
|
-
* Save workflow -
|
|
411
|
+
* Save workflow - thin wrapper that delegates to globalSaveWorkflow().
|
|
405
412
|
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
413
|
+
* All save logic (blur flush, metadata construction, API call, event hooks,
|
|
414
|
+
* toast notifications) lives in globalSave.ts — the single source of truth.
|
|
408
415
|
*/
|
|
409
416
|
async function saveWorkflow(): Promise<void> {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Wait for any pending DOM updates before saving
|
|
418
|
-
await tick();
|
|
419
|
-
|
|
420
|
-
// Use current workflow from global store
|
|
421
|
-
const workflowToSave = $workflowStore;
|
|
422
|
-
|
|
423
|
-
if (!workflowToSave) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Call onBeforeSave if provided - allows cancellation
|
|
428
|
-
if (eventHandlers?.onBeforeSave) {
|
|
429
|
-
const shouldContinue = await eventHandlers.onBeforeSave(workflowToSave);
|
|
430
|
-
if (shouldContinue === false) {
|
|
431
|
-
// Save cancelled by event handler
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Show loading toast (if enabled)
|
|
437
|
-
const loadingToast = features.showToasts ? apiToasts.loading('Saving workflow') : null;
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
// Import uuid for new workflow ID generation
|
|
441
|
-
const { v4: uuidv4 } = await import('uuid');
|
|
442
|
-
|
|
443
|
-
// Determine the workflow ID
|
|
444
|
-
let workflowId: string;
|
|
445
|
-
if (workflowToSave.id) {
|
|
446
|
-
workflowId = workflowToSave.id;
|
|
447
|
-
} else {
|
|
448
|
-
workflowId = uuidv4();
|
|
449
|
-
}
|
|
417
|
+
await globalSaveWorkflow({
|
|
418
|
+
apiClient: apiClient ?? undefined,
|
|
419
|
+
eventHandlers,
|
|
420
|
+
features,
|
|
421
|
+
onMarkAsSaved: markAsSaved
|
|
422
|
+
});
|
|
423
|
+
}
|
|
450
424
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
version: workflowToSave.metadata?.version || '1.0.0',
|
|
461
|
-
format: workflowToSave.metadata?.format || DEFAULT_WORKFLOW_FORMAT,
|
|
462
|
-
createdAt: workflowToSave.metadata?.createdAt || new Date().toISOString(),
|
|
463
|
-
updatedAt: new Date().toISOString()
|
|
464
|
-
}
|
|
465
|
-
};
|
|
425
|
+
/**
|
|
426
|
+
* Export workflow - thin wrapper that delegates to globalExportWorkflow().
|
|
427
|
+
*
|
|
428
|
+
* All export logic (flush, metadata construction, file download) lives
|
|
429
|
+
* in globalSave.ts — the single source of truth.
|
|
430
|
+
*/
|
|
431
|
+
async function exportWorkflow(): Promise<void> {
|
|
432
|
+
await globalExportWorkflow({ features });
|
|
433
|
+
}
|
|
466
434
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
savedWorkflow = await apiClient.updateWorkflow(finalWorkflow.id, finalWorkflow);
|
|
480
|
-
} else {
|
|
481
|
-
savedWorkflow = await apiClient.saveWorkflow(finalWorkflow);
|
|
435
|
+
/**
|
|
436
|
+
* Import workflow from a JSON file
|
|
437
|
+
*
|
|
438
|
+
* Reads the selected file, validates its structure, and loads it into the workflow store.
|
|
439
|
+
*/
|
|
440
|
+
function importWorkflow(file: File): void {
|
|
441
|
+
const reader = new FileReader();
|
|
442
|
+
reader.onload = (event) => {
|
|
443
|
+
try {
|
|
444
|
+
const text = event.target?.result;
|
|
445
|
+
if (typeof text !== 'string') {
|
|
446
|
+
throw new Error('Could not read file contents.');
|
|
482
447
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
// Update the workflow ID if it changed (new workflow)
|
|
490
|
-
// Keep our current workflow state, only update ID and metadata from backend
|
|
491
|
-
if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
|
|
492
|
-
workflowActions.batchUpdate({
|
|
493
|
-
nodes: finalWorkflow.nodes,
|
|
494
|
-
edges: finalWorkflow.edges,
|
|
495
|
-
name: finalWorkflow.name,
|
|
496
|
-
metadata: {
|
|
497
|
-
...finalWorkflow.metadata,
|
|
498
|
-
...savedWorkflow.metadata
|
|
448
|
+
const data = JSON.parse(text);
|
|
449
|
+
const validation = validateWorkflowData(data);
|
|
450
|
+
if (!validation.valid) {
|
|
451
|
+
if (features.showToasts) {
|
|
452
|
+
apiToasts.error('Import workflow', validation.error ?? 'Invalid workflow JSON');
|
|
499
453
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
454
|
+
logger.warn('Workflow import validation failed:', validation.error);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
workflowActions.initialize(data as Workflow);
|
|
458
|
+
if (features.showToasts) {
|
|
459
|
+
apiToasts.success('Import workflow', 'Workflow imported successfully');
|
|
460
|
+
}
|
|
461
|
+
if (eventHandlers?.onWorkflowLoad) {
|
|
462
|
+
eventHandlers.onWorkflowLoad(data as Workflow);
|
|
463
|
+
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
|
|
466
|
+
logger.error('Workflow import failed:', errorObj);
|
|
467
|
+
if (features.showToasts) {
|
|
468
|
+
apiToasts.error('Import workflow', errorObj.message);
|
|
469
|
+
}
|
|
509
470
|
}
|
|
471
|
+
};
|
|
472
|
+
reader.onerror = () => {
|
|
473
|
+
const message = 'Failed to read the selected file.';
|
|
474
|
+
logger.error(message);
|
|
510
475
|
if (features.showToasts) {
|
|
511
|
-
apiToasts.
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Call onAfterSave if provided
|
|
515
|
-
if (eventHandlers?.onAfterSave) {
|
|
516
|
-
await eventHandlers.onAfterSave(savedWorkflow);
|
|
517
|
-
}
|
|
518
|
-
} catch (error) {
|
|
519
|
-
// Dismiss loading toast
|
|
520
|
-
if (loadingToast) {
|
|
521
|
-
dismissToast(loadingToast);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
|
|
525
|
-
|
|
526
|
-
// Call onSaveError if provided
|
|
527
|
-
if (eventHandlers?.onSaveError && workflowToSave) {
|
|
528
|
-
await eventHandlers.onSaveError(errorObj, workflowToSave);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Check if parent wants to handle API errors
|
|
532
|
-
let suppressToast = false;
|
|
533
|
-
if (eventHandlers?.onApiError) {
|
|
534
|
-
suppressToast = eventHandlers.onApiError(errorObj, 'save') === true;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// Show error toast if not suppressed
|
|
538
|
-
if (features.showToasts && !suppressToast) {
|
|
539
|
-
apiToasts.error('Save workflow', errorObj.message);
|
|
476
|
+
apiToasts.error('Import workflow', message);
|
|
540
477
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
478
|
+
};
|
|
479
|
+
reader.readAsText(file);
|
|
544
480
|
}
|
|
545
481
|
|
|
546
482
|
/**
|
|
547
|
-
*
|
|
483
|
+
* Handle file input change event for workflow import
|
|
548
484
|
*/
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
// Use current workflow from global store
|
|
555
|
-
const workflowToExport = $workflowStore;
|
|
556
|
-
|
|
557
|
-
if (!workflowToExport) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Create workflow object for export (spread existing metadata to preserve format, tags, etc.)
|
|
562
|
-
const finalWorkflow = {
|
|
563
|
-
id: workflowToExport.id || 'untitled-workflow',
|
|
564
|
-
name: workflowToExport.name || 'Untitled Workflow',
|
|
565
|
-
nodes: workflowToExport.nodes || [],
|
|
566
|
-
edges: workflowToExport.edges || [],
|
|
567
|
-
metadata: {
|
|
568
|
-
...workflowToExport.metadata,
|
|
569
|
-
version: workflowToExport.metadata?.version || '1.0.0',
|
|
570
|
-
format: workflowToExport.metadata?.format || DEFAULT_WORKFLOW_FORMAT,
|
|
571
|
-
createdAt: workflowToExport.metadata?.createdAt || new Date().toISOString(),
|
|
572
|
-
updatedAt: new Date().toISOString()
|
|
573
|
-
}
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
// Create and download the file
|
|
577
|
-
const dataStr = JSON.stringify(finalWorkflow, null, 2);
|
|
578
|
-
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
|
579
|
-
const url = URL.createObjectURL(dataBlob);
|
|
580
|
-
const link = document.createElement('a');
|
|
581
|
-
link.href = url;
|
|
582
|
-
link.download = `${finalWorkflow.name}.json`;
|
|
583
|
-
link.click();
|
|
584
|
-
URL.revokeObjectURL(url);
|
|
585
|
-
} catch {
|
|
586
|
-
// Export failed
|
|
485
|
+
function handleImportFileChange(event: Event): void {
|
|
486
|
+
const input = event.target as HTMLInputElement;
|
|
487
|
+
const file = input.files?.[0];
|
|
488
|
+
if (file) {
|
|
489
|
+
importWorkflow(file);
|
|
587
490
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
// Expose save and export functions globally for external access
|
|
591
|
-
if (typeof window !== 'undefined') {
|
|
592
|
-
window.flowdropSave = saveWorkflow;
|
|
593
|
-
window.flowdropExport = exportWorkflow;
|
|
491
|
+
// Reset input so same file can be re-imported
|
|
492
|
+
input.value = '';
|
|
594
493
|
}
|
|
595
494
|
|
|
596
495
|
// Function to handle clicks outside the sidebar
|
|
597
496
|
function handleCanvasClick(event: MouseEvent): void {
|
|
598
497
|
// Check if the click is outside the right sidebar
|
|
599
|
-
const rightSidebar = document.querySelector('.flowdrop-
|
|
498
|
+
const rightSidebar = document.querySelector('.flowdrop-main-layout__sidebar--right');
|
|
600
499
|
if (rightSidebar && !rightSidebar.contains(event.target as Node)) {
|
|
601
500
|
// Close sidebar when clicking outside of it
|
|
602
501
|
if (isConfigSidebarOpen) {
|
|
@@ -657,10 +556,10 @@
|
|
|
657
556
|
},
|
|
658
557
|
onError: (error) => {
|
|
659
558
|
// Don't show toast for auto-save errors to avoid noise
|
|
660
|
-
|
|
559
|
+
logger.warn('Auto-save failed:', error);
|
|
661
560
|
},
|
|
662
561
|
onSuccess: () => {
|
|
663
|
-
|
|
562
|
+
logger.debug('Auto-saved workflow');
|
|
664
563
|
}
|
|
665
564
|
});
|
|
666
565
|
|
|
@@ -672,7 +571,7 @@
|
|
|
672
571
|
|
|
673
572
|
// Monitor workflow store changes for testing node drag updates
|
|
674
573
|
$effect(() => {
|
|
675
|
-
const currentWorkflow =
|
|
574
|
+
const currentWorkflow = getWorkflowStore();
|
|
676
575
|
if (currentWorkflow) {
|
|
677
576
|
// Workflow store updated
|
|
678
577
|
}
|
|
@@ -689,7 +588,12 @@
|
|
|
689
588
|
* Calculate left sidebar width based on collapsed state
|
|
690
589
|
* When collapsed, use 48px; otherwise use user-configured width
|
|
691
590
|
*/
|
|
692
|
-
const leftSidebarWidth = $derived(
|
|
591
|
+
const leftSidebarWidth = $derived(
|
|
592
|
+
getUiSettings().sidebarCollapsed ? 48 : getUiSettings().sidebarWidth
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// File input reference for workflow import
|
|
596
|
+
let fileInputRef = $state<HTMLInputElement | null>(null);
|
|
693
597
|
</script>
|
|
694
598
|
|
|
695
599
|
<svelte:head>
|
|
@@ -697,6 +601,15 @@
|
|
|
697
601
|
<meta name="description" content="A modern drag-and-drop workflow editor for LLM applications" />
|
|
698
602
|
</svelte:head>
|
|
699
603
|
|
|
604
|
+
<!-- Hidden file input for workflow JSON import -->
|
|
605
|
+
<input
|
|
606
|
+
bind:this={fileInputRef}
|
|
607
|
+
type="file"
|
|
608
|
+
accept=".json,application/json"
|
|
609
|
+
style="display: none;"
|
|
610
|
+
onchange={handleImportFileChange}
|
|
611
|
+
/>
|
|
612
|
+
|
|
700
613
|
<!-- MainLayout wrapper for workflow editor -->
|
|
701
614
|
<MainLayout
|
|
702
615
|
showHeader={showNavbar}
|
|
@@ -707,8 +620,8 @@
|
|
|
707
620
|
headerHeight={60}
|
|
708
621
|
{leftSidebarWidth}
|
|
709
622
|
rightSidebarWidth={400}
|
|
710
|
-
leftSidebarMinWidth={
|
|
711
|
-
leftSidebarMaxWidth={
|
|
623
|
+
leftSidebarMinWidth={getUiSettings().sidebarCollapsed ? 48 : 280}
|
|
624
|
+
leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ? 48 : 450}
|
|
712
625
|
rightSidebarMinWidth={320}
|
|
713
626
|
rightSidebarMaxWidth={550}
|
|
714
627
|
enableLeftSplitPane={false}
|
|
@@ -742,6 +655,16 @@
|
|
|
742
655
|
exportWorkflow();
|
|
743
656
|
}
|
|
744
657
|
},
|
|
658
|
+
{
|
|
659
|
+
label: 'Import',
|
|
660
|
+
href: '#import',
|
|
661
|
+
icon: 'heroicons:arrow-up-tray',
|
|
662
|
+
variant: 'outline',
|
|
663
|
+
onclick: (e) => {
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
fileInputRef?.click();
|
|
666
|
+
}
|
|
667
|
+
},
|
|
745
668
|
{
|
|
746
669
|
label: 'Workflow Settings',
|
|
747
670
|
href: '#settings',
|
|
@@ -760,7 +683,7 @@
|
|
|
760
683
|
|
|
761
684
|
<!-- Left Sidebar: Node Components -->
|
|
762
685
|
{#snippet leftSidebar()}
|
|
763
|
-
<NodeSidebar {nodes} activeFormat={
|
|
686
|
+
<NodeSidebar {nodes} activeFormat={getWorkflowFormat()} />
|
|
764
687
|
{/snippet}
|
|
765
688
|
|
|
766
689
|
<!-- Right Sidebar: Configuration or Workflow Settings -->
|
|
@@ -768,10 +691,10 @@
|
|
|
768
691
|
{#if isWorkflowSettingsOpen}
|
|
769
692
|
<ConfigPanel
|
|
770
693
|
title="Workflow Settings"
|
|
771
|
-
id={
|
|
694
|
+
id={getWorkflowStore()?.id}
|
|
772
695
|
details={[
|
|
773
|
-
{ label: 'Nodes', value: String(
|
|
774
|
-
{ label: 'Connections', value: String(
|
|
696
|
+
{ label: 'Nodes', value: String(getWorkflowStore()?.nodes?.length ?? 0) },
|
|
697
|
+
{ label: 'Connections', value: String(getWorkflowStore()?.edges?.length ?? 0) }
|
|
775
698
|
]}
|
|
776
699
|
configTitle="Settings"
|
|
777
700
|
onClose={() => (isWorkflowSettingsOpen = false)}
|
|
@@ -783,18 +706,19 @@
|
|
|
783
706
|
showUIExtensions={false}
|
|
784
707
|
onChange={(config) => {
|
|
785
708
|
// Sync workflow settings changes immediately on field blur
|
|
786
|
-
|
|
709
|
+
const wf = getWorkflowStore();
|
|
710
|
+
if (wf) {
|
|
787
711
|
const newFormat = (config.format as string) || DEFAULT_WORKFLOW_FORMAT;
|
|
788
|
-
const currentFormat =
|
|
712
|
+
const currentFormat = wf.metadata?.format || DEFAULT_WORKFLOW_FORMAT;
|
|
789
713
|
|
|
790
714
|
// Warn about incompatible nodes when format changes
|
|
791
715
|
if (newFormat !== currentFormat) {
|
|
792
|
-
const incompatibleNodes =
|
|
716
|
+
const incompatibleNodes = wf.nodes?.filter((node) => {
|
|
793
717
|
const formats = node.data?.metadata?.formats;
|
|
794
718
|
return formats && formats.length > 0 && !formats.includes(newFormat);
|
|
795
719
|
});
|
|
796
720
|
if (incompatibleNodes && incompatibleNodes.length > 0) {
|
|
797
|
-
|
|
721
|
+
logger.warn(
|
|
798
722
|
`Format changed to '${newFormat}'. ${incompatibleNodes.length} node(s) are not compatible with this format and may not export correctly:`,
|
|
799
723
|
incompatibleNodes.map((n) => n.data?.label || n.type)
|
|
800
724
|
);
|
|
@@ -805,7 +729,7 @@
|
|
|
805
729
|
name: config.name as string,
|
|
806
730
|
description: config.description as string | undefined,
|
|
807
731
|
metadata: {
|
|
808
|
-
|
|
732
|
+
...wf.metadata,
|
|
809
733
|
format: newFormat
|
|
810
734
|
}
|
|
811
735
|
});
|
|
@@ -814,7 +738,7 @@
|
|
|
814
738
|
/>
|
|
815
739
|
</ConfigPanel>
|
|
816
740
|
{:else if selectedNodeForConfig()}
|
|
817
|
-
{@const currentNode = selectedNodeForConfig()}
|
|
741
|
+
{@const currentNode = selectedNodeForConfig()!}
|
|
818
742
|
<ConfigPanel
|
|
819
743
|
title={currentNode.data.label}
|
|
820
744
|
id={currentNode.id}
|
|
@@ -828,9 +752,9 @@
|
|
|
828
752
|
<ConfigForm
|
|
829
753
|
{authProvider}
|
|
830
754
|
node={currentNode}
|
|
831
|
-
workflowId={
|
|
832
|
-
workflowNodes={
|
|
833
|
-
workflowEdges={
|
|
755
|
+
workflowId={getWorkflowStore()?.id}
|
|
756
|
+
workflowNodes={getWorkflowStore()?.nodes}
|
|
757
|
+
workflowEdges={getWorkflowStore()?.edges}
|
|
834
758
|
onChange={async (updatedConfig, uiExtensions) => {
|
|
835
759
|
// Sync config changes to workflow immediately on field blur
|
|
836
760
|
if (selectedNodeId && currentNode) {
|
|
@@ -857,10 +781,10 @@
|
|
|
857
781
|
|
|
858
782
|
// Update the local editor state to reflect config changes immediately
|
|
859
783
|
// This is needed for nodeType changes to take effect visually
|
|
860
|
-
workflowEditorRef
|
|
784
|
+
workflowEditorRef?.updateNodeData(selectedNodeId, updatedData);
|
|
861
785
|
|
|
862
786
|
// Refresh edge positions in case config changes affect handles
|
|
863
|
-
await workflowEditorRef
|
|
787
|
+
await workflowEditorRef?.refreshEdgePositions(selectedNodeId);
|
|
864
788
|
}
|
|
865
789
|
}}
|
|
866
790
|
/>
|
|
@@ -869,9 +793,9 @@
|
|
|
869
793
|
{/snippet}
|
|
870
794
|
|
|
871
795
|
<!-- Main Content: Workflow Editor with Error Status -->
|
|
872
|
-
<!-- Status Display -->
|
|
796
|
+
<!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
|
|
873
797
|
{#if error}
|
|
874
|
-
<div class="flowdrop-status flowdrop-status--error">
|
|
798
|
+
<div class="flowdrop-status flowdrop-status--error" aria-live="polite" aria-atomic="true">
|
|
875
799
|
<div class="flowdrop-status__content">
|
|
876
800
|
<div class="flowdrop-flex flowdrop-gap--3">
|
|
877
801
|
<div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
|
|
@@ -920,7 +844,7 @@
|
|
|
920
844
|
{/if}
|
|
921
845
|
|
|
922
846
|
<!-- Main Editor Area -->
|
|
923
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
847
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions — interactive workflow canvas region with keyboard support -->
|
|
924
848
|
<div
|
|
925
849
|
class="flowdrop-editor-main"
|
|
926
850
|
class:pipeline-view={!!pipelineId}
|
|
@@ -934,7 +858,7 @@
|
|
|
934
858
|
{nodes}
|
|
935
859
|
{height}
|
|
936
860
|
{width}
|
|
937
|
-
{endpointConfig}
|
|
861
|
+
endpointConfig={endpointConfig ?? undefined}
|
|
938
862
|
{isConfigSidebarOpen}
|
|
939
863
|
selectedNodeForConfig={selectedNodeForConfig()}
|
|
940
864
|
{openConfigSidebar}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script module>
|
|
2
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
|
+
import CanvasBanner from "./CanvasBanner.svelte";
|
|
4
|
+
import CanvasDecorator from "../stories/CanvasDecorator.svelte";
|
|
5
|
+
|
|
6
|
+
const { Story } = defineMeta({
|
|
7
|
+
title: "Display/CanvasBanner",
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "centered",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Story name="Default">
|
|
16
|
+
<CanvasDecorator>
|
|
17
|
+
<CanvasBanner title="Empty Canvas" description="Drag nodes from the sidebar to get started" iconName="heroicons:squares-plus" />
|
|
18
|
+
</CanvasDecorator>
|
|
19
|
+
</Story>
|
|
20
|
+
|
|
21
|
+
<Story name="No Icon">
|
|
22
|
+
<CanvasDecorator>
|
|
23
|
+
<CanvasBanner title="No Workflows" description="Create your first workflow to begin" />
|
|
24
|
+
</CanvasDecorator>
|
|
25
|
+
</Story>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default CanvasBanner;
|
|
2
|
+
type CanvasBanner = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const CanvasBanner: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
+
[x: string]: never;
|
|
11
|
+
}, {
|
|
12
|
+
[evt: string]: CustomEvent<any>;
|
|
13
|
+
}, {}, {}, string>;
|
|
14
|
+
import CanvasBanner from "./CanvasBanner.svelte";
|
|
15
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
16
|
+
new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
|
|
17
|
+
$$bindings?: Bindings;
|
|
18
|
+
} & Exports;
|
|
19
|
+
(internal: unknown, props: {
|
|
20
|
+
$$events?: Events;
|
|
21
|
+
$$slots?: Slots;
|
|
22
|
+
}): Exports & {
|
|
23
|
+
$set?: any;
|
|
24
|
+
$on?: any;
|
|
25
|
+
};
|
|
26
|
+
z_$$bindings?: Bindings;
|
|
27
|
+
}
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
font-size: 1.125rem;
|
|
48
48
|
font-weight: 700;
|
|
49
49
|
margin-bottom: 0.5rem;
|
|
50
|
-
color: #111827;
|
|
50
|
+
color: var(--fd-foreground, #111827);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.flowdrop-canvas-banner__description {
|
|
54
54
|
font-size: 0.875rem;
|
|
55
|
-
color: #6b7280;
|
|
55
|
+
color: var(--fd-muted-foreground, #6b7280);
|
|
56
56
|
}
|
|
57
57
|
</style>
|