@d34dman/flowdrop 0.0.47 → 0.0.49

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.
Files changed (36) hide show
  1. package/dist/components/App.svelte +11 -0
  2. package/dist/components/App.svelte.d.ts +2 -0
  3. package/dist/components/ConfigForm.svelte +31 -5
  4. package/dist/components/ConfigForm.svelte.d.ts +3 -1
  5. package/dist/components/NodeSidebar.svelte +1 -0
  6. package/dist/components/SchemaForm.svelte +4 -2
  7. package/dist/components/SettingsPanel.svelte +3 -3
  8. package/dist/components/form/FormAutocomplete.svelte +9 -4
  9. package/dist/components/form/FormCodeEditor.svelte +17 -15
  10. package/dist/components/form/FormField.svelte +32 -4
  11. package/dist/components/form/FormField.svelte.d.ts +11 -0
  12. package/dist/components/form/FormFieldLight.svelte +0 -1
  13. package/dist/components/form/FormTemplateEditor.svelte +300 -79
  14. package/dist/components/form/FormTemplateEditor.svelte.d.ts +11 -7
  15. package/dist/components/form/index.d.ts +1 -1
  16. package/dist/components/form/index.js +1 -1
  17. package/dist/components/form/templateAutocomplete.d.ts +2 -11
  18. package/dist/components/form/templateAutocomplete.js +49 -104
  19. package/dist/components/form/types.d.ts +0 -6
  20. package/dist/components/nodes/TerminalNode.svelte +27 -15
  21. package/dist/components/nodes/ToolNode.svelte +4 -6
  22. package/dist/services/apiVariableService.d.ts +116 -0
  23. package/dist/services/apiVariableService.js +338 -0
  24. package/dist/services/globalSave.js +6 -0
  25. package/dist/services/variableService.d.ts +50 -9
  26. package/dist/services/variableService.js +139 -44
  27. package/dist/svelte-app.d.ts +5 -0
  28. package/dist/svelte-app.js +7 -1
  29. package/dist/types/index.d.ts +138 -1
  30. package/dist/types/settings.js +4 -4
  31. package/dist/utils/colors.js +1 -0
  32. package/dist/utils/handlePositioning.d.ts +31 -0
  33. package/dist/utils/handlePositioning.js +35 -0
  34. package/dist/utils/icons.js +1 -0
  35. package/dist/utils/nodeTypes.js +3 -3
  36. package/package.json +8 -4
@@ -71,6 +71,8 @@
71
71
  variant?: 'primary' | 'secondary' | 'outline';
72
72
  onclick?: (event: Event) => void;
73
73
  }>;
74
+ /** Show settings gear icon in navbar */
75
+ showSettings?: boolean;
74
76
  /** API base URL */
75
77
  apiBaseUrl?: string;
76
78
  /** Endpoint configuration */
@@ -96,6 +98,7 @@
96
98
  pipelineId,
97
99
  navbarTitle,
98
100
  navbarActions = [],
101
+ showSettings = true,
99
102
  apiBaseUrl,
100
103
  endpointConfig: propEndpointConfig,
101
104
  authProvider,
@@ -390,6 +393,13 @@
390
393
  * Uses enhanced API client with authProvider support when available.
391
394
  */
392
395
  async function saveWorkflow(): Promise<void> {
396
+ // Flush any pending form changes by blurring the active element.
397
+ // This ensures focusout handlers (like ConfigForm's handleFormBlur)
398
+ // sync local state to the global store before we read it.
399
+ if (document.activeElement instanceof HTMLElement) {
400
+ document.activeElement.blur();
401
+ }
402
+
393
403
  // Wait for any pending DOM updates before saving
394
404
  await tick();
395
405
 
@@ -705,6 +715,7 @@
705
715
  }
706
716
  ]}
707
717
  showStatus={true}
718
+ {showSettings}
708
719
  />
709
720
  {/snippet}
710
721
 
@@ -36,6 +36,8 @@ interface Props {
36
36
  variant?: 'primary' | 'secondary' | 'outline';
37
37
  onclick?: (event: Event) => void;
38
38
  }>;
