windmill-components 1.501.24 → 1.502.2

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 (29) hide show
  1. package/package/components/Dev.svelte +4 -5
  2. package/package/components/apps/components/display/AppCarouselList.svelte +39 -4
  3. package/package/components/apps/editor/appUtils.js +4 -0
  4. package/package/components/apps/editor/componentsPanel/componentControlUtils.js +3 -1
  5. package/package/components/apps/editor/settingsPanel/ComponentPanelDataSource.svelte +0 -1
  6. package/package/components/auditLogs/AuditLogsFilters.svelte +12 -2
  7. package/package/components/auditLogs/AuditLogsTable.svelte +209 -122
  8. package/package/components/auditLogs/AuditLogsTable.svelte.d.ts +5 -20
  9. package/package/components/auditLogs/AuditLogsTimeline.svelte +449 -0
  10. package/package/components/auditLogs/AuditLogsTimeline.svelte.d.ts +16 -0
  11. package/package/components/copilot/chat/AIChatDisplay.svelte +23 -165
  12. package/package/components/copilot/chat/AIChatInput.svelte +128 -0
  13. package/package/components/copilot/chat/AIChatInput.svelte.d.ts +16 -0
  14. package/package/components/copilot/chat/AIChatManager.svelte.d.ts +4 -1
  15. package/package/components/copilot/chat/AIChatManager.svelte.js +33 -13
  16. package/package/components/copilot/chat/AIChatMessage.svelte +93 -0
  17. package/package/components/copilot/chat/AIChatMessage.svelte.d.ts +12 -0
  18. package/package/components/copilot/chat/ContextTextarea.svelte +13 -15
  19. package/package/components/copilot/chat/ContextTextarea.svelte.d.ts +4 -2
  20. package/package/components/copilot/chat/flow/FlowAIChat.svelte +11 -0
  21. package/package/components/copilot/chat/shared.d.ts +13 -3
  22. package/package/components/flow_builder.d.ts +10 -1
  23. package/package/components/graph/FlowGraphV2.svelte +1 -0
  24. package/package/components/search/GlobalSearchModal.svelte +28 -18
  25. package/package/gen/core/OpenAPI.js +1 -1
  26. package/package/gen/schemas.gen.d.ts +11 -2
  27. package/package/gen/schemas.gen.js +11 -2
  28. package/package/gen/types.gen.d.ts +5 -2
  29. package/package.json +2 -1
