windmill-components 1.531.1 → 1.532.0

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 (45) hide show
  1. package/package/components/ArgInput.svelte +44 -1
  2. package/package/components/EditableSchemaForm.svelte +27 -18
  3. package/package/components/EditableSchemaForm.svelte.d.ts +1 -0
  4. package/package/components/Editor.svelte +2 -2
  5. package/package/components/Editor.svelte.d.ts +1 -0
  6. package/package/components/EditorBar.svelte +2 -2
  7. package/package/components/EditorBar.svelte.d.ts +1 -0
  8. package/package/components/FieldHeader.svelte +1 -1
  9. package/package/components/ModuleTest.svelte +4 -1
  10. package/package/components/ResourceEditor.svelte +4 -0
  11. package/package/components/ScriptEditor.svelte +1 -0
  12. package/package/components/StringTypeNarrowing.svelte.d.ts +1 -1
  13. package/package/components/copilot/FlowInlineScriptAIButton.svelte +4 -2
  14. package/package/components/copilot/FlowInlineScriptAIButton.svelte.d.ts +4 -1
  15. package/package/components/copilot/autocomplete/Autocompletor.js +0 -2
  16. package/package/components/copilot/chat/AIChat.svelte +2 -4
  17. package/package/components/copilot/chat/AIChatInput.svelte +4 -3
  18. package/package/components/copilot/chat/AIChatManager.svelte.js +21 -9
  19. package/package/components/copilot/chat/AvailableContextList.svelte +115 -24
  20. package/package/components/copilot/chat/AvailableContextList.svelte.d.ts +1 -0
  21. package/package/components/copilot/chat/ContextElementBadge.svelte +31 -15
  22. package/package/components/copilot/chat/ContextElementBadge.svelte.d.ts +5 -20
  23. package/package/components/copilot/chat/ContextManager.svelte.d.ts +15 -2
  24. package/package/components/copilot/chat/ContextManager.svelte.js +102 -15
  25. package/package/components/copilot/chat/ContextTextarea.svelte +1 -1
  26. package/package/components/copilot/chat/context.d.ts +14 -1
  27. package/package/components/copilot/chat/context.js +1 -0
  28. package/package/components/copilot/chat/flow/FlowAIChat.svelte +1 -1
  29. package/package/components/copilot/chat/flow/core.d.ts +2 -1
  30. package/package/components/copilot/chat/flow/core.js +52 -18
  31. package/package/components/copilot/chat/script/core.d.ts +2 -2
  32. package/package/components/copilot/chat/script/core.js +51 -122
  33. package/package/components/copilot/chat/shared.d.ts +13 -2
  34. package/package/components/copilot/chat/shared.js +155 -4
  35. package/package/components/flows/FlowEditor.svelte +12 -0
  36. package/package/components/flows/FlowModuleIcon.svelte +39 -0
  37. package/package/components/flows/FlowModuleIcon.svelte.d.ts +10 -0
  38. package/package/components/flows/content/FlowModuleComponent.svelte +2 -0
  39. package/package/components/flows/map/MapItem.svelte +8 -39
  40. package/package/components/schema/FlowPropertyEditor.svelte +75 -17
  41. package/package/components/schema/PropertyEditor.svelte.d.ts +1 -1
  42. package/package/components/schema/jsonSchemaResource.svelte.d.ts +2 -0
  43. package/package/components/schema/jsonSchemaResource.svelte.js +40 -0
  44. package/package/utils.d.ts +1 -1
  45. package/package.json +13 -13
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">import { preventDefault, stopPropagation, createBubbler } from 'svelte/legacy';
2
2
  const bubble = createBubbler();
3
- import { setInputCat as computeInputCat, debounce, emptyString, getSchemaFromProperties } from '../utils';
3
+ import { setInputCat as computeInputCat, debounce, emptySchema, emptyString, getSchemaFromProperties } from '../utils';
4
4
  import { DollarSign, Plus, X, Check, Loader2, ExternalLink } from 'lucide-svelte';
5
5
  import { createEventDispatcher, onDestroy, onMount, tick, untrack } from 'svelte';
6
6
  import { fade } from 'svelte/transition';
@@ -28,6 +28,8 @@ import MultiSelect from './select/MultiSelect.svelte';
28
28
  import { safeSelectItems } from './select/utils.svelte';
29
29
  import S3ArgInput from './common/fileUpload/S3ArgInput.svelte';
30
30
  import { base } from '../base';