39
+ /** Show settings gear icon in navbar */
40
+ showSettings?: boolean;
39
41
  /** API base URL */
40
42
  apiBaseUrl?: string;
41
43
  /** Endpoint configuration */
@@ -18,13 +18,15 @@
18
18
  -->
19
19
 
20
20
  <script lang="ts">
21
+ import { setContext } from 'svelte';
21
22
  import Icon from '@iconify/svelte';
22
23
  import type {
23
24
  ConfigSchema,
24
25
  WorkflowNode,
25
26
  WorkflowEdge,
26
27
  NodeUIExtensions,
27
- ConfigEditOptions
28
+ ConfigEditOptions,
29
+ AuthProvider
28
30
  } from '../types/index.js';
29
31
  import { FormField, FormFieldWrapper, FormToggle } from './form/index.js';
30
32
  import type { FieldSchema } from './form/index.js';
@@ -61,6 +63,8 @@
61
63
  * When provided along with workflowNodes, enables autocomplete for template fields.
62
64
  */
63
65
  workflowEdges?: WorkflowEdge[];
66
+ /** Auth provider for API requests (used for template variable API mode) */
67
+ authProvider?: AuthProvider;
64
68
  /** Callback when any field value changes (fired on blur for immediate sync) */
65
69
  onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
66
70
  /** Callback when form is saved (includes both config and extensions if enabled) */
@@ -78,11 +82,17 @@
78
82
  saveWorkflowWhenSavingConfig = false,
79
83
  workflowNodes = [],
80
84
  workflowEdges = [],
85
+ authProvider,
81
86
  onChange,
82
87
  onSave,
83
88
  onCancel
84
89
  }: Props = $props();
85
90
 
91
+ // Set context for child components (e.g., FormAutocomplete)
92
+ // Use getter functions to ensure child components always get the current prop value,
93
+ // even if the prop changes after initial mount
94
+ setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
95
+
86
96
  /**
87
97
  * State for dynamic schema loading
88
98
  */
@@ -132,13 +142,19 @@
132
142
  });
133
143
 
134
144
  /**
135
- * Check if the node has no static schema and needs dynamic loading
145
+ * Check if the node needs dynamic schema loading
146
+ * Loads when: no static schema OR preferDynamicSchema is true
136
147
  */
137
148
  const needsDynamicSchemaLoad = $derived.by(() => {
138
149
  if (!node) return false;
139
150
  const staticSchema = schema ?? node.data.metadata?.configSchema;
140
- // Need to load if: no static schema AND dynamic schema is configured
141
- return !staticSchema && useDynamicSchema && !fetchedDynamicSchema && !dynamicSchemaLoading;
151
+ // Need to load if: (no static schema OR preferDynamicSchema is true) AND dynamic schema is configured
152
+ return (
153
+ (!staticSchema || configEditOptions?.preferDynamicSchema === true) &&
154
+ useDynamicSchema &&
155
+ !fetchedDynamicSchema &&
156
+ !dynamicSchemaLoading
157
+ );
142
158
  });
143
159
 
144
160
  /**
@@ -388,7 +404,12 @@
388
404
  const fieldSchema = property as FieldSchema;
389
405
 
390
406
  // Process template fields to compute variable schema
391
- if (fieldSchema.format === 'template' && node && workflowNodes.length > 0 && workflowEdges.length > 0) {
407
+ if (
408
+ fieldSchema.format === 'template' &&
409
+ node &&
410
+ workflowNodes.length > 0 &&
411
+ workflowEdges.length > 0
412
+ ) {
392
413
  // Get the variables config (may be undefined or partially defined)
393
414
  const variablesConfig = fieldSchema.variables;
394
415
 
@@ -537,6 +558,11 @@
537
558
  value={configValues[key]}
538
559
  {required}
539
560
  animationIndex={index}
561
+ {node}
562
+ nodes={workflowNodes}
563
+ edges={workflowEdges}
564
+ {workflowId}
565
+ {authProvider}
540
566
  onChange={(val) => handleFieldChange(key, val)}
541
567
  />
542
568
  {/each}
@@ -1,4 +1,4 @@
1
- import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions } from '../types/index.js';
1
+ import type { ConfigSchema, WorkflowNode, WorkflowEdge, NodeUIExtensions, AuthProvider } from '../types/index.js';
2
2
  interface Props {
3
3
  /** Optional workflow node (if provided, schema and values are derived from it) */
