windmill-components 1.532.0 → 1.537.1

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 (135) hide show
  1. package/package/components/ArgInput.svelte +25 -18
  2. package/package/components/Auth0Setting.svelte +8 -3
  3. package/package/components/Dev.svelte +5 -4
  4. package/package/components/DiffDrawer.svelte +2 -2
  5. package/package/components/DiffEditor.svelte +34 -37
  6. package/package/components/DiffEditor.svelte.d.ts +23 -39
  7. package/package/components/EditableSchemaForm.svelte +42 -51
  8. package/package/components/EditableSchemaForm.svelte.d.ts +2 -3
  9. package/package/components/Editor.svelte +30 -9
  10. package/package/components/Editor.svelte.d.ts +5 -0
  11. package/package/components/FlowBuilder.svelte +7 -4
  12. package/package/components/FlowPreviewContent.svelte +3 -3
  13. package/package/components/FlowStatusViewer.svelte +28 -0
  14. package/package/components/FlowStatusViewerInner.svelte +72 -20
  15. package/package/components/FlowStatusViewerInner.svelte.d.ts +7 -0
  16. package/package/components/ModulePreview.svelte +2 -1
  17. package/package/components/ModulePreview.svelte.d.ts +1 -0
  18. package/package/components/ModulePreviewForm.svelte +72 -65
  19. package/package/components/ModulePreviewResultViewer.svelte +13 -18
  20. package/package/components/ModuleTest.svelte +6 -5
  21. package/package/components/ModuleTest.svelte.d.ts +1 -0
  22. package/package/components/OktaSetting.svelte +8 -3
  23. package/package/components/Portal.svelte +11 -7
  24. package/package/components/Portal.svelte.d.ts +19 -39
  25. package/package/components/RunForm.svelte +2 -2
  26. package/package/components/RunForm.svelte.d.ts +1 -1
  27. package/package/components/RunFormAdvancedPopup.svelte +13 -1
  28. package/package/components/SchemaForm.svelte +1 -2
  29. package/package/components/ScriptBuilder.svelte +1 -1
  30. package/package/components/ScriptEditor.svelte +21 -7
  31. package/package/components/SimpleEditor.svelte +0 -1
  32. package/package/components/apps/components/layout/AppModal.svelte +2 -2
  33. package/package/components/apps/editor/component/ComponentNavigation.svelte +3 -2
  34. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +1 -1
  35. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptRunnableByPath.svelte +0 -1
  36. package/package/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte +3 -1
  37. package/package/components/apps/editor/settingsPanel/GridCondition.svelte +3 -1
  38. package/package/components/apps/editor/settingsPanel/GridNavbar.svelte +3 -1
  39. package/package/components/apps/editor/settingsPanel/GridTab.svelte +3 -1
  40. package/package/components/apps/editor/settingsPanel/OneOfInputSpecsEditor.svelte +55 -53
  41. package/package/components/apps/editor/settingsPanel/TableActions.svelte +3 -1
  42. package/package/components/common/button/model.d.ts +1 -1
  43. package/package/components/common/drawer/Disposable.svelte +51 -30
  44. package/package/components/common/drawer/Disposable.svelte.d.ts +12 -44
  45. package/package/components/common/drawer/Drawer.svelte +15 -11
  46. package/package/components/copilot/MetadataGen.svelte +14 -3
  47. package/package/components/copilot/chat/AIChatInput.svelte +0 -1
  48. package/package/components/copilot/chat/AIChatManager.svelte.js +3 -3
  49. package/package/components/copilot/chat/AvailableContextList.svelte +192 -66
  50. package/package/components/copilot/chat/AvailableContextList.svelte.d.ts +2 -2
  51. package/package/components/copilot/chat/ContextElementBadge.svelte +3 -3
  52. package/package/components/copilot/chat/ContextManager.svelte.js +36 -13
  53. package/package/components/copilot/chat/ContextTextarea.svelte +21 -48
  54. package/package/components/copilot/chat/ToolContentDisplay.svelte +10 -1
  55. package/package/components/copilot/chat/ToolExecutionDisplay.svelte +3 -3
  56. package/package/components/copilot/chat/context.d.ts +7 -2
  57. package/package/components/copilot/chat/flow/FlowAIChat.svelte +110 -8
  58. package/package/components/copilot/chat/flow/core.d.ts +11 -0
  59. package/package/components/copilot/chat/flow/core.js +121 -3
  60. package/package/components/copilot/chat/flow/uiIntents.d.ts +8 -0
  61. package/package/components/copilot/chat/flow/uiIntents.js +5 -0
  62. package/package/components/copilot/chat/flow/useUiIntent.d.ts +5 -0
  63. package/package/components/copilot/chat/flow/useUiIntent.js +12 -0
  64. package/package/components/copilot/chat/monaco-adapter.d.ts +22 -4
  65. package/package/components/copilot/chat/monaco-adapter.js +55 -16
  66. package/package/components/copilot/chat/script/core.js +3 -2
  67. package/package/components/copilot/chat/shared.d.ts +3 -2
  68. package/package/components/copilot/chat/shared.js +24 -12
  69. package/package/components/copilot/lib.js +12 -7
  70. package/package/components/copilot/shared.d.ts +1 -1
  71. package/package/components/copilot/shared.js +16 -10
  72. package/package/components/flows/FlowEditor.svelte +4 -2
  73. package/package/components/flows/FlowEditor.svelte.d.ts +1 -0
  74. package/package/components/flows/FlowModuleIcon.svelte +8 -8
  75. package/package/components/flows/common/FlowCardHeader.svelte +4 -1
  76. package/package/components/flows/content/FlowBranchesAllWrapper.svelte +6 -0
  77. package/package/components/flows/content/FlowBranchesOneWrapper.svelte +6 -0
  78. package/package/components/flows/content/FlowEditorPanel.svelte +2 -1
  79. package/package/components/flows/content/FlowEditorPanel.svelte.d.ts +1 -0
  80. package/package/components/flows/content/FlowInput.svelte +31 -34
  81. package/package/components/flows/content/FlowInput.svelte.d.ts +1 -0
  82. package/package/components/flows/content/FlowLoop.svelte +7 -0
  83. package/package/components/flows/content/FlowModuleComponent.svelte +37 -44
  84. package/package/components/flows/content/FlowModuleScript.svelte +1 -1
  85. package/package/components/flows/content/FlowModuleSuspend.svelte +16 -18
  86. package/package/components/flows/content/FlowWhileLoop.svelte +6 -0
  87. package/package/components/flows/content/ScriptEditorDrawer.svelte +9 -11
  88. package/package/components/flows/dfs.d.ts +1 -1
  89. package/package/components/flows/dfs.js +6 -6
  90. package/package/components/flows/flowInfers.js +7 -7
  91. package/package/components/flows/flowStateUtils.svelte.js +1 -2
  92. package/package/components/flows/map/FlowModuleSchemaItem.svelte +12 -26
  93. package/package/components/flows/map/MapItem.svelte +8 -4
  94. package/package/components/flows/map/VirtualItem.svelte +1 -1
  95. package/package/components/flows/pickers/TopLevelNode.svelte +1 -1
  96. package/package/components/flows/propPicker/InputPickerInner.svelte +5 -5
  97. package/package/components/flows/propPicker/OutputPickerInner.svelte +143 -118
  98. package/package/components/flows/propPicker/OutputPickerInner.svelte.d.ts +7 -16
  99. package/package/components/flows/{testSteps.svelte.d.ts → stepsInputArgs.svelte.d.ts} +2 -1
  100. package/package/components/flows/{testSteps.svelte.js → stepsInputArgs.svelte.js} +15 -3
  101. package/package/components/flows/types.d.ts +16 -3
  102. package/package/components/flows/utils.js +3 -0
  103. package/package/components/graph/FlowGraphV2.svelte +1 -1
  104. package/package/components/graph/renderers/nodes/AIToolNode.svelte +4 -4
  105. package/package/components/graph/renderers/nodes/NewAIToolNode.svelte +71 -54
  106. package/package/components/propertyPicker/ObjectViewer.svelte +11 -3
  107. package/package/components/raw_apps/RawAppInlineScriptEditor.svelte +1 -1
  108. package/package/components/schema/AddPropertyV2.svelte +2 -7
  109. package/package/components/schema/AddPropertyV2.svelte.d.ts +3 -20
  110. package/package/components/schema/EditableSchemaDrawer.svelte +109 -115
  111. package/package/components/schema/EditableSchemaDrawer.svelte.d.ts +2 -1
  112. package/package/components/schema/EditableSchemaSdkWrapper.svelte +16 -3
  113. package/package/components/schema/EditableSchemaSdkWrapper.svelte.d.ts +4 -1
  114. package/package/components/schema/EditableSchemaWrapper.svelte +3 -10
  115. package/package/components/schema/FlowPropertyEditor.svelte +9 -41
  116. package/package/components/schema/FlowPropertyEditor.svelte.d.ts +1 -1
  117. package/package/components/schema/SchemaFormDND.svelte +11 -10
  118. package/package/components/schema/SchemaFormDND.svelte.d.ts +3 -2
  119. package/package/components/schema/editable_schema_wrapper.d.ts +0 -3
  120. package/package/components/settings/PremiumInfo.svelte +7 -2
  121. package/package/components/triggers/CaptureWrapper.svelte +2 -13
  122. package/package/components/triggers/CaptureWrapper.svelte.d.ts +1 -1
  123. package/package/components/triggers/TriggersWrapper.svelte +1 -0
  124. package/package/components/triggers/http/RouteEditorInner.svelte +1 -1
  125. package/package/components/triggers/nats/NatsTriggerEditorInner.svelte +23 -20
  126. package/package/components/triggers/nats/NatsTriggersConfigSection.svelte +15 -27
  127. package/package/components/triggers/nats/NatsTriggersConfigSection.svelte.d.ts +7 -5
  128. package/package/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +16 -16
  129. package/package/hubPaths.json +3 -1
  130. package/package/script_helpers.d.ts +2 -2
  131. package/package/script_helpers.js +2 -0
  132. package/package/stores.d.ts +1 -0
  133. package/package/stores.js +8 -1
  134. package/package.json +2 -2
  135. package/package/components/ModulePreviewResultViewer.svelte.d.ts +0 -28
