@flowdrop/flowdrop 1.4.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 +351 -12
- package/dist/components/App.svelte.d.ts +3 -0
- 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 +2 -10
- package/dist/components/LogsSidebar.svelte +5 -5
- package/dist/components/NodeSidebar.svelte +15 -49
- 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/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/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/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,13 +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
12
|
import Icon from "@iconify/svelte";
|
|
13
13
|
import ConfigForm from "./ConfigForm.svelte";
|
|
14
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";
|
|
15
20
|
import Navbar from "./Navbar.svelte";
|
|
16
21
|
import { api, setEndpointConfig } from "../services/api.js";
|
|
17
22
|
import { EnhancedFlowDropApiClient } from "../api/enhanced-client.js";
|
|
@@ -22,6 +27,14 @@
|
|
|
22
27
|
ConfigSchema,
|
|
23
28
|
NodeUIExtensions,
|
|
24
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";
|
|
25
38
|
import { DEFAULT_WORKFLOW_FORMAT } from "../types/index.js";
|
|
26
39
|
import { createEndpointConfig } from "../config/endpoints.js";
|
|
27
40
|
import type { EndpointConfig } from "../config/endpoints.js";
|
|
@@ -51,7 +64,7 @@
|
|
|
51
64
|
getUiSettings,
|
|
52
65
|
updateSettings,
|
|
53
66
|
} from "../stores/settingsStore.svelte.js";
|
|
54
|
-
import { initializePortCompatibility } from "../utils/connections.js";
|
|
67
|
+
import { initializePortCompatibility, getPortCompatibilityChecker } from "../utils/connections.js";
|
|
55
68
|
import { DEFAULT_PORT_CONFIG } from "../config/defaultPortConfig.js";
|
|
56
69
|
import { workflowFormatRegistry } from "../registry/workflowFormatRegistry.js";
|
|
57
70
|
import { logger } from "../utils/logger.js";
|
|
@@ -115,6 +128,8 @@
|
|
|
115
128
|
showSettingsSyncButton?: boolean;
|
|
116
129
|
/** Show the reset buttons in the settings modal */
|
|
117
130
|
showSettingsResetButton?: boolean;
|
|
131
|
+
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
132
|
+
swapStrategies?: SwapStrategy[];
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
let {
|
|
@@ -140,6 +155,7 @@
|
|
|
140
155
|
settingsCategories,
|
|
141
156
|
showSettingsSyncButton,
|
|
142
157
|
showSettingsResetButton,
|
|
158
|
+
swapStrategies,
|
|
143
159
|
}: Props = $props();
|
|
144
160
|
|
|
145
161
|
// svelte-ignore state_referenced_locally — feature flags don't change at runtime
|
|
@@ -180,7 +196,7 @@
|
|
|
180
196
|
});
|
|
181
197
|
|
|
182
198
|
// Create breadcrumb-style title - at top level to avoid store subscription issues
|
|
183
|
-
let breadcrumbTitle = $derived(() => {
|
|
199
|
+
let breadcrumbTitle = $derived.by(() => {
|
|
184
200
|
// Use custom navbar title if provided
|
|
185
201
|
if (navbarTitle) {
|
|
186
202
|
return navbarTitle;
|
|
@@ -213,6 +229,11 @@
|
|
|
213
229
|
// Workflow settings sidebar state
|
|
214
230
|
let isWorkflowSettingsOpen = $state(false);
|
|
215
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
|
+
|
|
216
237
|
// Workflow configuration schema (derived to pick up dynamic format options)
|
|
217
238
|
let workflowConfigSchema: ConfigSchema = $derived({
|
|
218
239
|
type: "object" as const,
|
|
@@ -249,7 +270,7 @@
|
|
|
249
270
|
});
|
|
250
271
|
|
|
251
272
|
// Get the current node from the workflow store
|
|
252
|
-
let selectedNodeForConfig = $derived(() => {
|
|
273
|
+
let selectedNodeForConfig = $derived.by(() => {
|
|
253
274
|
const wf = getWorkflowStore();
|
|
254
275
|
if (!selectedNodeId || !wf) return null;
|
|
255
276
|
return wf.nodes.find((node) => node.id === selectedNodeId) || null;
|
|
@@ -444,11 +465,19 @@
|
|
|
444
465
|
}
|
|
445
466
|
selectedNodeId = node.id;
|
|
446
467
|
isConfigSidebarOpen = true;
|
|
468
|
+
// Reset swap state when switching nodes
|
|
469
|
+
swapMode = "idle";
|
|
470
|
+
swapTargetMetadata = null;
|
|
471
|
+
swapInteractiveState = null;
|
|
447
472
|
}
|
|
448
473
|
|
|
449
474
|
function closeConfigSidebar(): void {
|
|
450
475
|
isConfigSidebarOpen = false;
|
|
451
476
|
selectedNodeId = null;
|
|
477
|
+
// Reset swap state when closing
|
|
478
|
+
swapMode = "idle";
|
|
479
|
+
swapTargetMetadata = null;
|
|
480
|
+
swapInteractiveState = null;
|
|
452
481
|
}
|
|
453
482
|
|
|
454
483
|
/**
|
|
@@ -462,6 +491,144 @@
|
|
|
462
491
|
}
|
|
463
492
|
}
|
|
464
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
|
+
|
|
465
632
|
/**
|
|
466
633
|
* Handle workflow configuration save
|
|
467
634
|
*/
|
|
@@ -675,7 +842,9 @@
|
|
|
675
842
|
* Config panel always appears on the right side
|
|
676
843
|
*/
|
|
677
844
|
const hasConfigPanelOpen = $derived(
|
|
678
|
-
isWorkflowSettingsOpen ||
|
|
845
|
+
isWorkflowSettingsOpen ||
|
|
846
|
+
!!selectedNodeForConfig ||
|
|
847
|
+
swapMode !== "idle",
|
|
679
848
|
);
|
|
680
849
|
const showRightPanel = $derived(!disableSidebar && hasConfigPanelOpen);
|
|
681
850
|
|
|
@@ -699,8 +868,76 @@
|
|
|
699
868
|
|
|
700
869
|
// File input reference for workflow import
|
|
701
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
|
+
}
|
|
702
937
|
</script>
|
|
703
938
|
|
|
939
|
+
<svelte:window onkeydown={handleGlobalKeydown} />
|
|
940
|
+
|
|
704
941
|
<svelte:head>
|
|
705
942
|
<title>FlowDrop - Visual Workflow Manager</title>
|
|
706
943
|
<meta
|
|
@@ -724,7 +961,8 @@
|
|
|
724
961
|
showHeader={showNavbar}
|
|
725
962
|
showLeftSidebar={!disableSidebar}
|
|
726
963
|
showRightSidebar={showRightPanel}
|
|
727
|
-
showBottomPanel={
|
|
964
|
+
showBottomPanel={getUiSettings().consoleOpen && !readOnly && !lockWorkflow}
|
|
965
|
+
bottomPanelHeight={getUiSettings().consoleHeight}
|
|
728
966
|
showFooter={false}
|
|
729
967
|
headerHeight={60}
|
|
730
968
|
{leftSidebarWidth}
|
|
@@ -740,7 +978,7 @@
|
|
|
740
978
|
<!-- Header: Navbar -->
|
|
741
979
|
{#snippet header()}
|
|
742
980
|
<Navbar
|
|
743
|
-
title={breadcrumbTitle
|
|
981
|
+
title={breadcrumbTitle}
|
|
744
982
|
primaryActions={navbarActions.length > 0
|
|
745
983
|
? navbarActions
|
|
746
984
|
: [
|
|
@@ -804,9 +1042,26 @@
|
|
|
804
1042
|
/>
|
|
805
1043
|
{/snippet}
|
|
806
1044
|
|
|
807
|
-
<!-- Right Sidebar: Configuration or Workflow Settings -->
|
|
1045
|
+
<!-- Right Sidebar: Configuration, Swap, or Workflow Settings -->
|
|
808
1046
|
{#snippet rightSidebar()}
|
|
809
|
-
{#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}
|
|
810
1065
|
<ConfigPanel
|
|
811
1066
|
title="Workflow Settings"
|
|
812
1067
|
id={getWorkflowStore()?.id}
|
|
@@ -867,8 +1122,8 @@
|
|
|
867
1122
|
}}
|
|
868
1123
|
/>
|
|
869
1124
|
</ConfigPanel>
|
|
870
|
-
{:else if selectedNodeForConfig
|
|
871
|
-
{@const currentNode = selectedNodeForConfig
|
|
1125
|
+
{:else if selectedNodeForConfig}
|
|
1126
|
+
{@const currentNode = selectedNodeForConfig}
|
|
872
1127
|
<ConfigPanel
|
|
873
1128
|
title={currentNode.data.label}
|
|
874
1129
|
id={currentNode.id}
|
|
@@ -885,6 +1140,7 @@
|
|
|
885
1140
|
},
|
|
886
1141
|
]}
|
|
887
1142
|
onClose={closeConfigSidebar}
|
|
1143
|
+
onSwap={!readOnly && !lockWorkflow && features.enableNodeSwap ? startSwap : undefined}
|
|
888
1144
|
>
|
|
889
1145
|
<ConfigForm
|
|
890
1146
|
{authProvider}
|
|
@@ -929,6 +1185,34 @@
|
|
|
929
1185
|
{/if}
|
|
930
1186
|
{/snippet}
|
|
931
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
|
+
|
|
932
1216
|
<!-- Main Content: Workflow Editor with Error Status -->
|
|
933
1217
|
<!-- Status Display: aria-live announces API errors dynamically without requiring focus -->
|
|
934
1218
|
{#if error}
|
|
@@ -1022,13 +1306,15 @@
|
|
|
1022
1306
|
{width}
|
|
1023
1307
|
endpointConfig={endpointConfig ?? undefined}
|
|
1024
1308
|
{isConfigSidebarOpen}
|
|
1025
|
-
selectedNodeForConfig={selectedNodeForConfig
|
|
1309
|
+
selectedNodeForConfig={selectedNodeForConfig}
|
|
1026
1310
|
{openConfigSidebar}
|
|
1027
1311
|
{closeConfigSidebar}
|
|
1028
1312
|
{lockWorkflow}
|
|
1029
1313
|
{readOnly}
|
|
1030
1314
|
{nodeStatuses}
|
|
1031
1315
|
{pipelineId}
|
|
1316
|
+
consoleOpen={getUiSettings().consoleOpen}
|
|
1317
|
+
onToggleConsole={toggleConsole}
|
|
1032
1318
|
/>
|
|
1033
1319
|
</div>
|
|
1034
1320
|
</MainLayout>
|
|
@@ -1182,4 +1468,57 @@
|
|
|
1182
1468
|
overflow: hidden;
|
|
1183
1469
|
background: var(--fd-layout-background);
|
|
1184
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
|
+
|
|
1185
1524
|
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -58,6 +59,8 @@ interface Props {
|
|
|
58
59
|
showSettingsSyncButton?: boolean;
|
|
59
60
|
/** Show the reset buttons in the settings modal */
|
|
60
61
|
showSettingsResetButton?: boolean;
|
|
62
|
+
/** Pluggable swap strategies — instance-scoped, checked in order */
|
|
63
|
+
swapStrategies?: SwapStrategy[];
|
|
61
64
|
}
|
|
62
65
|
declare const App: import("svelte").Component<Props, {}, "">;
|
|
63
66
|
type App = ReturnType<typeof App>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CanvasController Component
|
|
3
|
+
Provides viewport control methods (fitView, zoom, pan) via useSvelteFlow().
|
|
4
|
+
Must be rendered inside SvelteFlowProvider context.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { useSvelteFlow } from "@xyflow/svelte";
|
|
9
|
+
import { getEditorSettings } from "../stores/settingsStore.svelte.js";
|
|
10
|
+
|
|
11
|
+
const { fitView, zoomIn, zoomOut, setZoom, setCenter, setViewport } =
|
|
12
|
+
useSvelteFlow();
|
|
13
|
+
|
|
14
|
+
export function canvasFitView(): void {
|
|
15
|
+
fitView({ padding: 0.2, duration: 300 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function canvasZoomIn(): void {
|
|
19
|
+
zoomIn({ duration: 300 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function canvasZoomOut(): void {
|
|
23
|
+
zoomOut({ duration: 300 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function canvasZoomTo(level: number): void {
|
|
27
|
+
setZoom(level, { duration: 300 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function canvasPanTo(x: number, y: number): void {
|
|
31
|
+
setCenter(x, y, { duration: 300 });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function canvasResetView(): void {
|
|
35
|
+
const defaultZoom = getEditorSettings().defaultZoom;
|
|
36
|
+
setViewport({ x: 0, y: 0, zoom: defaultZoom }, { duration: 300 });
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
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> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const CanvasController: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
canvasFitView?: () => void;
|
|
16
|
+
canvasZoomIn?: () => void;
|
|
17
|
+
canvasZoomOut?: () => void;
|
|
18
|
+
canvasZoomTo?: (level: number) => void;
|
|
19
|
+
canvasPanTo?: (x: number, y: number) => void;
|
|
20
|
+
canvasResetView?: () => void;
|
|
21
|
+
}, {
|
|
22
|
+
[evt: string]: CustomEvent<any>;
|
|
23
|
+
}, {}, {
|
|
24
|
+
canvasFitView: () => void;
|
|
25
|
+
canvasZoomIn: () => void;
|
|
26
|
+
canvasZoomOut: () => void;
|
|
27
|
+
canvasZoomTo: (level: number) => void;
|
|
28
|
+
canvasPanTo: (x: number, y: number) => void;
|
|
29
|
+
canvasResetView: () => void;
|
|
30
|
+
}, string>;
|
|
31
|
+
type CanvasController = InstanceType<typeof CanvasController>;
|
|
32
|
+
export default CanvasController;
|