mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 00:03:09 +00:00
* 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>
207 lines
6.6 KiB
Svelte
207 lines
6.6 KiB
Svelte
<script lang="ts">
|
|
import { getContext } from 'svelte'
|
|
import type { FlowEditorContext } from '../types'
|
|
import FlowModuleWrapper from './FlowModuleWrapper.svelte'
|
|
import { moduleSlot, savedModuleById } from '../moduleSlot'
|
|
import FlowSettings from './FlowSettings.svelte'
|
|
import FlowInput from './FlowInput.svelte'
|
|
import FlowFailureModule from './FlowFailureModule.svelte'
|
|
import FlowEnvironmentVariables from './FlowEnvironmentVariables.svelte'
|
|
import type { FlowModule, Flow, Job } from '$lib/gen'
|
|
import FlowPreprocessorModule from './FlowPreprocessorModule.svelte'
|
|
import type { TriggerContext } from '$lib/components/triggers'
|
|
import { insertNewPreprocessorModule } from '../flowStateUtils.svelte'
|
|
import TriggersEditor from '../../triggers/TriggersEditor.svelte'
|
|
import { handleSelectTriggerFromKind, type Trigger } from '$lib/components/triggers/utils'
|
|
import { computeMissingInputWarnings } from '../missingInputWarnings'
|
|
import FlowResult from './FlowResult.svelte'
|
|
import ExpandedSubflowStep from './ExpandedSubflowStep.svelte'
|
|
import type { StateStore } from '$lib/utils'
|
|
import FlowSelectionPanel from './FlowSelectionPanel.svelte'
|
|
import {
|
|
resolveSelectedModuleIds,
|
|
locateModules,
|
|
areContiguousSiblings
|
|
} from '../multiSelectUtils'
|
|
|
|
interface Props {
|
|
noEditor?: boolean
|
|
enableAi?: boolean
|
|
newFlow?: boolean
|
|
disabledFlowInputs?: boolean
|
|
savedFlow?:
|
|
| (Flow & {
|
|
draft?: Flow | undefined
|
|
})
|
|
| undefined
|
|
onDeployTrigger?: (trigger: Trigger) => void
|
|
forceTestTab?: Record<string, boolean>
|
|
highlightArg?: Record<string, string | undefined>
|
|
onTestFlow?: (conversationId?: string) => Promise<string | undefined>
|
|
job?: Job
|
|
isOwner?: boolean
|
|
suspendStatus?: StateStore<Record<string, { job: Job; nb: number }>>
|
|
onOpenDetails?: () => void
|
|
previewOpen?: boolean
|
|
flowModuleSchemaMap?: import('../map/FlowModuleSchemaMap.svelte').default
|
|
}
|
|
|
|
let {
|
|
noEditor = false,
|
|
enableAi = false,
|
|
newFlow = false,
|
|
disabledFlowInputs = false,
|
|
savedFlow = undefined,
|
|
onDeployTrigger = () => {},
|
|
forceTestTab,
|
|
highlightArg,
|
|
onTestFlow,
|
|
job,
|
|
isOwner,
|
|
suspendStatus,
|
|
onOpenDetails,
|
|
previewOpen = false,
|
|
flowModuleSchemaMap = undefined
|
|
}: Props = $props()
|
|
|
|
const {
|
|
selectionManager,
|
|
flowStore,
|
|
flowStateStore,
|
|
flowInputsStore,
|
|
pathStore,
|
|
initialPathStore,
|
|
fakeInitialPath,
|
|
previewArgs,
|
|
flowInputEditorState,
|
|
stepsInputArgs
|
|
} = getContext<FlowEditorContext>('FlowEditorContext')
|
|
|
|
const selectedId = $derived(selectionManager.getSelectedId())
|
|
|
|
const { showCaptureHint, triggersState, triggersCount } =
|
|
getContext<TriggerContext>('TriggerContext')
|
|
|
|
function checkDup(modules: FlowModule[]): string | undefined {
|
|
let seenModules: string[] = []
|
|
for (const m of modules) {
|
|
if (seenModules.includes(m.id)) {
|
|
console.error(`Duplicate module id: ${m.id}`)
|
|
return m.id
|
|
}
|
|
seenModules.push(m.id)
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
computeMissingInputWarnings(flowStore, flowStateStore.val, flowInputsStore)
|
|
})
|
|
|
|
// Derived state for multi-select operations in the side panel
|
|
let resolvedModuleIds = $derived(
|
|
resolveSelectedModuleIds(selectionManager.selectedIds, flowStore.val.value.modules ?? [])
|
|
)
|
|
let canMoveSelected = $derived(
|
|
resolvedModuleIds.length > 0 &&
|
|
areContiguousSiblings(locateModules(resolvedModuleIds, flowStore.val.value.modules ?? []))
|
|
)
|
|
</script>
|
|
|
|
{#if selectionManager && selectionManager.selectedIds.length > 1}
|
|
<FlowSelectionPanel
|
|
{selectionManager}
|
|
{noEditor}
|
|
onDeleteSelected={() => flowModuleSchemaMap?.deleteMultiple(resolvedModuleIds)}
|
|
onDuplicateSelected={() => flowModuleSchemaMap?.duplicateMultiple(resolvedModuleIds)}
|
|
onMoveSelected={() => flowModuleSchemaMap?.moveMultiple(resolvedModuleIds)}
|
|
onCreateGroup={() => flowModuleSchemaMap?.createGroup(selectionManager.selectedIds)}
|
|
{canMoveSelected}
|
|
resolvedCount={resolvedModuleIds.length}
|
|
/>
|
|
{:else if selectedId?.startsWith('settings')}
|
|
<FlowSettings {enableAi} {noEditor} />
|
|
{:else if selectedId === 'Input'}
|
|
<FlowInput
|
|
{noEditor}
|
|
disabled={disabledFlowInputs}
|
|
on:openTriggers={(ev) => {
|
|
selectionManager.selectId('Trigger', { openPanel: true })
|
|
handleSelectTriggerFromKind(triggersState, triggersCount, savedFlow?.path, ev.detail.kind)
|
|
showCaptureHint.set(true)
|
|
}}
|
|
on:applyArgs
|
|
{onTestFlow}
|
|
{previewOpen}
|
|
{flowModuleSchemaMap}
|
|
/>
|
|
{:else if selectedId === 'Result'}
|
|
<FlowResult {noEditor} {job} {isOwner} {suspendStatus} {onOpenDetails} />
|
|
{:else if selectedId === 'constants'}
|
|
<FlowEnvironmentVariables {noEditor} />
|
|
{:else if selectedId === 'failure'}
|
|
<FlowFailureModule {noEditor} savedModule={savedFlow?.value.failure_module} />
|
|
{:else if selectedId === 'preprocessor'}
|
|
<FlowPreprocessorModule {noEditor} savedModule={savedFlow?.value.preprocessor_module} />
|
|
{:else if selectedId === 'Trigger'}
|
|
<TriggersEditor
|
|
on:applyArgs
|
|
on:addPreprocessor={async (e) => {
|
|
await insertNewPreprocessorModule(flowStore, flowStateStore, {
|
|
language: 'bun'
|
|
})
|
|
stepsInputArgs.setStepArgs('preprocessor', e.detail.args ?? {})
|
|
selectionManager.selectId('preprocessor')
|
|
}}
|
|
on:updateSchema={(e) => {
|
|
const { payloadData, redirect } = e.detail
|
|
if (payloadData) {
|
|
previewArgs.val = JSON.parse(JSON.stringify(payloadData))
|
|
}
|
|
if (redirect) {
|
|
selectionManager.selectId('Input')
|
|
$flowInputEditorState.selectedTab = 'captures'
|
|
$flowInputEditorState.payloadData = payloadData
|
|
}
|
|
}}
|
|
on:testWithArgs
|
|
currentPath={$pathStore}
|
|
initialPath={$initialPathStore}
|
|
{fakeInitialPath}
|
|
{noEditor}
|
|
newItem={newFlow}
|
|
isFlow={true}
|
|
hasPreprocessor={!!flowStore.val.value.preprocessor_module}
|
|
canHavePreprocessor={true}
|
|
args={previewArgs.val}
|
|
isDeployed={savedFlow && !savedFlow?.draft_only}
|
|
schema={flowStore.val.schema}
|
|
{onDeployTrigger}
|
|
/>
|
|
{:else if selectedId?.startsWith('subflow:')}
|
|
<ExpandedSubflowStep
|
|
{selectedId}
|
|
onSubflowUpdated={() => flowModuleSchemaMap?.reloadExpandedSubflows()}
|
|
/>
|
|
{:else}
|
|
{@const dup = checkDup(flowStore.val.value.modules)}
|
|
{#if dup}
|
|
<div class="text-red-600 text-xl p-2">There are duplicate modules in the flow at id: {dup}</div>
|
|
{:else}
|
|
{#key selectedId}
|
|
{#each flowStore.val.value.modules as flowModule, index (flowModule.id ?? index)}
|
|
{@const slot = moduleSlot(() => flowStore.val.value.modules, flowModule.id, flowModule)}
|
|
<FlowModuleWrapper
|
|
{noEditor}
|
|
bind:flowModule={slot.get, slot.set}
|
|
previousModule={flowStore.val.value.modules[index - 1]}
|
|
{enableAi}
|
|
savedModule={savedModuleById(savedFlow?.value.modules, flowModule.id)}
|
|
{forceTestTab}
|
|
{highlightArg}
|
|
{flowModuleSchemaMap}
|
|
/>
|
|
{/each}
|
|
{/key}
|
|
{/if}
|
|
{/if}
|