@@ -29,7 +29,7 @@ const isDeletable = $derived(deletable && contextElement.deletable !== false);
29
29
  <button onclick={isDeletable ? onDelete : undefined} class:cursor-default={!isDeletable}>
30
30
  {#if showDelete && isDeletable}
31
31
  <X size={16} />
32
- {:else if contextElement.type === 'flow_module'}
32
+ {:else if contextElement.type === 'flow_module' || contextElement.type === 'flow_module_code_piece'}
33
33
  <FlowModuleIcon module={contextElement as FlowModule} size={16} />
34
34
  {:else}
35
35
  {@const SvelteComponent = icon}
@@ -37,7 +37,7 @@ const isDeletable = $derived(deletable && contextElement.deletable !== false);
37
37
  {/if}
38
38
  </button>
39
39
  <span class="truncate">
40
- {contextElement.type === 'diff' || contextElement.type === 'flow_module'
40
+ {contextElement.type === 'diff'
41
41
  ? contextElement.title.replace(/_/g, ' ')
42
42
  : contextElement.title}
43
43
  </span>
@@ -65,7 +65,7 @@ const isDeletable = $derived(deletable && contextElement.deletable !== false);
65
65
  <div class="text-tertiary">Not loaded yet</div>
66
66
  {/if}
67
67
  </div>
68
- {:else if contextElement.type === 'code' || contextElement.type === 'code_piece' || contextElement.type === 'diff'}
68
+ {:else if contextElement.type === 'code' || contextElement.type === 'code_piece' || contextElement.type === 'diff' || contextElement.type === 'flow_module_code_piece'}
69
69
  <div class="max-w-96 max-h-[300px] text-xs overflow-auto">
70
70
  <HighlightCode
71
71
  language={contextElement.lang}
@@ -66,7 +66,7 @@ export default class ContextManager {
66
66
  newAvailableContext.push({
67
67
  type: 'flow_module',
68
68
  id: module.id,
69
- title: `module_[${module.id}]`,
69
+ title: `${module.id}`,
70
70
  value: {
71
71
  language: 'language' in module.value ? module.value.language : 'bunnative',
72
72
  path: 'path' in module.value ? module.value.path : '',
@@ -215,22 +215,45 @@ export default class ContextManager {
215
215
  this.scriptOptions = scriptOptions;
216
216
  }
217
217
  addSelectedLinesToContext(lines, startLine, endLine, moduleId) {
218
- const title = moduleId ? `[${moduleId}] L${startLine}-L${endLine}` : `L${startLine}-L${endLine}`;
218
+ const title = moduleId ? `${moduleId} L${startLine}-L${endLine}` : `L${startLine}-L${endLine}`;
219
219
  if (!this.scriptOptions ||
220
- this.selectedContext.find((c) => c.type === 'code_piece' && c.title === title)) {
220
+ this.selectedContext.find((c) => (c.type === 'code_piece' && c.title === title) ||
221
+ (c.type === 'flow_module_code_piece' && c.id === moduleId && c.title === title))) {
221
222
  return;
222
223
  }
223
- this.selectedContext = [
224
- ...this.selectedContext,
225
- {
226
- type: 'code_piece',
227
- title: title,
228
- startLine,
229
- endLine,
230
- content: lines,
231
- lang: this.scriptOptions.lang
224
+ if (moduleId) {
225
+ const module = [...this.availableContext, ...this.selectedContext].find((c) => c.type === 'flow_module' && c.id === moduleId);
226
+ if (!module) {
227
+ console.error('Module not found', moduleId);
228
+ return;
232
229
  }
233
- ];
230
+ this.selectedContext = [
231
+ ...this.selectedContext,
232
+ {
233
+ type: 'flow_module_code_piece',
234
+ id: moduleId,
235
+ title: title,
236
+ startLine,
237
+ endLine,
238
+ content: lines,
239
+ lang: this.scriptOptions.lang,
240
+ value: module.value
241
+ }
242
+ ];
243
+ }
244
+ else {
245
+ this.selectedContext = [
246
+ ...this.selectedContext,
247
+ {
248
+ type: 'code_piece',
249
+ title: title,
250
+ startLine,
251
+ endLine,
252
+ content: lines,
253
+ lang: this.scriptOptions.lang
254
+ }
255
+ ];
256
+ }
234
257
  }
235
258
  setFixContext() {
236
259
  const codeContext = this.availableContext.find((c) => c.type === 'code');
@@ -10,7 +10,7 @@ let contextTooltipWord = $state('');
10
10
  let tooltipPosition = $state({ x: 0, y: 0 });
11
11
  let textarea = $state(undefined);
12
12
  let tooltipElement = $state(undefined);
13
- let selectedSuggestionIndex = $state(0);
13
+ let tooltipCurrentViewNumber = $state(0);
14
14
  // Properties to copy for caret position calculation
15
15
  const properties = [
16
16
  'direction',
@@ -142,18 +142,17 @@ function handleContextSelection(contextElement) {
142
142
  updateInstructionsWithContext(contextElement);
143
143
  showContextTooltip = false;
144
144
  }
145
- async function updateTooltipPosition(availableContext, showContextTooltip, contextTooltipWord) {
146
- if (!textarea || !showContextTooltip)
145
+ async function updateTooltipPosition(currentViewItemsNumber) {
146
+ if (!textarea)
147
147
  return;
148
148
  try {
149
149
  const coords = getCaretCoordinates(textarea, textarea.selectionEnd);
150
150
  const rect = textarea.getBoundingClientRect();
151
- const filteredAvailableContext = availableContext.filter((c) => !contextTooltipWord || c.title.toLowerCase().includes(contextTooltipWord.slice(1)));
152
151
  const itemHeight = 28; // Estimated height of one item + gap (Button: p-1(8px) + text-xs(16px) = 24px; Parent: gap-1(4px) = 28px)
153
152
  const containerPadding = 8; // p-1 top + p-1 bottom = 4px + 4px = 8px
154
153
  const maxHeight = 192 + containerPadding; // max-h-48 (192px) + containerPadding (8px)
155
154
  // Calculate uncapped height, subtract gap from last item as it's not needed
156
- const numItems = filteredAvailableContext.length;
155
+ const numItems = currentViewItemsNumber;
157
156
  let uncappedHeight = numItems > 0 ? numItems * itemHeight - 4 + containerPadding : containerPadding;
158
157
  // Ensure height is at least containerPadding even if no items
159
158
  uncappedHeight = Math.max(uncappedHeight, containerPadding);
@@ -211,59 +210,29 @@ function handleInput(e) {
211
210
  else {
212
211
  showContextTooltip = false;
213
212
  contextTooltipWord = '';
214
- selectedSuggestionIndex = 0;
215
- }
216
- }
217
- function handleKeyPress(e) {
218
- if (e.key === 'Enter' && !e.shiftKey) {
219
- e.preventDefault();
220
- if (contextTooltipWord) {
221
- const filteredContext = availableContext.filter((c) => !contextTooltipWord || c.title.toLowerCase().includes(contextTooltipWord.slice(1)));
222
- const contextElement = filteredContext[selectedSuggestionIndex];
223
- if (contextElement) {
224
- const isInSelectedContext = selectedContext.find((c) => c.title === contextElement.title && c.type === contextElement.type);
225
- // If the context element is already in the selected context and the last word in the instructions is the same as the context element title, send request
226
- if (isInSelectedContext && value.split(' ').pop() === '@' + contextElement.title) {
227
- onSendRequest();
228
- return;
229
- }
230
- handleContextSelection(contextElement);
231
- }
232
- else if (contextTooltipWord === '@' && availableContext.length > 0) {
233
- handleContextSelection(availableContext[0]);
234
- }
235
- }
236
- else {
237
- onSendRequest();
238
- }
239
213
  }
240
214
  }
241
215
  function handleKeyDown(e) {
216
+ // Pass to parent first if provided
242
217
  if (onKeyDown) {
243
218
  onKeyDown(e);
244
219
  }
245
- if (!showContextTooltip)
246
- return;
247
- const filteredContext = availableContext.filter((c) => !contextTooltipWord || c.title.toLowerCase().includes(contextTooltipWord.slice(1)));
248
- if (e.key === 'Tab') {
249
- e.preventDefault();
250
- const contextElement = filteredContext[selectedSuggestionIndex];
251
- if (contextElement) {
252
- handleContextSelection(contextElement);
220
+ if (showContextTooltip) {
221
+ // avoid new line after Enter in the tooltip
222
+ if (e.key === 'Enter') {
223
+ e.preventDefault();
253
224
  }
225
+ return;
254
226
  }
255
- if (e.key === 'ArrowDown') {
256
- e.preventDefault();
257
- selectedSuggestionIndex = (selectedSuggestionIndex + 1) % filteredContext.length;
258
- }
259
- else if (e.key === 'ArrowUp') {
227
+ if (e.key === 'Enter' && !e.shiftKey) {
260
228
  e.preventDefault();
261
- selectedSuggestionIndex =
262
- (selectedSuggestionIndex - 1 + filteredContext.length) % filteredContext.length;
229
+ onSendRequest();
263
230
  }
264
231
  }
265
232
  $effect(() => {
266
- updateTooltipPosition(availableContext, showContextTooltip, contextTooltipWord);
233
+ if (showContextTooltip) {
234
+ updateTooltipPosition(tooltipCurrentViewNumber);
235
+ }
267
236
  });
268
237
  export function focus() {
269
238
  textarea?.focus();
@@ -283,7 +252,6 @@ export function focus() {
283
252
  </div>
284
253
  <textarea
285
254
  bind:this={textarea}
286
- onkeypress={handleKeyPress}
287
255
  onkeydown={handleKeyDown}
288
256
  bind:value
289
257
  use:autosize
@@ -319,7 +287,12 @@ export function focus() {
319
287
  }}
320
288
  showAllAvailable={true}
321
289
  stringSearch={contextTooltipWord.slice(1)}
322
- selectedIndex={selectedSuggestionIndex}
290
+ onViewChange={(newNumber) => {
291
+ tooltipCurrentViewNumber = newNumber
292
+ }}
293
+ setShowing={(showing) => {
294
+ showContextTooltip = showing
295
+ }}
323
296
  />
324
297
  </div>
325
298
  </Portal>
@@ -13,6 +13,13 @@ function formatJson(obj) {
13
13
  return obj;
14
14
  }
15
15
  }
16
+ for (const key in obj) {
17
+ try {
18
+ const parsed = JSON.parse(obj[key]);
19
+ obj[key] = parsed;
20
+ }
21
+ catch { }
22
+ }
16
23
  return JSON.stringify(obj, null, 2);
17
24
  }
18
25
  catch {
@@ -71,7 +78,9 @@ async function copyToClipboard() {
71
78
  <div
72
79
  class="bg-surface-secondary border border-gray-200 dark:border-gray-700 rounded p-3 overflow-x-auto max-h-64 overflow-y-auto"
73
80
  >
74
- <pre class="text-2xs text-primary whitespace-pre-wrap">{formatJson(content)}</pre>
81
+ <pre class="text-2xs text-primary whitespace-pre-wrap"
82
+ >{formatJson($state.snapshot(content))}</pre
83
+ >
75
84
  </div>
76
85
  {:else}
77
86
  <div
@@ -14,8 +14,8 @@ const hasParameters = $derived(message.parameters !== undefined && Object.keys(m
14
14
  <!-- Collapsible Header -->
15
15
  <button
16
16
  class={twMerge(
17
- "w-full p-3 bg-surface-secondary hover:bg-surface-hover transition-colors flex items-center justify-between text-left border-b border-gray-200 dark:border-gray-700",
18
- message.needsConfirmation ? "opacity-80" : ""
17
+ 'w-full p-3 bg-surface-secondary hover:bg-surface-hover transition-colors flex items-center justify-between text-left border-b border-gray-200 dark:border-gray-700',
18
+ message.needsConfirmation ? 'opacity-80' : ''
19
19
  )}
20
20
  onclick={() => (isExpanded = !isExpanded)}
21
21
  disabled={!message.showDetails}
@@ -46,7 +46,7 @@ const hasParameters = $derived(message.parameters !== undefined && Object.keys(m
46
46
  {#if isExpanded}
47
47
  <div class="p-3 bg-surface space-y-3">
48
48
  <!-- Parameters Section -->
49
- <div class={message.needsConfirmation ? "opacity-80" : ""}>
49
+ <div class={message.needsConfirmation ? 'opacity-80' : ''}>
50
50
  <ToolContentDisplay title="Parameters" content={message.parameters} />
51
51
  </div>
52
52
 
@@ -40,7 +40,7 @@ export interface CodePieceElement {
40
40
  title: string;
41
41
  lang: ScriptLang | 'bunnative';
42
42
  }
43
- export interface FlowModule {
43
+ export interface FlowModuleElement {
44
44
  type: 'flow_module';
45
45
  id: string;
46
46
  title: string;
@@ -51,7 +51,12 @@ export interface FlowModule {
51
51
  type: string;
52
52
  };
53
53
  }
54
- export type ContextElement = (CodeElement | ErrorElement | DBElement | DiffElement | CodePieceElement | FlowModule) & {
54
+ export interface FlowModuleCodePieceElement extends Omit<CodePieceElement, 'type'> {
55
+ type: 'flow_module_code_piece';
56
+ id: string;
57
+ value: FlowModuleElement['value'];
58
+ }
59
+ export type ContextElement = (CodeElement | ErrorElement | DBElement | DiffElement | CodePieceElement | FlowModuleElement | FlowModuleCodePieceElement) & {
55
60
  deletable?: boolean;
56
61
  };
57
62
  export {};
@@ -9,7 +9,7 @@ import { loadSchemaFromModule } from '../../../flows/flowInfers';
9
9
  import { aiChatManager } from '../AIChatManager.svelte';
10
10
  import { refreshStateStore } from '../../../../svelte5Utils.svelte';
11
11
  import DiffDrawer from '../../../DiffDrawer.svelte';
12
- let { flowModuleSchemaMap, } = $props();
12
+ let { flowModuleSchemaMap } = $props();
13
13
  const { flowStore, flowStateStore, selectedId, currentEditor } = getContext('FlowEditorContext');
14
14
  const { exprsToSet } = getContext('FlowCopilotContext') ?? {};
15
15
  let affectedModules = $state({});
@@ -71,13 +71,10 @@ const flowHelpers = {
71
71
  hasDiff: () => {
72
72
  return Object.keys(affectedModules).length > 0;
73
73
  },
74
- acceptAllModuleActions: () => {
75
- for (const [id, affectedModule] of Object.entries(affectedModules)) {
76
- if (affectedModule.action === 'removed') {
77
- deleteStep(id);
78
- }
74
+ acceptAllModuleActions() {
75
+ for (const id of Object.keys(affectedModules)) {
76
+ this.acceptModuleAction(id);
79
77
  }
80
- affectedModules = {};
81
78
  },
82
79
  rejectAllModuleActions() {
83
80
  for (const id of Object.keys(affectedModules)) {
@@ -151,7 +148,18 @@ const flowHelpers = {
151
148
  if (!newModule) {
152
149
  throw new Error('Module not found');
153
150
  }
154
- newModule.value = oldModule.value;
151
+ // Apply the old code to the editor and hide diff editor if the reverted module is a rawscript
152
+ if (newModule.value.type === 'rawscript' &&
153
+ $currentEditor?.type === 'script' &&
154
+ $currentEditor.stepId === id) {
155
+ const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler();
156
+ if (aiChatEditorHandler) {
157
+ aiChatEditorHandler.revertAll({ disableReviewCallback: true });
158
+ $currentEditor.hideDiffMode();
159
+ }
160
+ }
161
+ Object.keys(newModule).forEach((k) => delete newModule[k]);
162
+ Object.assign(newModule, $state.snapshot(oldModule));
155
163
  }
156
164
  refreshStateStore(flowStore);
157
165
  delete affectedModules[id];
@@ -162,6 +170,15 @@ const flowHelpers = {
162
170
  if (affectedModules[id]?.action === 'removed') {
163
171
  deleteStep(id);
164
172
  }
173
+ if (affectedModules[id]?.action === 'modified' &&
174
+ $currentEditor &&
175
+ $currentEditor.type === 'script' &&
176
+ $currentEditor.stepId === id) {
177
+ const aiChatEditorHandler = $currentEditor.editor.getAiChatEditorHandler();
178
+ if (aiChatEditorHandler) {
179
+ aiChatEditorHandler.keepAll({ disableReviewCallback: true });
180
+ }
181
+ }
165
182
  delete affectedModules[id];
166
183
  },
167
184
  // ai chat tools
@@ -416,6 +433,72 @@ const flowHelpers = {
416
433
  refreshStateStore(flowStore);
417
434
  }
418
435
  setModuleStatus(id, 'modified');
436
+ },
437
+ setForLoopOptions: async (id, opts) => {
438
+ const module = getModule(id);
439
+ if (!module) {
440
+ throw new Error('Module not found');
441
+ }
442
+ if (module.value.type !== 'forloopflow') {
443
+ throw new Error('Module is not a forloopflow');
444
+ }
445
+ // Apply skip_failures if provided
446
+ if (typeof opts.skip_failures === 'boolean') {
447
+ module.value.skip_failures = opts.skip_failures;
448
+ }
449
+ // Apply parallel if provided
450
+ if (typeof opts.parallel === 'boolean') {
451
+ module.value.parallel = opts.parallel;
452
+ }
453
+ // Handle parallelism
454
+ if (opts.parallel === false) {
455
+ // If parallel is disabled, clear parallelism
456
+ module.value.parallelism = undefined;
457
+ }
458
+ else if (opts.parallelism !== undefined) {
459
+ if (opts.parallelism === null) {
460
+ // Explicitly clear parallelism
461
+ module.value.parallelism = undefined;
462
+ }
463
+ else if (module.value.parallel || opts.parallel === true) {
464
+ // Only set parallelism if parallel is enabled
465
+ const n = Math.max(1, Math.floor(Math.abs(opts.parallelism)));
466
+ module.value.parallelism = n;
467
+ }
468
+ }
469
+ refreshStateStore(flowStore);
470
+ setModuleStatus(id, 'modified');
471
+ },
472
+ setModuleControlOptions: async (id, opts) => {
473
+ const module = getModule(id);
474
+ if (!module) {
475
+ throw new Error('Module not found');
476
+ }
477
+ // Handle stop_after_if
478
+ if (typeof opts.stop_after_if === 'boolean') {
479
+ if (opts.stop_after_if === false) {
480
+ module.stop_after_if = undefined;
481
+ }
482
+ else {
483
+ module.stop_after_if = {
484
+ expr: opts.stop_after_if_expr ?? '',
485
+ skip_if_stopped: opts.stop_after_if
486
+ };
487
+ }
488
+ }
489
+ // Handle skip_if
490
+ if (typeof opts.skip_if === 'boolean') {
491
+ if (opts.skip_if === false) {
492
+ module.skip_if = undefined;
493
+ }
494
+ else {
495
+ module.skip_if = {
496
+ expr: opts.skip_if_expr ?? ''
497
+ };
498
+ }
499
+ }
500
+ refreshStateStore(flowStore);
501
+ setModuleStatus(id, 'modified');
419
502
  }
420
503
  };
421
504
  function deleteStep(id) {
@@ -454,6 +537,25 @@ $effect(() => {
454
537
  const cleanup = aiChatManager.listenForCurrentEditorChanges($currentEditor);
455
538
  return cleanup;
456
539
  });
540
+ // Automatically show revert review when selecting a rawscript module with pending changes
541
+ $effect(() => {
542
+ if ($currentEditor?.type === 'script' &&
543
+ $selectedId &&
544
+ affectedModules[$selectedId] &&
545
+ $currentEditor.editor.getAiChatEditorHandler()) {
546
+ const moduleLastSnapshot = getModule($selectedId, lastSnapshot);
547
+ const content = moduleLastSnapshot?.value.type === 'rawscript' ? moduleLastSnapshot.value.content : '';
548
+ if (content.length > 0) {
549
+ untrack(() => $currentEditor.editor.reviewAppliedCode(content, {
550
+ onFinishedReview: () => {
551
+ const id = $selectedId;
552
+ flowHelpers.acceptModuleAction(id);
553
+ $currentEditor.hideDiffMode();
554
+ }
555
+ }));
556
+ }
557
+ }
558
+ });
457
559
  let diffDrawer = $state(undefined);
458
560
  </script>
459
561
 
@@ -33,6 +33,17 @@ export interface FlowAIChatHelpers {
33
33
  addBranch: (id: string) => Promise<void>;
34
34
  removeBranch: (id: string, branchIndex: number) => Promise<void>;
35
35
  setForLoopIteratorExpression: (id: string, expression: string) => Promise<void>;
36
+ setForLoopOptions: (id: string, opts: {
37
+ skip_failures?: boolean | null;
38
+ parallel?: boolean | null;
39
+ parallelism?: number | null;
40
+ }) => Promise<void>;
41
+ setModuleControlOptions: (id: string, opts: {
42
+ stop_after_if?: boolean | null;
43
+ stop_after_if_expr?: string | null;
44
+ skip_if?: boolean | null;
45
+ skip_if_expr?: string | null;
46
+ }) => Promise<void>;
36
47
  setCode: (id: string, code: string) => Promise<void>;
37
48
  }
38
49
  declare const newStepSchema: z.ZodUnion<[z.ZodObject<{
@@ -1,4 +1,5 @@
1
1
  import { ScriptService, JobService } from '../../../../gen';
2
+ import { emitUiIntent } from './uiIntents';
2
3
  import YAML from 'yaml';
3
4
  import { z } from 'zod';
4
5
  import uFuzzy from '@leeoniya/ufuzzy';
@@ -98,6 +99,51 @@ const setForLoopIteratorExpressionSchema = z.object({
98
99
  expression: z.string().describe('The JavaScript expression to set for the iterator')
99
100
  });
100
101
  const setForLoopIteratorExpressionToolDef = createToolDef(setForLoopIteratorExpressionSchema, 'set_forloop_iterator_expression', 'Set the iterator JavaScript expression for the given forloop step');
102
+ const setForLoopOptionsSchema = z.object({
103
+ id: z.string().describe('The id of the forloop step to configure'),
104
+ skip_failures: z
105
+ .boolean()
106
+ .nullable()
107
+ .optional()
108
+ .describe('Whether to skip failures in the loop (null to not change)'),
109
+ parallel: z
110
+ .boolean()
111
+ .nullable()
112
+ .optional()
113
+ .describe('Whether to run iterations in parallel (null to not change)'),
114
+ parallelism: z
115
+ .number()
116
+ .int()
117
+ .min(1)
118
+ .nullable()
119
+ .optional()
120
+ .describe('Maximum number of parallel iterations (null to not change)')
121
+ });
122
+ const setForLoopOptionsToolDef = createToolDef(setForLoopOptionsSchema, 'set_forloop_options', 'Set advanced options for a forloop step: skip_failures, parallel, and parallelism');
123
+ const setModuleControlOptionsSchema = z.object({
124
+ id: z.string().describe('The id of the module to configure'),
125
+ stop_after_if: z
126
+ .boolean()
127
+ .nullable()
128
+ .optional()
129
+ .describe('Early stop condition (true to set, false to clear, null to not change)'),
130
+ stop_after_if_expr: z
131
+ .string()
132
+ .nullable()
133
+ .optional()
134
+ .describe('JavaScript expression for early stop condition. Can use `flow_input` or `result`. `result` is the result of the step. `results.<step_id>` is not supported, do not use it. Only used if stop_after_if is true. Example: `flow_input.x > 10` or `result === "failure"`'),
135
+ skip_if: z
136
+ .boolean()
137
+ .nullable()
138
+ .optional()
139
+ .describe('Skip condition (true to set, false to clear, null to not change)'),
140
+ skip_if_expr: z
141
+ .string()
142
+ .nullable()
143
+ .optional()
144
+ .describe('JavaScript expression for skip condition. Can use `flow_input` or `results.<step_id>`. Only used if skip_if is true. Example: `flow_input.x > 10` or `results.a === "failure"`')
145
+ });
146
+ const setModuleControlOptionsToolDef = createToolDef(setModuleControlOptionsSchema, 'set_module_control_options', 'Set control options for any module: stop_after_if (early stop) and skip_if (conditional skip)');
101
147
  const setBranchPredicateSchema = z.object({
102
148
  id: z.string().describe('The id of the branchone step to set the predicates for'),
103
149
  branchIndex: z
@@ -378,6 +424,64 @@ export const flowTools = [
378
424
  return `Forloop '${parsedArgs.id}' iterator expression set`;
379
425
  }
380
426
  },
427
+ {
428
+ def: setForLoopOptionsToolDef,
429
+ fn: async ({ args, helpers, toolId, toolCallbacks }) => {
430
+ const parsedArgs = setForLoopOptionsSchema.parse(args);
431
+ await helpers.setForLoopOptions(parsedArgs.id, {
432
+ skip_failures: parsedArgs.skip_failures,
433
+ parallel: parsedArgs.parallel,
434
+ parallelism: parsedArgs.parallelism
435
+ });
436
+ helpers.selectStep(parsedArgs.id);
437
+ const message = `Set forloop '${parsedArgs.id}' options`;
438
+ toolCallbacks.setToolStatus(toolId, {
439
+ content: message
440
+ });
441
+ return `${message}: ${JSON.stringify(parsedArgs)}`;
442
+ }
443
+ },
444
+ {
445
+ def: setModuleControlOptionsToolDef,
446
+ fn: async ({ args, helpers, toolId, toolCallbacks }) => {
447
+ const parsedArgs = setModuleControlOptionsSchema.parse(args);
448
+ await helpers.setModuleControlOptions(parsedArgs.id, {
449
+ stop_after_if: parsedArgs.stop_after_if,
450
+ stop_after_if_expr: parsedArgs.stop_after_if_expr,
451
+ skip_if: parsedArgs.skip_if,
452
+ skip_if_expr: parsedArgs.skip_if_expr
453
+ });
454
+ helpers.selectStep(parsedArgs.id);
455
+ // Emit UI intent to show early-stop tab when stop_after_if is configured
456
+ const modules = helpers.getModules();
457
+ const module = findModuleById(modules, parsedArgs.id);
458
+ if (!module) {
459
+ throw new Error(`Module with id '${parsedArgs.id}' not found in flow.`);
460
+ }
461
+ const moduleType = module?.value.type;
462
+ const hasSpecificComponents = ['forloopflow', 'whileloopflow', 'branchall', 'branchone'];
463
+ const prefix = hasSpecificComponents.includes(moduleType) ? `${moduleType}` : 'flow';
464
+ if (typeof parsedArgs.stop_after_if === 'boolean') {
465
+ emitUiIntent({
466
+ kind: 'open_module_tab',
467
+ componentId: `${prefix}-${parsedArgs.id}`,
468
+ tab: 'early-stop'
469
+ });
470
+ }
471
+ if (typeof parsedArgs.skip_if === 'boolean') {
472
+ emitUiIntent({
473
+ kind: 'open_module_tab',
474
+ componentId: `${prefix}-${parsedArgs.id}`,
475
+ tab: 'skip'
476
+ });
477
+ }
478
+ const message = `Set module '${parsedArgs.id}' control options`;
479
+ toolCallbacks.setToolStatus(toolId, {
480
+ content: message
481
+ });
482
+ return `${message}: ${JSON.stringify(parsedArgs)}`;
483
+ }
484
+ },
381
485
  {
382
486
  def: resourceTypeToolDef,
383
487
  fn: async ({ args, toolId, workspace, toolCallbacks }) => {
@@ -584,10 +688,17 @@ When creating new steps, follow this process for EACH step:
584
688
 
585
689
  ### Special Step Types
586
690
  For special step types, follow these additional steps:
587
- - For forloop steps: Set the iterator expression using set_forloop_iterator_expression
691
+ - For forloop steps:
692
+ - Set the iterator expression using set_forloop_iterator_expression
693
+ - Set advanced options (parallel, parallelism, skip_failures) using set_forloop_options
588
694
  - For branchone steps: Set the predicates for each branch using set_branch_predicate
589
695
  - For branchall steps: No additional setup needed
590
696
 
697
+ ### Module Control Options
698
+ For any module type, you can set control flow options using set_module_control_options:
699
+ - **stop_after_if**: Early stop condition - stops the module if expression evaluates to true. Can use "flow_input" or "result". "result" is the result of the step. "results.<step_id>" is not supported, do not use it. Example: "flow_input.x > 10" or "result === "failure""
700
+ - **skip_if**: Skip condition - skips the module entirely if expression evaluates to true. Can use "flow_input" or "results.<step_id>". Example: "flow_input.x > 10" or "results.a === "failure""
701
+
591
702
  ### Step Insertion Rules
592
703
  When adding steps, carefully consider the execution order:
593
704
  1. Steps are executed in the order they appear in the flow definition, not in the order they were added
@@ -608,6 +719,7 @@ When adding steps, carefully consider the execution order:
608
719
  ### JavaScript Expressions
609
720
  For step inputs, forloop iterator expressions and branch predicates, use JavaScript expressions with these variables:
610
721
  - Step results: results.stepid or results.stepid.property_name
722
+ - Break condition (stop_after_if) in for loops: result (contains the result of the last iteration)
611
723
  - Loop iterator: flow_input.iter.value (inside loops)
612
724
  - Flow inputs: flow_input.property_name
613
725
  - Static values: Use JavaScript syntax (e.g., "hello", true, 3)
@@ -616,6 +728,12 @@ Note: These variables are only accessible in step inputs, forloop iterator expre
616
728
 
617
729
  For truly static values in step inputs (those not linked to previous steps or loop iterations), prefer using flow inputs by default unless explicitly specified otherwise. This makes the flow more configurable and reusable. For example, instead of hardcoding an email address in a step input, create a flow input for it.
618
730
 
731
+ ### For Loop Advanced Options
732
+ When configuring for-loop steps, consider these options:
733
+ - **parallel: true** - Run iterations in parallel for independent operations (significantly faster for I/O bound tasks)
734
+ - **parallelism: N** - Limit concurrent iterations (only applies when parallel=true). Use to prevent overwhelming external APIs
735
+ - **skip_failures: true** - Continue processing remaining iterations even if some fail. Failed iterations return error objects as results
736
+
619
737
  ### Special Modules
620
738
  - Preprocessor: Runs before the first step when triggered externally
621
739
  - ID: 'preprocessor'
@@ -649,7 +767,7 @@ If the user wants a specific resource as step input, you should set the step val
649
767
  content
650
768
  };
651
769
  }
652
- export function prepareFlowUserMessage(instructions, flowAndSelectedId, selectedContext) {
770
+ export function prepareFlowUserMessage(instructions, flowAndSelectedId, selectedContext = []) {
653
771
  const flow = flowAndSelectedId?.flow;
654
772
  const selectedId = flowAndSelectedId?.selectedId;
655
773
  // Handle context elements
@@ -662,7 +780,7 @@ ${instructions}`;
662
780
  content: userMessage
663
781
  };
664
782
  }
665
- const codePieces = selectedContext?.filter((c) => c.type === 'code_piece') ?? [];
783
+ const codePieces = selectedContext.filter((c) => c.type === 'flow_module_code_piece');
666
784
  const flowModulesYaml = applyCodePiecesToFlowModules(codePieces, flow.value.modules);
667
785
  let flowContent = `## FLOW:
668
786
  flow_input schema:
@@ -0,0 +1,8 @@
1
+ import { type Writable } from 'svelte/store';
2
+ export type UiIntent = {
3
+ kind: 'open_module_tab';
4
+ componentId: string;
5
+ tab: string;
6
+ };
7
+ export declare const uiIntentStore: Writable<UiIntent | null>;
8
+ export declare function emitUiIntent(intent: UiIntent): void;
@@ -0,0 +1,5 @@
1
+ import { writable } from 'svelte/store';
2
+ export const uiIntentStore = writable(null);
3
+ export function emitUiIntent(intent) {
4
+ uiIntentStore.set(intent);
5
+ }
@@ -0,0 +1,5 @@
1
+ type Handlers = {
2
+ openTab?: (tab: string) => void;
3
+ };
4
+ export declare function useUiIntent(componentId: string, handlers: Handlers): void;
5
+ export {};