windmill-components 1.700.1 → 1.700.3

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 (77) hide show
  1. package/dist/appPolicy/myFunction.es.js +1337 -0
  2. package/dist/sharedUtils/common.d.ts +2 -5
  3. package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +0 -2
  4. package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +3 -14
  5. package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +1 -1
  6. package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +1 -12
  7. package/dist/sharedUtils/components/apps/editor/component/components.d.ts +2 -68
  8. package/dist/sharedUtils/components/apps/inputType.d.ts +2 -4
  9. package/dist/sharedUtils/components/apps/sharedTypes.d.ts +0 -2
  10. package/dist/sharedUtils/components/dbTypes.d.ts +0 -3
  11. package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +1 -1
  12. package/dist/sharedUtils/components/raw_apps/utils.d.ts +1 -1
  13. package/dist/sharedUtils/components/triggers/utils.d.ts +3 -2
  14. package/dist/sharedUtils/gen/schemas.gen.d.ts +71 -915
  15. package/dist/sharedUtils/gen/services.gen.d.ts +23 -329
  16. package/dist/sharedUtils/gen/types.gen.d.ts +141 -1870
  17. package/dist/sharedUtils/hub.d.ts +0 -1
  18. package/dist/sharedUtils/jsr.json +5 -5
  19. package/dist/sharedUtils/lib.d.ts +1 -1
  20. package/dist/sharedUtils/lib.es.js +79 -241
  21. package/dist/sharedUtils/package.json +11 -11
  22. package/dist/sharedUtils/stores.d.ts +0 -1
  23. package/dist/sharedUtils/svelte5Utils.svelte.d.ts +1 -32
  24. package/dist/sharedUtils/utils.d.ts +4 -19
  25. package/package/components/DisplayResultControlBar.svelte +26 -11
  26. package/package/components/JobArgs.svelte +43 -24
  27. package/package/components/ShareModal.svelte.d.ts +1 -1
  28. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +0 -3
  29. package/package/components/copilot/CustomAIPrompts.svelte +3 -2
  30. package/package/components/copilot/chat/AIChatInput.svelte +2 -0
  31. package/package/components/copilot/chat/AIChatManager.svelte.js +52 -14
  32. package/package/components/copilot/chat/CreatedResourceActionDrawers.svelte +15 -0
  33. package/package/components/copilot/chat/ToolMessageActions.svelte +4 -2
  34. package/package/components/copilot/chat/app/core.js +2 -30
  35. package/package/components/copilot/chat/flow/core.js +13 -351
  36. package/package/components/copilot/chat/flow/editableFlowJson.d.ts +52 -0
  37. package/package/components/copilot/chat/flow/editableFlowJson.js +328 -0
  38. package/package/components/copilot/chat/flow/inlineScriptsUtils.js +2 -2
  39. package/package/components/copilot/chat/global/core.d.ts +5 -0
  40. package/package/components/copilot/chat/global/core.js +1739 -0
  41. package/package/components/copilot/chat/global/core.test.d.ts +1 -0
  42. package/package/components/copilot/chat/global/core.test.js +123 -0
  43. package/package/components/copilot/chat/global/deployRequests.d.ts +7 -0
  44. package/package/components/copilot/chat/global/deployRequests.js +76 -0
  45. package/package/components/copilot/chat/global/deployRequests.test.d.ts +1 -0
  46. package/package/components/copilot/chat/global/deployRequests.test.js +142 -0
  47. package/package/components/copilot/chat/global/draftStore.svelte.d.ts +55 -0
  48. package/package/components/copilot/chat/global/draftStore.svelte.js +78 -0
  49. package/package/components/copilot/chat/global/draftStore.test.d.ts +1 -0
  50. package/package/components/copilot/chat/global/draftStore.test.js +44 -0
  51. package/package/components/copilot/chat/global/gate.d.ts +1 -0
  52. package/package/components/copilot/chat/global/gate.js +27 -0
  53. package/package/components/copilot/chat/shared.d.ts +16 -2
  54. package/package/components/copilot/chat/shared.js +40 -0
  55. package/package/components/copilot/chat/workspaceToolsZod.gen.d.ts +28 -9
  56. package/package/components/copilot/chat/workspaceToolsZod.gen.js +19 -0
  57. package/package/components/raw_apps/templates.d.ts +77 -0
  58. package/package/components/raw_apps/templates.js +618 -0
  59. package/package/components/runs/runsFilter.d.ts +1 -1
  60. package/package/components/settings/AIPromptsModal.svelte +5 -2
  61. package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte.d.ts +1 -1
  62. package/package/gen/core/OpenAPI.js +1 -1
  63. package/package/gen/schemas.gen.d.ts +37 -355
  64. package/package/gen/schemas.gen.js +39 -359
  65. package/package/gen/services.gen.d.ts +4 -280
  66. package/package/gen/services.gen.js +7 -565
  67. package/package/gen/types.gen.d.ts +77 -1135
  68. package/package/system_prompts/index.d.ts +2 -0
  69. package/package/system_prompts/index.js +8 -0
  70. package/package/system_prompts/prompts.d.ts +2 -0
  71. package/package/system_prompts/prompts.js +381 -0
  72. package/package/utils/downloadFile.d.ts +6 -4
  73. package/package/utils/downloadFile.js +9 -15
  74. package/package/utils_deployable.d.ts +5 -318
  75. package/package.json +1 -1
  76. package/dist/sharedUtils/components/assets/lib.d.ts +0 -25
  77. package/dist/sharedUtils/components/icons/index.d.ts +0 -101