@@ -0,0 +1,128 @@
1
+ <script lang="ts">import Popover from '../../meltComponents/Popover.svelte';
2
+ import AvailableContextList from './AvailableContextList.svelte';
3
+ import ContextElementBadge from './ContextElementBadge.svelte';
4
+ import ContextTextarea from './ContextTextarea.svelte';
5
+ import autosize from '../../../autosize';
6
+ import { aiChatManager } from './AIChatManager.svelte';
7
+ let { availableContext, selectedContext = $bindable([]), disabled = false, isFirstMessage = false, placeholder = 'Ask anything', initialInstructions = '', editingMessageIndex = null, onEditEnd = () => { } } = $props();
8
+ let contextTextareaComponent = $state();
9
+ let instructionsTextareaComponent = $state();
10
+ let instructions = $state(initialInstructions);
11
+ export function focusInput() {
12
+ if (aiChatManager.mode === 'script') {
13
+ contextTextareaComponent?.focus();
14
+ }
15
+ else {
16
+ instructionsTextareaComponent?.focus();
17
+ }
18
+ }
19
+ function clickOutside(node) {
20
+ function handleClick(event) {
21
+ if (node && !node.contains(event.target) && editingMessageIndex !== null) {
22
+ onEditEnd();
23
+ }
24
+ }
25
+ document.addEventListener('click', handleClick, true);
26
+ return {
27
+ destroy() {
28
+ document.removeEventListener('click', handleClick, true);
29
+ }
30
+ };
31
+ }
32
+ function addContextToSelection(contextElement) {
33
+ if (selectedContext &&
34
+ availableContext &&
35
+ !selectedContext.find((c) => c.type === contextElement.type && c.title === contextElement.title) &&
36
+ availableContext.find((c) => c.type === contextElement.type && c.title === contextElement.title)) {
37
+ selectedContext = [...selectedContext, contextElement];
38
+ }
39
+ }
40
+ function sendRequest() {
41
+ if (aiChatManager.loading) {
42
+ return;
43
+ }
44
+ if (editingMessageIndex !== null) {
45
+ aiChatManager.restartGeneration(editingMessageIndex, instructions);
46
+ onEditEnd();
47
+ }
48
+ else {
49
+ aiChatManager.sendRequest({ instructions });
50
+ instructions = '';
51
+ }
52
+ }
53
+ $effect(() => {
54
+ if (editingMessageIndex !== null) {
55
+ focusInput();
56
+ }
57
+ });
58
+ </script>
59
+
60
+ <div use:clickOutside>
61
+ {#if aiChatManager.mode === 'script'}
62
+ <div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 px-2 no-scrollbar">
63
+ <Popover>
64
+ <svelte:fragment slot="trigger">
65
+ <div
66
+ class="border rounded-md px-1 py-0.5 font-normal text-tertiary text-xs hover:bg-surface-hover"
67
+ >@</div
68
+ >
69
+ </svelte:fragment>
70
+ <svelte:fragment slot="content" let:close>
71
+ <AvailableContextList
72
+ {availableContext}
73
+ {selectedContext}
74
+ onSelect={(element) => {
75
+ addContextToSelection(element)
76
+ close()
77
+ }}
78
+ />
79
+ </svelte:fragment>
80
+ </Popover>
81
+ {#each selectedContext as element}
82
+ <ContextElementBadge
83
+ contextElement={element}
84
+ deletable
85
+ on:delete={() => {
86
+ selectedContext = selectedContext?.filter(
87
+ (c) => c.type !== element.type || c.title !== element.title
88
+ )
89
+ }}
90
+ />
91
+ {/each}
92
+ </div>
93
+ <ContextTextarea
94
+ bind:this={contextTextareaComponent}
95
+ bind:value={instructions}
96
+ {availableContext}
97
+ {selectedContext}
98
+ {isFirstMessage}
99
+ {placeholder}
100
+ onAddContext={(contextElement) => addContextToSelection(contextElement)}
101
+ onSendRequest={() => {
102
+ sendRequest()
103
+ }}
104
+ {disabled}
105
+ onEscape={onEditEnd}
106
+ />
107
+ {:else}
108
+ <div class="relative w-full px-2 scroll-pb-2 pt-2">
109
+ <textarea
110
+ bind:this={instructionsTextareaComponent}
111
+ bind:value={instructions}
112
+ use:autosize
113
+ onkeydown={(e) => {
114
+ if (e.key === 'Enter' && !e.shiftKey) {
115
+ e.preventDefault()
116
+ sendRequest()
117
+ } else if (e.key === 'Escape') {
118
+ onEditEnd()
119
+ }
120
+ }}
121
+ rows={3}
122
+ {placeholder}
123
+ class="resize-none"
124
+ {disabled}
125
+ ></textarea>
126
+ </div>
127
+ {/if}
128
+ </div>
@@ -0,0 +1,16 @@
1
+ import type { ContextElement } from './context';
2
+ interface Props {
3
+ availableContext: ContextElement[];
4
+ selectedContext: ContextElement[];
5
+ isFirstMessage?: boolean;
6
+ disabled?: boolean;
7
+ placeholder?: string;
8
+ initialInstructions?: string;
9
+ editingMessageIndex?: number | null;
10
+ onEditEnd?: () => void;
11
+ }
12
+ declare const AiChatInput: import("svelte").Component<Props, {
13
+ focusInput: () => void;
14
+ }, "selectedContext">;
15
+ type AiChatInput = ReturnType<typeof AiChatInput>;
16
+ export default AiChatInput;
@@ -53,6 +53,9 @@ declare class AIChatManager {
53
53
  withCode?: boolean;
54
54
  withDiff?: boolean;
55
55
  }) => void;
56
+ retryRequest: (messageIndex: number) => void;
57
+ private getLastUserMessage;
58
+ private flagLastMessageAsError;
56
59
  private chatRequest;
57
60
  sendRequest: (options?: {
58
61
  removeDiff?: boolean;
@@ -63,7 +66,7 @@ declare class AIChatManager {
63
66
  isPreprocessor?: boolean;
64
67
  }) => Promise<void>;
65
68
  cancel: () => void;
66
- restartLastGeneration: (displayMessageIndex: number) => void;
69
+ restartGeneration: (displayMessageIndex: number, newContent?: string) => void;
67
70
  fix: () => void;
68
71
  addSelectedLinesToContext: (lines: string, startLine: number, endLine: number) => void;
69
72
  saveAndClear: () => Promise<void>;
@@ -175,6 +175,30 @@ class AIChatManager {
175
175
  this.scriptEditorShowDiffMode?.();
176
176
  }
177
177
  };
178
+ retryRequest = (messageIndex) => {
179
+ const message = this.displayMessages[messageIndex];
180
+ if (message && message.role === 'user') {
181
+ this.restartGeneration(messageIndex);
182
+ message.error = false;
183
+ }
184
+ else {
185
+ throw new Error('No user message found at the specified index');
186
+ }
187
+ };
188
+ getLastUserMessage = () => {
189
+ for (let i = this.displayMessages.length - 1; i >= 0; i--) {
190
+ const message = this.displayMessages[i];
191
+ if (message.role === 'user') {
192
+ return message;
193
+ }
194
+ }
195
+ };
196
+ flagLastMessageAsError = () => {
197
+ const lastUserMessage = this.getLastUserMessage();
198
+ if (lastUserMessage) {
199
+ lastUserMessage.error = true;
200
+ }
201
+ };
178
202
  chatRequest = async ({ messages, abortController, callbacks }) => {
179
203
  try {
180
204
  let completion = null;
@@ -320,7 +344,8 @@ class AIChatManager {
320
344
  role: 'user',
321
345
  content: this.instructions,
322
346
  contextElements: this.mode === AIMode.SCRIPT ? oldSelectedContext : undefined,
323
- snapshot
347
+ snapshot,
348
+ index: this.messages.length // matching with actual messages index. not -1 because it's not yet added to the messages array
324
349
  }
325
350
  ];
326
351
  const oldInstructions = this.instructions;
@@ -378,6 +403,7 @@ class AIChatManager {
378
403
  }
379
404
  catch (err) {
380
405
  console.error(err);
406
+ this.flagLastMessageAsError();
381
407
  if (err instanceof Error) {
382
408
  sendUserToast('Failed to send request: ' + err.message, true);
383
409
  }
@@ -392,27 +418,21 @@ class AIChatManager {
392
418
  cancel = () => {
393
419
  this.abortController?.abort();
394
420
  };
395
- restartLastGeneration = (displayMessageIndex) => {
421
+ restartGeneration = (displayMessageIndex, newContent) => {
396
422
  const userMessage = this.displayMessages[displayMessageIndex];
397
423
  if (!userMessage || userMessage.role !== 'user') {
398
424
  throw new Error('No user message found at the specified index');
399
425
  }
400
426
  // Remove all messages including and after the specified user message
401
427
  this.displayMessages = this.displayMessages.slice(0, displayMessageIndex);
402
- // Find the last user message in actual messages and remove it and everything after it
403
- let lastActualUserMessageIndex = -1;
404
- for (let i = this.messages.length - 1; i >= 0; i--) {
405
- if (this.messages[i].role === 'user') {
406
- lastActualUserMessageIndex = i;
407
- break;
408
- }
409
- }
410
- if (lastActualUserMessageIndex === -1) {
428
+ // Find corresponding message in actual messages and remove it and everything after it
429
+ let actualMessageIndex = this.messages.findIndex((_, i) => i === userMessage.index);
430
+ if (actualMessageIndex === -1) {
411
431
  throw new Error('No actual user message found to restart from');
412
432
  }
413
- this.messages = this.messages.slice(0, lastActualUserMessageIndex);
433
+ this.messages = this.messages.slice(0, actualMessageIndex);
414
434
  // Resend the request with the same instructions
415
- this.instructions = userMessage.content;
435
+ this.instructions = newContent ?? userMessage.content;
416
436
  this.sendRequest();
417
437
  };
418
438
  fix = () => {
@@ -0,0 +1,93 @@
1
+ <script lang="ts">import { twMerge } from 'tailwind-merge';
2
+ import ContextElementBadge from './ContextElementBadge.svelte';
3
+ import AssistantMessage from './AssistantMessage.svelte';
4
+ import { aiChatManager } from './AIChatManager.svelte';
5
+ import { Button } from '../../common';
6
+ import { RefreshCwIcon, Undo2Icon } from 'lucide-svelte';
7
+ import AIChatInput from './AIChatInput.svelte';
8
+ let { message, messageIndex, availableContext, selectedContext = $bindable(), editingMessageIndex = $bindable(null) } = $props();
9
+ function editMessage() {
10
+ if (message.role !== 'user' || editingMessageIndex !== null || aiChatManager.loading) {
11
+ return;
12
+ }
13
+ editingMessageIndex = messageIndex;
14
+ }
15
+ </script>
16
+
17
+ <div
18
+ class={twMerge(
19
+ message.role === 'user' && messageIndex > 0 && 'mt-6',
20
+ 'mb-2',
21
+ message.role !== 'user' ? 'cursor-default' : 'cursor-pointer'
22
+ )}
23
+ role="button"
24
+ tabindex="0"
25
+ onclick={() => editMessage()}
26
+ onkeydown={() => {}}
27
+ >
28
+ {#if message.role === 'user' && message.contextElements && editingMessageIndex !== messageIndex}
29
+ <div class="flex flex-row gap-1 mb-1 overflow-scroll no-scrollbar px-2">
30
+ {#each message.contextElements as element}
31
+ <ContextElementBadge contextElement={element} />
32
+ {/each}
33
+ </div>
34
+ {/if}
35
+ {#if message.role === 'user' && editingMessageIndex === messageIndex}
36
+ <AIChatInput
37
+ {availableContext}
38
+ bind:selectedContext
39
+ initialInstructions={message.content}
40
+ {editingMessageIndex}
41
+ onEditEnd={() => (editingMessageIndex = null)}
42
+ />
43
+ {:else}
44
+ <div
45
+ class={twMerge(
46
+ 'text-sm py-1 mx-2',
47
+ message.role === 'user' &&
48
+ 'px-2 border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 rounded-lg relative group',
49
+ (message.role === 'assistant' || message.role === 'tool') && 'px-[1px]',
50
+ message.role === 'tool' && 'text-tertiary'
51
+ )}
52
+ >
53
+ {#if message.role === 'assistant'}
54
+ <AssistantMessage {message} />
55
+ {:else}
56
+ {message.content}
57
+ {/if}
58
+ </div>
59
+ {/if}
60
+ {#if message.role === 'user' && message.snapshot}
61
+ <div class="mx-2 text-sm text-tertiary flex flex-row items-center justify-between gap-2 mt-2">
62
+ Saved a flow snapshot
63
+ <Button
64
+ size="xs2"
65
+ variant="border"
66
+ color="light"
67
+ on:click={() => {
68
+ if (message.snapshot) {
69
+ aiChatManager.flowAiChatHelpers?.revertToSnapshot(message.snapshot)
70
+ }
71
+ }}
72
+ title="Revert to snapshot"
73
+ startIcon={{ icon: Undo2Icon }}
74
+ >
75
+ Revert
76
+ </Button>
77
+ </div>
78
+ {/if}
79
+ </div>
80
+ {#if message.role === 'user' && message.error}
81
+ <div class="flex justify-end px-2 -mt-1">
82
+ <Button
83
+ size="xs2"
84
+ variant="border"
85
+ title="Retry generation"
86
+ color="light"
87
+ startIcon={{ icon: RefreshCwIcon }}
88
+ onclick={() => aiChatManager.retryRequest(messageIndex)}
89
+ >
90
+ Retry
91
+ </Button>
92
+ </div>
93
+ {/if}
@@ -0,0 +1,12 @@
1
+ import type { DisplayMessage } from './shared';
2
+ import type { ContextElement } from './context';
3
+ interface Props {
4
+ availableContext: ContextElement[];
5
+ selectedContext: ContextElement[];
6
+ message: DisplayMessage;
7
+ messageIndex: number;
8
+ editingMessageIndex: number | null;
9
+ }
10
+ declare const AiChatMessage: import("svelte").Component<Props, {}, "selectedContext" | "editingMessageIndex">;
11
+ type AiChatMessage = ReturnType<typeof AiChatMessage>;
12
+ export default AiChatMessage;
@@ -1,10 +1,9 @@
1
1
  <script lang="ts">import autosize from '../../../autosize';
2
2
  import { tick } from 'svelte';
3
3
  import AvailableContextList from './AvailableContextList.svelte';
4
- import { aiChatManager } from './AIChatManager.svelte';
5
4
  import Portal from '../../Portal.svelte';
6
5
  import { zIndexes } from '../../../zIndexes';
7
- const { availableContext, selectedContext, isFirstMessage, disabled, onUpdateInstructions, onSendRequest, onAddContext } = $props();
6
+ let { value = $bindable(''), availableContext, selectedContext, isFirstMessage, placeholder, disabled, onSendRequest, onAddContext, onEscape } = $props();
8
7
  let showContextTooltip = $state(false);
9
8
  let contextTooltipWord = $state('');
10
9
  let tooltipPosition = $state({ x: 0, y: 0 });
@@ -131,10 +130,10 @@ function addContextToSelection(contextElement) {
131
130
  onAddContext(contextElement);
132
131
  }
133
132
  function updateInstructionsWithContext(contextElement) {
134
- const index = aiChatManager.instructions.lastIndexOf('@');
133
+ const index = value.lastIndexOf('@');
135
134
  if (index !== -1) {
136
- const newInstructions = aiChatManager.instructions.substring(0, index) + `@${contextElement.title}`;
137
- onUpdateInstructions(newInstructions);
135
+ const newInstructions = value.substring(0, index) + `@${contextElement.title}`;
136
+ value = newInstructions;
138
137
  }
139
138
  }
140
139
  function handleContextSelection(contextElement) {
@@ -200,7 +199,7 @@ async function updateTooltipPosition(availableContext, showContextTooltip, conte
200
199
  }
201
200
  function handleInput(e) {
202
201
  textarea = e.target;
203
- const words = aiChatManager.instructions.split(/\s+/);
202
+ const words = value.split(/\s+/);
204
203
  const lastWord = words[words.length - 1];
205
204
  if (lastWord.startsWith('@') &&
206
205
  (!availableContext.find((c) => c.title === lastWord.slice(1)) ||
@@ -213,7 +212,6 @@ function handleInput(e) {
213
212
  contextTooltipWord = '';
214
213
  selectedSuggestionIndex = 0;
215
214
  }
216
- onUpdateInstructions(aiChatManager.instructions);
217
215
  }
218
216
  function handleKeyPress(e) {
219
217
  if (e.key === 'Enter' && !e.shiftKey) {
@@ -224,8 +222,7 @@ function handleKeyPress(e) {
224
222
  if (contextElement) {
225
223
  const isInSelectedContext = selectedContext.find((c) => c.title === contextElement.title && c.type === contextElement.type);
226
224
  // 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
227
- if (isInSelectedContext &&
228
- aiChatManager.instructions.split(' ').pop() === '@' + contextElement.title) {
225
+ if (isInSelectedContext && value.split(' ').pop() === '@' + contextElement.title) {
229
226
  onSendRequest();
230
227
  return;
231
228
  }
@@ -241,6 +238,9 @@ function handleKeyPress(e) {
241
238
  }
242
239
  }
243
240
  function handleKeyDown(e) {
241
+ if (e.key === 'Escape') {
242
+ onEscape();
243
+ }
244
244
  if (!showContextTooltip)
245
245
  return;
246
246
  const filteredContext = availableContext.filter((c) => !contextTooltipWord || c.title.toLowerCase().includes(contextTooltipWord.slice(1)));
@@ -272,14 +272,14 @@ export function focus() {
272
272
  <div class="relative w-full px-2 scroll-pb-2">
273
273
  <div class="textarea-input absolute top-0 left-0 pointer-events-none">
274
274
  <span class="break-words">
275
- {@html getHighlightedText(aiChatManager.instructions)}
275
+ {@html getHighlightedText(value)}
276
276
  </span>
277
277
  </div>
278
278
  <textarea
279
279
  bind:this={textarea}
280
280
  onkeypress={handleKeyPress}
281
281
  onkeydown={handleKeyDown}
282
- bind:value={aiChatManager.instructions}
282
+ bind:value
283
283
  use:autosize
284
284
  rows={3}
285
285
  oninput={handleInput}
@@ -288,11 +288,9 @@ export function focus() {
288
288
  showContextTooltip = false
289
289
  }, 200)
290
290
  }}
291
- placeholder={isFirstMessage ? 'Ask anything' : 'Ask followup'}
291
+ {placeholder}
292
292
  class="textarea-input resize-none bg-transparent caret-black dark:caret-white"
293
- style={aiChatManager.instructions.length > 0
294
- ? 'color: transparent; -webkit-text-fill-color: transparent;'
295
- : ''}
293
+ style={value.length > 0 ? 'color: transparent; -webkit-text-fill-color: transparent;' : ''}
296
294
  {disabled}
297
295
  ></textarea>
298
296
  </div>
@@ -1,15 +1,17 @@
1
1
  import type { ContextElement } from './context';
2
2
  interface Props {
3
+ value: string;
3
4
  availableContext: ContextElement[];
4
5
  selectedContext: ContextElement[];
5
6
  isFirstMessage: boolean;
7
+ placeholder: string;
6
8
  disabled: boolean;
7
- onUpdateInstructions: (value: string) => void;
8
9
  onSendRequest: () => void;
9
10
  onAddContext: (contextElement: ContextElement) => void;
11
+ onEscape: () => void;
10
12
  }
11
13
  declare const ContextTextarea: import("svelte").Component<Props, {
12
14
  focus: () => void;
13
- }, "">;
15
+ }, "value">;
14
16
  type ContextTextarea = ReturnType<typeof ContextTextarea>;
15
17
  export default ContextTextarea;
@@ -93,6 +93,17 @@ const flowHelpers = {
93
93
  if (snapshot) {
94
94
  flowStore.val = snapshot;
95
95
  refreshStateStore(flowStore);
96
+ if ($currentEditor) {
97
+ const module = getModule($currentEditor.stepId, snapshot);
98
+ if (module) {
99
+ if ($currentEditor.type === 'script' && module.value.type === 'rawscript') {
100
+ $currentEditor.editor.setCode(module.value.content);
101
+ }
102
+ else if ($currentEditor.type === 'iterator' && module.value.type === 'forloopflow') {
103
+ $currentEditor.editor.setCode(module.value.iterator.type === 'javascript' ? module.value.iterator.expr : '');
104
+ }
105
+ }
106
+ }
96
107
  }
97
108
  },
98
109
  showModuleDiff(id) {
@@ -1,16 +1,25 @@
1
1
  import type { ChatCompletionMessageParam, ChatCompletionMessageToolCall, ChatCompletionTool } from 'openai/resources/chat/completions.mjs';
2
2
  import type { ContextElement } from './context';
3
3
  import type { ExtendedOpenFlow } from '../../flows/types';
4
- export type DisplayMessage = {
5
- role: 'user' | 'assistant';
4
+ type BaseDisplayMessage = {
6
5
  content: string;
7
6
  contextElements?: ContextElement[];
8
7
  snapshot?: ExtendedOpenFlow;
9
- } | {
8
+ };
9
+ export type UserDisplayMessage = BaseDisplayMessage & {
10
+ role: 'user';
11
+ index: number;
12
+ error?: boolean;
13
+ };
14
+ export type ToolDisplayMessage = {
10
15
  role: 'tool';
11
16
  tool_call_id: string;
12
17
  content: string;
13
18
  };
19
+ export type AssistantDisplayMessage = BaseDisplayMessage & {
20
+ role: 'assistant';
21
+ };
22
+ export type DisplayMessage = UserDisplayMessage | ToolDisplayMessage | AssistantDisplayMessage;
14
23
  export declare function processToolCall<T>({ tools, toolCall, messages, helpers, toolCallbacks }: {
15
24
  tools: Tool<T>[];
16
25
  toolCall: ChatCompletionMessageToolCall;
@@ -35,3 +44,4 @@ export interface Tool<T> {
35
44
  export interface ToolCallbacks {
36
45
  setToolStatus: (id: string, content: string) => void;
37
46
  }
47
+ export {};
@@ -1,3 +1,12 @@
1
+ import type { OpenFlow } from '../gen';
2
+ import type { StateStore } from '../utils';
3
+ import type { Writable } from 'svelte/store';
4
+ import type { FlowState } from './flows/flowState';
5
+ import type { FlowWithDraftAndDraftTriggers, Trigger } from './triggers/utils';
6
+ import type { DiffDrawerI } from './diff_drawer';
7
+ import type { FlowBuilderWhitelabelCustomUi } from './custom_ui';
8
+ import type { ScheduleTrigger } from './triggers';
9
+ import type { stepState } from './stepHistoryLoader.svelte';
1
10
  export type FlowBuilderProps = {
2
11
  initialPath?: string;
3
12
  pathStoreInit?: string | undefined;
@@ -8,7 +17,7 @@ export type FlowBuilderProps = {
8
17
  flowStore: StateStore<OpenFlow>;
9
18
  flowStateStore: Writable<FlowState>;
10
19
  savedFlow?: FlowWithDraftAndDraftTriggers | undefined;
11
- diffDrawer?: DiffDrawer | undefined;
20
+ diffDrawer?: DiffDrawerI | undefined;
12
21
  customUi?: FlowBuilderWhitelabelCustomUi;
13
22
  disableAi?: boolean;
14
23
  disabledFlowInputs?: boolean;
@@ -207,6 +207,7 @@ async function updateStores() {
207
207
  return;
208
208
  }
209
209
  let newGraph = graph;
210
+ newGraph.nodes.sort((a, b) => b.id.localeCompare(a.id));
210
211
  nodes = layoutNodes(newGraph.nodes);
211
212
  edges = newGraph.edges;
212
213
  await tick();
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">import { onDestroy, onMount, tick, untrack } from 'svelte';
2
2
  import { AppService, FlowService, RawAppService, ScriptService } from '../../gen';
3
3
  import { clickOutside, isMac, scroll_into_view_if_needed_polyfill } from '../../utils';
4
- import { AlertTriangle, BoxesIcon, CalendarIcon, Code2Icon, Database, DollarSignIcon, HomeIcon, LayoutDashboardIcon, PlayIcon, Route, Search, SearchCode, Unplug } from 'lucide-svelte';
4
+ import { AlertTriangle, BoxesIcon, CalendarIcon, Code2Icon, Database, DollarSignIcon, HomeIcon, LayoutDashboardIcon, PlayIcon, Route, Search, SearchCode, Unplug, WandSparkles } from 'lucide-svelte';
5
5
  import Portal from '../Portal.svelte';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import ContentSearchInner from '../ContentSearchInner.svelte';
@@ -187,7 +187,7 @@ function removePrefix(str, prefix) {
187
187
  }
188
188
  let opts = {};
189
189
  let uf = new uFuzzy(opts);
190
- let defaultMenuItemLabels = defaultMenuItems.map((item) => item.label);
190
+ // let defaultMenuItemLabels = defaultMenuItems.map((item) => item.label)
191
191
  let defaultMenuItemAndHiddenLabels = defaultMenuItemsWithHidden.map((item) => item.label);
192
192
  let switchModeItemLabels = switchModeItems.map((item) => item.label);
193
193
  let askAiButton = $state();
@@ -234,7 +234,7 @@ async function handleSearch() {
234
234
  }
235
235
  if (tab === 'default') {
236
236
  if (searchTerm === '')
237
- itemMap['default'] = fuzzyFilter(searchTerm, defaultMenuItems, defaultMenuItemLabels);
237
+ itemMap['default'] = defaultMenuItems;
238
238
  else
239
239
  itemMap['default'] = fuzzyFilter(searchTerm, defaultMenuItemsWithHidden, defaultMenuItemAndHiddenLabels);
240
240
  if (combinedItems) {
@@ -299,9 +299,6 @@ async function handleKeydown(event) {
299
299
  }
300
300
  }
301
301
  }
302
- if ((itemMap[tab] ?? []).length === 0 && searchTerm.length > 0 && event.key === 'Enter') {
303
- askAiButton?.onClick();
304
- }
305
302
  }
306
303
  }
307
304
  //internal, should not be called outside of the handleSearch function
@@ -541,10 +538,14 @@ let indexMetadata = $state(undefined);
541
538
  <div class="overflow-y-auto relative {maxModalHeight(tab)}">
542
539
  {#if tab === 'default' || tab === 'switch-mode'}
543
540
  {@const items = (itemMap[tab] ?? []).filter((e) =>
544
- defaultMenuItemsWithHidden.includes(e)
541
+ defaultMenuItemsWithHidden.some((x) => e.search_id === x.search_id)
545
542
  )}
546
543
  {#if items.length > 0}
547
- <div class={tab === 'switch-mode' ? 'p-2' : 'p-2 border-b'}>
544
+ <div
545
+ class={tab === 'switch-mode' || itemMap[tab].length === items.length
546
+ ? 'p-2'
547
+ : 'p-2 border-b'}
548
+ >
548
549
  {#each items as el}
549
550
  <QuickMenuItem
550
551
  onselect={(shift) => el?.action(shift)}
@@ -562,8 +563,8 @@ let indexMetadata = $state(undefined);
562
563
  {/if}
563
564
 
564
565
  {#if tab === 'default'}
565
- <div class="p-2">
566
- {#if (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)).length > 0}
566
+ {#if (itemMap[tab] ?? []).filter((e) => (combinedItems ?? []).includes(e)).length > 0}
567
+ <div class="p-2">
567
568
  <div class="py-2 px-1 text-xs font-semibold text-tertiary">
568
569
  Flows/Scripts/Apps
569
570
  </div>
@@ -582,19 +583,28 @@ let indexMetadata = $state(undefined);
582
583
  bind:mouseMoved
583
584
  />
584
585
  {/each}
585
- {/if}
586
+ </div>
587
+ {/if}
586
588
 
587
- {#if (itemMap[tab] ?? []).length === 0}
589
+ {#if (itemMap[tab] ?? []).length === 0}
590
+ <div class="p-2">
591
+ <QuickMenuItem
592
+ onselect={() => {
593
+ askAiButton?.onClick()
594
+ }}
595
+ id={'ai:no-results-ask-ai'}
596
+ hovered={true}
597
+ label={`Try asking \`${searchTerm}\` to AI`}
598
+ icon={WandSparkles}
599
+ bind:mouseMoved
600
+ />
588
601
  <div class="flex w-full justify-center items-center">
589
602
  <div class="text-tertiary text-center">
590
- <div class="text-2xl font-bold"
591
- >Nothing found, ask the AI to find what you need!</div
592
- >
593
- <div class="text-sm">Tip: press `esc` to quickly clear the search bar</div>
603
+ <div class="pt-1 text-sm">Tip: press `esc` to quickly clear the search bar</div>
594
604
  </div>
595
605
  </div>
596
- {/if}
597
- </div>
606
+ </div>
607
+ {/if}
598
608
  {:else if tab === 'content'}
599
609
  <ContentSearchInner
600
610
  search={removePrefix(searchTerm, '#')}
@@ -21,7 +21,7 @@ export const OpenAPI = {
21
21
  PASSWORD: undefined,
22
22
  TOKEN: undefined,
23
23
  USERNAME: undefined,
24
- VERSION: '1.501.3',
24
+ VERSION: '1.502.2',
25
25
  WITH_CREDENTIALS: false,
26
26
  interceptors: {
27
27
  request: new Interceptors(),