From 3413a856dc88894d1323c53c53a5e77b0eb44de2 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Tue, 28 Oct 2025 23:00:57 +0100 Subject: [PATCH] Better AI Fill inputs (#6984) * Remove most yPadding={7} * migrate DynamicInputHelpBox to Alert * fix button heights * Fix TemplateEditor padding and FakeMonacoPlaceholder * SimpleEditor fix left padding * Better AI Fill inputs * update svelte * Fix svelte bug, propertyType not updating * Revert "update svelte" This reverts commit 1c0f5ed15caecc62f289330b60af32fb7fb9ddcb. * MONACO_Y_PADDING var * Fixed Flow Loop AI gen * nit button fixes --- frontend/src/lib/components/ArgInput.svelte | 13 +- .../src/lib/components/AuthSettings.svelte | 2 +- frontend/src/lib/components/Editor.svelte | 12 +- .../components/ErrorOrRecoveryHandler.svelte | 4 +- .../components/FakeMonacoPlaceHolder.svelte | 17 +- .../lib/components/InputTransformForm.svelte | 348 +++++++++--------- frontend/src/lib/components/JsonEditor.svelte | 1 - .../src/lib/components/SimpleEditor.svelte | 62 ++-- .../src/lib/components/TemplateEditor.svelte | 41 ++- .../settingsPanel/ComponentPanel.svelte | 15 +- .../components/copilot/StepInputGen.svelte | 110 +++--- .../copilot/chat/flow/FlowAIButton.svelte | 1 - .../flows/content/DynamicInputHelpBox.svelte | 11 +- .../components/flows/content/FlowLoop.svelte | 42 ++- .../flows/content/FlowModuleEarlyStop.svelte | 2 - .../flows/content/FlowModuleSkip.svelte | 1 - .../flows/content/FlowRetries.svelte | 1 - .../flows/content/FlowSettings.svelte | 1 - .../flows/map/FlowErrorHandlerItem.svelte | 4 +- .../schema/FlowPropertyEditor.svelte | 1 - .../components/text_input/TextInput.svelte | 15 +- frontend/src/lib/components/vscode.ts | 2 + 22 files changed, 349 insertions(+), 357 deletions(-) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index 501c347ffe..d89495b351 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -46,7 +46,11 @@ import { workspaceStore } from '$lib/stores' import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte' import AIProviderPicker from './AIProviderPicker.svelte' - import TextInput, { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte' + import TextInput, { + inputBaseClass, + inputBorderClass, + inputSizeClasses + } from './text_input/TextInput.svelte' import FileInput from './common/fileInput/FileInput.svelte' interface Props { @@ -1401,7 +1405,12 @@ use:autosize onkeydown={onKeyDown} {disabled} - class={twMerge('w-full', inputBaseClass, inputBorderClass({ error: !!error }))} + class={twMerge( + 'w-full', + inputBaseClass, + inputSizeClasses.md, + inputBorderClass({ error: !!error }) + )} placeholder={placeholder ?? defaultValue ?? ''} bind:value > diff --git a/frontend/src/lib/components/AuthSettings.svelte b/frontend/src/lib/components/AuthSettings.svelte index 6e9ea3b533..0959970b8c 100644 --- a/frontend/src/lib/components/AuthSettings.svelte +++ b/frontend/src/lib/components/AuthSettings.svelte @@ -169,7 +169,7 @@ {/if} {/each} -
+
sendTeamsMessage(handlerExtraArgs['channel'] ?? '')} size="xs">Send test message import { getOS } from '$lib/utils' + import { MONACO_Y_PADDING } from './vscode' type Props = { code?: string @@ -16,13 +17,15 @@ let { code, autoheight = false, - lineNumbersWidth = 51, + lineNumbersWidth = 0, lineNumbersOffset = 0, class: className = '', showNumbers = true, fontSize = 14 }: Props = $props() + let yPadding = MONACO_Y_PADDING + // https://github.com/microsoft/vscode/blob/baa2dad3cdacd97ac02eff0604984faf1167ff1e/src/vs/editor/common/config/editorOptions.ts#L5421 const DEFAULT_WINDOWS_FONT_FAMILY = "Consolas, 'Courier New', monospace" const DEFAULT_MAC_FONT_FAMILY = "Menlo, Monaco, 'Courier New', monospace" @@ -51,7 +54,9 @@ let [editorWidth, editorHeight] = $derived([ clientWidth, - autoheight ? lines.length * lineHeight + (showHorizontalScrollbar ? 12 : 0) : clientHeight + autoheight + ? lines.length * lineHeight + (showHorizontalScrollbar ? 12 : 0) + yPadding * 2 + : clientHeight ]) @@ -60,7 +65,7 @@
{#each lines as _, i} -
+
{i + 1}
{/each} @@ -99,11 +104,11 @@ >
{#each lines as line, i}
{line} diff --git a/frontend/src/lib/components/InputTransformForm.svelte b/frontend/src/lib/components/InputTransformForm.svelte index b96ec616da..ca033335a9 100644 --- a/frontend/src/lib/components/InputTransformForm.svelte +++ b/frontend/src/lib/components/InputTransformForm.svelte @@ -39,6 +39,7 @@ import { deepEqual } from 'fast-equals' import S3ArrayHelperButton from './S3ArrayHelperButton.svelte' import { inputBorderClass } from './text_input/TextInput.svelte' + import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte' // We add 'ai' for ai agent tools. 'ai' means the field will be filled by the AI agent dynamically. type PropertyType = InputTransform['type'] | 'ai' @@ -457,6 +458,17 @@ inputCat === 'list' && ['s3object', 's3_object'].includes(schema?.properties?.[argName]?.items?.resourceType) ) + + let suggestion: string | undefined = $state() + + // Svelte bug ... + // Somehow the value is updated in the UI of the parent, but not in the children + // when passed as a prop. setTimeout is a workaround to force the update + let visiblePropertyType = $state(untrack(() => (suggestion ? 'javascript' : propertyType))) + $effect(() => { + let value = suggestion ? 'javascript' : propertyType + setTimeout(() => (visiblePropertyType = value), 1) + }) {#if arg != undefined && !hidden} @@ -497,22 +509,9 @@ {focused} {arg} schemaProperty={schema?.properties?.[argName]} - showPopup={(isStaticTemplate(inputCat) && propertyType == 'static') || - propertyType === undefined || - propertyType === 'static' || - arg?.expr?.length > 0} - on:showExpr={(e) => { - setTimeout(() => { - if (monaco && propertyType === 'javascript') { - monaco.setSuggestion(e.detail) - } - }, 0) - }} + on:showExpr={(e) => (suggestion = e.detail || undefined)} on:setExpr={(e) => { - arg = { - type: 'javascript', - expr: e.detail - } + arg = { type: 'javascript', expr: e.detail } propertyType = 'javascript' monaco?.setCode('') monaco?.insertAtCursor(e.detail) @@ -555,10 +554,10 @@
{ - if (e.detail == propertyType) return + if (e.detail == propertyType || suggestion) return const staticTemplate = isStaticTemplate(inputCat) if (e.detail === 'ai') { @@ -691,172 +690,189 @@ {propertyType} -->
- {#if propertyType === 'ai'} + {#if suggestion}
- - - This field will be filled by the AI agent dynamically - +
- {#if argName && schema?.properties?.[argName]?.description} -
-
{schema.properties[argName].description}
+ {/if} +
{ + if (e.key === 'Tab' && suggestion) { + e.preventDefault() + } + }} + > + {@render innerInput()} +
+ + {#snippet innerInput()} + {#if propertyType === 'ai'} +
+ + + This field will be filled by the AI agent dynamically +
- {/if} - {:else if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle} -
{#if argName && schema?.properties?.[argName]?.description} -
+
-											{schema.properties[argName].description}
-											
+ {schema.properties[argName].description} +
{/if} + {:else if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle} +
+ {#if argName && schema?.properties?.[argName]?.description} +
+
+										{schema.properties[argName].description}
+										
+
+ {/if} - {#if arg} - { - focused = false - }} - bind:code={arg.value} - fontSize={12} - on:change={() => { - dispatch('change', { argName, arg }) - }} - loadAsync - class="bg-surface-input" - /> - {/if} -
- {:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]} - { - focused = false - }} - shouldDispatchChanges - on:change={() => { - dispatch('change', { argName, arg }) - }} - label={argName} - bind:editor={monaco} - bind:description={schema.properties[argName].description} - bind:value={arg.value} - type={schema.properties[argName].type} - oneOf={schema.properties[argName].oneOf} - required={schema.required?.includes(argName)} - bind:pattern={schema.properties[argName].pattern} - bind:valid={inputCheck} - defaultValue={schema.properties[argName].default} - bind:enum_={schema.properties[argName].enum} - bind:format={schema.properties[argName].format} - contentEncoding={schema.properties[argName].contentEncoding} - bind:itemsType={schema.properties[argName].items} - properties={schema.properties[argName].properties} - nestedRequired={schema.properties[argName].required} - displayHeader={false} - extra={argExtra} - {variableEditor} - {itemPicker} - bind:pickForField - showSchemaExplorer - nullable={schema.properties[argName].nullable} - bind:title={schema.properties[argName].title} - bind:placeholder={schema.properties[argName].placeholder} - {helperScript} - otherArgs={Object.fromEntries( - Object.entries(otherArgs).map(([key, transform]) => [ - key, - transform?.type === 'static' ? transform.value : transform?.expr - ]) - )} - > - {#snippet innerBottomSnippet()} - {#if shouldShowS3ArrayHelper} - - switchToJsAndConnect((path) => appendPathToArrayExpr(arg.expr, path))} + {#if arg} + { + focused = false + }} + bind:code={arg.value} + fontSize={12} + on:change={() => { + dispatch('change', { argName, arg }) + }} + loadAsync + class="bg-surface-input" /> {/if} - {/snippet} - - {:else if arg.expr != undefined} -
- (focused = true)} - on:blur={() => (focused = false)} +
+ {:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]} + { + focused = false + }} + shouldDispatchChanges on:change={() => { dispatch('change', { argName, arg }) }} - autoHeight - loadAsync - /> - -
- - {#if shouldShowS3ArrayHelper} - - focusProp?.(argName, 'connect', (path) => { - appendPathToArrayExpr(arg.expr, path) - return true - })} - /> - {/if} - - {#if argName && schema?.properties?.[argName]?.description} -
-
{schema.properties[argName].description}
+ label={argName} + bind:editor={monaco} + bind:description={schema.properties[argName].description} + bind:value={arg.value} + type={schema.properties[argName].type} + oneOf={schema.properties[argName].oneOf} + required={schema.required?.includes(argName)} + bind:pattern={schema.properties[argName].pattern} + bind:valid={inputCheck} + defaultValue={schema.properties[argName].default} + bind:enum_={schema.properties[argName].enum} + bind:format={schema.properties[argName].format} + contentEncoding={schema.properties[argName].contentEncoding} + bind:itemsType={schema.properties[argName].items} + properties={schema.properties[argName].properties} + nestedRequired={schema.properties[argName].required} + displayHeader={false} + extra={argExtra} + {variableEditor} + {itemPicker} + bind:pickForField + showSchemaExplorer + nullable={schema.properties[argName].nullable} + bind:title={schema.properties[argName].title} + bind:placeholder={schema.properties[argName].placeholder} + {helperScript} + otherArgs={Object.fromEntries( + Object.entries(otherArgs).map(([key, transform]) => [ + key, + transform?.type === 'static' ? transform.value : transform?.expr + ]) + )} + > + {#snippet innerBottomSnippet()} + {#if shouldShowS3ArrayHelper} + + switchToJsAndConnect((path) => appendPathToArrayExpr(arg.expr, path))} + /> + {/if} + {/snippet} + + {:else if arg.expr != undefined} +
+ (focused = true)} + on:blur={() => (focused = false)} + on:change={() => { + dispatch('change', { argName, arg }) + }} + autoHeight + loadAsync + /> +
- {/if} - {#if !hideHelpButton} - - {/if} + {#if shouldShowS3ArrayHelper} + + focusProp?.(argName, 'connect', (path) => { + appendPathToArrayExpr(arg.expr, path) + return true + })} + /> + {/if} -
- {:else} - - Not recognized input type {argName} ({arg.expr}, {propertyType}) - -
-
- {/if} + {#if argName && schema?.properties?.[argName]?.description} +
+
{schema.properties[argName].description}
+
+ {/if} + + {#if !hideHelpButton} + + {/if} + +
+ {:else} + + Not recognized input type {argName} ({arg.expr}, {propertyType}) + +
+
+ {/if} + {/snippet}
diff --git a/frontend/src/lib/components/JsonEditor.svelte b/frontend/src/lib/components/JsonEditor.svelte index 63e23eed71..ea6d063f1f 100644 --- a/frontend/src/lib/components/JsonEditor.svelte +++ b/frontend/src/lib/components/JsonEditor.svelte @@ -87,7 +87,6 @@ {disabled} {fixedOverflowWidgets} renderLineHighlight="none" - yPadding={7} />
{#if error != ''} diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index cb888eaca6..083bb30577 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -31,7 +31,11 @@ import libStdContent from '$lib/es6.d.ts.txt?raw' import domContent from '$lib/dom.d.ts.txt?raw' - import { initializeVscode, keepModelAroundToAvoidDisposalOfWorkers } from './vscode' + import { + initializeVscode, + keepModelAroundToAvoidDisposalOfWorkers, + MONACO_Y_PADDING + } from './vscode' import EditorTheme from './EditorTheme.svelte' import { vimMode, relativeLineNumbers } from '$lib/stores' import { initVim } from './monaco_keybindings' @@ -49,6 +53,7 @@ type IPosition } from 'monaco-editor' import { setMonacoJavascriptOptions, setMonacoJsonOptions } from './monacoLanguagesOptions' + import { twMerge } from 'tailwind-merge' // import { createConfiguredEditor } from 'vscode/monaco' // import type { IStandaloneCodeEditor } from 'vscode/vscode/vs/editor/standalone/browser/standaloneCodeEditor' @@ -59,7 +64,6 @@ let statusDiv = $state(null) let width = $state(0) let initialized = $state(false) - let suggestion = $state('') let placeholderVisible = $state(false) let mounted = $state(false) @@ -85,13 +89,12 @@ allowVim = false, tailwindClasses = [], class: className = '', - fakeMonacoPlaceholderClass = '', loadAsync = false, key, disabled = false, minHeight = 1000, renderLineHighlight = 'none', - yPadding + suggestion }: { lang: string code?: string @@ -113,16 +116,17 @@ allowVim?: boolean tailwindClasses?: string[] class?: string - fakeMonacoPlaceholderClass?: string loadAsync?: boolean initialCursorPos?: IPosition key?: string disabled?: boolean minHeight?: number renderLineHighlight?: 'all' | 'line' | 'gutter' | 'none' - yPadding?: number + suggestion?: string } = $props() + let yPadding = MONACO_Y_PADDING + const dispatch = createEventDispatcher() const uri = `file:///${hash}.${langToExt(lang)}` @@ -214,17 +218,6 @@ export function hide(): void { divEl?.classList.add('hidden') } - - export function setSuggestion(value: string): void { - suggestion = value - } - - let disableTabCond: meditor.IContextKey | undefined - - $effect(() => { - disableTabCond?.set(!code && !!suggestion) - }) - let vimDisposable: IDisposable | undefined = undefined $effect(() => { @@ -340,7 +333,7 @@ model, ...(yPadding !== undefined ? { padding: { bottom: yPadding, top: yPadding } } : {}), renderLineHighlight, - lineDecorationsWidth: 6, + lineDecorationsWidth: 0, lineNumbersMinChars: 2, fontSize: fontSize, quickSuggestions: disableSuggestions @@ -369,7 +362,6 @@ let timeoutModel: number | undefined = undefined editor.onDidChangeModelContent((event) => { - suggestion = '' timeoutModel && clearTimeout(timeoutModel) timeoutModel = setTimeout(() => { updateCode() @@ -389,9 +381,6 @@ shouldBindKey && format && format() }) - disableTabCond = editor.createContextKey('disableTabCond', !code) - editor.addCommand(KeyCode.Tab, function () {}, 'disableTabCond') - editor.addCommand(KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit7, function () { // CMD + slash (toggle comment) on some EU keyboards editor?.trigger('keyboard', 'editor.action.commentLine', {}) @@ -597,38 +586,32 @@ -{#if editor && suggestion && code?.length === 0} -
- {suggestion} -
-{/if} - -{#if !editor} +{#if !editor || suggestion} {/if} -
{#if placeholder}
@@ -636,6 +619,7 @@
{/if}
+ {#if allowVim && vimMode}
{/if} diff --git a/frontend/src/lib/components/TemplateEditor.svelte b/frontend/src/lib/components/TemplateEditor.svelte index 432f206274..9c01537eea 100644 --- a/frontend/src/lib/components/TemplateEditor.svelte +++ b/frontend/src/lib/components/TemplateEditor.svelte @@ -22,11 +22,12 @@ // import '@codingame/monaco-vscode-standalone-typescript-language-features' - import { initializeVscode } from './vscode' + import { initializeVscode, MONACO_Y_PADDING } from './vscode' import EditorTheme from './EditorTheme.svelte' import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte' import { setMonacoJsonOptions } from './monacoLanguagesOptions' import { inputBorderClass } from './text_input/TextInput.svelte' + import { twMerge } from 'tailwind-merge' export const conf = { wordPattern: @@ -381,9 +382,10 @@ export let autoHeight = true export let fixedOverflowWidgets = true export let fontSize = 12 - export let yPadding: number | undefined = undefined export let loadAsync = false + let yPadding = MONACO_Y_PADDING + if (typeof code != 'string') { code = '' } @@ -448,7 +450,7 @@ model, // overflowWidgetsDomNode: widgets, // lineNumbers: 'on', - lineDecorationsWidth: 6, + lineDecorationsWidth: 0, lineNumbersMinChars: 2, fontSize, suggestOnTriggerCharacters: true, @@ -656,24 +658,23 @@ -{#if !editor} - -{/if}
+ class={twMerge( + inputBorderClass({ forceFocus: isFocus }), + 'rounded-md overflow-auto pl-2', + $$props.class + )} +> + {#if !editor} + + {/if} +
+