@@ -1,6 +1,4 @@
1
- import { type StateStore } from './utils';
2
- import * as runed from 'runed/kit';
3
- import type z from 'zod';
1
+ import type { StateStore } from './utils';
4
2
  export declare function withProps<Component, Props>(component: Component, props: Props): {
5
3
  component: Component;
6
4
  props: Props;
@@ -32,9 +30,6 @@ export type UsePromiseOptions = {
32
30
  loadInit?: boolean;
33
31
  clearValueOnRefresh?: boolean;
34
32
  };
35
- /**
36
- * @deprecated Use `resource` from `runed` instead
37
- */
38
33
  export declare function usePromise<T>(createPromise: () => Promise<T>, { loadInit, clearValueOnRefresh }?: UsePromiseOptions): UsePromiseResult<T>;
39
34
  /**
40
35
  * Generic change tracker class that monitors changes in state using deep equality comparison
@@ -52,29 +47,3 @@ export declare class ChangeTracker<T> {
52
47
  */
53
48
  track(value: T): boolean;
54
49
  }
55
- /**
56
- * This allows using async resources that only fetch missing data based on a set of keys.
57
- * It maintains a Record of the fetched data and only calls the fetcher for keys that
58
- * are not already present in the map.
59
- * The fetcher takes a record of keys to allow fetching multiple items in a single call.
60
- */
61
- export declare class MapResource<U, T> {
62
- private _cached;
63
- private _fetcherResource;
64
- constructor(getValues: () => Record<string, U>, fetcher: (toFetch: Record<string, U>) => Promise<Record<string, T>>);
65
- get current(): Record<string, T> | undefined;
66
- get loading(): boolean;
67
- get error(): Error | undefined;
68
- }
69
- export declare class ChangeOnDeepInequality<T> {
70
- private _cached;
71
- constructor(compute: () => T);
72
- get value(): T;
73
- }
74
- export declare function useSearchParams<S extends z.ZodType>(schema: S, options?: runed.SearchParamsOptions): runed.ReturnUseSearchParams<S>;
75
- export declare class StaleWhileLoading<T> {
76
- private _current;
77
- private _currentTimeout;
78
- constructor(getter: () => T, timeout?: number);
79
- get current(): T | undefined;
80
- }
@@ -38,7 +38,7 @@ export declare function validateUsername(username: string): string;
38
38
  export declare function parseQueryParams(url: string | undefined): Record<string, string>;
39
39
  export declare function displayDateOnly(dateString: string | Date | undefined): string;
40
40
  export declare function retrieveCommonWorkerPrefix(workerName: string): string;
41
- export declare function subtractDaysFromDateString(dateString: string | null, days: number): string | undefined;
41
+ export declare function subtractDaysFromDateString(dateString: string | undefined, days: number): string | undefined;
42
42
  export declare function displayDate(dateString: string | Date | undefined, displaySecond?: boolean, displayDate?: boolean): string;
43
43
  export declare function displayTime(dateString: string | Date | undefined): string;
44
44
  export declare function displaySize(sizeInBytes: number | undefined): string | undefined;
@@ -63,7 +63,6 @@ export declare function clickOutside(node: Node, options?: ClickOutsideOptions |
63
63
  destroy(): void;
64
64
  update(newOptions: ClickOutsideOptions | boolean): void;
65
65
  };
