mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-21 16:03:26 +00:00
feat: name the action on chat tool confirmation buttons (#11795)
* feat: name the action on chat tool confirmation buttons Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: size chat confirmation footer buttons with unifiedSize and the UI font Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ed589963f4
commit
7d01d252b5
@@ -977,7 +977,7 @@ the panel, or the Escape-to-stop focus check would wrongly reject them. -->
|
||||
<div class="absolute -top-10 w-full flex flex-row justify-center gap-2">
|
||||
<Button
|
||||
startIcon={{ icon: CheckIcon }}
|
||||
size="xs"
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
btnClasses="bg-green-500 hover:bg-green-600 text-white hover:text-white"
|
||||
onclick={() => {
|
||||
@@ -989,7 +989,7 @@ the panel, or the Escape-to-stop focus check would wrongly reject them. -->
|
||||
<div class="rounded bg-surface">
|
||||
<Button
|
||||
startIcon={{ icon: XIcon }}
|
||||
size="xs"
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
btnClasses="dark:opacity-50 opacity-60 hover:opacity-100"
|
||||
onclick={() => {
|
||||
|
||||
@@ -34,10 +34,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class={twMerge('flex flex-row items-center justify-end gap-2', className)}>
|
||||
<!-- font-main: the tool cards around it are font-mono, which reads as bold on a button label. -->
|
||||
<div class={twMerge('flex flex-row items-center justify-end gap-2 font-main', className)}>
|
||||
<Button
|
||||
variant="default"
|
||||
size="xs"
|
||||
unifiedSize="sm"
|
||||
destructive={rejectDestructive}
|
||||
startIcon={rejectIcon ? { icon: rejectIcon } : undefined}
|
||||
on:click={() => respond(false)}
|
||||
@@ -46,7 +47,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
variant="accent"
|
||||
size="xs"
|
||||
unifiedSize="sm"
|
||||
startIcon={confirmIcon ? { icon: confirmIcon } : undefined}
|
||||
on:click={() => respond(true)}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
Loader2,
|
||||
ChevronRight,
|
||||
XCircle,
|
||||
Play,
|
||||
ClipboardList,
|
||||
Check,
|
||||
CircleMinus,
|
||||
@@ -43,6 +42,7 @@
|
||||
import RunScriptCard from './RunScriptCard.svelte'
|
||||
import ToolDiffCard from './ToolDiffCard.svelte'
|
||||
import { hasToolCodeDiff } from './toolCodeDiff'
|
||||
import { TOOL_CONFIRM_ACTIONS } from './toolConfirmAction'
|
||||
import WebSearchSourcesDisplay from './WebSearchSourcesDisplay.svelte'
|
||||
import ExpandableImage from '$lib/components/common/image/ExpandableImage.svelte'
|
||||
import McpServerIcon from '$lib/components/mcp/McpServerIcon.svelte'
|
||||
@@ -92,6 +92,8 @@
|
||||
// The preview pane's renderers, so a model-written link opens in a new tab either way.
|
||||
const planPlugins = [gfmPlugin(), { renderer: { pre: CodeDisplay, a: LinkRenderer } }]
|
||||
|
||||
const confirmAction = $derived(TOOL_CONFIRM_ACTIONS[message.confirmAction ?? 'confirm'])
|
||||
|
||||
const hasParameters = $derived(
|
||||
message.parameters !== undefined && Object.keys(message.parameters).length > 0
|
||||
)
|
||||
@@ -118,7 +120,7 @@
|
||||
// stream, while it waits on the user, and once it has an error or details to keep in view.
|
||||
// The user's toggle lives in state keyed by call id: the `message` prop is replaced on every
|
||||
// chat update, so an override written onto a derived would be recomputed away. A pending
|
||||
// confirmation wins over it, since the Run/Reject footer renders inside the card.
|
||||
// confirmation wins over it, since the confirm/reject footer renders inside the card.
|
||||
let toggled = $state<{ id: string | undefined; open: boolean } | undefined>(undefined)
|
||||
const isExpanded = $derived(
|
||||
Boolean(message.isLoading && message.needsConfirmation) ||
|
||||
@@ -285,7 +287,7 @@
|
||||
</div>
|
||||
<!-- Collapsed: the buttons stand on their own rather than boxing empty space. -->
|
||||
{:else if message.needsConfirmation}
|
||||
{@render confirmFooter('py-1 font-main')}
|
||||
{@render confirmFooter('py-1')}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
@@ -373,8 +375,8 @@
|
||||
toolCallId={message.tool_call_id}
|
||||
rejectIcon={XCircle}
|
||||
rejectDestructive
|
||||
confirmLabel="Run"
|
||||
confirmIcon={Play}
|
||||
confirmLabel={confirmAction.label}
|
||||
confirmIcon={confirmAction.icon}
|
||||
/>
|
||||
|
||||
<!-- Logs and Result - hide while streaming -->
|
||||
|
||||
@@ -109,6 +109,7 @@ export function createApiTools(
|
||||
def: chatTool,
|
||||
planModeSafe: !!endpoint && ['GET', 'HEAD', 'OPTIONS'].includes(method),
|
||||
requiresConfirmation: needsConfirmation,
|
||||
confirmAction: method === 'DELETE' ? 'delete' : 'confirm',
|
||||
confirmationMessage: `Run ${toolName}`,
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
|
||||
@@ -213,6 +213,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] {
|
||||
requires: RUN_PREVIEW,
|
||||
def: getCreateMigrationToolDef(),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: (args) => (args?.apply === false ? 'create' : 'apply'),
|
||||
confirmationMessage: (args) =>
|
||||
`${args?.version === undefined ? 'Create' : 'Rewrite'}${args?.apply === false ? '' : ' and apply'} data table migration`,
|
||||
queuedLabel: (args) => `Write migration "${args?.name ?? ''}"`,
|
||||
@@ -341,6 +342,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] {
|
||||
requires: RUN_PREVIEW,
|
||||
def: getRunMigrationsToolDef(),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'apply',
|
||||
confirmationMessage: 'Apply pending data table migrations',
|
||||
queuedLabel: (args) => `Apply migrations on "${args?.datatable_name ?? 'datatable'}"`,
|
||||
showDetails: true,
|
||||
@@ -376,6 +378,7 @@ export function getDatatableMigrationTools(): SessionTool<{}>[] {
|
||||
requires: RUN_PREVIEW,
|
||||
def: getRollbackMigrationToolDef(),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'rollback',
|
||||
confirmationMessage: (args) => `Roll back data table migration ${args?.version ?? ''}`,
|
||||
queuedLabel: (args) => `Roll back migration ${args?.version ?? ''}`,
|
||||
showDetails: true,
|
||||
|
||||
@@ -344,6 +344,7 @@ export function getDatatableTools(): SessionTool<{}>[] {
|
||||
requires: RUN_PREVIEW,
|
||||
def: getExecDatatableSqlToolDef(),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: 'Execute SQL on datatable',
|
||||
queuedLabel: (args) => `Execute SQL on "${args?.datatable_name ?? 'datatable'}"`,
|
||||
showDetails: true,
|
||||
|
||||
@@ -441,6 +441,7 @@ export const flowTools: Tool<FlowAIChatHelpers>[] = [
|
||||
return def
|
||||
},
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: 'Run a test of the current flow',
|
||||
showDetails: true,
|
||||
autoCollapseDetails: false
|
||||
@@ -486,6 +487,7 @@ export const flowTools: Tool<FlowAIChatHelpers>[] = [
|
||||
})
|
||||
},
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: (args) => `Run a test of step "${args?.stepId ?? ''}"`,
|
||||
showDetails: true,
|
||||
autoCollapseDetails: false
|
||||
|
||||
@@ -4134,6 +4134,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
'Create a new shared folder (addressable as f/<name>/) in the workspace. The current user is added as an owner.'
|
||||
),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'create',
|
||||
confirmationMessage: 'Create folder',
|
||||
showDetails: true,
|
||||
fn: async ({ args, workspace, toolId, toolCallbacks }) => {
|
||||
@@ -4568,6 +4569,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'deploy',
|
||||
confirmationMessage: 'Deploy draft to workspace',
|
||||
fn: async (ctx) => {
|
||||
const parsed = deployWorkspaceItemSchema.parse(ctx.args)
|
||||
@@ -4650,6 +4652,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'delete',
|
||||
confirmationMessage: 'Delete workspace item',
|
||||
validateBeforeConfirmation: validateDeleteWorkspaceItemTarget,
|
||||
fn: async (ctx) => {
|
||||
@@ -4669,6 +4672,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'discard',
|
||||
confirmationMessage: 'Discard draft',
|
||||
fn: async (ctx) => {
|
||||
const parsed = discardLocalDraftSchema.parse(ctx.args)
|
||||
@@ -4764,6 +4768,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
),
|
||||
showDetails: true,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'create',
|
||||
confirmationMessage: 'Create resource type',
|
||||
fn: async ({ args, workspace, toolId, toolCallbacks }) => {
|
||||
const parsed = createResourceTypeSchema.parse(args)
|
||||
@@ -4944,6 +4949,7 @@ export const globalTools: SessionTool<{}>[] = [
|
||||
return testRunAppRunnable(parsed, ctx)
|
||||
},
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: (args) =>
|
||||
`Run the backend runnable "${args?.key ?? ''}" of ${pathLeaf(args?.path, 'the app')}`,
|
||||
queuedLabel: (args) => `Test runnable "${args?.key ?? ''}" of ${args?.path ?? 'the app'}`,
|
||||
|
||||
@@ -366,6 +366,7 @@ function createCallTool(servers: McpServer[], mode: 'read' | 'write'): SessionTo
|
||||
? { planModeSafe: true }
|
||||
: {
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: (args: any) => `Call ${args?.tool ?? ''} on ${args?.server ?? ''}`
|
||||
}),
|
||||
fn: async ({ args, workspace, toolId, toolCallbacks }) => {
|
||||
|
||||
@@ -105,6 +105,7 @@ export const workspaceSettingsTools: SessionTool<{}>[] = [
|
||||
'Change workspace settings. Keys the yaml omits are left as they are. The user reviews the changes before they apply. Read get_instructions subject "workspace_settings" before your first change.'
|
||||
),
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'apply',
|
||||
confirmationMessage: 'Change workspace settings',
|
||||
cancellationMessage: 'The user declined: no setting was changed.',
|
||||
showDetails: true,
|
||||
|
||||
@@ -568,6 +568,7 @@ export const pipelineTools: SessionTool<PipelineToolHelpers>[] = [
|
||||
requires: RUN_PREVIEW,
|
||||
def: testPipelineNodeToolDef,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: 'Run pipeline node',
|
||||
showDetails: true,
|
||||
autoCollapseDetails: false,
|
||||
|
||||
@@ -1007,6 +1007,7 @@ export const testRunScriptTool: Tool<ScriptChatHelpers> = {
|
||||
})
|
||||
},
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'run',
|
||||
confirmationMessage: 'Run a test of the current script',
|
||||
showDetails: true,
|
||||
autoCollapseDetails: false
|
||||
|
||||
@@ -642,6 +642,16 @@ export type ToolCodeDiff = {
|
||||
beforeLang?: string
|
||||
}
|
||||
|
||||
export type ToolConfirmAction =
|
||||
| 'run'
|
||||
| 'deploy'
|
||||
| 'create'
|
||||
| 'apply'
|
||||
| 'delete'
|
||||
| 'discard'
|
||||
| 'rollback'
|
||||
| 'confirm'
|
||||
|
||||
export type ToolDisplayMessage = {
|
||||
role: 'tool'
|
||||
tool_call_id: string
|
||||
@@ -657,6 +667,8 @@ export type ToolDisplayMessage = {
|
||||
isQueued?: boolean
|
||||
error?: string
|
||||
needsConfirmation?: boolean
|
||||
/** What the confirm button says the call will do. */
|
||||
confirmAction?: ToolConfirmAction
|
||||
showDetails?: boolean
|
||||
autoCollapseDetails?: boolean
|
||||
isStreamingArguments?: boolean
|
||||
@@ -1103,6 +1115,8 @@ export async function processToolCall<T>({
|
||||
typeof tool?.confirmationMessage === 'function'
|
||||
? tool.confirmationMessage(args)
|
||||
: tool?.confirmationMessage
|
||||
const confirmAction =
|
||||
typeof tool?.confirmAction === 'function' ? tool.confirmAction(args) : tool?.confirmAction
|
||||
|
||||
// preAction fires at promotion, not stream time, so its "-ing" label covers
|
||||
// only the execution window — queued cards keep their imperative header.
|
||||
@@ -1111,7 +1125,7 @@ export async function processToolCall<T>({
|
||||
|
||||
toolCallbacks.setToolStatus(toolCall.id, {
|
||||
...(requiresConfirmation
|
||||
? { content: confirmationContent ?? 'Waiting for confirmation...' }
|
||||
? { content: confirmationContent ?? 'Waiting for confirmation...', confirmAction }
|
||||
: {}),
|
||||
parameters: args,
|
||||
isLoading: true,
|
||||
@@ -1287,6 +1301,8 @@ export interface Tool<T> {
|
||||
/** Header shown on the confirmation card before the tool runs. Pass a function
|
||||
* to derive it from the parsed arguments (e.g. name the script being tested). */
|
||||
confirmationMessage?: string | ((args: any) => string)
|
||||
/** The verb on the confirm button; `confirm` when omitted. */
|
||||
confirmAction?: ToolConfirmAction | ((args: any) => ToolConfirmAction)
|
||||
/** Only when a card gates the call, so `fn` must not rely on it. Not awaited, so it may
|
||||
* not throw, and must be safe for a call the user then declines. */
|
||||
onConfirmationRequested?: (p: { args: any; toolCallbacks: ToolCallbacks; toolId: string }) => void
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Check, Play, Plus, Save, Trash2, Undo2 } from 'lucide-svelte'
|
||||
import type { ToolConfirmAction } from './shared'
|
||||
|
||||
export const TOOL_CONFIRM_ACTIONS: Record<ToolConfirmAction, { label: string; icon: any }> = {
|
||||
run: { label: 'Run', icon: Play },
|
||||
deploy: { label: 'Deploy', icon: Save },
|
||||
create: { label: 'Create', icon: Plus },
|
||||
apply: { label: 'Apply', icon: Check },
|
||||
delete: { label: 'Delete', icon: Trash2 },
|
||||
discard: { label: 'Discard', icon: Trash2 },
|
||||
rollback: { label: 'Roll back', icon: Undo2 },
|
||||
confirm: { label: 'Confirm', icon: Check }
|
||||
}
|
||||
@@ -309,6 +309,7 @@ function setToolError(toolCallbacks: ToolCallbacks, toolId: string, error: unkno
|
||||
const createScheduleTool: Tool<any> = {
|
||||
def: createScheduleToolDef,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'create',
|
||||
confirmationMessage: 'Create schedule',
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
@@ -418,6 +419,7 @@ async function resolveEmailTriggerAvailability(): Promise<EmailTriggerAvailabili
|
||||
const createTriggerTool: Tool<any> = {
|
||||
def: createTriggerToolDef,
|
||||
requiresConfirmation: true,
|
||||
confirmAction: 'create',
|
||||
confirmationMessage: 'Create trigger',
|
||||
showDetails: true,
|
||||
showFade: true,
|
||||
|
||||
Reference in New Issue
Block a user