@flowdrop/flowdrop 1.3.0 → 1.5.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/README.md +68 -24
- package/dist/adapters/WorkflowAdapter.js +2 -22
- package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
- package/dist/adapters/agentspec/autoLayout.js +120 -23
- package/dist/chat/commandClassifier.d.ts +19 -0
- package/dist/chat/commandClassifier.js +30 -0
- package/dist/chat/index.d.ts +27 -0
- package/dist/chat/index.js +32 -0
- package/dist/chat/responseParser.d.ts +21 -0
- package/dist/chat/responseParser.js +87 -0
- package/dist/commands/batch.d.ts +18 -0
- package/dist/commands/batch.js +56 -0
- package/dist/commands/executor.d.ts +37 -0
- package/dist/commands/executor.js +1044 -0
- package/dist/commands/index.d.ts +14 -0
- package/dist/commands/index.js +17 -0
- package/dist/commands/parser.d.ts +16 -0
- package/dist/commands/parser.js +278 -0
- package/dist/commands/positioner.d.ts +19 -0
- package/dist/commands/positioner.js +33 -0
- package/dist/commands/storeIntegration.svelte.d.ts +16 -0
- package/dist/commands/storeIntegration.svelte.js +67 -0
- package/dist/commands/types.d.ts +343 -0
- package/dist/commands/types.js +45 -0
- package/dist/components/App.svelte +431 -17
- package/dist/components/App.svelte.d.ts +10 -0
- package/dist/components/CanvasBanner.stories.svelte +6 -2
- package/dist/components/CanvasController.svelte +38 -0
- package/dist/components/CanvasController.svelte.d.ts +32 -0
- package/dist/components/ConfigMappingRow.svelte +130 -0
- package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
- package/dist/components/ConfigPanel.svelte +56 -7
- package/dist/components/ConfigPanel.svelte.d.ts +2 -0
- package/dist/components/FlowDropEdge.svelte +8 -57
- package/dist/components/Logo.svelte +14 -14
- package/dist/components/LogsSidebar.svelte +5 -5
- package/dist/components/Navbar.svelte +58 -10
- package/dist/components/Navbar.svelte.d.ts +7 -0
- package/dist/components/NodeSidebar.svelte +238 -362
- package/dist/components/NodeSwapPicker.svelte +537 -0
- package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
- package/dist/components/PortMappingRow.svelte +209 -0
- package/dist/components/PortMappingRow.svelte.d.ts +12 -0
- package/dist/components/SwapMappingEditor.svelte +550 -0
- package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
- package/dist/components/WorkflowEditor.svelte +99 -4
- package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
- package/dist/components/chat/AIChatPanel.svelte +658 -0
- package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
- package/dist/components/chat/CommandPreview.svelte +184 -0
- package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
- package/dist/components/console/CommandConsole.stories.svelte +93 -0
- package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
- package/dist/components/console/CommandConsole.svelte +259 -0
- package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
- package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
- package/dist/components/console/ConsoleInput.svelte +712 -0
- package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
- package/dist/components/console/ConsoleOutput.svelte +121 -0
- package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
- package/dist/components/console/formatters.d.ts +26 -0
- package/dist/components/console/formatters.js +118 -0
- package/dist/components/interrupt/index.d.ts +1 -0
- package/dist/components/interrupt/index.js +1 -0
- package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
- package/dist/components/nodes/SimpleNode.svelte +27 -11
- package/dist/components/nodes/SquareNode.stories.svelte +45 -0
- package/dist/components/nodes/SquareNode.svelte +27 -11
- package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
- package/dist/config/endpoints.d.ts +8 -0
- package/dist/config/endpoints.js +5 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +9 -0
- package/dist/editor/index.d.ts +3 -1
- package/dist/editor/index.js +4 -2
- package/dist/helpers/proximityConnect.js +8 -1
- package/dist/helpers/workflowEditorHelper.d.ts +3 -53
- package/dist/helpers/workflowEditorHelper.js +13 -228
- package/dist/playground/index.d.ts +1 -1
- package/dist/playground/index.js +1 -1
- package/dist/schemas/v1/workflow.schema.json +107 -22
- package/dist/services/chatService.d.ts +65 -0
- package/dist/services/chatService.js +131 -0
- package/dist/services/historyService.d.ts +6 -4
- package/dist/services/historyService.js +21 -6
- package/dist/skins/slate.js +16 -0
- package/dist/stores/interruptStore.svelte.js +6 -1
- package/dist/stores/playgroundStore.svelte.d.ts +1 -1
- package/dist/stores/playgroundStore.svelte.js +11 -2
- package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
- package/dist/stores/portCoordinateStore.svelte.js +20 -26
- package/dist/stores/workflowStore.svelte.d.ts +31 -2
- package/dist/stores/workflowStore.svelte.js +84 -64
- package/dist/stories/EdgeDecorator.svelte +4 -4
- package/dist/styles/base.css +48 -0
- package/dist/svelte-app.d.ts +7 -1
- package/dist/svelte-app.js +4 -1
- package/dist/types/chat.d.ts +63 -0
- package/dist/types/chat.js +9 -0
- package/dist/types/events.d.ts +28 -2
- package/dist/types/events.js +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/settings.d.ts +6 -0
- package/dist/types/settings.js +3 -0
- package/dist/utils/edgeStyling.d.ts +42 -0
- package/dist/utils/edgeStyling.js +176 -0
- package/dist/utils/nodeIds.d.ts +31 -0
- package/dist/utils/nodeIds.js +42 -0
- package/dist/utils/nodeSwap.d.ts +221 -0
- package/dist/utils/nodeSwap.js +686 -0
- package/package.json +6 -1
- package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
- package/dist/helpers/nodeLayoutHelper.js +0 -19
|
@@ -5,12 +5,18 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import { onMount } from "svelte";
|
|
8
|
+
import { onMount, tick } from "svelte";
|
|
9
9
|
import MainLayout from "./layouts/MainLayout.svelte";
|
|
10
10
|
import WorkflowEditor from "./WorkflowEditor.svelte";
|
|
11
11
|
import NodeSidebar from "./NodeSidebar.svelte";
|
|
12
|
+
import Icon from "@iconify/svelte";
|
|
12
13
|
import ConfigForm from "./ConfigForm.svelte";
|
|
13
14
|
import ConfigPanel from "./ConfigPanel.svelte";
|
|
15
|
+
import CommandConsole from "./console/CommandConsole.svelte";
|
|
16
|
+
import AIChatPanel from "./chat/AIChatPanel.svelte";
|
|
17
|
+
import type { UIAction } from "../commands/index.js";
|
|
18
|
+
import NodeSwapPicker from "./NodeSwapPicker.svelte";
|
|
19
|
+
import SwapMappingEditor from "./SwapMappingEditor.svelte";
|
|
14
20
|
import Navbar from "./Navbar.svelte";
|
|
15
21
|
import { api, setEndpointConfig } from "../services/api.js";
|
|
16
22
|
import { EnhancedFlowDropApiClient } from "../api/enhanced-client.js";
|
|
@@ -21,6 +27,14 @@
|
|
|
21
27
|
ConfigSchema,
|
|
22
28
|
NodeUIExtensions,
|
|
23
29
|
} from "../types/index.js";
|
|
30
|
+
import type { InteractiveSwapState, SwapEventContext } from "../utils/nodeSwap.js";
|
|
31
|
+
import {
|
|
32
|
+
computeInteractiveState,
|
|
33
|
+
buildSwapPreviewFromState,
|
|
34
|
+
executeSwap,
|
|
35
|
+
validateSwapResult,
|
|
36
|
+
} from "../utils/nodeSwap.js";
|
|
37
|
+
import type { SwapStrategy } from "../utils/nodeSwap.js";
|
|
24
38
|
import { DEFAULT_WORKFLOW_FORMAT } from "../types/index.js";
|
|
25
39
|
import { createEndpointConfig } from "../config/endpoints.js";
|
|
26
40
|
import type { EndpointConfig } from "../config/endpoints.js";
|
|
@@ -46,12 +60,16 @@
|
|
|
46
60
|
} from "../services/globalSave.js";
|
|
47
61
|
import { apiToasts, dismissToast } from "../services/toastService.js";
|
|
48
62
|
import { initAutoSave } from "../services/autoSaveService.js";
|
|
49
|
-
import {
|
|
50
|
-
|
|
63
|
+
import {
|
|
64
|
+
getUiSettings,
|
|
65
|
+
updateSettings,
|
|
66
|
+
} from "../stores/settingsStore.svelte.js";
|
|
67
|
+
import { initializePortCompatibility, getPortCompatibilityChecker } from "../utils/connections.js";
|
|
51
68
|
import { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
|
|
52
69
|
import { workflowFormatRegistry } from "../registry/workflowFormatRegistry.js";
|
|
53
70
|
import { logger } from "../utils/logger.js";
|
|
54
71
|
import { validateWorkflowData } from "../utils/validation.js";
|
|
72
|
+
import type { SettingsCategory } from "../types/settings.js";
|
|
55
73
|
|
|
56
74
|
/**
|
|
57
75
|
* Configuration props for runtime customization
|
|
@@ -104,6 +122,14 @@
|
|
|
104
122
|
features?: FlowDropFeatures;
|
|
105
123
|
/** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
|
|
106
124
|
theme?: FlowDropTheme | FlowDropThemeName;
|
|
125
|
+
/** Which settings tabs to show in the modal */
|
|
126
|
+
settingsCategories?: SettingsCategory[];
|
|
127
|
+
/** Show the "Sync to Cloud" button in the settings modal */
|
|
128
|
+
showSettingsSyncButton?: boolean;
|
|
129
|
+
/** Show the reset buttons in the settings modal */
|
|
130
|
+
showSettingsResetButton?: boolean;
|
|
131
|
+
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
132
|
+
swapStrategies?: SwapStrategy[];
|
|
107
133
|
}
|
|
108
134
|
|
|
109
135
|
let {
|
|
@@ -126,6 +152,10 @@
|
|
|
126
152
|
eventHandlers,
|
|
127
153
|
features: propFeatures,
|
|
128
154
|
theme: themeProp,
|
|
155
|
+
settingsCategories,
|
|
156
|
+
showSettingsSyncButton,
|
|
157
|
+
showSettingsResetButton,
|
|
158
|
+
swapStrategies,
|
|
129
159
|
}: Props = $props();
|
|
130
160
|
|
|
131
161
|
// svelte-ignore state_referenced_locally — feature flags don't change at runtime
|
|
@@ -166,7 +196,7 @@
|
|
|
166
196
|
});
|
|
167
197
|
|
|
168
198
|
// Create breadcrumb-style title - at top level to avoid store subscription issues
|
|
169
|
-
let breadcrumbTitle = $derived(() => {
|
|
199
|
+
let breadcrumbTitle = $derived.by(() => {
|
|
170
200
|
// Use custom navbar title if provided
|
|
171
201
|
if (navbarTitle) {
|
|
172
202
|
return navbarTitle;
|
|
@@ -199,6 +229,11 @@
|
|
|
199
229
|
// Workflow settings sidebar state
|
|
200
230
|
let isWorkflowSettingsOpen = $state(false);
|
|
201
231
|
|
|
232
|
+
// Node swap state
|
|
233
|
+
let swapMode = $state<"idle" | "picking" | "mapping">("idle");
|
|
234
|
+
let swapTargetMetadata = $state<NodeMetadata | null>(null);
|
|
235
|
+
let swapInteractiveState = $state<InteractiveSwapState | null>(null);
|
|
236
|
+
|
|
202
237
|
// Workflow configuration schema (derived to pick up dynamic format options)
|
|
203
238
|
let workflowConfigSchema: ConfigSchema = $derived({
|
|
204
239
|
type: "object" as const,
|
|
@@ -235,7 +270,7 @@
|
|
|
235
270
|
});
|
|
236
271
|
|
|
237
272
|
// Get the current node from the workflow store
|
|
238
|
-
let selectedNodeForConfig = $derived(() => {
|
|
273
|
+
let selectedNodeForConfig = $derived.by(() => {
|
|
239
274
|
const wf = getWorkflowStore();
|
|
240
275
|
if (!selectedNodeId || !wf) return null;
|
|
241
276
|
return wf.nodes.find((node) => node.id === selectedNodeId) || null;
|
|
@@ -430,11 +465,19 @@
|
|
|
430
465
|
}
|
|
431
466
|
selectedNodeId = node.id;
|
|
432
467
|
isConfigSidebarOpen = true;
|
|
468
|
+
// Reset swap state when switching nodes
|
|
469
|
+
swapMode = "idle";
|
|
470
|
+
swapTargetMetadata = null;
|
|
471
|
+
swapInteractiveState = null;
|
|
433
472
|
}
|
|
434
473
|
|
|
435
474
|
function closeConfigSidebar(): void {
|
|
436
475
|
isConfigSidebarOpen = false;
|
|
437
476
|
selectedNodeId = null;
|
|
477
|
+
// Reset swap state when closing
|
|
478
|
+
swapMode = "idle";
|
|
479
|
+
swapTargetMetadata = null;
|
|
480
|
+
swapInteractiveState = null;
|
|
438
481
|
}
|
|
439
482
|
|
|
440
483
|
/**
|
|
@@ -448,6 +491,144 @@
|
|
|
448
491
|
}
|
|
449
492
|
}
|
|
450
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Start swap mode — transitions the right sidebar to the node picker
|
|
496
|
+
*/
|
|
497
|
+
function startSwap(): void {
|
|
498
|
+
swapMode = "picking";
|
|
499
|
+
swapTargetMetadata = null;
|
|
500
|
+
swapInteractiveState = null;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Handle selection of a target node type for swap
|
|
505
|
+
*/
|
|
506
|
+
function handleSwapSelect(metadata: NodeMetadata): void {
|
|
507
|
+
const node = selectedNodeForConfig;
|
|
508
|
+
if (!node) return;
|
|
509
|
+
|
|
510
|
+
const wf = getWorkflowStore();
|
|
511
|
+
if (!wf) return;
|
|
512
|
+
|
|
513
|
+
// Format compatibility guard — defence-in-depth behind picker's own filter
|
|
514
|
+
const currentFormat = getWorkflowFormat();
|
|
515
|
+
if (metadata.formats?.length && !metadata.formats.includes(currentFormat)) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Get port compatibility checker (may be null if not initialized)
|
|
520
|
+
let checker: import("../utils/connections.js").PortCompatibilityChecker | null = null;
|
|
521
|
+
try {
|
|
522
|
+
checker = getPortCompatibilityChecker();
|
|
523
|
+
} catch {
|
|
524
|
+
// Checker not initialized — computeSwapPreview will use exact dataType matching
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const interactive = computeInteractiveState(
|
|
528
|
+
node,
|
|
529
|
+
metadata,
|
|
530
|
+
wf.edges,
|
|
531
|
+
wf.nodes,
|
|
532
|
+
{ checker, strategies: swapStrategies },
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
swapTargetMetadata = metadata;
|
|
536
|
+
swapInteractiveState = interactive;
|
|
537
|
+
swapMode = "mapping";
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Execute the confirmed node swap
|
|
542
|
+
*/
|
|
543
|
+
async function executeNodeSwap(finalState?: InteractiveSwapState): Promise<void> {
|
|
544
|
+
const state = finalState ?? swapInteractiveState;
|
|
545
|
+
if (!state) return;
|
|
546
|
+
|
|
547
|
+
const wf = getWorkflowStore();
|
|
548
|
+
if (!wf) return;
|
|
549
|
+
|
|
550
|
+
const oldLabel = state.oldNode.data.label;
|
|
551
|
+
const newLabel = state.newMetadata.name;
|
|
552
|
+
|
|
553
|
+
// Convert interactive state to swap preview
|
|
554
|
+
const preview = buildSwapPreviewFromState(state, wf.edges);
|
|
555
|
+
|
|
556
|
+
// Execute the swap
|
|
557
|
+
const result = executeSwap(
|
|
558
|
+
state.oldNode,
|
|
559
|
+
state.newMetadata,
|
|
560
|
+
preview,
|
|
561
|
+
wf.nodes,
|
|
562
|
+
wf.edges,
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// Post-swap validation
|
|
566
|
+
const validation = validateSwapResult(result);
|
|
567
|
+
if (!validation.valid) {
|
|
568
|
+
logger.error("Swap validation failed:", validation.error);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// onBeforeSwap hook — abort if returns false
|
|
573
|
+
if (eventHandlers?.onBeforeSwap) {
|
|
574
|
+
const swapEventCtx: SwapEventContext = {
|
|
575
|
+
oldNode: state.oldNode,
|
|
576
|
+
newMetadata: state.newMetadata,
|
|
577
|
+
preview,
|
|
578
|
+
portOverrides: [],
|
|
579
|
+
configOverrides: [],
|
|
580
|
+
};
|
|
581
|
+
const shouldProceed = await eventHandlers.onBeforeSwap(swapEventCtx);
|
|
582
|
+
if (shouldProceed === false) return;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Apply as a single atomic swap with descriptive history entry
|
|
586
|
+
workflowActions.swapNode({
|
|
587
|
+
nodes: result.updatedNodes,
|
|
588
|
+
edges: result.updatedEdges,
|
|
589
|
+
description: `Swap node: ${oldLabel} → ${newLabel}`,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// onAfterSwap hook (fire-and-forget — swap is already applied)
|
|
593
|
+
if (eventHandlers?.onAfterSwap) {
|
|
594
|
+
try {
|
|
595
|
+
eventHandlers.onAfterSwap(result, state.oldNode, state.newNodeId);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
logger.error("onAfterSwap hook error:", err);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Select the new node in the sidebar
|
|
602
|
+
const newNodeId = state.newNodeId;
|
|
603
|
+
selectedNodeId = newNodeId;
|
|
604
|
+
|
|
605
|
+
// Reset swap state
|
|
606
|
+
swapMode = "idle";
|
|
607
|
+
swapTargetMetadata = null;
|
|
608
|
+
swapInteractiveState = null;
|
|
609
|
+
|
|
610
|
+
// Wait for SvelteFlow to process the new node before updating visual state
|
|
611
|
+
await tick();
|
|
612
|
+
|
|
613
|
+
// Refresh the editor visual state
|
|
614
|
+
if (workflowEditorRef) {
|
|
615
|
+
const newNode = result.updatedNodes.find((n) => n.id === newNodeId);
|
|
616
|
+
if (newNode) {
|
|
617
|
+
workflowEditorRef.updateNodeData(newNodeId, newNode.data);
|
|
618
|
+
await workflowEditorRef.refreshEdgePositions(newNodeId);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Cancel swap and return to normal config view
|
|
625
|
+
*/
|
|
626
|
+
function cancelSwap(): void {
|
|
627
|
+
swapMode = "idle";
|
|
628
|
+
swapTargetMetadata = null;
|
|
629
|
+
swapInteractiveState = null;
|
|
630
|
+
}
|
|
631
|
+
|
|
451
632
|
/**
|
|
452
633
|
* Handle workflow configuration save
|
|
453
634
|
*/
|
|
@@ -661,22 +842,102 @@
|
|
|
661
842
|
* Config panel always appears on the right side
|
|
662
843
|
*/
|
|
663
844
|
const hasConfigPanelOpen = $derived(
|
|
664
|
-
isWorkflowSettingsOpen ||
|
|
845
|
+
isWorkflowSettingsOpen ||
|
|
846
|
+
!!selectedNodeForConfig ||
|
|
847
|
+
swapMode !== "idle",
|
|
665
848
|
);
|
|
666
849
|
const showRightPanel = $derived(!disableSidebar && hasConfigPanelOpen);
|
|
667
850
|
|
|
668
851
|
/**
|
|
669
852
|
* Calculate left sidebar width based on collapsed state
|
|
670
|
-
* When collapsed, use
|
|
853
|
+
* When collapsed, use 0; otherwise use user-configured width
|
|
671
854
|
*/
|
|
672
855
|
const leftSidebarWidth = $derived(
|
|
673
|
-
getUiSettings().sidebarCollapsed ?
|
|
856
|
+
getUiSettings().sidebarCollapsed ? 0 : getUiSettings().sidebarWidth,
|
|
674
857
|
);
|
|
675
858
|
|
|
859
|
+
/** Whether the sidebar is collapsed */
|
|
860
|
+
const isSidebarCollapsed = $derived(getUiSettings().sidebarCollapsed);
|
|
861
|
+
|
|
862
|
+
/** Toggle sidebar collapsed state */
|
|
863
|
+
function toggleSidebar(): void {
|
|
864
|
+
updateSettings({
|
|
865
|
+
ui: { sidebarCollapsed: !getUiSettings().sidebarCollapsed },
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
676
869
|
// File input reference for workflow import
|
|
677
870
|
let fileInputRef = $state<HTMLInputElement | null>(null);
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Handle global keyboard shortcut for console toggle.
|
|
874
|
+
* Backtick (`) toggles the console open/closed unless user is typing in an input.
|
|
875
|
+
*/
|
|
876
|
+
function handleGlobalKeydown(event: KeyboardEvent): void {
|
|
877
|
+
// Dead key on international keyboards — do not intercept
|
|
878
|
+
if (event.key === "Dead") return;
|
|
879
|
+
|
|
880
|
+
if (event.key !== "`") return;
|
|
881
|
+
|
|
882
|
+
// Don't intercept when user is typing in an input, textarea, or contenteditable
|
|
883
|
+
const target = event.target as HTMLElement;
|
|
884
|
+
const isInputElement =
|
|
885
|
+
target.tagName === "INPUT" ||
|
|
886
|
+
target.tagName === "TEXTAREA" ||
|
|
887
|
+
target.isContentEditable;
|
|
888
|
+
|
|
889
|
+
if (isInputElement) return;
|
|
890
|
+
|
|
891
|
+
event.preventDefault();
|
|
892
|
+
toggleConsole();
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function handleConsoleUIAction(action: UIAction): void {
|
|
896
|
+
if (action.type === "open_config") {
|
|
897
|
+
const wf = getWorkflowStore();
|
|
898
|
+
if (!wf) return;
|
|
899
|
+
const node = wf.nodes.find((n) => n.id === action.nodeId);
|
|
900
|
+
if (node) openConfigSidebar(node);
|
|
901
|
+
} else if (action.type === "select_node") {
|
|
902
|
+
selectedNodeId = action.nodeId;
|
|
903
|
+
} else if (action.type === "canvas_fit_view") {
|
|
904
|
+
workflowEditorRef?.canvasFitView();
|
|
905
|
+
} else if (action.type === "canvas_zoom_in") {
|
|
906
|
+
workflowEditorRef?.canvasZoomIn();
|
|
907
|
+
} else if (action.type === "canvas_zoom_out") {
|
|
908
|
+
workflowEditorRef?.canvasZoomOut();
|
|
909
|
+
} else if (action.type === "canvas_zoom_to") {
|
|
910
|
+
workflowEditorRef?.canvasZoomTo(action.level);
|
|
911
|
+
} else if (action.type === "canvas_pan_to") {
|
|
912
|
+
workflowEditorRef?.canvasPanTo(action.position.x, action.position.y);
|
|
913
|
+
} else if (action.type === "canvas_reset_view") {
|
|
914
|
+
workflowEditorRef?.canvasResetView();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function toggleConsole(): void {
|
|
919
|
+
const currentOpen = getUiSettings().consoleOpen;
|
|
920
|
+
updateSettings({ ui: { consoleOpen: !currentOpen } });
|
|
921
|
+
|
|
922
|
+
// Focus management after DOM update
|
|
923
|
+
tick().then(() => {
|
|
924
|
+
if (currentOpen) {
|
|
925
|
+
// Console was open, now closing — focus the canvas
|
|
926
|
+
const canvas = document.querySelector<HTMLElement>(".flowdrop-editor-main");
|
|
927
|
+
canvas?.focus();
|
|
928
|
+
} else {
|
|
929
|
+
// Console was closed, now opening — focus first focusable element inside console
|
|
930
|
+
const consoleEl = document.querySelector<HTMLElement>(".command-console");
|
|
931
|
+
const focusTarget =
|
|
932
|
+
consoleEl?.querySelector<HTMLElement>("input, button, [tabindex]");
|
|
933
|
+
focusTarget?.focus();
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
678
937
|
</script>
|
|
679
938
|
|
|
939
|
+
<svelte:window onkeydown={handleGlobalKeydown} />
|
|
940
|
+
|
|
680
941
|
<svelte:head>
|
|
681
942
|
<title>FlowDrop - Visual Workflow Manager</title>
|
|
682
943
|
<meta
|
|
@@ -700,13 +961,14 @@
|
|
|
700
961
|
showHeader={showNavbar}
|
|
701
962
|
showLeftSidebar={!disableSidebar}
|
|
702
963
|
showRightSidebar={showRightPanel}
|
|
703
|
-
showBottomPanel={
|
|
964
|
+
showBottomPanel={getUiSettings().consoleOpen && !readOnly && !lockWorkflow}
|
|
965
|
+
bottomPanelHeight={getUiSettings().consoleHeight}
|
|
704
966
|
showFooter={false}
|
|
705
967
|
headerHeight={60}
|
|
706
968
|
{leftSidebarWidth}
|
|
707
969
|
rightSidebarWidth={400}
|
|
708
|
-
leftSidebarMinWidth={getUiSettings().sidebarCollapsed ?
|
|
709
|
-
leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ?
|
|
970
|
+
leftSidebarMinWidth={getUiSettings().sidebarCollapsed ? 0 : 280}
|
|
971
|
+
leftSidebarMaxWidth={getUiSettings().sidebarCollapsed ? 0 : 450}
|
|
710
972
|
rightSidebarMinWidth={320}
|
|
711
973
|
rightSidebarMaxWidth={550}
|
|
712
974
|
enableLeftSplitPane={false}
|
|
@@ -716,7 +978,7 @@
|
|
|
716
978
|
<!-- Header: Navbar -->
|
|
717
979
|
{#snippet header()}
|
|
718
980
|
<Navbar
|
|
719
|
-
title={breadcrumbTitle
|
|
981
|
+
title={breadcrumbTitle}
|
|
720
982
|
primaryActions={navbarActions.length > 0
|
|
721
983
|
? navbarActions
|
|
722
984
|
: [
|
|
@@ -763,6 +1025,9 @@
|
|
|
763
1025
|
]}
|
|
764
1026
|
showStatus={true}
|
|
765
1027
|
{showSettings}
|
|
1028
|
+
{settingsCategories}
|
|
1029
|
+
{showSettingsSyncButton}
|
|
1030
|
+
{showSettingsResetButton}
|
|
766
1031
|
/>
|
|
767
1032
|
{/snippet}
|
|
768
1033
|
|
|
@@ -777,9 +1042,26 @@
|
|
|
777
1042
|
/>
|
|
778
1043
|
{/snippet}
|
|
779
1044
|
|
|
780
|
-
<!-- Right Sidebar: Configuration or Workflow Settings -->
|
|
1045
|
+
<!-- Right Sidebar: Configuration, Swap, or Workflow Settings -->
|
|
781
1046
|
{#snippet rightSidebar()}
|
|
782
|
-
{#if
|
|
1047
|
+
{#if swapMode === "mapping" && swapInteractiveState && selectedNodeForConfig}
|
|
1048
|
+
{@const swapChecker = (() => { try { return getPortCompatibilityChecker(); } catch { return null; } })()}
|
|
1049
|
+
<SwapMappingEditor
|
|
1050
|
+
interactiveState={swapInteractiveState}
|
|
1051
|
+
checker={swapChecker}
|
|
1052
|
+
onConfirm={executeNodeSwap}
|
|
1053
|
+
onCancel={cancelSwap}
|
|
1054
|
+
onBack={() => { swapMode = "picking"; swapInteractiveState = null; }}
|
|
1055
|
+
/>
|
|
1056
|
+
{:else if swapMode === "picking" && selectedNodeForConfig}
|
|
1057
|
+
<NodeSwapPicker
|
|
1058
|
+
currentNode={selectedNodeForConfig}
|
|
1059
|
+
availableNodes={nodes}
|
|
1060
|
+
activeFormat={getWorkflowFormat()}
|
|
1061
|
+
onSelect={handleSwapSelect}
|
|
1062
|
+
onCancel={cancelSwap}
|
|
1063
|
+
/>
|
|
1064
|
+
{:else if isWorkflowSettingsOpen}
|
|
783
1065
|
<ConfigPanel
|
|
784
1066
|
title="Workflow Settings"
|
|
785
1067
|
id={getWorkflowStore()?.id}
|
|
@@ -840,8 +1122,8 @@
|
|
|
840
1122
|
}}
|
|
841
1123
|
/>
|
|
842
1124
|
</ConfigPanel>
|
|
843
|
-
{:else if selectedNodeForConfig
|
|
844
|
-
{@const currentNode = selectedNodeForConfig
|
|
1125
|
+
{:else if selectedNodeForConfig}
|
|
1126
|
+
{@const currentNode = selectedNodeForConfig}
|
|
845
1127
|
<ConfigPanel
|
|
846
1128
|
title={currentNode.data.label}
|
|
847
1129
|
id={currentNode.id}
|
|
@@ -858,6 +1140,7 @@
|
|
|
858
1140
|
},
|
|
859
1141
|
]}
|
|
860
1142
|
onClose={closeConfigSidebar}
|
|
1143
|
+
onSwap={!readOnly && !lockWorkflow && features.enableNodeSwap ? startSwap : undefined}
|
|
861
1144
|
>
|
|
862
1145
|
<ConfigForm
|
|
863
1146
|
{authProvider}
|
|
@@ -902,6 +1185,34 @@
|
|
|
902
1185
|
{/if}
|
|
903
1186
|
{/snippet}
|
|
904
1187
|
|
|
1188
|
+
<!-- Bottom Panel: Tabbed Console / AI Chat -->
|
|
1189
|
+
{#snippet bottomPanel()}
|
|
1190
|
+
<div class="bottom-panel-tabs">
|
|
1191
|
+
<div class="bottom-panel-tabs__bar">
|
|
1192
|
+
<button
|
|
1193
|
+
class="bottom-panel-tabs__tab {getUiSettings().bottomPanelTab === 'console' ? 'bottom-panel-tabs__tab--active' : ''}"
|
|
1194
|
+
onclick={() => updateSettings({ ui: { bottomPanelTab: 'console' } })}
|
|
1195
|
+
>
|
|
1196
|
+
Console
|
|
1197
|
+
</button>
|
|
1198
|
+
<button
|
|
1199
|
+
class="bottom-panel-tabs__tab {getUiSettings().bottomPanelTab === 'chat' ? 'bottom-panel-tabs__tab--active' : ''}"
|
|
1200
|
+
onclick={() => updateSettings({ ui: { bottomPanelTab: 'chat' } })}
|
|
1201
|
+
>
|
|
1202
|
+
AI Chat
|
|
1203
|
+
</button>
|
|
1204
|
+
</div>
|
|
1205
|
+
<div class="bottom-panel-tabs__content">
|
|
1206
|
+
<div class="bottom-panel-tabs__panel" style:display={getUiSettings().bottomPanelTab === 'console' ? 'contents' : 'none'}>
|
|
1207
|
+
<CommandConsole nodeTypes={nodes} onUIAction={handleConsoleUIAction} />
|
|
1208
|
+
</div>
|
|
1209
|
+
<div class="bottom-panel-tabs__panel" style:display={getUiSettings().bottomPanelTab === 'chat' ? 'flex' : 'none'}>
|
|
1210
|
+
<AIChatPanel nodeTypes={nodes} workflowId={getWorkflowStore()?.id} onUIAction={handleConsoleUIAction} endpointConfig={endpointConfig} />
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
{/snippet}
|
|
1215
|
+
|
|
905
1216
|
<!-- Main Content: Workflow Editor with Error Status -->
|
|
906
1217
|
<!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
|
|
907
1218
|
{#if error}
|
|
@@ -974,6 +1285,20 @@
|
|
|
974
1285
|
role="region"
|
|
975
1286
|
aria-label="Workflow canvas"
|
|
976
1287
|
>
|
|
1288
|
+
<!-- Floating sidebar toggle — always visible on the canvas top-left -->
|
|
1289
|
+
{#if !disableSidebar}
|
|
1290
|
+
<button
|
|
1291
|
+
class="flowdrop-sidebar-fab"
|
|
1292
|
+
onclick={toggleSidebar}
|
|
1293
|
+
aria-label={isSidebarCollapsed
|
|
1294
|
+
? "Expand sidebar"
|
|
1295
|
+
: "Collapse sidebar"}
|
|
1296
|
+
title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
1297
|
+
>
|
|
1298
|
+
<Icon icon={isSidebarCollapsed ? "mdi:menu" : "mdi:menu-open"} />
|
|
1299
|
+
</button>
|
|
1300
|
+
{/if}
|
|
1301
|
+
|
|
977
1302
|
<WorkflowEditor
|
|
978
1303
|
bind:this={workflowEditorRef}
|
|
979
1304
|
{nodes}
|
|
@@ -981,13 +1306,15 @@
|
|
|
981
1306
|
{width}
|
|
982
1307
|
endpointConfig={endpointConfig ?? undefined}
|
|
983
1308
|
{isConfigSidebarOpen}
|
|
984
|
-
selectedNodeForConfig={selectedNodeForConfig
|
|
1309
|
+
selectedNodeForConfig={selectedNodeForConfig}
|
|
985
1310
|
{openConfigSidebar}
|
|
986
1311
|
{closeConfigSidebar}
|
|
987
1312
|
{lockWorkflow}
|
|
988
1313
|
{readOnly}
|
|
989
1314
|
{nodeStatuses}
|
|
990
1315
|
{pipelineId}
|
|
1316
|
+
consoleOpen={getUiSettings().consoleOpen}
|
|
1317
|
+
onToggleConsole={toggleConsole}
|
|
991
1318
|
/>
|
|
992
1319
|
</div>
|
|
993
1320
|
</MainLayout>
|
|
@@ -1098,6 +1425,40 @@
|
|
|
1098
1425
|
font-weight: 500;
|
|
1099
1426
|
}
|
|
1100
1427
|
|
|
1428
|
+
/* Floating sidebar toggle button */
|
|
1429
|
+
.flowdrop-sidebar-fab {
|
|
1430
|
+
position: absolute;
|
|
1431
|
+
top: 12px;
|
|
1432
|
+
left: 12px;
|
|
1433
|
+
z-index: 50;
|
|
1434
|
+
display: flex;
|
|
1435
|
+
align-items: center;
|
|
1436
|
+
justify-content: center;
|
|
1437
|
+
width: 2.25rem;
|
|
1438
|
+
height: 2.25rem;
|
|
1439
|
+
border: 1px solid var(--fd-border);
|
|
1440
|
+
border-radius: var(--fd-radius-md);
|
|
1441
|
+
background-color: var(--fd-background);
|
|
1442
|
+
color: var(--fd-muted-foreground);
|
|
1443
|
+
cursor: pointer;
|
|
1444
|
+
box-shadow: var(--fd-shadow-md);
|
|
1445
|
+
transition:
|
|
1446
|
+
color var(--fd-transition-fast),
|
|
1447
|
+
background-color var(--fd-transition-fast),
|
|
1448
|
+
box-shadow var(--fd-transition-fast);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.flowdrop-sidebar-fab:hover {
|
|
1452
|
+
color: var(--fd-foreground);
|
|
1453
|
+
background-color: var(--fd-subtle);
|
|
1454
|
+
box-shadow: var(--fd-shadow-lg);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
.flowdrop-sidebar-fab:focus {
|
|
1458
|
+
outline: none;
|
|
1459
|
+
box-shadow: 0 0 0 2px var(--fd-ring);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1101
1462
|
/* Main editor area */
|
|
1102
1463
|
.flowdrop-editor-main {
|
|
1103
1464
|
flex: 1;
|
|
@@ -1107,4 +1468,57 @@
|
|
|
1107
1468
|
overflow: hidden;
|
|
1108
1469
|
background: var(--fd-layout-background);
|
|
1109
1470
|
}
|
|
1471
|
+
|
|
1472
|
+
/* Bottom panel tab system */
|
|
1473
|
+
.bottom-panel-tabs {
|
|
1474
|
+
display: flex;
|
|
1475
|
+
flex-direction: column;
|
|
1476
|
+
height: 100%;
|
|
1477
|
+
overflow: hidden;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.bottom-panel-tabs__bar {
|
|
1481
|
+
display: flex;
|
|
1482
|
+
gap: 0;
|
|
1483
|
+
background: var(--fd-muted);
|
|
1484
|
+
border-bottom: 1px solid var(--fd-border);
|
|
1485
|
+
flex-shrink: 0;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.bottom-panel-tabs__tab {
|
|
1489
|
+
padding: 0.375rem 0.75rem;
|
|
1490
|
+
font-size: 0.75rem;
|
|
1491
|
+
font-weight: 500;
|
|
1492
|
+
cursor: pointer;
|
|
1493
|
+
border: none;
|
|
1494
|
+
border-bottom: 2px solid transparent;
|
|
1495
|
+
background: transparent;
|
|
1496
|
+
color: var(--fd-muted-foreground);
|
|
1497
|
+
transition: all var(--fd-transition-fast);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.bottom-panel-tabs__tab:hover {
|
|
1501
|
+
color: var(--fd-foreground);
|
|
1502
|
+
background: var(--fd-background);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
.bottom-panel-tabs__tab--active {
|
|
1506
|
+
color: var(--fd-foreground);
|
|
1507
|
+
border-bottom-color: var(--fd-primary);
|
|
1508
|
+
background: var(--fd-background);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.bottom-panel-tabs__content {
|
|
1512
|
+
flex: 1;
|
|
1513
|
+
overflow: hidden;
|
|
1514
|
+
display: flex;
|
|
1515
|
+
flex-direction: column;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
.bottom-panel-tabs__panel {
|
|
1519
|
+
flex: 1;
|
|
1520
|
+
overflow: hidden;
|
|
1521
|
+
flex-direction: column;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1110
1524
|
</style>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { NodeMetadata, Workflow } from "../types/index.js";
|
|
2
|
+
import type { SwapStrategy } from "../utils/nodeSwap.js";
|
|
2
3
|
import type { EndpointConfig } from "../config/endpoints.js";
|
|
3
4
|
import type { AuthProvider } from "../types/auth.js";
|
|
4
5
|
import type { FlowDropEventHandlers, FlowDropFeatures } from "../types/events.js";
|
|
5
6
|
import type { FlowDropTheme, FlowDropThemeName } from "../types/theme.js";
|
|
7
|
+
import type { SettingsCategory } from "../types/settings.js";
|
|
6
8
|
/**
|
|
7
9
|
* Configuration props for runtime customization
|
|
8
10
|
*/
|
|
@@ -51,6 +53,14 @@ interface Props {
|
|
|
51
53
|
features?: FlowDropFeatures;
|
|
52
54
|
/** Visual theme — named built-in ('default' | 'minimal') or custom theme object */
|
|
53
55
|
theme?: FlowDropTheme | FlowDropThemeName;
|
|
56
|
+
/** Which settings tabs to show in the modal */
|
|
57
|
+
settingsCategories?: SettingsCategory[];
|
|
58
|
+
/** Show the "Sync to Cloud" button in the settings modal */
|
|
59
|
+
showSettingsSyncButton?: boolean;
|
|
60
|
+
/** Show the reset buttons in the settings modal */
|
|
61
|
+
showSettingsResetButton?: boolean;
|
|
62
|
+
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
63
|
+
swapStrategies?: SwapStrategy[];
|
|
54
64
|
}
|
|
55
65
|
declare const App: import("svelte").Component<Props, {}, "">;
|
|
56
66
|
type App = ReturnType<typeof App>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
3
3
|
import CanvasBanner from "./CanvasBanner.svelte";
|
|
4
4
|
import CanvasDecorator from "../stories/CanvasDecorator.svelte";
|
|
5
|
+
import Icon from "@iconify/svelte";
|
|
5
6
|
|
|
6
7
|
const { Story } = defineMeta({
|
|
7
8
|
title: "Display/CanvasBanner",
|
|
@@ -17,8 +18,11 @@
|
|
|
17
18
|
<CanvasBanner
|
|
18
19
|
title="Empty Canvas"
|
|
19
20
|
description="Drag nodes from the sidebar to get started"
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
>
|
|
22
|
+
{#snippet icon()}
|
|
23
|
+
<Icon icon="heroicons:squares-plus" />
|
|
24
|
+
{/snippet}
|
|
25
|
+
</CanvasBanner>
|
|
22
26
|
</CanvasDecorator>
|
|
23
27
|
</Story>
|
|
24
28
|
|