4
4
  node?: WorkflowNode;
@@ -22,6 +22,8 @@ interface Props {
22
22
  * When provided along with workflowNodes, enables autocomplete for template fields.
23
23
  */
24
24
  workflowEdges?: WorkflowEdge[];
25
+ /** Auth provider for API requests (used for template variable API mode) */
26
+ authProvider?: AuthProvider;
25
27
  /** Callback when any field value changes (fired on blur for immediate sync) */
26
28
  onChange?: (config: Record<string, unknown>, uiExtensions?: NodeUIExtensions) => void;
27
29
  /** Callback when form is saved (includes both config and extensions if enabled) */
@@ -161,6 +161,7 @@
161
161
  memories: 'Memories',
162
162
  agents: 'Agents',
163
163
  ai: 'AI',
164
+ interrupts: 'Interrupts',
164
165
  bundles: 'Bundles'
165
166
  };
166
167
  return names[category] || category;
@@ -157,8 +157,10 @@
157
157
  }: Props = $props();
158
158
 
159
159
  // Set context for child components (e.g., FormAutocomplete)
160
- setContext<AuthProvider | undefined>('flowdrop:authProvider', authProvider);
161
- setContext<string>('flowdrop:baseUrl', baseUrl);
160
+ // Use getter functions to ensure child components always get the current prop value,
161
+ // even if the prop changes after initial mount
162
+ setContext<() => AuthProvider | undefined>('flowdrop:getAuthProvider', () => authProvider);
163
+ setContext<() => string>('flowdrop:getBaseUrl', () => baseUrl);
162
164
 
163
165
  /**
164
166
  * Internal reactive state for form values
@@ -188,10 +188,10 @@
188
188
  undoHistoryLimit: {
189
189
  type: 'number',
190
190
  title: 'Undo History Limit',
191
- description: 'Maximum number of undo steps',
192
- minimum: 10,
191
+ description: 'Maximum number of undo steps (0 to disable)',
192
+ minimum: 0,
193
193
  maximum: 200,
194
- default: 50
194
+ default: 0
195
195
  },
196
196
  confirmDelete: {
197
197
  type: 'boolean',
@@ -59,9 +59,12 @@
59
59
  onChange
60
60
  }: Props = $props();
61
61
 
62
- // Get AuthProvider from context (set by SchemaForm or parent)
63
- const authProvider = getContext<AuthProvider | undefined>('flowdrop:authProvider');
64
- const baseUrl = getContext<string | undefined>('flowdrop:baseUrl') ?? '';
62
+ // Get AuthProvider and baseUrl from context via getter functions
63
+ // This pattern ensures we always get the current value, even if props change after mount
64
+ const getAuthProvider = getContext<(() => AuthProvider | undefined) | undefined>(
65
+ 'flowdrop:getAuthProvider'
66
+ );
67
+ const getBaseUrl = getContext<(() => string) | undefined>('flowdrop:getBaseUrl');
65
68
 
66
69
  // Configuration with defaults
67
70
  const queryParam = $derived(autocomplete.queryParam ?? 'q');
@@ -142,6 +145,7 @@
142
145
  * @returns Full URL with query parameter
143
146
  */
144
147
  function buildUrl(query: string): string {
148
+ const baseUrl = getBaseUrl?.() ?? '';
145
149
  const url = autocomplete.url.startsWith('http')
146
150
  ? autocomplete.url
147
151
  : `${baseUrl}${autocomplete.url}`;
@@ -194,7 +198,8 @@
194
198
  'Content-Type': 'application/json'
195
199
  };
196
200
 