31
+ import { workspaceStore } from '../stores';
32
+ import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte';
31
33
  let { label = '', value = $bindable(), defaultValue = $bindable(undefined), description = $bindable(undefined), format = $bindable(undefined), contentEncoding = undefined, type = undefined, oneOf = $bindable(undefined), required = false, pattern = $bindable(undefined), valid = $bindable(undefined), enum_ = $bindable(undefined), disabled = false, itemsType = $bindable(undefined), displayHeader = true, properties = $bindable(undefined), nestedRequired = undefined, autofocus = null, compact = false, password = false, pickForField = $bindable(undefined), variableEditor = undefined, itemPicker = undefined, noMargin = false, extra = {}, minW = true, prettifyHeader = false, resourceTypes, disablePortal = false, showSchemaExplorer = false, simpleTooltip = undefined, customErrorMessage = undefined, onlyMaskPassword = false, nullable = false, title = $bindable(undefined), placeholder = $bindable(undefined), order = $bindable(undefined), editor = $bindable(undefined), orderEditable = false, shouldDispatchChanges = false, noDefaultOnSelectFirst = false, helperScript = undefined, otherArgs = {}, lightHeader = false, diffStatus = undefined, hideNested = false, nestedParent = undefined, nestedClasses = '', displayType = true, css = undefined, appPath = undefined, computeS3ForceViewerPolicies = undefined, workspace = undefined, actions } = $props();
