mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-22 00:03:35 +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>
230 lines
7.2 KiB
Svelte
230 lines
7.2 KiB
Svelte
<script lang="ts">
|
|
import { Badge, Tab, Tabs } from '$lib/components/common'
|
|
import { refreshStateStore } from '$lib/svelte5Utils.svelte'
|
|
import { GripVertical, Plus, Trash2 } from 'lucide-svelte'
|
|
import { getContext } from 'svelte'
|
|
import type { FlowEditorContext } from '../types'
|
|
import {
|
|
addBranch as addBranchOp,
|
|
removeBranch as removeBranchOp,
|
|
reorderBranches as reorderBranchesOp,
|
|
graphBranchIndex
|
|
} from '../branchOps'
|
|
import Button from '$lib/components/common/button/Button.svelte'
|
|
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
|
import { dragHandle, dragHandleZone } from '@windmill-labs/svelte-dnd-action'
|
|
import { randomUUID } from '$lib/utils/uuid'
|
|
import Toggle from '$lib/components/Toggle.svelte'
|
|
import StepSettingsBadges from './StepSettingsBadges.svelte'
|
|
import { tick, untrack } from 'svelte'
|
|
|
|
import type { BranchAll, FlowModule } from '$lib/gen'
|
|
import FlowCard from '../common/FlowCard.svelte'
|
|
import FlowRunSettings from './FlowRunSettings.svelte'
|
|
import { useUiIntent } from '$lib/components/copilot/chat/flow/useUiIntent'
|
|
|
|
interface Props {
|
|
noEditor: boolean
|
|
flowModule: FlowModule
|
|
previousModule: FlowModule | undefined
|
|
parentModule: FlowModule | undefined
|
|
}
|
|
|
|
let { noEditor, flowModule = $bindable(), previousModule, parentModule }: Props = $props()
|
|
|
|
let value = $state(flowModule.value as BranchAll)
|
|
$effect(() => {
|
|
value = flowModule.value as BranchAll
|
|
})
|
|
|
|
// dnd needs a stable id per item; branches have none and must not gain one (it would
|
|
// land in the saved flow), so ids are held beside them, keyed by object identity.
|
|
const branchIds = new WeakMap<object, string>()
|
|
function idFor(branch: object): string {
|
|
let id = branchIds.get(branch)
|
|
if (!id) {
|
|
id = randomUUID()
|
|
branchIds.set(branch, id)
|
|
}
|
|
return id
|
|
}
|
|
|
|
let items = $state(value.branches.map((b) => ({ id: idFor(b), branch: b })))
|
|
// dnd owns `items` for the length of a gesture: mid-drag it holds a shadow placeholder
|
|
// alongside the real entries, so rebuilding from `value.branches` there would splice a
|
|
// second copy of the dragged branch into the list (duplicate keys).
|
|
let dragging = false
|
|
|
|
$effect(() => {
|
|
const next = value.branches.map((b) => ({ id: idFor(b), branch: b }))
|
|
// untrack: this reads and writes `items`, which would otherwise re-invalidate itself.
|
|
untrack(() => {
|
|
if (dragging) return
|
|
const same = next.length === items.length && next.every((it, i) => it.id === items[i].id)
|
|
if (!same) items = next
|
|
})
|
|
})
|
|
|
|
function handleConsider(e: CustomEvent<{ items: typeof items }>) {
|
|
dragging = true
|
|
items = e.detail.items
|
|
}
|
|
function handleFinalize(e: CustomEvent<{ items: typeof items }>) {
|
|
items = e.detail.items
|
|
reorderBranchesOp(
|
|
flowModule.id,
|
|
items.map((it) => it.branch),
|
|
{ flowStore, history }
|
|
)
|
|
dragging = false
|
|
}
|
|
|
|
const { flowStore, flowStateStore, history } = getContext<FlowEditorContext>('FlowEditorContext')
|
|
|
|
function addBranch() {
|
|
addBranchOp(flowModule.id, { flowStore, history })
|
|
refreshStateStore(flowStore)
|
|
}
|
|
function removeBranch(arrayIndex: number) {
|
|
// The shared op counts branches the way the graph does; see graphBranchIndex.
|
|
removeBranchOp(flowModule.id, graphBranchIndex(value.type, arrayIndex), {
|
|
flowStore,
|
|
flowStateStore,
|
|
history
|
|
})
|
|
refreshStateStore(flowStore)
|
|
}
|
|
|
|
let runSettings: FlowRunSettings | undefined = $state(undefined)
|
|
let selectedTab = $state('branches')
|
|
|
|
useUiIntent(`branchall-${flowModule.id}`, {
|
|
openTab: async (tab) => {
|
|
// Every setting the intent can name lives in the other tab, which only mounts
|
|
// `runSettings` once selected.
|
|
selectedTab = 'settings'
|
|
await tick()
|
|
runSettings?.openSetting(tab)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<div class="h-full flex flex-col w-full" id="flow-editor-branch-all-wrapper">
|
|
<FlowCard
|
|
{noEditor}
|
|
title={value.type == 'branchall' ? 'Run all branches' : 'Run one branch'}
|
|
subtitle="Every branch runs. The result of this step is the list of each branch's result."
|
|
subtitleDocLink="https://www.windmill.dev/docs/flows/flow_branches#branch-all"
|
|
>
|
|
<div class="flex h-full min-h-0 flex-col">
|
|
<Tabs bind:selected={selectedTab} wrapperClass="shrink-0">
|
|
<Tab value="branches" label="Branches" />
|
|
<Tab value="settings" label="Run settings">
|
|
{#snippet extra()}
|
|
<StepSettingsBadges {flowModule} />
|
|
{/snippet}
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
<div
|
|
class="flex min-h-0 flex-1 flex-col gap-6 overflow-auto p-4"
|
|
style="scrollbar-gutter: stable"
|
|
>
|
|
{#if selectedTab === 'branches'}
|
|
<section class="flex w-full flex-col gap-4">
|
|
<div>
|
|
<section
|
|
class="flex flex-col gap-3"
|
|
use:dragHandleZone={{ items, flipDurationMs: 150, dropTargetStyle: {} }}
|
|
onconsider={handleConsider}
|
|
onfinalize={handleFinalize}
|
|
>
|
|
{#each items as item, i (item.id)}
|
|
<!-- The handle and the delete button each own a column, so the row below
|
|
lines up with the summary instead of running under them. -->
|
|
<div
|
|
class="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-0 rounded-md bg-surface-tertiary p-3 shadow-sm"
|
|
>
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
class="cursor-move text-tertiary hover:text-primary"
|
|
use:dragHandle
|
|
aria-label="Reorder branch"
|
|
>
|
|
<GripVertical size={16} />
|
|
</div>
|
|
<div class="flex min-w-0 items-center gap-2">
|
|
<Badge color="blue" class="text-xs">Branch {i + 1}</Badge>
|
|
<TextInput
|
|
size="sm"
|
|
class="grow"
|
|
bind:value={
|
|
() => item.branch.summary ?? '', (v) => (item.branch.summary = String(v))
|
|
}
|
|
inputProps={{ placeholder: 'Summary' }}
|
|
/>
|
|
</div>
|
|
<Button
|
|
unifiedSize="sm"
|
|
variant="subtle"
|
|
destructive
|
|
iconOnly
|
|
startIcon={{ icon: Trash2 }}
|
|
title="Delete branch"
|
|
on:click={() => removeBranch(i)}
|
|
/>
|
|
<div class="col-start-2 py-2">
|
|
<Toggle
|
|
size="xs"
|
|
textClass="text-xs font-normal text-primary"
|
|
bind:checked={item.branch.skip_failure}
|
|
options={{
|
|
right: 'Skip failure'
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</section>
|
|
<Button
|
|
unifiedSize="sm"
|
|
variant="default"
|
|
startIcon={{ icon: Plus }}
|
|
wrapperClasses="mt-4 self-start"
|
|
on:click={addBranch}
|
|
>
|
|
Add branch
|
|
</Button>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="branchall-parallel-{flowModule.id}"
|
|
class="mb-2 block w-fit cursor-pointer text-xs font-semibold text-emphasis"
|
|
>
|
|
Run in parallel
|
|
</label>
|
|
<Toggle
|
|
id="branchall-parallel-{flowModule.id}"
|
|
bind:checked={value.parallel}
|
|
options={{
|
|
right: 'All branches run in parallel'
|
|
}}
|
|
/>
|
|
</div>
|
|
</section>
|
|
{:else}
|
|
<FlowRunSettings
|
|
embedded
|
|
loopSubset
|
|
bind:this={runSettings}
|
|
bind:flowModule
|
|
{parentModule}
|
|
{previousModule}
|
|
selectedId={flowModule.id}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</FlowCard>
|
|
</div>
|