197
- // Add auth headers if provider is available
201
+ // Add auth headers if provider is available (call getter to get current value)
202
+ const authProvider = getAuthProvider?.();
198
203
  if (authProvider) {
199
204
  const authHeaders = await authProvider.getAuthHeaders();
200
205
  Object.assign(headers, authHeaders);
@@ -25,12 +25,12 @@
25
25
  import { EditorState } from '@codemirror/state';
26
26
  import { history, historyKeymap } from '@codemirror/commands';
27
27
  import { highlightSpecialChars, highlightActiveLine } from '@codemirror/view';
28
- import { syntaxHighlighting, defaultHighlightStyle, indentOnInput } from '@codemirror/language';
28
+ import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
29
29
  import { keymap } from '@codemirror/view';
30
30
  import { defaultKeymap, indentWithTab } from '@codemirror/commands';
31
31
  import { json, jsonParseLinter } from '@codemirror/lang-json';
32
32
  import { oneDark } from '@codemirror/theme-one-dark';
33
- import { linter, lintGutter } from '@codemirror/lint';
33
+ import { linter } from '@codemirror/lint';
34
34
 
35
35
  interface Props {
36
36
  /** Field identifier */
@@ -80,6 +80,9 @@
80
80
  /** Flag to prevent update loops */
81
81
  let isInternalUpdate = false;
82
82
 
83
+ /** Flag to skip $effect when change originated from the editor */
84
+ let isEditorUpdate = false;
85
+
83
86
  /**
84
87
  * Convert value to JSON string for editor display
85
88
  */
@@ -129,6 +132,7 @@
129
132
  const content = update.state.doc.toString();
130
133
  const result = validateAndParse(content);
131
134
 
135
+ isEditorUpdate = true;
132
136
  if (result.valid) {
133
137
  validationError = undefined;
134
138
  // Emit the parsed value (object) not the string
@@ -184,22 +188,17 @@
184
188
  // Editing features (skip when read-only)
185
189
  ...(disabled
186
190
  ? []
187
- : [
188
- history(),
189
- indentOnInput(),
190
- keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab])
191
- ]),
191
+ : [history(), keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab])]),
192
192
 
193
193
  // Read-only: prevent document changes and mark content as non-editable
194
194
  ...(disabled ? [EditorState.readOnly.of(true), EditorView.editable.of(false)] : []),
195
195
 
196
- // Syntax highlighting
197
- syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
196
+ // Syntax highlighting - use default for light mode, oneDark handles dark mode
197
+ ...(darkTheme ? [oneDark] : [syntaxHighlighting(defaultHighlightStyle, { fallback: true })]),
198
198
 
199
199
  // JSON-specific features
200
200
  json(),
201
- linter(jsonParseLinter()),
202
- lintGutter(),
201
+ linter(jsonParseLinter(), { delay: 1000 }),
203
202
 
204
203
  // Update listener (only fires on user edit when not disabled)
205
204
  EditorView.updateListener.of(handleUpdate),
@@ -224,10 +223,6 @@
224
223
  EditorView.lineWrapping
225
224
  ];
226
225
 
227
- if (darkTheme) {
228
- extensions.push(oneDark);
229
- }
230
-
231
226
  return extensions;
232
227
  }
233
228
 
@@ -276,6 +271,13 @@
276
271
  }
277
272
 
278
273
  const newContent = valueToString(value);
274
+
275
+ // Skip if the change originated from the editor itself
276
+ if (isEditorUpdate) {
277
+ isEditorUpdate = false;
278
+ return;
279
+ }
280
+
279
281
  const currentContent = editorView.state.doc.toString();
280
282
 
281
283
  // Only update if content actually changed and wasn't from internal edit
@@ -43,6 +43,8 @@
43
43
  import FormAutocomplete from './FormAutocomplete.svelte';
44
44
  import type { FieldSchema } from './types.js';
45
45
  import { getSchemaOptions } from './types.js';
46
+ import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
47
+ import { resolvedTheme } from '../../stores/settingsStore.js';
46
48
 