66
- export declare function undefinedIfEmpty(obj: any): any;
67
66
  export declare function pointerDownOutside(node: Node, options?: ClickOutsideOptions): {
68
67
  destroy(): void;
69
68
  update(newOptions: ClickOutsideOptions): void;
@@ -137,12 +136,6 @@ export declare function throttle<T>(func: (...args: any[]) => T, wait: number):
137
136
  export declare function isMac(): boolean;
138
137
  export declare function getModifierKey(): string;
139
138
  export declare function isValidHexColor(color: string): boolean;
140
- /**
141
- * Generates a text color with the same hue as the background but adjusted lightness for good contrast
142
- * @param backgroundColor Hex color string (e.g., "#FF0000" or "#F00")
143
- * @returns Hex color string with same hue but good contrast, or undefined if invalid
144
- */
145
- export declare function getContrastTextColor(backgroundColor: string | null | undefined): string | undefined;
146
139
  export declare function sortObject<T>(o: T & object): T;
147
140
  export declare function sortArray<T>(array: T[], compareFn?: (a: T, b: T) => number): T[];
148
141
  export declare function generateRandomString(len?: number): string;
@@ -187,7 +180,7 @@ export declare function getSchemaFromProperties(properties: {
187
180
  }): Schema;
188
181
  export declare function validateFileExtension(ext: string): boolean;
189
182
  export declare function isFlowPreview(job_kind: Job['job_kind'] | undefined): job_kind is "flowpreview" | "flownode";
190
- export declare function isNotFlow(job_kind: Job['job_kind'] | undefined): job_kind is "script" | "aiagent" | "identity" | "preview" | "dependencies" | "flowdependencies" | "appdependencies" | "script_hub" | "deploymentcallback" | "flowscript" | "appscript" | "unassigned_script" | "unassigned_flow" | "unassigned_singlestepflow" | undefined;
183
+ export declare function isNotFlow(job_kind: Job['job_kind'] | undefined): job_kind is "script" | "aiagent" | "identity" | "preview" | "dependencies" | "flowdependencies" | "appdependencies" | "script_hub" | "deploymentcallback" | "flowscript" | "appscript" | undefined;
191
184
  export declare function isScriptPreview(job_kind: Job['job_kind'] | undefined): job_kind is "preview" | "flowscript" | "appscript";
192
185
  export declare function conditionalMelt(node: HTMLElement, meltItem: AnyMeltElement | undefined): void | import("svelte/action").ActionReturn<undefined, Record<never, any>>;
193
186
  export type Item = {
@@ -202,7 +195,6 @@ export type Item = {
202
195
  hide?: boolean | undefined;
203
196
  extra?: Snippet;
204
197
  id?: string;
205
- tooltip?: string;
206
198
  };
207
199
  export declare function isObjectTooBig(obj: any): boolean;
208
200
  export declare function localeConcatAnd(items: string[]): string;
@@ -210,9 +202,11 @@ export declare function formatDate(dateString: string | undefined): string;
210
202
  export declare function formatDateShort(dateString: string | undefined): string;
211
203
  export declare function toJsonStr(result: any): string;
212
204
  export declare function getOS(): "Windows" | "macOS" | "iOS" | "Android" | "Linux" | "Unknown OS";
205
+ import { type ClassValue } from 'clsx';
213
206
  import type { Component, Snippet } from 'svelte';
214
207
  import { OpenAPIV2, type OpenAPI, type OpenAPIV3, type OpenAPIV3_1 } from 'openapi-types';
215
208
  import type { IPosition } from 'monaco-editor';
209
+ export declare function cn(...inputs: ClassValue[]): string;
216
210
  export type StateStore<T> = {
217
211
  val: T;
218
212
  };
@@ -247,7 +241,6 @@ export declare function wait(ms: number): Promise<unknown>;
247
241
  export declare function validateRetryConfig(retry: Retry | undefined): string | null;
248
242
  export type CssColor = keyof (typeof tokensFile)['tokens']['light'];
249
243
  import tokensFile from './assets/tokens/tokens.json';
250
- import { Package } from 'lucide-svelte';
251
244
  export declare function getCssColor(color: CssColor, { alpha, format }: {
252
245
  alpha?: number;
253
246
  format?: 'css-var' | 'hex-dark' | 'hex-light';
@@ -255,11 +248,3 @@ export declare function getCssColor(color: CssColor, { alpha, format }: {
255
248
  export type IconType = Component<{
256
249
  size?: number;
257
250
  }> | typeof import('lucide-svelte').Dot;
258
- export declare function getJobKindIcon(jobKind: Job['job_kind']): import("svelte/legacy").LegacyComponentType | typeof Package | undefined;
259
- export declare function chunkBy<T>(array: T[], getKey: (key: T) => string): T[][];
260
- export declare function getQueryStmtCountHeuristic(query: string): number;
261
- export declare function countChars(str: string, char: string): number;
262
- export declare function buildReactiveObj<T extends object>(fields: {
263
- [name in keyof T]: [() => T[name], (v: T[name]) => void];
264
- }): T;
265
- export declare function pick<T extends object, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K>;
@@ -2,6 +2,7 @@
2
2
  import { Download, InfoIcon, ClipboardCopy, Expand } from 'lucide-svelte';
3
3
  import Popover from './Popover.svelte';
4
4
  import { copyToClipboard } from '../utils';
5
+ import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
5
6
  import { createEventDispatcher } from 'svelte';
6
7
  let { customUi = undefined, filename = undefined, workspaceId = undefined, jobId = undefined, nodeId = undefined, base, result, disableTooltips = false } = $props();
7
8
  const dispatch = createEventDispatcher();
@@ -13,21 +14,35 @@ function toJsonStr(result) {
13
14
  return 'error stringifying object: ' + e.toString();
14
15
  }
15
16
  }
17
+ let resultApiPath = $derived(workspaceId && jobId
18
+ ? nodeId
19
+ ? `/w/${workspaceId}/jobs/result_by_id/${jobId}/${nodeId}`
20
+ : `/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
21
+ : undefined);
22
+ let downloadName = $derived(`${filename ?? 'result'}.json`);
16
23
  </script>
17
24
 
18
25
  <div class={twMerge('flex flex-row gap-2.5 z-10 text-primary -mt-1 items-center')}>
19
26
  {#if customUi?.disableDownload !== true}
20
- <a
21
- download="{filename ?? 'result'}.json"
22
- class="text-current"
23
- href={workspaceId && jobId
24
- ? nodeId
25
- ? `${base}/api/w/${workspaceId}/jobs/result_by_id/${jobId}/${nodeId}`
26
- : `${base}/api/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
27
- : `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`}
28
- >
29
- <Download size={14} />
30
- </a>
27
+ {#if resultApiPath && shouldDownloadViaClient()}
28
+ <button
29
+ class="text-current"
30
+ onclick={() => downloadViaClient(resultApiPath!, downloadName)}
31
+ aria-label="Download result"
32
+ >
33
+ <Download size={14} />
34
+ </button>
35
+ {:else}
36
+ <a
37
+ download={downloadName}
38
+ class="text-current"
39
+ href={resultApiPath
40
+ ? `${base}/api${resultApiPath}`
41
+ : `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`}
42
+ >
43
+ <Download size={14} />
44
+ </a>
45
+ {/if}
31
46
  {/if}
32
47
  {#if disableTooltips !== true}
33
48
  <Popover documentationLink="https://www.windmill.dev/docs/core_concepts/rich_display_rendering">
@@ -11,10 +11,14 @@ import Row from './table/Row.svelte';
11
11
  import HighlightTheme from './HighlightTheme.svelte';
12
12
  import { deepEqual } from 'fast-equals';
13
13
  import { isWindmillTooBigObject } from './job_args';
14
+ import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
14
15
  let { id = undefined, args, argLabel = undefined, workspace = undefined } = $props();
15
16
  let jsonViewer = $state();
16
17
  let runLocally = $state();
17
18
  let jsonStr = $state('');
19
+ const argsDownloadName = 'windmill-args.json';
20
+ let argsApiPath = $derived(id && workspace ? `/w/${workspace}/jobs_u/get_args/${id}` : undefined);
21
+ let argsDataHref = $derived(`data:text/json;charset=utf-8,${encodeURIComponent(jsonStr)}`);
18
22
  function pythonCode() {
19
23
  return `
20
24
  if __name__ == "__main__":
@@ -47,10 +51,12 @@ ${Object.entries(args)
47
51
  {#if args && typeof args === 'object' && deepEqual( Object.keys(args ?? {}), ['reason'] ) && args['reason'] == 'PREPROCESSOR_ARGS_ARE_DISCARDED'}
48
52
  Preprocessor args are discarded
49
53
  {:else if id && workspace && args && typeof args === 'object' && deepEqual( Object.keys(args ?? {}), ['reason'] ) && args['reason'] == 'WINDMILL_TOO_BIG'}
50
- The args are too big in size to be able to fetch alongside job. Please <a
51
- href="/api/w/{workspace}/jobs_u/get_args/{id}"
52
- target="_blank">download the JSON file to view them</a
53
- >.
54
+ The args are too big in size to be able to fetch alongside job. Please {#if shouldDownloadViaClient()}<button
55
+ class="text-blue-500 hover:underline"
56
+ onclick={() => downloadViaClient(argsApiPath!, argsDownloadName)}
57
+ >download the JSON file to view them</button
58
+ >{:else}<a href="/api{argsApiPath}" target="_blank">download the JSON file to view them</a
59
+ >{/if}.
54
60
  {:else}
55
61
  <div class="relative">
56
62
  <DataTable size="sm" containerClass="bg-surface-tertiary">
@@ -107,17 +113,26 @@ ${Object.entries(args)
107
113
  <Drawer bind:this={jsonViewer} size="900px">
108
114
  <DrawerContent title="Expanded Args" on:close={jsonViewer.closeDrawer}>
109
115
  {#snippet actions()}
110
- <Button
111
- download="windmill-args.json"
112
- href={id && workspace
113
- ? `/api/w/${workspace}/jobs_u/get_args/${id}`
114
- : `data:text/json;charset=utf-8,${encodeURIComponent(jsonStr)}`}
115
- startIcon={{ icon: Download }}
116
- size="xs"
117
- color="light"
118
- >
119
- Download
120
- </Button>
116
+ {#if argsApiPath && shouldDownloadViaClient()}
117
+ <Button
118
+ on:click={() => downloadViaClient(argsApiPath!, argsDownloadName)}
119
+ startIcon={{ icon: Download }}
120
+ size="xs"
121
+ color="light"
122
+ >
123
+ Download
124
+ </Button>
125
+ {:else}
126
+ <Button
127
+ download={argsDownloadName}
128
+ href={argsApiPath ? `/api${argsApiPath}` : argsDataHref}
129
+ startIcon={{ icon: Download }}
130
+ size="xs"
131
+ color="light"
132
+ >
133
+ Download
134
+ </Button>
135
+ {/if}
121
136
  <Button
122
137
  on:click={() => runLocally?.openDrawer()}
123
138
  color="light"
@@ -137,15 +152,19 @@ ${Object.entries(args)
137
152
  {/snippet}
138
153
  {#if jsonStr.length > 100000 || (id && workspace && args && isWindmillTooBigObject(args))}
139
154
  <div class="text-sm mb-2 text-primary">
140
- <a
141
- download="windmill-args.json"
142
- href={id && workspace
143
- ? `/api/w/${workspace}/jobs_u/get_args/${id}`
144
- : `data:text/json;charset=utf-8,${encodeURIComponent(jsonStr)}`}
145
- >
146
- JSON is too large to be displayed in full.
147
- </a></div
148
- >
155
+ {#if argsApiPath && shouldDownloadViaClient()}
156
+ <button
157
+ class="underline"
158
+ onclick={() => downloadViaClient(argsApiPath!, argsDownloadName)}
159
+ >
160
+ JSON is too large to be displayed in full.
161
+ </button>
162
+ {:else}
163
+ <a download={argsDownloadName} href={argsApiPath ? `/api${argsApiPath}` : argsDataHref}>
164
+ JSON is too large to be displayed in full.
165
+ </a>
166
+ {/if}
167
+ </div>
149
168
  {:else}
150
169
  <Highlight language={json} code={jsonStr.replace(/\\n/g, '\n')} />
151
170
  {/if}
@@ -16,7 +16,7 @@ declare const ShareModal: $$__sveltets_2_IsomorphicComponent<Record<string, neve
16
16
  } & {
17
17
  [evt: string]: CustomEvent<any>;
18
18
  }, {}, {
19
- openDrawer: (newPath: string, kind_l: "resource" | "volume" | "script" | "flow" | "app" | "variable" | "schedule" | "raw_app" | "http_trigger" | "websocket_trigger" | "kafka_trigger" | "nats_trigger" | "postgres_trigger" | "mqtt_trigger" | "sqs_trigger" | "gcp_trigger" | "azure_trigger" | "email_trigger" | "group_", isOwnerOverride?: boolean) => Promise<void>;
19
+ openDrawer: (newPath: string, kind_l: "resource" | "volume" | "script" | "flow" | "app" | "variable" | "schedule" | "raw_app" | "group_" | "http_trigger" | "websocket_trigger" | "kafka_trigger" | "nats_trigger" | "postgres_trigger" | "mqtt_trigger" | "gcp_trigger" | "sqs_trigger" | "email_trigger" | "azure_trigger", isOwnerOverride?: boolean) => Promise<void>;
20
20
  }, "">;
21
21
  type ShareModal = InstanceType<typeof ShareModal>;
22
22
  export default ShareModal;
@@ -84,7 +84,6 @@ declare const RunnableComponent: $$__sveltets_2_IsomorphicComponent<Props, {
84
84
  path?: string;
85
85
  lock?: string;
86
86
  cache_ttl?: number;
87
- tag?: string;
88
87
  };
89
88
  id?: number;
90
89
  force_viewer_static_fields?: {
@@ -94,8 +93,6 @@ declare const RunnableComponent: $$__sveltets_2_IsomorphicComponent<Props, {
94
93
  [key: string]: unknown;
95
94
  };
96
95
  force_viewer_allow_user_resources?: Array<(string)>;
97
- force_viewer_sensitive_inputs?: Array<(string)>;
98
- force_viewer_delete_after_secs?: number;
99
96
  run_query_params?: {
100
97
  [key: string]: unknown;
101
98
  };
@@ -1,4 +1,4 @@
1
- <script lang="ts">import { AIMode } from './chat/AIChatManager.svelte';
1
+ <script lang="ts">import { AIMode, getVisibleAIModes } from './chat/AIChatManager.svelte';
2
2
  import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte';
3
3
  import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte';
4
4
  import Label from '../Label.svelte';
@@ -6,6 +6,7 @@ import autosize from '../../autosize';
6
6
  const MAX_CUSTOM_PROMPT_LENGTH = 5000;
7
7
  let { customPrompts = $bindable(), title, description } = $props();
8
8
  let selectedAiMode = $state(AIMode.ASK);
9
+ let visibleModes = $derived(getVisibleAIModes());
9
10
  </script>
10
11
 
11
12
  <div class="flex flex-col gap-2">
@@ -17,7 +18,7 @@ let selectedAiMode = $state(AIMode.ASK);
17
18
  <Label label="AI Mode">
18
19
  <ToggleButtonGroup bind:selected={selectedAiMode}>
19
20
  {#snippet children({ item })}
20
- {#each Object.values(AIMode) as mode}
21
+ {#each visibleModes as mode (mode)}
21
22
  <div class="relative">
22
23
  <ToggleButton
23
24
  value={mode}
@@ -30,6 +30,8 @@ const modePlaceholder = $derived.by(() => {
30
30
  return 'Navigate Windmill UI...';
31
31
  case AIMode.API:
32
32
  return 'Make API calls...';
33
+ case AIMode.GLOBAL:
34
+ return 'Work across workspace items...';
33
35
  case AIMode.ASK:
34
36
  return 'Ask questions about Windmill...';
35
37
  default:
@@ -22,6 +22,8 @@ import { createAppBackendRunnableContextElement, createAppFrontendFileContextEle
22
22
  import { prepareApiSystemMessage, prepareApiUserMessage } from './api/core';
23
23
  import { runChatLoop } from './chatLoop';
24
24
  import { getCurrentModel, tryGetCurrentModel, getCombinedCustomPrompt } from '../../../aiStore';
25
+ import { globalTools, prepareGlobalSystemMessage, prepareGlobalUserMessage } from './global/core';
26
+ import { isGlobalAiEnabled } from './global/gate';
25
27
  // If the estimated token usage is greater than the model context window - the threshold, we delete the oldest message
26
28
  const MAX_TOKENS_THRESHOLD_PERCENTAGE = 0.05;
27
29
  const MAX_TOKENS_HARD_LIMIT = 5000;
@@ -32,8 +34,19 @@ export var AIMode;
32
34
  AIMode["APP"] = "app";
33
35
  AIMode["NAVIGATOR"] = "navigator";
34
36
  AIMode["API"] = "API";
37
+ AIMode["GLOBAL"] = "global";
35
38
  AIMode["ASK"] = "ask";
36
39
  })(AIMode || (AIMode = {}));
40
+ const ALL_AI_MODES = Object.values(AIMode);
41
+ export function isAIMode(mode) {
42
+ return ALL_AI_MODES.includes(mode);
43
+ }
44
+ export function isAIModeVisible(mode) {
45
+ return mode !== AIMode.GLOBAL || isGlobalAiEnabled();
46
+ }
47
+ export function getVisibleAIModes() {
48
+ return ALL_AI_MODES.filter(isAIModeVisible);
49
+ }
37
50
  function isWorkspacePath(path) {
38
51
  return path?.startsWith('f/') === true || path?.startsWith('u/') === true;
39
52
  }
@@ -82,7 +95,9 @@ class AIChatManager {
82
95
  app: this.appAiChatHelpers !== undefined,
83
96
  navigator: true,
84
97
  ask: true,
85
- API: true
98
+ API: true,
99
+ // Dev-only gate. See `./global/gate.ts` for how to enable.
100
+ global: isAIModeVisible(AIMode.GLOBAL)
86
101
  });
87
102
  open = $derived(chatState.size > 0);
88
103
  checkTokenUsageOverLimit = (messages) => {
@@ -182,6 +197,8 @@ class AIChatManager {
182
197
  };
183
198
  };
184
199
  changeMode(mode, pendingPrompt, options) {
200
+ if (!isAIModeVisible(mode))
201
+ return;
185
202
  if (mode === AIMode.SCRIPT && !tryGetCurrentModel())
186
203
  return;
187
204
  this.mode = mode;
@@ -251,6 +268,12 @@ class AIChatManager {
251
268
  this.tools = [...this.apiTools];
252
269
  this.helpers = {};
253
270
  }
271
+ else if (mode === AIMode.GLOBAL) {
272
+ const customPrompt = getCombinedCustomPrompt(mode);
273
+ this.systemMessage = prepareGlobalSystemMessage(customPrompt);
274
+ this.tools = [...globalTools];
275
+ this.helpers = {};
276
+ }
254
277
  else if (mode === AIMode.APP) {
255
278
  const customPrompt = getCombinedCustomPrompt(mode);
256
279
  this.systemMessage = prepareAppSystemMessage(customPrompt);
@@ -264,14 +287,24 @@ class AIChatManager {
264
287
  type: 'function',
265
288
  function: {
266
289
  name: 'change_mode',
267
- description: 'Change the AI mode to the one specified. Script mode is used to create scripts. Flow mode is used to create flows. Navigator mode is used to navigate the application and help the user find what they are looking for. API mode is used to make API calls to the Windmill backend.',
290
+ description: 'Change the AI mode to the one specified. Script mode is used to create scripts. Flow mode is used to create flows.' +
291
+ (isGlobalAiEnabled()
292
+ ? ' Global mode is used to inspect workspace scripts and flows and create draft changes.'
293
+ : '') +
294
+ ' Navigator mode is used to navigate the application and help the user find what they are looking for. API mode is used to make API calls to the Windmill backend.',
268
295
  parameters: {
269
296
  type: 'object',
270
297
  properties: {
271
298
  mode: {
272
299
  type: 'string',
273
300
  description: 'The mode to change to',
274
- enum: ['script', 'flow', 'navigator', 'API']
301
+ enum: [
302
+ 'script',
303
+ 'flow',
304
+ ...(isGlobalAiEnabled() ? ['global'] : []),
305
+ 'navigator',
306
+ 'API'
307
+ ]
275
308
  },
276
309
  pendingPrompt: {
277
310
  type: 'string',
@@ -284,6 +317,9 @@ class AIChatManager {
284
317
  }
285
318
  },
286
319
  fn: async ({ args, toolId, toolCallbacks }) => {
320
+ if (!isAIMode(args.mode) || !isAIModeVisible(args.mode)) {
321
+ throw new Error(`AI mode "${args.mode}" is not enabled`);
322
+ }
287
323
  toolCallbacks.setToolStatus(toolId, { content: 'Switching to ' + args.mode + ' mode...' });
288
324
  this.changeMode(args.mode, args.pendingPrompt, {
289
325
  closeScriptSettings: true
@@ -393,6 +429,9 @@ class AIChatManager {
393
429
  else if (this.mode === AIMode.NAVIGATOR) {
394
430
  return prepareNavigatorUserMessage(pendingPrompt);
395
431
  }
432
+ else if (this.mode === AIMode.GLOBAL) {
433
+ return prepareGlobalUserMessage(pendingPrompt);
434
+ }
396
435
  return undefined;
397
436
  },
398
437
  onBeforeIteration: async (tools) => {
@@ -493,18 +532,14 @@ class AIChatManager {
493
532
  }
494
533
  };
495
534
  sendRequest = async (options = {}) => {
496
- if (options.mode) {
497
- this.changeMode(options.mode, undefined, {
498
- lang: options.lang,
499
- isPreprocessor: options.isPreprocessor
500
- });
501
- }
502
- else {
503
- this.changeMode(this.mode, undefined, {
504
- lang: options.lang,
505
- isPreprocessor: options.isPreprocessor
506
- });
535
+ const requestedMode = options.mode ?? this.mode;
536
+ if (!isAIModeVisible(requestedMode)) {
537
+ return;
507
538
  }
539
+ this.changeMode(requestedMode, undefined, {
540
+ lang: options.lang,
541
+ isPreprocessor: options.isPreprocessor
542
+ });
508
543
  if (options.instructions) {
509
544
  this.instructions = options.instructions;
510
545
  }
@@ -579,6 +614,9 @@ class AIChatManager {
579
614
  case AIMode.API:
580
615
  userMessage = prepareApiUserMessage(oldInstructions);
581
616
  break;
617
+ case AIMode.GLOBAL:
618
+ userMessage = prepareGlobalUserMessage(oldInstructions);
619
+ break;
582
620
  case AIMode.APP:
583
621
  userMessage = prepareAppUserMessage(oldInstructions, this.appAiChatHelpers?.getSelectedContext(), oldSelectedContext);
584
622
  break;
@@ -2,12 +2,16 @@
2
2
  import { Drawer } from '../../common';
3
3
  import DrawerContent from '../../common/drawer/DrawerContent.svelte';
4
4
  import { Loader2 } from 'lucide-svelte';
5
+ import ResourceEditorDrawer from '../../ResourceEditorDrawer.svelte';
6
+ import VariableEditor from '../../VariableEditor.svelte';
5
7
  import { registerToolDisplayActionHandler } from './createdResourceActions.svelte';
6
8
  const DRAWER_SIZE = '800px';
7
9
  let drawer = $state(undefined);
8
10
  let editor = $state(undefined);
9
11
  let activeDrawer = $state(undefined);
10
12
  let nextActiveDrawerId = 0;
13
+ let resourceEditorDrawer = $state(undefined);
14
+ let variableEditor = $state(undefined);
11
15
  const drawerConfigs = {
12
16
  schedule: {
13
17
  label: 'schedule',
@@ -84,6 +88,14 @@ async function openCreatedResource(action) {
84
88
  if (action.type !== 'open_created_resource') {
85
89
  return;
86
90
  }
91
+ if (action.resource === 'resource') {
92
+ await resourceEditorDrawer?.initEdit(action.path);
93
+ return;
94
+ }
95
+ if (action.resource === 'variable') {
96
+ await variableEditor?.editVariable(action.path);
97
+ return;
98
+ }
87
99
  const key = action.resource === 'schedule' ? 'schedule' : action.triggerKind;
88
100
  if (!key) {
89
101
  throw new Error('Missing trigger kind');
@@ -127,3 +139,6 @@ onDestroy(() => {
127
139
  {/if}
128
140
  </DrawerContent>
129
141
  </Drawer>
142
+
143
+ <ResourceEditorDrawer bind:this={resourceEditorDrawer} />
144
+ <VariableEditor bind:this={variableEditor} />
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">import { Button } from '../../common';
2
- import { Calendar, Database, Route, SquarePen, Unplug, Webhook } from 'lucide-svelte';
2
+ import { Calendar, Database, KeyRound, Package, Route, SquarePen, Unplug, Webhook } from 'lucide-svelte';
3
3
  import AwsIcon from '../../icons/AwsIcon.svelte';
4
4
  import AzureIcon from '../../icons/AzureIcon.svelte';
5
5
  import GoogleCloudIcon from '../../icons/GoogleCloudIcon.svelte';
@@ -11,6 +11,8 @@ let { actions } = $props();
11
11
  let runningActionId = $state(undefined);
12
12
  const actionCardConfigs = {
13
13
  schedule: { title: 'Schedule', icon: Calendar },
14
+ resource: { title: 'Resource', icon: Package },
15
+ variable: { title: 'Variable', icon: KeyRound },
14
16
  http: { title: 'HTTP trigger', icon: Route },
15
17
  websocket: { title: 'WebSocket trigger', icon: Unplug },
16
18
  postgres: { title: 'Postgres trigger', icon: Database },
@@ -22,7 +24,7 @@ const actionCardConfigs = {
22
24
  azure: { title: 'Azure Event Grid trigger', icon: AzureIcon }
23
25
  };
24
26
  function getActionCardConfig(action) {
25
- const key = action.resource === 'schedule' ? 'schedule' : action.triggerKind;
27
+ const key = action.resource === 'trigger' ? action.triggerKind : action.resource;
26
28
  return key
27
29
  ? actionCardConfigs[key]
28
30
  : { title: action.label.replace(/^Open\s+/i, ''), icon: Webhook };
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { createSearchHubScriptsTool, createToolDef, createSearchWorkspaceTool, createGetRunnableDetailsTool } from '../shared';
2
+ import { createSearchHubScriptsTool, createToolDef, createSearchWorkspaceTool, createGetRunnableDetailsTool, findAndReplace } from '../shared';
3
3
  import { aiChatManager } from '../AIChatManager.svelte';
4
4
  import { formatAppDatatableContextTitle } from '../context';
5
5
  // ============= Utility =============
@@ -8,25 +8,6 @@ const memo = (factory) => {
8
8
  let cached;
9
9
  return () => (cached ??= factory());
10
10
  };
11
- function countExactMatches(content, search) {
12
- if (search.length === 0) {
13
- return 0;
14
- }
15
- let count = 0;
16
- let index = 0;
17
- while ((index = content.indexOf(search, index)) !== -1) {
18
- count += 1;
19
- index += search.length;
20
- }
21
- return count;
22
- }
23
- function replaceFirstExactMatch(content, search, replace) {
24
- const index = content.indexOf(search);
25
- if (index === -1) {
26
- return content;
27
- }
28
- return content.slice(0, index) + replace + content.slice(index + search.length);
29
- }
30
11
  function resolveAppPatchTarget(rawPath) {
31
12
  const trimmedPath = rawPath.trim();
32
13
  const backendMatch = trimmedPath.match(/^backend\/([^/]+)\/main\.(ts|py)$/);
@@ -309,16 +290,7 @@ export const getAppTools = memo(() => [
309
290
  }
310
291
  currentContent = backendRunnable.inlineScript.content ?? '';
311
292
  }
312
- const matchCount = countExactMatches(currentContent, oldString);
313
- if (matchCount === 0) {
314
- throw new Error('old_string was not found in the current file content.');
315
- }
316
- if (!replaceAll && matchCount !== 1) {
317
- throw new Error(`old_string matched ${matchCount} locations. Make it more specific or set replace_all to true.`);
318
- }
319
- const updatedContent = replaceAll
320
- ? currentContent.split(oldString).join(newString)
321
- : replaceFirstExactMatch(currentContent, oldString, newString);
293
+ const updatedContent = findAndReplace(currentContent, oldString, newString, replaceAll, 'current file content');
322
294
  toolCallbacks.setToolStatus(toolId, {
323
295
  content: `Patching '${target.path}'...`
324
296
  });