Files
windmill/frontend/src/lib/components/InputTransformSchemaForm.svelte
T
hugocasaandClaude Opus 5 f037c73d10 feat(frontend): group the agent form and edit saved agents as drafts (#10880)
* feat(frontend): group the AI agent step form and edit saved agents in a modal

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: edit a saved AI agent through its own resource draft

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: drop the agent fork-for-edit session now that edits live in a draft

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat: edit ai_agent resources from the resources page with the agent editor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: send a standalone agent's brain from the module when testing a step

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: keep the agent draft faithful to the resource it deploys to

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: add the sqlx cache entry for the eval subject rename

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor: share the module insert between the graph and the agent editor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): open evals inside the agent editor, actions in its header

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): add tools from the agent editor and lighten its test pane

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): open an ai_agent deep link in the agent editor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): drop the failed result badge on a step that never ran

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): head the agent editor's levels with a back control

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): drop connect and fill inputs from the agent editor

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): lighten the agent editor's run panel

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): stop a nested agent tool's config reading as AI-filled

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): offer only AI or static on an agent tool's inputs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): give a saved agent's tool editor a static-only surface

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): open an agent tool in a drawer beside the agent

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(frontend): hide unset agent config in the run form

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(frontend): share the input forms' pickers and s3 lookup

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(frontend): drop a dead agent-editor export and fix two stale comments

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): reach an ai_agent's resource-level settings and copilot

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): open an ai_agent's resource view as JSON, not the generic form

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): address review findings on the agent editor's draft and streaming

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): close the agent editor on a version restore, as the resource editor does

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): stop the provider picker auto-writing a kind, and clear review nits

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(frontend): drop the fork-for-edit leftovers from the agent card

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): mount the agent editor in the dev flow editor and guard the deep-link race

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): deploy the agent config that was submitted, and refuse one no run could use

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(frontend): build the agent editor's rows from the design-system button

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): keep a draft-only agent's draft, and let a blank MCP summary deploy

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): guard read-only agents, incomplete MCP tools and duplicate editor mounts

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: read-only agent editor, linked-card refresh, atomic eval rename

* fix: eval rename needs the privileged pool, per-workspace write access

* fix(frontend): drop the agent editor target when its mount goes away

* refactor: drop the agent rename work from this PR, unban the bindable defaults

* fix(frontend): refuse a renaming deploy and drop the copilot from static-only fields

* fix(frontend): mirror the worker's streaming rule and scope agent writes to their target

* fix(frontend): read runtime streaming as off and reset the drawer's json view

* fix(frontend): read an unsettled output_type as non-streaming too

* fix(frontend): let the showing modal claim an agent opened from inside it

* fix(frontend): keep in-flight edits, tool replacements and every linked step in sync

* fix(frontend): keep attachments in the run form and bind the agent ref to its tools

* fix(frontend): preview the agent as authored and re-evaluate step args on run

* fix(frontend): scope agent-editor ownership to the flow's workspace

* fix(frontend): drop the tool drill-in where there is no graph to select on

* fix(frontend): require a provider kind and keep one resource editor open at a time

* fix(frontend): keep legacy nulls, static-only text literal, and the handover anchor

* test(worker): pin the agent streaming default

* fix(frontend): let an AI-fillable input be switched to static

* fix(frontend): report agent editor background failures instead of floating them

* fix(frontend): keep the version pane's path alive while the editor closes

* fix(frontend): clear the anchor-keep flag at the start of each drawer session

* fix(frontend): preview the agent without its synthetic path, refresh the baseline on external writes

* refactor(frontend): drop the unverifiable baseline refresh, state the synthetic-path rule

* fix(frontend): keep the synthetic path out of agent tool test runs too

* refactor(frontend): host the agent editor under the agent's own path

* fix(frontend): mark an agent editor's host explicitly instead of inferring it from the path

* fix(frontend): discard linked-agent responses from before a deploy

* fix(frontend): keep a flow mount from claiming an agent editor's nested target

* feat(frontend): keep an agent used as a tool inside the agent being edited

* fix(frontend): reserve the agent editor's root module id

* docs(frontend): record why the agent editor previews under the agent's path

* fix(frontend): refuse to open or deploy a resource that is not an agent

* docs(frontend): put the scope-migration comment on the function it describes

* fix(frontend): refuse an agent path whose resource type is not proven

* fix(frontend): recheck the resource type before deploying, and keep expressions off static-only inputs

* fix(frontend): lazy-load the agent editor and slide its levels like the evals pane

* refactor: drop unreachable non-list tools check from agent deploy

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): clear text-only agent fields on image output, reserve the root id

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): keep the agent editor usable for a non-list tools value

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): stop the parked eval run list from taking arrow keys

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): report a non-list tools value on deploy instead of throwing

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): keep temperature editable for image output

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): skip non-object tool entries when rendering an agent

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): guard tool entry reads instead of copying the tool array

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(frontend): key tool rows by position so duplicate ids render

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:32:17 +02:00