47
49
  interface Props {
48
50
  /** Unique key/id for the field */
@@ -57,9 +59,31 @@
57
59
  animationIndex?: number;
58
60
  /** Callback when the field value changes */
59
61
  onChange: (value: unknown) => void;
62
+ /** Current workflow node (optional, used for template variable API mode) */
63
+ node?: WorkflowNode;
64
+ /** All workflow nodes (optional, used for port-derived variables) */
65
+ nodes?: WorkflowNode[];
66
+ /** All workflow edges (optional, used for port-derived variables) */
67
+ edges?: WorkflowEdge[];
68
+ /** Workflow ID (optional, used for template variable API mode) */
69
+ workflowId?: string;
70
+ /** Auth provider (optional, used for API requests) */
71
+ authProvider?: AuthProvider;
60
72
  }
61
73
 
62
- let { fieldKey, schema, value, required = false, animationIndex = 0, onChange }: Props = $props();
74
+ let {
75
+ fieldKey,
76
+ schema,
77
+ value,
78
+ required = false,
79
+ animationIndex = 0,
80
+ onChange,
81
+ node,
82
+ nodes,
83
+ edges,
84
+ workflowId,
85
+ authProvider
86
+ }: Props = $props();
63
87
 
64
88
  /**
65
89
  * When schema.readOnly is true, disable all inputs (no editing).
@@ -322,7 +346,7 @@
322
346
  placeholder={schema.placeholder ?? '{}'}
323
347
  {required}
324
348
  height={(schema.height as string | undefined) ?? '200px'}
325
- darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
349
+ darkTheme={(schema.darkTheme as boolean | undefined) ?? $resolvedTheme === 'dark'}
326
350
  autoFormat={(schema.autoFormat as boolean | undefined) ?? true}
327
351
  ariaDescribedBy={descriptionId}
328
352
  disabled={isReadOnly}
@@ -350,13 +374,17 @@
350
374
  'Enter your template here...\nUse {{ variable }} for dynamic values.'}
351
375
  {required}
352
376
  height={(schema.height as string | undefined) ?? '250px'}
353
- darkTheme={(schema.darkTheme as boolean | undefined) ?? false}
377
+ darkTheme={(schema.darkTheme as boolean | undefined) ?? $resolvedTheme === 'dark'}
354
378
  variables={schema.variables}
355
- variableHints={(schema.variableHints as string[] | undefined) ?? []}
356
379
  placeholderExample={(schema.placeholderExample as string | undefined) ??
357
380
  'Hello {{ name }}, your order #{{ order_id }} is ready!'}
358
381
  ariaDescribedBy={descriptionId}
359
382
  disabled={isReadOnly}
383
+ {node}
384
+ {nodes}
385
+ {edges}
386
+ {workflowId}
387
+ {authProvider}
360
388
  onChange={(val) => onChange(val)}
361
389
  />
362
390
  {:else if fieldType === 'autocomplete' && schema.autocomplete}
@@ -1,4 +1,5 @@
1
1
  import type { FieldSchema } from './types.js';
2
+ import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
2
3
  interface Props {
3
4
  /** Unique key/id for the field */
4
5
  fieldKey: string;
@@ -12,6 +13,16 @@ interface Props {
12
13
  animationIndex?: number;
13
14
  /** Callback when the field value changes */
14
15
  onChange: (value: unknown) => void;
16
+ /** Current workflow node (optional, used for template variable API mode) */
17
+ node?: WorkflowNode;
18
+ /** All workflow nodes (optional, used for port-derived variables) */
19
+ nodes?: WorkflowNode[];
20
+ /** All workflow edges (optional, used for port-derived variables) */
21
+ edges?: WorkflowEdge[];
22
+ /** Workflow ID (optional, used for template variable API mode) */
23
+ workflowId?: string;
24
+ /** Auth provider (optional, used for API requests) */
25
+ authProvider?: AuthProvider;
15
26
  }
16
27
  declare const FormField: import("svelte").Component<Props, {}, "">;
17
28
  type FormField = ReturnType<typeof FormField>;
@@ -242,7 +242,6 @@
242
242
  showStatusBar={schema.showStatusBar as boolean | undefined}
243
243
  spellChecker={schema.spellChecker as boolean | undefined}
244
244
  variables={schema.variables}
245
- variableHints={schema.variableHints as string[] | undefined}
246
245
  placeholderExample={schema.placeholderExample as string | undefined}
247
246
  onChange={(val: unknown) => onChange(val)}
248
247
  />