32
34
  $effect(() => {
33
35
  if (description == undefined) {
@@ -488,6 +490,47 @@ onDestroy(() => {
488
490
  {appPath}
489
491
  {computeS3ForceViewerPolicies}
490
492
  />
493
+ {:else if inputCat == 'object' && format == 'json-schema'}
494
+ {#await import('./EditableSchemaForm.svelte')}
495
+ <Loader2 class="animate-spin" />
496
+ {:then Module}
497
+ <Module.default
498
+ bind:schema={
499
+ () =>
500
+ value && typeof value === 'object' && !Array.isArray(value) ? value : emptySchema(),
501
+ (v) => {
502
+ value = v
503
+ }
504
+ }
505
+ isFlowInput
506
+ editTab="inputEditor"
507
+ noPreview
508
+ addPropertyInEditorTab
509
+ />
510
+ {/await}
511
+ {:else if inputCat == 'object' && format?.startsWith('jsonschema-')}
512
+ {#await getJsonSchemaFromResource(format.substring('jsonschema-'.length), workspace ?? $workspaceStore ?? '')}
513
+ <Loader2 class="animate-spin" />
514
+ {:then schema}
515
+ {#if !schema || !schema.properties}
516
+ {#await import('./JsonEditor.svelte')}
517
+ <Loader2 class="animate-spin" />
518
+ {:then Module}
519
+ <Module.default code={JSON.stringify(value, null, 2)} bind:value />
520
+ {/await}
521
+ {:else}
522
+ <div class="py-4 pr-2 pl-6 border rounded-md w-full">
523
+ <SchemaForm
524
+ {onlyMaskPassword}
525
+ {disablePortal}
526
+ {disabled}
527
+ {prettifyHeader}
528
+ {schema}
529
+ bind:args={value}
530
+ />
531
+ </div>
532
+ {/if}
533
+ {/await}
491
534
  {:else if inputCat == 'list' && !isListJson}
492
535
  <div class="w-full flex gap-4">
493
536
  <div class="w-full">
@@ -24,9 +24,10 @@ import { deepEqual } from 'fast-equals';
24
24
  import { tweened } from 'svelte/motion';
25
25
  import Section from './Section.svelte';
26
26
  import Editor from './Editor.svelte';
27
+ import AddPropertyV2 from './schema/AddPropertyV2.svelte';
27
28
  // export let openEditTab: () => void = () => {}
28
29
  const dispatch = createEventDispatcher();
29
- let { schema = $bindable(), hiddenArgs = [], args = $bindable(undefined), shouldHideNoInputs = false, noVariablePicker = false, flexWrap = false, uiOnly = false, isFlowInput = false, noPreview = false, jsonEnabled = true, isAppInput = false, displayWebhookWarning = false, onlyMaskPassword = false, dndType = undefined, editTab, previewSchema = undefined, editPanelInitialSize = undefined, editPanelSize = $bindable(0), diff = {}, disableDnd = false, shouldDispatchChanges = false, isValid = $bindable(true), customUi = undefined, pannelExtraButtonWidth = 0, class: clazz = '', dynSelectCode = $bindable(), dynSelectLang = $bindable(), showDynSelectOpt = false, openEditTab, addProperty, runButton, extraTab } = $props();
30
+ let { schema = $bindable(), hiddenArgs = [], args = $bindable(undefined), shouldHideNoInputs = false, noVariablePicker = false, flexWrap = false, uiOnly = false, isFlowInput = false, noPreview = false, jsonEnabled = true, isAppInput = false, displayWebhookWarning = false, onlyMaskPassword = false, dndType = undefined, editTab, previewSchema = undefined, editPanelInitialSize = undefined, editPanelSize = $bindable(0), diff = {}, disableDnd = false, shouldDispatchChanges = false, isValid = $bindable(true), customUi = undefined, pannelExtraButtonWidth = 0, class: clazz = '', dynSelectCode = $bindable(), dynSelectLang = $bindable(), showDynSelectOpt = false, addPropertyInEditorTab = false, openEditTab, addProperty, runButton, extraTab } = $props();
30
31
  $effect.pre(() => {
31
32
  if (args == undefined) {
32
33
  args = {};
@@ -390,22 +391,31 @@ function updateDynSelectCode(functionName, lang = 'bun') {
390
391
  {:else}
391
392
  <!-- WIP -->
392
393
  {#if jsonEnabled && customUi?.jsonOnly != true}
393
- <div class="w-full p-3 flex justify-end">
394
- <Toggle
395
- bind:checked={jsonView}
396
- label="JSON View"
397
- size="xs"
398
- options={{
399
- right: 'JSON editor',
400
- rightTooltip:
401
- 'Arguments can be edited either using the wizard, or by editing their JSON Schema.'
402
- }}
403
- lightMode
404
- on:change={() => {
405
- schemaString = JSON.stringify(schema, null, '\t')
406
- editor?.setCode(schemaString)
407
- }}
408
- />
394
+ <div class="w-full p-3 flex gap-4 justify-end items-center">
395
+ {#if addPropertyInEditorTab}
396
+ <AddPropertyV2 bind:schema on:change>
397
+ {#snippet trigger()}
398
+ <Button color="light" size="xs" iconOnly startIcon={{ icon: Plus }} />
399
+ {/snippet}
400
+ </AddPropertyV2>
401
+ {/if}
402
+ <div class="shrink-0">
403
+ <Toggle
404
+ bind:checked={jsonView}
405
+ label="JSON View"
406
+ size="xs"
407
+ options={{
408
+ right: 'JSON editor',
409
+ rightTooltip:
410
+ 'Arguments can be edited either using the wizard, or by editing their JSON Schema.'
411
+ }}
412
+ lightMode
413
+ on:change={() => {
414
+ schemaString = JSON.stringify(schema, null, '\t')
415
+ editor?.setCode(schemaString)
416
+ }}
417
+ />
418
+ </div>
409
419
  </div>
410
420
  {/if}
411
421
 
@@ -536,7 +546,6 @@ function updateDynSelectCode(functionName, lang = 'bun') {
536
546
  const isS3 = v == 'S3'
537
547
  const isOneOf = v == 'oneOf'
538
548
  const isDynSelect = v == 'dynselect'
539
-
540
549
  const emptyProperty = {
541
550
  contentEncoding: undefined,
542
551
  enum_: undefined,
@@ -31,6 +31,7 @@ interface Props {
31
31
  dynSelectCode?: string | undefined;
32
32
  dynSelectLang?: ScriptLang | undefined;
33
33
  showDynSelectOpt?: boolean;
34
+ addPropertyInEditorTab?: boolean;
34
35
  openEditTab?: import('svelte').Snippet;
35
36
  addProperty?: import('svelte').Snippet;
36
37
  runButton?: import('svelte').Snippet;
@@ -114,7 +114,7 @@ import { getDbSchemas } from './apps/components/display/dbtable/utils';
114
114
  // import EditorTheme from './EditorTheme.svelte'
115
115
  let divEl = $state(null);
116
116
  let editor = $state(null);
117
- let { code = $bindable(), cmdEnterAction = undefined, formatAction = undefined, automaticLayout = true, websocketAlive = $bindable(), shouldBindKey = true, fixedOverflowWidgets = true, path = undefined, yContent = undefined, awareness = undefined, folding = false, args = undefined, useWebsockets = true, small = false, scriptLang, disabled = false, lineNumbersMinChars = 3, files = {}, extraLib = undefined, changeTimeout = 500, loadAsync = false, key = undefined, class: clazz = undefined } = $props();
117
+ let { code = $bindable(), cmdEnterAction = undefined, formatAction = undefined, automaticLayout = true, websocketAlive = $bindable(), shouldBindKey = true, fixedOverflowWidgets = true, path = undefined, yContent = undefined, awareness = undefined, folding = false, args = undefined, useWebsockets = true, small = false, scriptLang, disabled = false, lineNumbersMinChars = 3, files = {}, extraLib = undefined, changeTimeout = 500, loadAsync = false, key = undefined, class: clazz = undefined, moduleId = undefined } = $props();
118
118
  $effect.pre(() => {
119
119
  if (websocketAlive == undefined) {
120
120
  websocketAlive = {
@@ -1077,7 +1077,7 @@ async function loadMonaco() {
1077
1077
  (selection.startLineNumber !== selection.endLineNumber ||
1078
1078
  selection.startColumn !== selection.endColumn);
1079
1079
  if (hasSelection && selectedLines) {
1080
- aiChatManager.addSelectedLinesToContext(selectedLines, selection.startLineNumber, selection.endLineNumber);
1080
+ aiChatManager.addSelectedLinesToContext(selectedLines, selection.startLineNumber, selection.endLineNumber, moduleId);
1081
1081
  }
1082
1082
  else {
1083
1083
  aiChatManager.toggleOpen();
@@ -31,6 +31,7 @@ interface Props {
31
31
  loadAsync?: boolean;
32
32
  key?: string | undefined;
33
33
  class?: string | undefined;
34
+ moduleId?: string;
34
35
  }
35
36
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
36
37
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
@@ -31,7 +31,7 @@ import S3FilePicker from './S3FilePicker.svelte';
31
31
  import DucklakeIcon from './icons/DucklakeIcon.svelte';
32
32
  import FlowInlineScriptAiButton from './copilot/FlowInlineScriptAIButton.svelte';
33
33
  import ScriptGen from './copilot/ScriptGen.svelte';
34
- let { lang, editor, websocketAlive, iconOnly = false, validCode = true, kind = 'script', template = 'script', collabMode = false, collabLive = false, collabUsers = [], scriptPath = undefined, diffEditor = undefined, args, noHistory = false, saveToWorkspace = false, customUi = {}, lastDeployedCode = undefined, diffMode = false, showHistoryDrawer = $bindable(false), right, openAiChat = false } = $props();
34
+ let { lang, editor, websocketAlive, iconOnly = false, validCode = true, kind = 'script', template = 'script', collabMode = false, collabLive = false, collabUsers = [], scriptPath = undefined, diffEditor = undefined, args, noHistory = false, saveToWorkspace = false, customUi = {}, lastDeployedCode = undefined, diffMode = false, showHistoryDrawer = $bindable(false), right, openAiChat = false, moduleId = undefined } = $props();
35
35
  let contextualVariablePicker = $state();
36
36
  let variablePicker = $state();
37
37
  let resourcePicker = $state();
@@ -870,7 +870,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
870
870
 
871
871
  {#if customUi?.aiGen != false}
872
872
  {#if openAiChat}
873
- <FlowInlineScriptAiButton />
873
+ <FlowInlineScriptAiButton {moduleId} />
874
874
  {:else}
875
875
  <ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
876
876
  {/if}
@@ -33,6 +33,7 @@ interface Props {
33
33
  showHistoryDrawer?: boolean;
34
34
  right?: import('svelte').Snippet;
35
35
  openAiChat?: boolean;
36
+ moduleId?: string;
36
37
  }
37
38
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
38
39
  new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
@@ -38,7 +38,7 @@ export let simpleTooltipIconClass = '';
38
38
  {/if}
39
39
 
40
40
  {#if displayType}
41
- {#if format && !format.startsWith('resource')}
41
+ {#if format && !format.startsWith('resource') && !format.startsWith('jsonschema-')}
42
42
  <span class="text-xs italic ml-2 text-tertiary dark:text-indigo-400">
43
43
  {format}
44
44
  </span>
@@ -129,7 +129,10 @@ modulesTestStates.states[mod.id] = {
129
129
  }
130
130
  }
131
131
  }
132
- bind:job={modulesTestStates.states[mod.id].testJob}
132
+ bind:job={
133
+ () => modulesTestStates.states[mod.id]?.testJob,
134
+ (v) => modulesTestStates.states[mod.id] && (modulesTestStates.states[mod.id].testJob = v)
135
+ }
133
136
  loadPlaceholderJobOnStart={{
134
137
  type: 'QueuedJob',
135
138
  id: '',
@@ -18,6 +18,7 @@ import GfmMarkdown from './GfmMarkdown.svelte';
18
18
  import TestTriggerConnection from './triggers/TestTriggerConnection.svelte';
19
19
  import GitHubAppIntegration from './GitHubAppIntegration.svelte';
20
20
  import Button from './common/button/Button.svelte';
21
+ import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte';
21
22
  let { canSave = $bindable(true), resource_type = $bindable(undefined), path = $bindable(''), newResource = false, hidePath = false, onChange, defaultValues = undefined } = $props();
22
23
  let isValid = $state(true);
23
24
  let jsonError = $state('');
@@ -64,6 +65,9 @@ export async function editResource() {
64
65
  path: resourceToEdit.path,
65
66
  requestBody: { path, value: args, description }
66
67
  });
68
+ if (resourceToEdit.resource_type === 'json_schema') {
69
+ clearJsonSchemaResourceCache(resourceToEdit.path, $workspaceStore);
70
+ }
67
71
  sendUserToast(`Updated resource at ${path}`);
68
72
  dispatch('refresh', path);
69
73
  }
@@ -162,6 +162,7 @@ export async function inferSchema(code, nlang, resetArgs = false) {
162
162
  onMount(() => {
163
163
  inferSchema(code);
164
164
  loadPastTests();
165
+ aiChatManager.saveAndClear();
165
166
  aiChatManager.changeMode(AIMode.SCRIPT);
166
167
  });
167
168
  setLicense();
@@ -15,6 +15,6 @@ interface Props {
15
15
  overrideAllowKindChange?: boolean;
16
16
  originalType?: string | undefined;
17
17
  }
18
- declare const StringTypeNarrowing: import("svelte").Component<Props, {}, "password" | "disableCreate" | "pattern" | "format" | "enum_" | "enumLabels" | "contentEncoding" | "customErrorMessage" | "dateFormat" | "minRows" | "disableVariablePicker">;
18
+ declare const StringTypeNarrowing: import("svelte").Component<Props, {}, "password" | "disableCreate" | "pattern" | "format" | "enum_" | "enumLabels" | "contentEncoding" | "customErrorMessage" | "minRows" | "disableVariablePicker" | "dateFormat">;
19
19
  type StringTypeNarrowing = ReturnType<typeof StringTypeNarrowing>;
20
20
  export default StringTypeNarrowing;
@@ -6,6 +6,7 @@ import { ExternalLink, WandSparkles } from 'lucide-svelte';
6
6
  import { base } from '../../base';
7
7
  import { twMerge } from 'tailwind-merge';
8
8
  import { aiChatManager, AIMode } from './chat/AIChatManager.svelte';
9
+ const { moduleId } = $props();
9
10
  const aiChatScriptModeClasses = $derived(aiChatManager.mode === AIMode.SCRIPT && aiChatManager.isOpen
10
11
  ? 'dark:bg-violet-900 bg-violet-100'
11
12
  : '');
@@ -18,7 +19,7 @@ const aiChatScriptModeClasses = $derived(aiChatManager.mode === AIMode.SCRIPT &&
18
19
  btnClasses={twMerge('!px-2', aiChatScriptModeClasses)}
19
20
  {onClick}
20
21
  iconOnly
21
- title="Open AI chat in script mode"
22
+ title="Open AI chat"
22
23
  startIcon={{ icon: WandSparkles, classes: 'text-violet-800 dark:text-violet-400' }}
23
24
  />
24
25
  {/snippet}
@@ -26,7 +27,8 @@ const aiChatScriptModeClasses = $derived(aiChatManager.mode === AIMode.SCRIPT &&
26
27
  {#if $copilotInfo.enabled}
27
28
  {@render button(() => {
28
29
  aiChatManager.openChat()
29
- aiChatManager.changeMode(AIMode.SCRIPT)
30
+ const availableContext = aiChatManager.contextManager.getAvailableContext()
31
+ aiChatManager.contextManager.setSelectedModuleContext(moduleId, availableContext)
30
32
  })}
31
33
  {:else}
32
34
  <Popover
@@ -1,3 +1,6 @@
1
- declare const FlowInlineScriptAiButton: import("svelte").Component<Record<string, never>, {}, "">;
1
+ interface Props {
2
+ moduleId?: string;
3
+ }
4
+ declare const FlowInlineScriptAiButton: import("svelte").Component<Props, {}, "">;
2
5
  type FlowInlineScriptAiButton = ReturnType<typeof FlowInlineScriptAiButton>;
3
6
  export default FlowInlineScriptAiButton;
@@ -128,9 +128,7 @@ export class Autocompletor {
128
128
  ]
129
129
  };
130
130
  },
131
- // @ts-ignore
132
131
  disposeInlineCompletions: () => { },
133
- freeInlineCompletions: () => { }
134
132
  });
135
133
  this.#cursorDisposable = editor.onDidChangeCursorPosition(async (e) => {
136
134
  deletionsCues.clear();
@@ -47,7 +47,7 @@ $effect(() => {
47
47
  aiChatManager.listenForDbSchemasChanges($dbSchemas);
48
48
  });
49
49
  $effect(() => {
50
- aiChatManager.listenForScriptEditorContextChange($dbSchemas, $workspaceStore, $copilotSessionModel);
50
+ aiChatManager.listenForContextChange($dbSchemas, $workspaceStore, $copilotSessionModel);
51
51
  });
52
52
  $effect(() => {
53
53
  aiChatManager.updateMode(untrack(() => aiChatManager.mode));
@@ -80,9 +80,7 @@ $effect(() => {
80
80
  pastChats={historyManager.getPastChats()}
81
81
  bind:selectedContext={
82
82
  () => aiChatManager.contextManager.getSelectedContext(),
83
- (sc) => {
84
- aiChatManager.scriptEditorOptions && aiChatManager.contextManager.setSelectedContext(sc)
85
- }
83
+ (sc) => aiChatManager.contextManager.setSelectedContext(sc)
86
84
  }
87
85
  availableContext={aiChatManager.contextManager.getAvailableContext()}
88
86
  messages={aiChatManager.currentReply
@@ -33,7 +33,7 @@ let contextTextareaComponent = $state();
33
33
  let instructionsTextareaComponent = $state();
34
34
  let instructions = $state(initialInstructions);
35
35
  export function focusInput() {
36
- if (aiChatManager.mode === AIMode.SCRIPT) {
36
+ if (aiChatManager.mode === AIMode.SCRIPT || aiChatManager.mode === AIMode.FLOW) {
37
37
  contextTextareaComponent?.focus();
38
38
  }
39
39
  else {
@@ -82,7 +82,7 @@ $effect(() => {
82
82
  </script>
83
83
 
84
84
  <div use:clickOutside class="relative">
85
- {#if aiChatManager.mode === AIMode.SCRIPT}
85
+ {#if aiChatManager.mode === AIMode.SCRIPT || aiChatManager.mode === AIMode.FLOW}
86
86
  {#if showContext}
87
87
  <div class="flex flex-row gap-1 mb-1 overflow-scroll pt-2 no-scrollbar">
88
88
  <Popover>
@@ -100,6 +100,7 @@ $effect(() => {
100
100
  addContextToSelection(element)
101
101
  close()
102
102
  }}
103
+ categorize
103
104
  />
104
105
  </svelte:fragment>
105
106
  </Popover>
@@ -107,7 +108,7 @@ $effect(() => {
107
108
  <ContextElementBadge
108
109
  contextElement={element}
109
110
  deletable
110
- on:delete={() => {
111
+ onDelete={() => {
111
112
  selectedContext = selectedContext?.filter(
112
113
  (c) => c.type !== element.type || c.title !== element.title
113
114
  )
@@ -53,6 +53,7 @@ class AIChatManager {
53
53
  tools = $state([]);
54
54
  helpers = $state(undefined);
55
55
  scriptEditorOptions = $state(undefined);
56
+ flowOptions = $state(undefined);
56
57
  scriptEditorApplyCode = $state(undefined);
57
58
  scriptEditorShowDiffMode = $state(undefined);
58
59
  flowAiChatHelpers = $state(undefined);
@@ -61,7 +62,7 @@ class AIChatManager {
61
62
  aiChatInput = $state(null);
62
63
  confirmationCallback = $state(undefined);
63
64
  allowedModes = $derived({
64
- script: this.scriptEditorOptions !== undefined,
65
+ script: this.flowAiChatHelpers === undefined && this.scriptEditorOptions !== undefined,
65
66
  flow: this.flowAiChatHelpers !== undefined,
66
67
  navigator: true,
67
68
  ask: true,
@@ -507,7 +508,7 @@ class AIChatManager {
507
508
  }
508
509
  try {
509
510
  const oldSelectedContext = this.contextManager?.getSelectedContext() ?? [];
510
- if (this.mode === AIMode.SCRIPT) {
511
+ if (this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW) {
511
512
  this.contextManager?.updateContextOnRequest(options);
512
513
  }
513
514
  this.loading = true;
@@ -527,7 +528,9 @@ class AIChatManager {
527
528
  {
528
529
  role: 'user',
529
530
  content: this.instructions,
530
- contextElements: this.mode === AIMode.SCRIPT ? oldSelectedContext : undefined,
531
+ contextElements: this.mode === AIMode.SCRIPT || this.mode === AIMode.FLOW
532
+ ? oldSelectedContext
533
+ : undefined,
531
534
  snapshot,
532
535
  index: this.messages.length // matching with actual messages index. not -1 because it's not yet added to the messages array
533
536
  }
@@ -545,7 +548,7 @@ class AIChatManager {
545
548
  };
546
549
  switch (this.mode) {
547
550
  case AIMode.FLOW:
548
- userMessage = prepareFlowUserMessage(oldInstructions, this.flowAiChatHelpers.getFlowAndSelectedId());
551
+ userMessage = prepareFlowUserMessage(oldInstructions, this.flowAiChatHelpers.getFlowAndSelectedId(), oldSelectedContext);
549
552
  break;
550
553
  case AIMode.NAVIGATOR:
551
554
  userMessage = prepareNavigatorUserMessage(oldInstructions);
@@ -672,12 +675,14 @@ class AIChatManager {
672
675
  this.contextManager?.setFixContext();
673
676
  this.sendRequest();
674
677
  };
675
- addSelectedLinesToContext = (lines, startLine, endLine) => {
678
+ addSelectedLinesToContext = (lines, startLine, endLine, moduleId) => {
676
679
  if (!this.open) {
677
680
  this.toggleOpen();
678
681
  }
679
- this.changeMode(AIMode.SCRIPT);
680
- this.contextManager?.addSelectedLinesToContext(lines, startLine, endLine);
682
+ if (!moduleId) {
683
+ this.changeMode(AIMode.SCRIPT);
684
+ }
685
+ this.contextManager?.addSelectedLinesToContext(lines, startLine, endLine, moduleId);
681
686
  this.focusInput();
682
687
  };
683
688
  saveAndClear = async () => {
@@ -712,10 +717,16 @@ class AIChatManager {
712
717
  isPreprocessor: moduleId === 'preprocessor'
713
718
  });
714
719
  };
715
- listenForScriptEditorContextChange = (dbSchemas, workspaceStore, copilotSessionModel) => {
716
- if (this.scriptEditorOptions) {
720
+ listenForContextChange = (dbSchemas, workspaceStore, copilotSessionModel) => {
721
+ if (this.mode === AIMode.SCRIPT && this.scriptEditorOptions) {
717
722
  this.contextManager.updateAvailableContext(this.scriptEditorOptions, dbSchemas, workspaceStore ?? '', !copilotSessionModel?.model.endsWith('/thinking'), untrack(() => this.contextManager.getSelectedContext()));
718
723
  }
724
+ else if (this.mode === AIMode.FLOW && this.flowOptions) {
725
+ this.contextManager.updateAvailableContextForFlow(this.flowOptions, dbSchemas, workspaceStore ?? '', !copilotSessionModel?.model.endsWith('/thinking'), untrack(() => this.contextManager.getSelectedContext()));
726
+ }
727
+ if (this.scriptEditorOptions) {
728
+ this.contextManager.setScriptOptions(this.scriptEditorOptions);
729
+ }
719
730
  };
720
731
  listenForDbSchemasChanges = (dbSchemas) => {
721
732
  this.displayMessages = ContextManager.updateDisplayMessages(untrack(() => this.displayMessages), dbSchemas);
@@ -792,6 +803,7 @@ class AIChatManager {
792
803
  else {
793
804
  this.scriptEditorOptions = undefined;
794
805
  }
806
+ untrack(() => this.contextManager?.setSelectedModuleContext(selectedId, untrack(() => this.contextManager.getAvailableContext())));
795
807
  return () => {
796
808
  this.scriptEditorOptions = undefined;
797
809
  };
@@ -1,39 +1,130 @@
1
- <script lang="ts">import { ContextIconMap } from './context';
2
- const { availableContext, selectedContext, onSelect, showAllAvailable = false, stringSearch = '', selectedIndex = 0 } = $props();
3
- // Define priority map for context types
4
- const typePriority = {
5
- code: 1,
6
- diff: 2,
7
- default: 3
8
- };
9
- const actualAvailableContext = $derived(availableContext
10
- .filter((c) => (showAllAvailable ||
11
- !selectedContext.some((sc) => sc.type === c.type && sc.title === c.title)) &&
12
- (!stringSearch || c.title.toLowerCase().includes(stringSearch.toLowerCase())))
13
- .sort((a, b) => {
14
- const priorityA = typePriority[a.type] || typePriority.default;
15
- const priorityB = typePriority[b.type] || typePriority.default;
16
- return priorityA - priorityB;
1
+ <script lang="ts">import FlowModuleIcon from '../../flows/FlowModuleIcon.svelte';
2
+ import BarsStaggered from '../../icons/BarsStaggered.svelte';
3
+ import { ContextIconMap } from './context';
4
+ import { ArrowLeft, Diff, Database, Code, ChevronRight } from 'lucide-svelte';
5
+ const { availableContext, selectedContext, onSelect, showAllAvailable = false, stringSearch = '', selectedIndex = 0, categorize = false } = $props();
6
+ // Current view state: 'categories' or specific category type
7
+ let currentView = $state('categories');
8
+ // Category definitions
9
+ const categories = [
10
+ { id: 'diffs', label: 'Diffs', icon: Diff },
11
+ { id: 'modules', label: 'Modules', icon: BarsStaggered },
12
+ { id: 'databases', label: 'Databases', icon: Database },
13
+ { id: 'code', label: 'Code', icon: Code }
14
+ ];
15
+ const filteredAvailableContext = $derived(availableContext.filter((context) => {
16
+ const filtered = (showAllAvailable ||
17
+ !selectedContext.some((sc) => sc.type === context.type && sc.title === context.title)) &&
18
+ (!stringSearch || context.title.toLowerCase().includes(stringSearch.toLowerCase()));
19
+ return filtered;
17
20
  }));
21
+ // Group context by category
22
+ const contextByCategory = $derived.by(() => {
23
+ const grouped = {
24
+ diffs: [],
25
+ modules: [],
26
+ databases: [],
27
+ code: []
28
+ };
29
+ filteredAvailableContext.forEach((context) => {
30
+ if (context.type === 'diff')
31
+ grouped.diffs.push(context);
32
+ else if (context.type === 'flow_module')
33
+ grouped.modules.push(context);
34
+ else if (context.type === 'db')
35
+ grouped.databases.push(context);
36
+ else if (context.type === 'code')
37
+ grouped.code.push(context);
38
+ });
39
+ return grouped;
40
+ });
41
+ const currentCategoryItems = $derived(currentView !== 'categories' ? contextByCategory[currentView] : []);
42
+ function handleCategoryClick(categoryId) {
43
+ currentView = categoryId;
44
+ }
45
+ function handleBackClick() {
46
+ currentView = 'categories';
47
+ }
18
48
  </script>
19
49
 
20
- <div class="flex flex-col gap-1 text-tertiary text-xs p-1 min-w-24 max-h-48 overflow-y-scroll">
21
- {#if actualAvailableContext.length === 0}
22
- <div class="text-center text-tertiary text-xs">No available context</div>
50
+ <div class="flex flex-col gap-1 text-tertiary text-xs p-1 pr-0 min-w-24 max-h-48 overflow-y-scroll">
51
+ {#if categorize}
52
+ {#if currentView === 'categories'}
53
+ {#each categories as category}
54
+ {@const itemCount = contextByCategory[category.id].length}
55
+ {@const Icon = category.icon}
56
+ {#if itemCount > 0}
57
+ <button
58
+ class="hover:bg-surface-hover rounded-md p-1 pr-0 text-left flex flex-row gap-1 items-center font-normal transition-colors"
59
+ onclick={() => handleCategoryClick(category.id)}
60
+ >
61
+ <Icon size={16} />
62
+ <span class="flex-1">{category.label}</span>
63
+ <ChevronRight size={16} />
64
+ </button>
65
+ {/if}
66
+ {/each}
67
+ {#if categories.every((cat) => contextByCategory[cat.id].length === 0)}
68
+ <div class="text-center text-tertiary text-xs py-2">No available context</div>
69
+ {/if}
70
+ {:else}
71
+ <!-- Category items view -->
72
+ <button
73
+ class="hover:bg-surface-hover rounded-md text-left flex flex-row gap-1 items-center font-normal transition-colors mb-1"
74
+ onclick={handleBackClick}
75
+ >
76
+ <ArrowLeft size={12} />
77
+ <span class="text-xs">Go back</span>
78
+ </button>
79
+
80
+ {#if currentCategoryItems.length === 0}
81
+ <div class="text-center text-tertiary text-xs py-2">No items in this category</div>
82
+ {:else}
83
+ {#each currentCategoryItems as element}
84
+ {@const Icon = ContextIconMap[element.type]}
85
+ <button
86
+ class="hover:bg-surface-hover rounded-md p-1 text-left flex flex-row gap-1 items-center font-normal transition-colors"
87
+ onclick={() => {
88
+ onSelect(element)
89
+ currentView = 'categories' // Go back to categories after selection
90
+ }}
91
+ >
92
+ {#if element.type === 'flow_module'}
93
+ <FlowModuleIcon module={element as FlowModule} size={16} />
94
+ {:else if Icon}
95
+ <Icon size={16} />
96
+ {/if}
97
+ <span class="truncate">
98
+ {element.type === 'diff' || element.type === 'flow_module'
99
+ ? element.title.replace(/_/g, ' ')
100
+ : element.title}
101
+ </span>
102
+ </button>
103
+ {/each}
104
+ {/if}
105
+ {/if}
23
106
  {:else}
24
- {#each actualAvailableContext as element, i}
107
+ {#each filteredAvailableContext as element, i}
25
108
  {@const Icon = ContextIconMap[element.type]}
26
109
  <button
27
- class="hover:bg-surface-hover rounded-md p-1 text-left flex flex-row gap-1 items-center font-normal {i ===
110
+ class="hover:bg-surface-hover rounded-md p-1 text-left flex flex-row gap-1 items-center font-normal transition-colors {i ===
28
111
  selectedIndex
29
112
  ? 'bg-surface-hover'
30
113
  : ''}"
31
- onclick={() => onSelect(element)}
114
+ onclick={() => {
115
+ onSelect(element)
116
+ }}
32
117
  >
33
- {#if Icon}
118
+ {#if element.type === 'flow_module'}
119
+ <FlowModuleIcon module={element as FlowModule} size={16} />
120
+ {:else if Icon}
34
121
  <Icon size={16} />
35
122
  {/if}
36
- {element.type === 'diff' ? element.title.replace(/_/g, ' ') : element.title}
123
+ <span class="truncate">
124
+ {element.type === 'diff' || element.type === 'flow_module'
125
+ ? element.title.replace(/_/g, ' ')
126
+ : element.title}
127
+ </span>
37
128
  </button>
38
129
  {/each}
39
130
  {/if}
@@ -6,6 +6,7 @@ interface Props {
6
6
  showAllAvailable?: boolean;
7
7
  stringSearch?: string;
8
8
  selectedIndex?: number;
9
+ categorize?: boolean;
9
10
  }
10
11
  declare const AvailableContextList: import("svelte").Component<Props, {}, "">;
11
12
  type AvailableContextList = ReturnType<typeof AvailableContextList>;