163 lines
4.8 KiB
Svelte

<script lang="ts">
import type { Schema } from '$lib/common'
import { type InputTransform } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { allTrue, type DynamicInput as DynamicInputTypes } from '$lib/utils'
import { untrack } from 'svelte'
import StepInputsGen from './copilot/StepInputsGen.svelte'
import type { PickableProperties } from './flows/previousResults'
import InputTransformForm from './InputTransformForm.svelte'
import InputTransformPickers from './InputTransformPickers.svelte'
import { useS3StorageConfigured } from './inputTransformEnv.svelte'
import type ItemPicker from './ItemPicker.svelte'
import type VariableEditor from './VariableEditor.svelte'
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
interface Props {
schema: Schema | { properties?: Record<string, any> }
args?: Record<string, InputTransform | any>
isValid?: boolean
extraLib?: string
previousModuleId?: string | undefined
filter?: string[] | undefined
noDynamicToggle?: boolean
/** Passed to every field. See `InputTransformForm` for what each one drops. */
noConnect?: boolean
noJavascript?: boolean
pickableProperties?: PickableProperties | undefined
enableAi?: boolean
class?: string
helperScript?: DynamicInputTypes.HelperScript
isAgentTool?: boolean
allowedAiTransforms?: string[] | undefined
chatInputEnabled?: boolean
workspace?: string | undefined
}
let {
schema = $bindable(),
args = $bindable({}),
isValid = $bindable(true),
extraLib = $bindable('missing extraLib'),
previousModuleId = undefined,
filter = undefined,
noDynamicToggle = false,
noConnect = false,
noJavascript = false,
pickableProperties = undefined,
enableAi = false,
class: clazz = '',
helperScript = undefined,
isAgentTool = false,
allowedAiTransforms = isAgentTool ? undefined : [],
chatInputEnabled = false,
workspace
}: Props = $props()
let ws = $derived(workspace ?? $workspaceStore)
let inputCheck: { [id: string]: boolean } = $state({})
$effect(() => {
isValid = allTrue(inputCheck) ?? false
})
$effect(() => {
if (args == undefined || typeof args !== 'object') {
args = {}
}
})
export function setArgs(nargs: Record<string, InputTransform | any>) {
args = nargs
}
function removeExtraKey() {
const nargs = {}
Object.keys(args ?? {}).forEach((key) => {
if (keys.includes(key)) {
nargs[key] = args[key]
}
})
args = nargs
}
let pickForField: string | undefined = $state()
let itemPicker: ItemPicker | undefined = $state(undefined)
let variableEditor: VariableEditor | undefined = $state(undefined)
const s3Storage = useS3StorageConfigured(() => ws)
let keys: string[] = $state([])
$effect(() => {
let lkeys = Object.keys(schema?.properties ?? {})
if (schema?.properties && JSON.stringify(lkeys) != JSON.stringify(keys)) {
keys = lkeys
untrack(() => removeExtraKey())
}
})
</script>
<div class="w-full mb-6 {clazz}">
<!-- Not offered on a tool: the fields it fills are exactly the ones left empty for the agent
to fill at run time, and filling one rewrites it into an expression, which takes it out of
the schema the model is given. -->
{#if enableAi && !isAgentTool}
<div class="pt-2">
<StepInputsGen
{pickableProperties}
argNames={keys
? keys.filter(
(argName) =>
Object.keys(schema.properties ?? {}).includes(argName) &&
Object.keys(args ?? {}).includes(argName) &&
((args[argName].type === 'static' && !args[argName].value) ||
(args[argName].type === 'javascript' && !args[argName].expr))
)
: []}
{schema}
/>
</div>
{/if}
{#if keys.length > 0}
{#each keys as argName, index (argName)}
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
<ResizeTransitionWrapper class="mt-6" innerClass="w-full" vertical>
<InputTransformForm
{previousModuleId}
bind:arg={args[argName]}
bind:schema
bind:argName={keys[index]}
argExtra={schema.properties?.[argName] ?? {}}
bind:inputCheck={
() => inputCheck[argName] ?? false, (value) => (inputCheck[argName] = value)
}
bind:extraLib
{variableEditor}
{itemPicker}
bind:pickForField
{noDynamicToggle}
{noConnect}
{noJavascript}
{pickableProperties}
{enableAi}
{helperScript}
{isAgentTool}
{allowedAiTransforms}
s3StorageConfigured={s3Storage.current}
{chatInputEnabled}
{workspace}
otherArgs={Object.fromEntries(
Object.entries(args ?? {}).filter(([key]) => key !== argName)
)}
/>
</ResizeTransitionWrapper>
{/if}
{/each}
{:else}
<div class="text-primary text-xs mt-2">No inputs</div>
{/if}
</div>
<InputTransformPickers {args} {pickForField} {workspace} bind:itemPicker bind:variableEditor />