@d34dman/flowdrop 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ConfigForm.svelte +46 -4
- package/dist/components/ConfigForm.svelte.d.ts +2 -0
- package/dist/components/form/FormCodeEditor.svelte +29 -2
- package/dist/components/form/FormTemplateEditor.svelte +33 -3
- package/dist/components/nodes/GatewayNode.svelte +23 -5
- package/dist/components/nodes/IdeaNode.svelte +490 -0
- package/dist/components/nodes/IdeaNode.svelte.d.ts +24 -0
- package/dist/components/nodes/SimpleNode.svelte +22 -2
- package/dist/components/nodes/TerminalNode.svelte +54 -11
- package/dist/components/nodes/ToolNode.svelte +24 -9
- package/dist/components/nodes/WorkflowNode.svelte +20 -2
- package/dist/registry/builtinNodes.d.ts +1 -1
- package/dist/registry/builtinNodes.js +14 -1
- package/package.json +1 -1
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
invalidateSchemaCache,
|
|
35
35
|
type DynamicSchemaResult
|
|
36
36
|
} from '../services/dynamicSchemaService.js';
|
|
37
|
+
import { globalSaveWorkflow } from '../services/globalSave.js';
|
|
37
38
|
|
|
38
39
|
interface Props {
|
|
39
40
|
/** Optional workflow node (if provided, schema and values are derived from it) */
|
|
@@ -46,6 +47,8 @@
|
|
|
46
47
|
showUIExtensions?: boolean;
|
|
47
48
|
/** Optional workflow ID for context in external links */
|
|
48
49
|
workflowId?: string;
|
|
50
|
+
/** Whether to also save the workflow when saving config */
|
|
51
|
+
saveWorkflowWhenSavingConfig?: boolean;
|
|
49
52
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
50
53
|
onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
51
54
|
/** Callback when form is cancelled */
|
|
@@ -58,6 +61,7 @@
|
|
|
58
61
|
values,
|
|
59
62
|
showUIExtensions = true,
|
|
60
63
|
workflowId,
|
|
64
|
+
saveWorkflowWhenSavingConfig = false,
|
|
61
65
|
onSave,
|
|
62
66
|
onCancel
|
|
63
67
|
}: Props = $props();
|
|
@@ -137,6 +141,11 @@
|
|
|
137
141
|
*/
|
|
138
142
|
let uiExtensionValues = $state<NodeUIExtensions>({});
|
|
139
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Flag to track if workflow save is in progress
|
|
146
|
+
*/
|
|
147
|
+
let isSavingWorkflow = $state(false);
|
|
148
|
+
|
|
140
149
|
/**
|
|
141
150
|
* Get initial UI extensions from node (instance level overrides type level)
|
|
142
151
|
*/
|
|
@@ -270,8 +279,9 @@
|
|
|
270
279
|
/**
|
|
271
280
|
* Handle form submission
|
|
272
281
|
* Collects both config values and UI extension values
|
|
282
|
+
* Optionally saves the workflow if the option is enabled
|
|
273
283
|
*/
|
|
274
|
-
function handleSave(): void {
|
|
284
|
+
async function handleSave(): Promise<void> {
|
|
275
285
|
// Collect all form values including hidden fields
|
|
276
286
|
const form = document.querySelector('.config-form');
|
|
277
287
|
const updatedConfig: Record<string, unknown> = { ...configValues };
|
|
@@ -322,6 +332,18 @@
|
|
|
322
332
|
} else {
|
|
323
333
|
onSave(updatedConfig);
|
|
324
334
|
}
|
|
335
|
+
|
|
336
|
+
// Save workflow if the option is enabled
|
|
337
|
+
if (saveWorkflowWhenSavingConfig) {
|
|
338
|
+
isSavingWorkflow = true;
|
|
339
|
+
try {
|
|
340
|
+
await globalSaveWorkflow();
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error('Failed to save workflow after config save:', error);
|
|
343
|
+
} finally {
|
|
344
|
+
isSavingWorkflow = false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
325
347
|
}
|
|
326
348
|
|
|
327
349
|
/**
|
|
@@ -498,13 +520,23 @@
|
|
|
498
520
|
type="button"
|
|
499
521
|
class="config-form__button config-form__button--secondary"
|
|
500
522
|
onclick={onCancel}
|
|
523
|
+
disabled={isSavingWorkflow}
|
|
501
524
|
>
|
|
502
525
|
<Icon icon="heroicons:x-mark" class="config-form__button-icon" />
|
|
503
526
|
<span>Cancel</span>
|
|
504
527
|
</button>
|
|
505
|
-
<button
|
|
506
|
-
|
|
507
|
-
|
|
528
|
+
<button
|
|
529
|
+
type="submit"
|
|
530
|
+
class="config-form__button config-form__button--primary"
|
|
531
|
+
disabled={isSavingWorkflow}
|
|
532
|
+
>
|
|
533
|
+
{#if isSavingWorkflow}
|
|
534
|
+
<span class="config-form__button-spinner"></span>
|
|
535
|
+
<span>Saving...</span>
|
|
536
|
+
{:else}
|
|
537
|
+
<Icon icon="heroicons:check" class="config-form__button-icon" />
|
|
538
|
+
<span>Save Changes</span>
|
|
539
|
+
{/if}
|
|
508
540
|
</button>
|
|
509
541
|
</div>
|
|
510
542
|
</form>
|
|
@@ -560,6 +592,16 @@
|
|
|
560
592
|
margin-top: 0.5rem;
|
|
561
593
|
}
|
|
562
594
|
|
|
595
|
+
/* Button Spinner */
|
|
596
|
+
.config-form__button-spinner {
|
|
597
|
+
width: 1rem;
|
|
598
|
+
height: 1rem;
|
|
599
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
600
|
+
border-top-color: #ffffff;
|
|
601
|
+
border-radius: 50%;
|
|
602
|
+
animation: config-form-spin 0.6s linear infinite;
|
|
603
|
+
}
|
|
604
|
+
|
|
563
605
|
.config-form__button {
|
|
564
606
|
display: inline-flex;
|
|
565
607
|
align-items: center;
|
|
@@ -10,6 +10,8 @@ interface Props {
|
|
|
10
10
|
showUIExtensions?: boolean;
|
|
11
11
|
/** Optional workflow ID for context in external links */
|
|
12
12
|
workflowId?: string;
|
|
13
|
+
/** Whether to also save the workflow when saving config */
|
|
14
|
+
saveWorkflowWhenSavingConfig?: boolean;
|
|
13
15
|
/** Callback when form is saved (includes both config and extensions if enabled) */
|
|
14
16
|
onSave: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
|
|
15
17
|
/** Callback when form is cancelled */
|
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
<script lang="ts">
|
|
18
18
|
import { onMount, onDestroy } from 'svelte';
|
|
19
|
-
import { EditorView,
|
|
19
|
+
import { EditorView, lineNumbers, highlightActiveLineGutter, drawSelection } from '@codemirror/view';
|
|
20
20
|
import { EditorState } from '@codemirror/state';
|
|
21
|
+
import { history, historyKeymap } from '@codemirror/commands';
|
|
22
|
+
import { highlightSpecialChars, highlightActiveLine } from '@codemirror/view';
|
|
23
|
+
import { syntaxHighlighting, defaultHighlightStyle, indentOnInput } from '@codemirror/language';
|
|
24
|
+
import { keymap } from '@codemirror/view';
|
|
25
|
+
import { defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
21
26
|
import { json, jsonParseLinter } from '@codemirror/lang-json';
|
|
22
27
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
23
28
|
import { linter, lintGutter } from '@codemirror/lint';
|
|
@@ -156,14 +161,36 @@
|
|
|
156
161
|
|
|
157
162
|
/**
|
|
158
163
|
* Create editor extensions array
|
|
164
|
+
* Uses minimal setup for better performance (no auto-closing brackets, no autocompletion)
|
|
159
165
|
*/
|
|
160
166
|
function createExtensions() {
|
|
161
167
|
const extensions = [
|
|
162
|
-
|
|
168
|
+
// Essential visual features
|
|
169
|
+
lineNumbers(),
|
|
170
|
+
highlightActiveLineGutter(),
|
|
171
|
+
highlightSpecialChars(),
|
|
172
|
+
highlightActiveLine(),
|
|
173
|
+
drawSelection(),
|
|
174
|
+
|
|
175
|
+
// Editing features
|
|
176
|
+
history(),
|
|
177
|
+
indentOnInput(),
|
|
178
|
+
|
|
179
|
+
// Syntax highlighting
|
|
180
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
181
|
+
|
|
182
|
+
// Keymaps for basic editing
|
|
183
|
+
keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
|
|
184
|
+
|
|
185
|
+
// JSON-specific features
|
|
163
186
|
json(),
|
|
164
187
|
linter(jsonParseLinter()),
|
|
165
188
|
lintGutter(),
|
|
189
|
+
|
|
190
|
+
// Update listener
|
|
166
191
|
EditorView.updateListener.of(handleUpdate),
|
|
192
|
+
|
|
193
|
+
// Custom theme
|
|
167
194
|
EditorView.theme({
|
|
168
195
|
'&': {
|
|
169
196
|
height: height,
|
|
@@ -16,15 +16,23 @@
|
|
|
16
16
|
|
|
17
17
|
<script lang="ts">
|
|
18
18
|
import { onMount, onDestroy } from 'svelte';
|
|
19
|
-
import { EditorView, basicSetup } from 'codemirror';
|
|
20
|
-
import { EditorState } from '@codemirror/state';
|
|
21
19
|
import {
|
|
20
|
+
EditorView,
|
|
21
|
+
lineNumbers,
|
|
22
|
+
highlightActiveLineGutter,
|
|
23
|
+
drawSelection,
|
|
24
|
+
highlightSpecialChars,
|
|
25
|
+
highlightActiveLine,
|
|
26
|
+
keymap,
|
|
22
27
|
Decoration,
|
|
23
28
|
type DecorationSet,
|
|
24
29
|
ViewPlugin,
|
|
25
30
|
type ViewUpdate,
|
|
26
31
|
MatchDecorator
|
|
27
32
|
} from '@codemirror/view';
|
|
33
|
+
import { EditorState } from '@codemirror/state';
|
|
34
|
+
import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
35
|
+
import { syntaxHighlighting, defaultHighlightStyle, indentOnInput } from '@codemirror/language';
|
|
28
36
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
29
37
|
|
|
30
38
|
interface Props {
|
|
@@ -114,12 +122,34 @@
|
|
|
114
122
|
|
|
115
123
|
/**
|
|
116
124
|
* Create editor extensions array for template editing
|
|
125
|
+
* Uses minimal setup for better performance (no auto-closing brackets, no autocompletion)
|
|
117
126
|
*/
|
|
118
127
|
function createExtensions() {
|
|
119
128
|
const extensions = [
|
|
120
|
-
|
|
129
|
+
// Essential visual features
|
|
130
|
+
lineNumbers(),
|
|
131
|
+
highlightActiveLineGutter(),
|
|
132
|
+
highlightSpecialChars(),
|
|
133
|
+
highlightActiveLine(),
|
|
134
|
+
drawSelection(),
|
|
135
|
+
|
|
136
|
+
// Editing features
|
|
137
|
+
history(),
|
|
138
|
+
indentOnInput(),
|
|
139
|
+
|
|
140
|
+
// Syntax highlighting
|
|
141
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
142
|
+
|
|
143
|
+
// Keymaps for basic editing
|
|
144
|
+
keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
|
|
145
|
+
|
|
146
|
+
// Template-specific variable highlighter
|
|
121
147
|
variableHighlighter,
|
|
148
|
+
|
|
149
|
+
// Update listener
|
|
122
150
|
EditorView.updateListener.of(handleUpdate),
|
|
151
|
+
|
|
152
|
+
// Custom theme
|
|
123
153
|
EditorView.theme({
|
|
124
154
|
'&': {
|
|
125
155
|
height: height,
|
|
@@ -26,6 +26,24 @@
|
|
|
26
26
|
|
|
27
27
|
let props: Props = $props();
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Instance-specific title override from config.
|
|
31
|
+
* Falls back to the original label if not set.
|
|
32
|
+
* This allows users to customize the node title per-instance via config.
|
|
33
|
+
*/
|
|
34
|
+
const displayTitle = $derived(
|
|
35
|
+
(props.data.config?.instanceTitle as string) || props.data.label
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Instance-specific description override from config.
|
|
40
|
+
* Falls back to the metadata description if not set.
|
|
41
|
+
* This allows users to customize the node description per-instance via config.
|
|
42
|
+
*/
|
|
43
|
+
const displayDescription = $derived(
|
|
44
|
+
(props.data.config?.instanceDescription as string) || props.data.metadata.description
|
|
45
|
+
);
|
|
46
|
+
|
|
29
47
|
/**
|
|
30
48
|
* Get the hideUnconnectedHandles setting from extensions
|
|
31
49
|
* Merges node type defaults with instance overrides
|
|
@@ -137,7 +155,7 @@
|
|
|
137
155
|
onkeydown={handleKeydown}
|
|
138
156
|
role="button"
|
|
139
157
|
tabindex="0"
|
|
140
|
-
aria-label="Gateway node: {
|
|
158
|
+
aria-label="Gateway node: {displayTitle}"
|
|
141
159
|
aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
|
|
142
160
|
>
|
|
143
161
|
<!-- Node Header -->
|
|
@@ -151,17 +169,17 @@
|
|
|
151
169
|
<Icon icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)} />
|
|
152
170
|
</div>
|
|
153
171
|
|
|
154
|
-
<!-- Node Title -->
|
|
172
|
+
<!-- Node Title - uses instanceTitle override if set -->
|
|
155
173
|
<h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
|
|
156
|
-
{
|
|
174
|
+
{displayTitle}
|
|
157
175
|
</h3>
|
|
158
176
|
</div>
|
|
159
|
-
<!-- Node Description -->
|
|
177
|
+
<!-- Node Description - uses instanceDescription override if set -->
|
|
160
178
|
<p
|
|
161
179
|
class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1"
|
|
162
180
|
id="node-description-{props.data.nodeId || 'unknown'}"
|
|
163
181
|
>
|
|
164
|
-
{
|
|
182
|
+
{displayDescription}
|
|
165
183
|
</p>
|
|
166
184
|
</div>
|
|
167
185
|
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Idea Node Component
|
|
3
|
+
A BPMN-like conceptual flow node with card design and configurable ports.
|
|
4
|
+
Allows users to create and chain ideas together without committing to specific node types.
|
|
5
|
+
Supports 4 connection points: left, right, top, and bottom (configurable via checkboxes).
|
|
6
|
+
Styled with BEM syntax
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { Position, Handle } from "@xyflow/svelte";
|
|
11
|
+
import type { ConfigValues, NodeMetadata } from "../../types/index.js";
|
|
12
|
+
import Icon from "@iconify/svelte";
|
|
13
|
+
import { getDataTypeColor } from "../../utils/colors.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* IdeaNode component props
|
|
17
|
+
* Displays a card-style node for conceptual flow diagrams
|
|
18
|
+
*/
|
|
19
|
+
const props = $props<{
|
|
20
|
+
data: {
|
|
21
|
+
label: string;
|
|
22
|
+
config: ConfigValues;
|
|
23
|
+
metadata: NodeMetadata;
|
|
24
|
+
nodeId?: string;
|
|
25
|
+
onConfigOpen?: (node: {
|
|
26
|
+
id: string;
|
|
27
|
+
type: string;
|
|
28
|
+
data: { label: string; config: ConfigValues; metadata: NodeMetadata };
|
|
29
|
+
}) => void;
|
|
30
|
+
};
|
|
31
|
+
selected?: boolean;
|
|
32
|
+
isProcessing?: boolean;
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Instance-specific title override from config.
|
|
38
|
+
* Falls back to the original label if not set.
|
|
39
|
+
* This allows users to customize the node title per-instance via config.
|
|
40
|
+
* Note: Also supports legacy 'title' property for backward compatibility.
|
|
41
|
+
*/
|
|
42
|
+
const displayTitle = $derived(
|
|
43
|
+
(props.data.config?.instanceTitle as string) ||
|
|
44
|
+
(props.data.config?.title as string) ||
|
|
45
|
+
props.data.label ||
|
|
46
|
+
props.data.metadata?.name ||
|
|
47
|
+
"New Idea"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Instance-specific description override from config.
|
|
52
|
+
* Falls back to the metadata description if not set.
|
|
53
|
+
* This allows users to customize the node description per-instance via config.
|
|
54
|
+
* Note: Also supports legacy 'description' property for backward compatibility.
|
|
55
|
+
*/
|
|
56
|
+
const displayDescription = $derived(
|
|
57
|
+
(props.data.config?.instanceDescription as string) ||
|
|
58
|
+
(props.data.config?.description as string) ||
|
|
59
|
+
props.data.metadata?.description ||
|
|
60
|
+
"Click to add description..."
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get custom icon from config or metadata, with fallback
|
|
65
|
+
*/
|
|
66
|
+
const ideaIcon = $derived(
|
|
67
|
+
(props.data.config?.icon as string) ||
|
|
68
|
+
(props.data.metadata?.icon as string) ||
|
|
69
|
+
"mdi:lightbulb-outline"
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get accent color from config or metadata, with fallback
|
|
74
|
+
*/
|
|
75
|
+
const ideaColor = $derived(
|
|
76
|
+
(props.data.config?.color as string) ||
|
|
77
|
+
(props.data.metadata?.color as string) ||
|
|
78
|
+
"#6366f1"
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Port visibility configuration from config
|
|
83
|
+
* Left and Right are enabled by default, Top and Bottom are disabled by default
|
|
84
|
+
*/
|
|
85
|
+
const enableLeftPort = $derived(
|
|
86
|
+
(props.data.config?.enableLeftPort as boolean) ?? true
|
|
87
|
+
);
|
|
88
|
+
const enableRightPort = $derived(
|
|
89
|
+
(props.data.config?.enableRightPort as boolean) ?? true
|
|
90
|
+
);
|
|
91
|
+
const enableTopPort = $derived(
|
|
92
|
+
(props.data.config?.enableTopPort as boolean) ?? false
|
|
93
|
+
);
|
|
94
|
+
const enableBottomPort = $derived(
|
|
95
|
+
(props.data.config?.enableBottomPort as boolean) ?? false
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Data type for idea flow connections
|
|
100
|
+
*/
|
|
101
|
+
const IDEA_DATA_TYPE = "idea";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Opens the configuration sidebar for editing idea properties
|
|
105
|
+
*/
|
|
106
|
+
function openConfigSidebar(): void {
|
|
107
|
+
if (props.data.onConfigOpen) {
|
|
108
|
+
const nodeForConfig = {
|
|
109
|
+
id: props.data.nodeId || "unknown",
|
|
110
|
+
type: "idea",
|
|
111
|
+
data: props.data
|
|
112
|
+
};
|
|
113
|
+
props.data.onConfigOpen(nodeForConfig);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handles double-click to open config sidebar
|
|
119
|
+
*/
|
|
120
|
+
function handleDoubleClick(): void {
|
|
121
|
+
openConfigSidebar();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handle single click - selection handled by SvelteFlow
|
|
126
|
+
*/
|
|
127
|
+
function handleClick(): void {
|
|
128
|
+
// Node selection is handled by Svelte Flow
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Handles keyboard events for accessibility
|
|
133
|
+
* @param event - The keyboard event
|
|
134
|
+
*/
|
|
135
|
+
function handleKeydown(event: KeyboardEvent): void {
|
|
136
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
handleDoubleClick();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<!-- Idea Node -->
|
|
144
|
+
<div
|
|
145
|
+
class="flowdrop-idea-node"
|
|
146
|
+
class:flowdrop-idea-node--selected={props.selected}
|
|
147
|
+
class:flowdrop-idea-node--processing={props.isProcessing}
|
|
148
|
+
class:flowdrop-idea-node--error={props.isError}
|
|
149
|
+
style="--idea-accent-color: {ideaColor};"
|
|
150
|
+
onclick={handleClick}
|
|
151
|
+
ondblclick={handleDoubleClick}
|
|
152
|
+
onkeydown={handleKeydown}
|
|
153
|
+
role="button"
|
|
154
|
+
tabindex="0"
|
|
155
|
+
aria-label="Idea node: {displayTitle}"
|
|
156
|
+
>
|
|
157
|
+
<!-- Left Port (Target/Input) -->
|
|
158
|
+
{#if enableLeftPort}
|
|
159
|
+
<Handle
|
|
160
|
+
type="target"
|
|
161
|
+
position={Position.Left}
|
|
162
|
+
style="background-color: {getDataTypeColor(IDEA_DATA_TYPE)}; border-color: #ffffff; top: 50%; transform: translateY(-50%); z-index: 30;"
|
|
163
|
+
id={`${props.data.nodeId}-input-left`}
|
|
164
|
+
/>
|
|
165
|
+
{/if}
|
|
166
|
+
|
|
167
|
+
<!-- Top Port (Target/Input) -->
|
|
168
|
+
{#if enableTopPort}
|
|
169
|
+
<Handle
|
|
170
|
+
type="target"
|
|
171
|
+
position={Position.Top}
|
|
172
|
+
style="background-color: {getDataTypeColor(IDEA_DATA_TYPE)}; border-color: #ffffff; left: 50%; transform: translateX(-50%); z-index: 30;"
|
|
173
|
+
id={`${props.data.nodeId}-input-top`}
|
|
174
|
+
/>
|
|
175
|
+
{/if}
|
|
176
|
+
|
|
177
|
+
<!-- Card Content -->
|
|
178
|
+
<div class="flowdrop-idea-node__card">
|
|
179
|
+
<!-- Accent Bar -->
|
|
180
|
+
<div class="flowdrop-idea-node__accent-bar"></div>
|
|
181
|
+
|
|
182
|
+
<!-- Header with icon and title -->
|
|
183
|
+
<div class="flowdrop-idea-node__header">
|
|
184
|
+
<div class="flowdrop-idea-node__icon-wrapper">
|
|
185
|
+
<Icon icon={ideaIcon} class="flowdrop-idea-node__icon" />
|
|
186
|
+
</div>
|
|
187
|
+
<h3 class="flowdrop-idea-node__title">{displayTitle}</h3>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Description Body -->
|
|
191
|
+
<div class="flowdrop-idea-node__body">
|
|
192
|
+
<p class="flowdrop-idea-node__description">{displayDescription}</p>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- Processing indicator -->
|
|
196
|
+
{#if props.isProcessing}
|
|
197
|
+
<div class="flowdrop-idea-node__processing">
|
|
198
|
+
<div class="flowdrop-idea-node__spinner"></div>
|
|
199
|
+
<span>Processing...</span>
|
|
200
|
+
</div>
|
|
201
|
+
{/if}
|
|
202
|
+
|
|
203
|
+
<!-- Error indicator -->
|
|
204
|
+
{#if props.isError}
|
|
205
|
+
<div class="flowdrop-idea-node__error">
|
|
206
|
+
<Icon icon="mdi:alert-circle" class="flowdrop-idea-node__error-icon" />
|
|
207
|
+
<span>Error</span>
|
|
208
|
+
</div>
|
|
209
|
+
{/if}
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<!-- Config button -->
|
|
213
|
+
<button
|
|
214
|
+
class="flowdrop-idea-node__config-btn"
|
|
215
|
+
onclick={openConfigSidebar}
|
|
216
|
+
title="Configure idea"
|
|
217
|
+
>
|
|
218
|
+
<Icon icon="mdi:cog" />
|
|
219
|
+
</button>
|
|
220
|
+
|
|
221
|
+
<!-- Right Port (Source/Output) -->
|
|
222
|
+
{#if enableRightPort}
|
|
223
|
+
<Handle
|
|
224
|
+
type="source"
|
|
225
|
+
position={Position.Right}
|
|
226
|
+
style="background-color: {getDataTypeColor(IDEA_DATA_TYPE)}; border-color: #ffffff; top: 50%; transform: translateY(-50%); z-index: 30;"
|
|
227
|
+
id={`${props.data.nodeId}-output-right`}
|
|
228
|
+
/>
|
|
229
|
+
{/if}
|
|
230
|
+
|
|
231
|
+
<!-- Bottom Port (Source/Output) -->
|
|
232
|
+
{#if enableBottomPort}
|
|
233
|
+
<Handle
|
|
234
|
+
type="source"
|
|
235
|
+
position={Position.Bottom}
|
|
236
|
+
style="background-color: {getDataTypeColor(IDEA_DATA_TYPE)}; border-color: #ffffff; left: 50%; transform: translateX(-50%); z-index: 30;"
|
|
237
|
+
id={`${props.data.nodeId}-output-bottom`}
|
|
238
|
+
/>
|
|
239
|
+
{/if}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<style>
|
|
243
|
+
.flowdrop-idea-node {
|
|
244
|
+
position: relative;
|
|
245
|
+
width: 18rem;
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
transition: all 0.2s ease-in-out;
|
|
248
|
+
z-index: 10;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.flowdrop-idea-node__card {
|
|
252
|
+
background-color: #ffffff;
|
|
253
|
+
border-radius: 0.75rem;
|
|
254
|
+
border: 1px solid #e5e7eb;
|
|
255
|
+
box-shadow:
|
|
256
|
+
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
257
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
transition: all 0.2s ease-in-out;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.flowdrop-idea-node:hover .flowdrop-idea-node__card {
|
|
263
|
+
box-shadow:
|
|
264
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
265
|
+
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
266
|
+
transform: translateY(-1px);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.flowdrop-idea-node--selected .flowdrop-idea-node__card {
|
|
270
|
+
border-color: #3b82f6;
|
|
271
|
+
box-shadow:
|
|
272
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
273
|
+
0 0 0 3px rgba(59, 130, 246, 0.3);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.flowdrop-idea-node--processing .flowdrop-idea-node__card {
|
|
277
|
+
opacity: 0.8;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.flowdrop-idea-node--error .flowdrop-idea-node__card {
|
|
281
|
+
border-color: #ef4444 !important;
|
|
282
|
+
background-color: #fef2f2 !important;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Accent bar at top of card */
|
|
286
|
+
.flowdrop-idea-node__accent-bar {
|
|
287
|
+
height: 4px;
|
|
288
|
+
background-color: var(--idea-accent-color, #6366f1);
|
|
289
|
+
transition: background-color 0.2s ease-in-out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Header section */
|
|
293
|
+
.flowdrop-idea-node__header {
|
|
294
|
+
display: flex;
|
|
295
|
+
align-items: center;
|
|
296
|
+
gap: 0.625rem;
|
|
297
|
+
padding: 0.75rem 1rem 0.5rem;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.flowdrop-idea-node__icon-wrapper {
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
justify-content: center;
|
|
304
|
+
width: 2rem;
|
|
305
|
+
height: 2rem;
|
|
306
|
+
background-color: color-mix(in srgb, var(--idea-accent-color, #6366f1) 15%, transparent);
|
|
307
|
+
border-radius: 0.5rem;
|
|
308
|
+
flex-shrink: 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
:global(.flowdrop-idea-node__icon) {
|
|
312
|
+
width: 1.25rem;
|
|
313
|
+
height: 1.25rem;
|
|
314
|
+
color: var(--idea-accent-color, #6366f1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.flowdrop-idea-node__title {
|
|
318
|
+
font-size: 0.9375rem;
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
color: #1f2937;
|
|
321
|
+
margin: 0;
|
|
322
|
+
line-height: 1.3;
|
|
323
|
+
overflow: hidden;
|
|
324
|
+
text-overflow: ellipsis;
|
|
325
|
+
white-space: nowrap;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Body section */
|
|
329
|
+
.flowdrop-idea-node__body {
|
|
330
|
+
padding: 0 1rem 0.875rem;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.flowdrop-idea-node__description {
|
|
334
|
+
font-size: 0.8125rem;
|
|
335
|
+
color: #6b7280;
|
|
336
|
+
margin: 0;
|
|
337
|
+
line-height: 1.5;
|
|
338
|
+
display: -webkit-box;
|
|
339
|
+
-webkit-line-clamp: 3;
|
|
340
|
+
-webkit-box-orient: vertical;
|
|
341
|
+
overflow: hidden;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Processing indicator */
|
|
345
|
+
.flowdrop-idea-node__processing {
|
|
346
|
+
display: flex;
|
|
347
|
+
align-items: center;
|
|
348
|
+
gap: 0.5rem;
|
|
349
|
+
padding: 0.5rem 1rem;
|
|
350
|
+
font-size: 0.75rem;
|
|
351
|
+
color: #6b7280;
|
|
352
|
+
border-top: 1px solid #f3f4f6;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.flowdrop-idea-node__spinner {
|
|
356
|
+
width: 0.875rem;
|
|
357
|
+
height: 0.875rem;
|
|
358
|
+
border: 2px solid #e5e7eb;
|
|
359
|
+
border-top-color: var(--idea-accent-color, #6366f1);
|
|
360
|
+
border-radius: 50%;
|
|
361
|
+
animation: idea-spin 1s linear infinite;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Error indicator */
|
|
365
|
+
.flowdrop-idea-node__error {
|
|
366
|
+
display: flex;
|
|
367
|
+
align-items: center;
|
|
368
|
+
gap: 0.5rem;
|
|
369
|
+
padding: 0.5rem 1rem;
|
|
370
|
+
font-size: 0.75rem;
|
|
371
|
+
color: #ef4444;
|
|
372
|
+
border-top: 1px solid #fecaca;
|
|
373
|
+
background-color: #fef2f2;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
:global(.flowdrop-idea-node__error-icon) {
|
|
377
|
+
width: 0.875rem;
|
|
378
|
+
height: 0.875rem;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
@keyframes idea-spin {
|
|
382
|
+
to {
|
|
383
|
+
transform: rotate(360deg);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Config button */
|
|
388
|
+
.flowdrop-idea-node__config-btn {
|
|
389
|
+
position: absolute;
|
|
390
|
+
top: 0.625rem;
|
|
391
|
+
right: 0.625rem;
|
|
392
|
+
width: 1.5rem;
|
|
393
|
+
height: 1.5rem;
|
|
394
|
+
background-color: rgba(255, 255, 255, 0.95);
|
|
395
|
+
border: 1px solid #e5e7eb;
|
|
396
|
+
border-radius: 0.375rem;
|
|
397
|
+
color: #6b7280;
|
|
398
|
+
cursor: pointer;
|
|
399
|
+
display: flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
justify-content: center;
|
|
402
|
+
opacity: 0;
|
|
403
|
+
transition: all 0.2s ease-in-out;
|
|
404
|
+
backdrop-filter: blur(4px);
|
|
405
|
+
z-index: 15;
|
|
406
|
+
font-size: 0.875rem;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.flowdrop-idea-node:hover .flowdrop-idea-node__config-btn {
|
|
410
|
+
opacity: 1;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.flowdrop-idea-node__config-btn:hover {
|
|
414
|
+
background-color: #f9fafb;
|
|
415
|
+
border-color: #d1d5db;
|
|
416
|
+
color: #374151;
|
|
417
|
+
transform: scale(1.05);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* Handle styles */
|
|
421
|
+
:global(.flowdrop-idea-node .svelte-flow__handle) {
|
|
422
|
+
width: 16px !important;
|
|
423
|
+
height: 16px !important;
|
|
424
|
+
border-radius: 50% !important;
|
|
425
|
+
border: 2px solid #ffffff !important;
|
|
426
|
+
transition: all 0.2s ease-in-out !important;
|
|
427
|
+
cursor: pointer !important;
|
|
428
|
+
z-index: 20 !important;
|
|
429
|
+
pointer-events: auto !important;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Left handle positioning */
|
|
433
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-left) {
|
|
434
|
+
left: -8px !important;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Right handle positioning */
|
|
438
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-right) {
|
|
439
|
+
right: -8px !important;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Top handle positioning */
|
|
443
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-top) {
|
|
444
|
+
top: -8px !important;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* Bottom handle positioning */
|
|
448
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-bottom) {
|
|
449
|
+
bottom: -8px !important;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* Handle hover effects */
|
|
453
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-left:hover),
|
|
454
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-right:hover) {
|
|
455
|
+
transform: translateY(-50%) scale(1.2) !important;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-top:hover),
|
|
459
|
+
:global(.flowdrop-idea-node .svelte-flow__handle-bottom:hover) {
|
|
460
|
+
transform: translateX(-50%) scale(1.2) !important;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
:global(.flowdrop-idea-node .svelte-flow__handle:focus) {
|
|
464
|
+
outline: 2px solid #3b82f6 !important;
|
|
465
|
+
outline-offset: 2px !important;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/* Responsive design */
|
|
469
|
+
@media (max-width: 640px) {
|
|
470
|
+
.flowdrop-idea-node {
|
|
471
|
+
width: 16rem;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.flowdrop-idea-node__header {
|
|
475
|
+
padding: 0.625rem 0.75rem 0.375rem;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.flowdrop-idea-node__body {
|
|
479
|
+
padding: 0 0.75rem 0.625rem;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.flowdrop-idea-node__title {
|
|
483
|
+
font-size: 0.875rem;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.flowdrop-idea-node__description {
|
|
487
|
+
font-size: 0.75rem;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ConfigValues, NodeMetadata } from "../../types/index.js";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
data: {
|
|
4
|
+
label: string;
|
|
5
|
+
config: ConfigValues;
|
|
6
|
+
metadata: NodeMetadata;
|
|
7
|
+
nodeId?: string;
|
|
8
|
+
onConfigOpen?: (node: {
|
|
9
|
+
id: string;
|
|
10
|
+
type: string;
|
|
11
|
+
data: {
|
|
12
|
+
label: string;
|
|
13
|
+
config: ConfigValues;
|
|
14
|
+
metadata: NodeMetadata;
|
|
15
|
+
};
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
isProcessing?: boolean;
|
|
20
|
+
isError?: boolean;
|
|
21
|
+
};
|
|
22
|
+
declare const IdeaNode: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
23
|
+
type IdeaNode = ReturnType<typeof IdeaNode>;
|
|
24
|
+
export default IdeaNode;
|
|
@@ -52,6 +52,26 @@
|
|
|
52
52
|
(props.data.metadata?.color as string) || (props.data.config?.color as string) || '#6366f1'
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Instance-specific title override from config.
|
|
57
|
+
* Falls back to the original label if not set.
|
|
58
|
+
* This allows users to customize the node title per-instance via config.
|
|
59
|
+
*/
|
|
60
|
+
const displayTitle = $derived(
|
|
61
|
+
(props.data.config?.instanceTitle as string) || props.data.label
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Instance-specific description override from config.
|
|
66
|
+
* Falls back to the metadata description if not set.
|
|
67
|
+
* This allows users to customize the node description per-instance via config.
|
|
68
|
+
*/
|
|
69
|
+
const displayDescription = $derived(
|
|
70
|
+
(props.data.config?.instanceDescription as string) ||
|
|
71
|
+
props.data.metadata?.description ||
|
|
72
|
+
'A configurable simple node'
|
|
73
|
+
);
|
|
74
|
+
|
|
55
75
|
// Handle configuration sidebar - now using global ConfigSidebar
|
|
56
76
|
function openConfigSidebar(): void {
|
|
57
77
|
if (props.data.onConfigOpen) {
|
|
@@ -198,13 +218,13 @@
|
|
|
198
218
|
|
|
199
219
|
<!-- Node Title -->
|
|
200
220
|
<h3 class="flowdrop-simple-node__title">
|
|
201
|
-
{
|
|
221
|
+
{displayTitle}
|
|
202
222
|
</h3>
|
|
203
223
|
</div>
|
|
204
224
|
|
|
205
225
|
<!-- Node Description -->
|
|
206
226
|
<p class="flowdrop-simple-node__description">
|
|
207
|
-
{
|
|
227
|
+
{displayDescription}
|
|
208
228
|
</p>
|
|
209
229
|
</div>
|
|
210
230
|
|
|
@@ -150,9 +150,27 @@
|
|
|
150
150
|
);
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
*
|
|
153
|
+
* Instance-specific title override from config.
|
|
154
|
+
* Falls back to the original label if not set.
|
|
155
|
+
* This allows users to customize the node title per-instance via config.
|
|
154
156
|
*/
|
|
155
|
-
|
|
157
|
+
const displayTitle = $derived(
|
|
158
|
+
(props.data.config?.instanceTitle as string) ||
|
|
159
|
+
props.data.label ||
|
|
160
|
+
props.data.metadata?.name ||
|
|
161
|
+
variantConfig.label
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Instance-specific description override from config.
|
|
166
|
+
* Falls back to the metadata description if not set.
|
|
167
|
+
* This allows users to customize the node description per-instance via config.
|
|
168
|
+
*/
|
|
169
|
+
const displayDescription = $derived(
|
|
170
|
+
(props.data.config?.instanceDescription as string) ||
|
|
171
|
+
props.data.metadata?.description ||
|
|
172
|
+
""
|
|
173
|
+
);
|
|
156
174
|
|
|
157
175
|
/**
|
|
158
176
|
* Check if metadata explicitly defines inputs (including empty array)
|
|
@@ -282,7 +300,7 @@
|
|
|
282
300
|
onkeydown={handleKeydown}
|
|
283
301
|
role="button"
|
|
284
302
|
tabindex="0"
|
|
285
|
-
aria-label="{variant} node: {
|
|
303
|
+
aria-label="{variant} node: {displayTitle}"
|
|
286
304
|
>
|
|
287
305
|
<!-- Config button at top -->
|
|
288
306
|
<button
|
|
@@ -333,9 +351,16 @@
|
|
|
333
351
|
{/if}
|
|
334
352
|
</div>
|
|
335
353
|
|
|
336
|
-
<!-- Label below the circle -->
|
|
337
|
-
<div class="flowdrop-terminal-node__label">
|
|
338
|
-
|
|
354
|
+
<!-- Label and description below the circle -->
|
|
355
|
+
<div class="flowdrop-terminal-node__label-container">
|
|
356
|
+
<div class="flowdrop-terminal-node__label">
|
|
357
|
+
{displayTitle}
|
|
358
|
+
</div>
|
|
359
|
+
{#if displayDescription}
|
|
360
|
+
<div class="flowdrop-terminal-node__description">
|
|
361
|
+
{displayDescription}
|
|
362
|
+
</div>
|
|
363
|
+
{/if}
|
|
339
364
|
</div>
|
|
340
365
|
|
|
341
366
|
<!-- Processing indicator -->
|
|
@@ -440,19 +465,37 @@
|
|
|
440
465
|
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
|
|
441
466
|
}
|
|
442
467
|
|
|
468
|
+
.flowdrop-terminal-node__label-container {
|
|
469
|
+
display: flex;
|
|
470
|
+
flex-direction: column;
|
|
471
|
+
align-items: center;
|
|
472
|
+
gap: 0.125rem;
|
|
473
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
474
|
+
padding: 0.25rem 0.5rem;
|
|
475
|
+
border-radius: 0.25rem;
|
|
476
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
477
|
+
max-width: 140px;
|
|
478
|
+
}
|
|
479
|
+
|
|
443
480
|
.flowdrop-terminal-node__label {
|
|
444
481
|
font-size: 0.75rem;
|
|
445
482
|
font-weight: 500;
|
|
446
483
|
color: #374151;
|
|
447
484
|
text-align: center;
|
|
448
|
-
max-width: 100px;
|
|
449
485
|
overflow: hidden;
|
|
450
486
|
text-overflow: ellipsis;
|
|
451
487
|
white-space: nowrap;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
488
|
+
max-width: 100%;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.flowdrop-terminal-node__description {
|
|
492
|
+
font-size: 0.625rem;
|
|
493
|
+
color: #6b7280;
|
|
494
|
+
text-align: center;
|
|
495
|
+
overflow: hidden;
|
|
496
|
+
text-overflow: ellipsis;
|
|
497
|
+
white-space: nowrap;
|
|
498
|
+
max-width: 100%;
|
|
456
499
|
}
|
|
457
500
|
|
|
458
501
|
.flowdrop-terminal-node__processing {
|
|
@@ -47,17 +47,32 @@
|
|
|
47
47
|
let toolColor = $derived(
|
|
48
48
|
(props.data.metadata?.color as string) || (props.data.config?.color as string) || '#f59e0b'
|
|
49
49
|
);
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Instance-specific title override from config.
|
|
53
|
+
* Falls back to metadata name, toolName config, or label if not set.
|
|
54
|
+
* This allows users to customize the tool title per-instance via config.
|
|
55
|
+
*/
|
|
56
|
+
const displayTitle = $derived(
|
|
57
|
+
(props.data.config?.instanceTitle as string) ||
|
|
58
|
+
(props.data.metadata?.name as string) ||
|
|
52
59
|
(props.data.config?.toolName as string) ||
|
|
53
60
|
props.data.label ||
|
|
54
|
-
|
|
61
|
+
"Tool"
|
|
55
62
|
);
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Instance-specific description override from config.
|
|
66
|
+
* Falls back to metadata description or toolDescription config if not set.
|
|
67
|
+
* This allows users to customize the tool description per-instance via config.
|
|
68
|
+
*/
|
|
69
|
+
const displayDescription = $derived(
|
|
70
|
+
(props.data.config?.instanceDescription as string) ||
|
|
71
|
+
(props.data.metadata?.description as string) ||
|
|
58
72
|
(props.data.config?.toolDescription as string) ||
|
|
59
|
-
|
|
73
|
+
"A configurable tool for agents"
|
|
60
74
|
);
|
|
75
|
+
|
|
61
76
|
let toolVersion = $derived(
|
|
62
77
|
(props.data.metadata?.version as string) ||
|
|
63
78
|
(props.data.config?.toolVersion as string) ||
|
|
@@ -159,7 +174,7 @@
|
|
|
159
174
|
<!-- Tool Info -->
|
|
160
175
|
<div class="flowdrop-tool-node__info">
|
|
161
176
|
<h3 class="flowdrop-tool-node__title">
|
|
162
|
-
{
|
|
177
|
+
{displayTitle}
|
|
163
178
|
</h3>
|
|
164
179
|
<div class="flowdrop-tool-node__version">
|
|
165
180
|
v{toolVersion}
|
|
@@ -170,9 +185,9 @@
|
|
|
170
185
|
<div class="flowdrop-tool-node__badge">TOOL</div>
|
|
171
186
|
</div>
|
|
172
187
|
|
|
173
|
-
<!-- Tool Description -->
|
|
188
|
+
<!-- Tool Description - uses instanceDescription override if set -->
|
|
174
189
|
<p class="flowdrop-tool-node__description">
|
|
175
|
-
{
|
|
190
|
+
{displayDescription}
|
|
176
191
|
</p>
|
|
177
192
|
</div>
|
|
178
193
|
|
|
@@ -28,6 +28,24 @@
|
|
|
28
28
|
let props: Props = $props();
|
|
29
29
|
let isHandleInteraction = $state(false);
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Instance-specific title override from config.
|
|
33
|
+
* Falls back to the original label if not set.
|
|
34
|
+
* This allows users to customize the node title per-instance via config.
|
|
35
|
+
*/
|
|
36
|
+
const displayTitle = $derived(
|
|
37
|
+
(props.data.config?.instanceTitle as string) || props.data.label
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Instance-specific description override from config.
|
|
42
|
+
* Falls back to the metadata description if not set.
|
|
43
|
+
* This allows users to customize the node description per-instance via config.
|
|
44
|
+
*/
|
|
45
|
+
const displayDescription = $derived(
|
|
46
|
+
(props.data.config?.instanceDescription as string) || props.data.metadata.description
|
|
47
|
+
);
|
|
48
|
+
|
|
31
49
|
/**
|
|
32
50
|
* Get the hideUnconnectedHandles setting from extensions
|
|
33
51
|
* Merges node type defaults with instance overrides
|
|
@@ -173,7 +191,7 @@
|
|
|
173
191
|
|
|
174
192
|
<!-- Node Title - Icon and Title on same line -->
|
|
175
193
|
<h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
|
|
176
|
-
{
|
|
194
|
+
{displayTitle}
|
|
177
195
|
</h3>
|
|
178
196
|
|
|
179
197
|
<!-- Status Indicators -->
|
|
@@ -186,7 +204,7 @@
|
|
|
186
204
|
class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1"
|
|
187
205
|
id="node-description-{props.data.nodeId || 'unknown'}"
|
|
188
206
|
>
|
|
189
|
-
{
|
|
207
|
+
{displayDescription}
|
|
190
208
|
</p>
|
|
191
209
|
</div>
|
|
192
210
|
|
|
@@ -70,7 +70,7 @@ export declare function getBuiltinTypes(): string[];
|
|
|
70
70
|
* Type for built-in node types.
|
|
71
71
|
* Use this when you specifically need a built-in type.
|
|
72
72
|
*/
|
|
73
|
-
export type BuiltinNodeType = 'workflowNode' | 'simple' | 'square' | 'tool' | 'gateway' | 'note' | 'terminal';
|
|
73
|
+
export type BuiltinNodeType = 'workflowNode' | 'simple' | 'square' | 'tool' | 'gateway' | 'note' | 'terminal' | 'idea';
|
|
74
74
|
/**
|
|
75
75
|
* Array of built-in type strings for runtime validation.
|
|
76
76
|
*/
|
|
@@ -13,6 +13,7 @@ import ToolNode from '../components/nodes/ToolNode.svelte';
|
|
|
13
13
|
import GatewayNode from '../components/nodes/GatewayNode.svelte';
|
|
14
14
|
import NotesNode from '../components/nodes/NotesNode.svelte';
|
|
15
15
|
import TerminalNode from '../components/nodes/TerminalNode.svelte';
|
|
16
|
+
import IdeaNode from '../components/nodes/IdeaNode.svelte';
|
|
16
17
|
/**
|
|
17
18
|
* Source identifier for built-in FlowDrop components
|
|
18
19
|
*/
|
|
@@ -98,6 +99,17 @@ export const BUILTIN_NODE_COMPONENTS = [
|
|
|
98
99
|
source: FLOWDROP_SOURCE,
|
|
99
100
|
statusPosition: 'top-right',
|
|
100
101
|
statusSize: 'sm'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'idea',
|
|
105
|
+
displayName: 'Idea (Conceptual Flow)',
|
|
106
|
+
description: 'Conceptual idea node for BPMN-like flow diagrams',
|
|
107
|
+
component: IdeaNode,
|
|
108
|
+
icon: 'mdi:lightbulb-outline',
|
|
109
|
+
category: 'layout',
|
|
110
|
+
source: FLOWDROP_SOURCE,
|
|
111
|
+
statusPosition: 'top-right',
|
|
112
|
+
statusSize: 'sm'
|
|
101
113
|
}
|
|
102
114
|
];
|
|
103
115
|
/**
|
|
@@ -188,7 +200,8 @@ export const BUILTIN_NODE_TYPES = [
|
|
|
188
200
|
'tool',
|
|
189
201
|
'gateway',
|
|
190
202
|
'note',
|
|
191
|
-
'terminal'
|
|
203
|
+
'terminal',
|
|
204
|
+
'idea'
|
|
192
205
|
];
|
|
193
206
|
// Auto-register built-ins when this module is imported
|
|
194
207
|
registerBuiltinNodes();
|