mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 08:02:18 +00:00
feat: restore the link on cancel and simplify the agent bar
Cancel on an agent edit forked the step into a standalone copy, which is the opposite of what the word means and needed a paragraph under the card to explain. It discards the edits and re-links the step now, leaving the agent untouched; diverging from an agent is Unlink's job, on the linked card. This flow's `tool_inputs` survive the round trip as overrides, so Cancel no longer folds them into the tools the way Unlink does. Linking a step to a saved agent happens in the step picker at insert time, so the bar's own resource picker is gone and "Save as agent" is the one action left. Its `+` button was a trap besides: it opened the generic resource form, where an agent would have to be written as raw JSON. The card itself was `surface-secondary`, the sections token, so in dark mode it was darker than the pane and read as a sunken well rather than an elevated card. It uses `surface-tertiary` as the brand table prescribes, its tool chips are `Badge`s, and the editing card no longer overflows the pane and clips its own buttons. The remaining tooltip follows the inline `Label` convention rather than sitting in a flex row whose gap stacked on the trigger's own margin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
55cbd9b0c1
commit
7db58a3945
@@ -461,9 +461,9 @@ pub async fn handle_ai_agent_job(
|
||||
(args, tools)
|
||||
} else {
|
||||
let args = serde_json::from_str::<AIAgentArgs>(&serde_json::to_string(&local_args)?)?;
|
||||
// "Edit" on a linked step clears `agent` but keeps the host's `tool_inputs` until Save or
|
||||
// Cancel folds them back, so overlay them here too: a flow persisted mid-edit must still
|
||||
// bind its tools to this flow's context rather than the agent author's.
|
||||
// "Edit" on a linked step clears `agent` but keeps the host's `tool_inputs`, so overlay them
|
||||
// here too: a flow persisted mid-edit must still bind its tools to this flow's context
|
||||
// rather than the agent author's.
|
||||
let mut tools = module_tools;
|
||||
overlay_tool_inputs(&mut tools, &tool_inputs);
|
||||
(args, tools)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import LightweightResourcePicker from '$lib/components/LightweightResourcePicker.svelte'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
import { ResourceService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
@@ -60,7 +60,6 @@
|
||||
let pathError = $state('')
|
||||
let description = $state('')
|
||||
let saving = $state(false)
|
||||
let pickerValue: string | undefined = $state(undefined)
|
||||
// The path "Save changes" upserts back to, for a step forked from a saved agent. Lives in an
|
||||
// external store so it survives this component unmounting when another node is selected, keyed
|
||||
// by the forked `tools` identity so a stale entry can't resurface (see agentEditStore).
|
||||
@@ -158,20 +157,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
// Link the step as soon as a saved agent is picked. Linking is rigid, so the step keeps no tools
|
||||
// of its own — they come from the resource. The picked value is consumed immediately: a stale
|
||||
// pickerValue must not re-link over an external change to `agent` (undo, session drafts).
|
||||
$effect(() => {
|
||||
if (pickerValue && pickerValue !== agent) {
|
||||
agent = pickerValue
|
||||
pickerValue = undefined
|
||||
tools = []
|
||||
toolInputs = {}
|
||||
// Drop the step's brain transforms; a linked step keeps only the flow-local inputs.
|
||||
inputTransforms = flowLocalInputs(inputTransforms)
|
||||
}
|
||||
})
|
||||
|
||||
function toolLabel(tool: AgentTool): string {
|
||||
return tool.summary || tool.value?.tool_type || tool.id
|
||||
}
|
||||
@@ -276,7 +261,7 @@
|
||||
if (!sameSession) {
|
||||
// The resource is written either way; say so, or the drawer just closes with no outcome.
|
||||
sendUserToast(
|
||||
`Saved ${path}, but the step changed while saving — it was not linked to the agent`,
|
||||
`Saved ${path}, but the step changed while saving, so it was not linked to the agent`,
|
||||
true
|
||||
)
|
||||
return false
|
||||
@@ -285,7 +270,7 @@
|
||||
// from the step too, losing them entirely. Keep the step as-is and let the user save again.
|
||||
if (discardedOnLinkSnapshot() !== savedSnapshot) {
|
||||
sendUserToast(
|
||||
`Saved ${path}, but changes made while saving are not in it — save again to include them`,
|
||||
`Saved ${path}, but changes made while saving are not in it. Save again to include them`,
|
||||
true
|
||||
)
|
||||
return false
|
||||
@@ -377,7 +362,6 @@
|
||||
}
|
||||
tools = forkedTools
|
||||
agent = undefined
|
||||
pickerValue = undefined
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -387,9 +371,9 @@
|
||||
const path = await forkFromResource(true)
|
||||
if (path) {
|
||||
setAgentEditingPath(tools, undefined)
|
||||
sendUserToast('Forked agent — its configuration was copied into this step')
|
||||
sendUserToast('Forked agent. Its configuration was copied into this step')
|
||||
} else {
|
||||
sendUserToast('The step changed while loading the agent — nothing was unlinked', true)
|
||||
sendUserToast('The step changed while loading the agent, so nothing was unlinked', true)
|
||||
}
|
||||
} catch (e) {
|
||||
sendUserToast(`Failed to unlink agent: ${e}`, true)
|
||||
@@ -403,27 +387,28 @@
|
||||
const path = await forkFromResource(false)
|
||||
if (path) {
|
||||
setAgentEditingPath(tools, path)
|
||||
sendUserToast(`Editing ${path} — make changes, then Save changes to update it`)
|
||||
sendUserToast(`Editing ${path}. Make changes, then Save changes to update it`)
|
||||
} else {
|
||||
sendUserToast('The step changed while loading the agent — try Edit again', true)
|
||||
sendUserToast('The step changed while loading the agent. Try Edit again', true)
|
||||
}
|
||||
} catch (e) {
|
||||
sendUserToast(`Failed to edit agent: ${e}`, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel keeps the edits as a standalone step. Edit preserved the flow-local overrides for the
|
||||
// re-link; fold them into the tools (as Unlink does) so the step owns its bindings outright,
|
||||
// leaving nothing for the runtime's unlinked overlay to apply.
|
||||
// Cancel discards the edits and re-links the step, leaving the agent untouched. Diverging from
|
||||
// the agent is Unlink's job, on the linked card. Edit kept this flow's `tool_inputs` off the
|
||||
// forked tools rather than folding them in, so they survive the round trip as overrides.
|
||||
function cancelEdit() {
|
||||
for (const tool of tools) {
|
||||
const overrides = toolInputs?.[tool.id]
|
||||
if (overrides && tool.value?.input_transforms) {
|
||||
tool.value.input_transforms = { ...tool.value.input_transforms, ...overrides }
|
||||
}
|
||||
}
|
||||
toolInputs = {}
|
||||
const path = editingPath
|
||||
// Clear the entry while `tools` is still the fork's array, which is what keys it.
|
||||
setAgentEditingPath(tools, undefined)
|
||||
if (!path) {
|
||||
return
|
||||
}
|
||||
agent = path
|
||||
tools = []
|
||||
inputTransforms = flowLocalInputs(inputTransforms)
|
||||
}
|
||||
let evalsOpen = $state(false)
|
||||
</script>
|
||||
@@ -432,25 +417,15 @@
|
||||
|
||||
<div class="px-2 xl:px-4 pt-2">
|
||||
{#if agent}
|
||||
<div class="rounded-md border border-light bg-surface-secondary px-3 py-2">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<div class="rounded-md border border-light bg-surface-tertiary px-3 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Bot size={16} class="text-primary shrink-0" />
|
||||
<span class="text-secondary shrink-0">Linked to</span>
|
||||
<span class="flex min-w-0 flex-1 items-center gap-1">
|
||||
<a
|
||||
class="font-medium truncate"
|
||||
href={`/resources?path=${agent}&workspace=${ws}`}
|
||||
title={agent}>{agent}</a
|
||||
>
|
||||
<Tooltip small placement="bottom">
|
||||
{#snippet text()}
|
||||
Read-only: the configuration comes from this saved agent, and only the message and
|
||||
inputs are set in this flow. Edit changes the agent everywhere it's used. Unlink forks
|
||||
an editable copy into just this step.
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</span>
|
||||
<div class="ml-auto flex items-center gap-1 shrink-0">
|
||||
<a
|
||||
class="min-w-0 flex-1 truncate text-xs font-medium"
|
||||
href={`/resources?path=${agent}&workspace=${ws}`}
|
||||
title={agent}>{agent}</a
|
||||
>
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="default"
|
||||
@@ -490,12 +465,7 @@
|
||||
<dt class="text-tertiary shrink-0 w-28">Tools</dt>
|
||||
<dd class="flex flex-wrap gap-1">
|
||||
{#each inheritedTools as tool (tool.id)}
|
||||
<span
|
||||
class="inline-flex items-center rounded border border-light bg-surface px-1.5 py-0.5 text-secondary"
|
||||
title={tool.id}
|
||||
>
|
||||
{toolLabel(tool)}
|
||||
</span>
|
||||
<Badge color="gray" title={tool.id}>{toolLabel(tool)}</Badge>
|
||||
{/each}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -513,13 +483,24 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else if editingPath}
|
||||
<div
|
||||
class="flex items-center gap-2 rounded-md border border-light bg-surface-secondary px-3 py-2 text-xs"
|
||||
>
|
||||
<Pencil size={16} class="text-primary shrink-0" />
|
||||
<span class="text-secondary">Editing</span>
|
||||
<span class="font-medium truncate" title={editingPath}>{editingPath}</span>
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<div class="rounded-md border border-light bg-surface-tertiary px-3 py-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<Pencil size={16} class="text-primary shrink-0 mt-0.5" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-xs font-medium" title={editingPath}>{editingPath}</div>
|
||||
<div class="text-2xs text-secondary">
|
||||
Saving affects every flow using it<Tooltip small placement="bottom">
|
||||
{#snippet text()}
|
||||
Save changes writes back to the saved agent and re-links this step, so every flow
|
||||
linking to it picks the change up. Cancel discards the edits and re-links it
|
||||
unchanged.
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center justify-end gap-1">
|
||||
<Button size="xs2" variant="default" onclick={cancelEdit}>Cancel</Button>
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="accent"
|
||||
@@ -529,33 +510,24 @@
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
<Button size="xs2" variant="default" onclick={cancelEdit}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-2xs text-tertiary mt-1">
|
||||
Editing the saved agent. Save changes updates it and re-links this step — the update
|
||||
propagates to every flow that links to it. Cancel keeps your edits here as a standalone step
|
||||
instead.
|
||||
</p>
|
||||
{#if providerSaveError}
|
||||
<p class="text-2xs text-red-600 dark:text-red-400 mt-1">
|
||||
<p class="text-2xs text-red-500 mt-1">
|
||||
{providerSaveError}
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="grow min-w-0">
|
||||
<LightweightResourcePicker
|
||||
bind:value={pickerValue}
|
||||
resourceType="ai_agent"
|
||||
workspace={ws}
|
||||
/>
|
||||
</div>
|
||||
<span class="text-2xs text-tertiary">or</span>
|
||||
<Button size="xs2" variant="default" startIcon={{ icon: Save }} onclick={openSave}>
|
||||
Save as agent
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
size="xs2"
|
||||
variant="default"
|
||||
startIcon={{ icon: Save }}
|
||||
wrapperClasses="w-full"
|
||||
btnClasses="w-full"
|
||||
onclick={openSave}
|
||||
>
|
||||
Save as agent
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user