mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-19 08:03:16 +00:00
fix: explain when an agent cannot be run and drop the broken agent move
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
ff42120888
commit
0f60dca4bd
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$lib/base'
|
||||
import Dropdown from '$lib/components/DropdownV2.svelte'
|
||||
import type MoveDrawer from '$lib/components/MoveDrawer.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import DraftBadge from '$lib/components/DraftBadge.svelte'
|
||||
import type ShareModal from '$lib/components/ShareModal.svelte'
|
||||
@@ -13,7 +12,7 @@
|
||||
import { isDeployable } from '$lib/utils_deployable'
|
||||
import { getDeployUiSettings } from '$lib/components/home/deploy_ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { FileUp, FolderInput, Pen, Shield, Trash } from 'lucide-svelte'
|
||||
import { FileUp, Pen, Shield, Trash } from 'lucide-svelte'
|
||||
import Button from '../button/Button.svelte'
|
||||
import Row from './Row.svelte'
|
||||
|
||||
@@ -25,7 +24,6 @@
|
||||
agent: ListableResource & { canWrite: boolean }
|
||||
marked: string | undefined
|
||||
shareModal: ShareModal
|
||||
moveDrawer: MoveDrawer
|
||||
deploymentDrawer: DeployWorkspaceDrawer
|
||||
deleteConfirmedCallback?: (() => void) | undefined
|
||||
depth?: number
|
||||
@@ -37,7 +35,6 @@
|
||||
agent,
|
||||
marked,
|
||||
shareModal,
|
||||
moveDrawer,
|
||||
deploymentDrawer,
|
||||
deleteConfirmedCallback = $bindable(),
|
||||
depth = 0,
|
||||
@@ -113,14 +110,6 @@
|
||||
shareModal.openDrawer?.(path, 'resource')
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Move/Rename',
|
||||
icon: FolderInput,
|
||||
disabled: !agent.canWrite || Boolean(agent.draft_only),
|
||||
action: () => {
|
||||
moveDrawer.openDrawer(path, agent.description, 'resource')
|
||||
}
|
||||
},
|
||||
...(!agent.ws_specific &&
|
||||
!agent.draft_only &&
|
||||
isDeployable('resource', path, await getDeployUiSettings())
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
/** The deployed agent to run rather than a draft to edit: the run pane leads, and the form
|
||||
* beside it only shows the configuration. Fixed for the mount's lifetime. */
|
||||
view?: boolean
|
||||
/** Why this reader cannot run the agent, if they cannot. Shown in place of the run pane. */
|
||||
runBlockedReason?: string
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -76,7 +78,8 @@
|
||||
onSelectTool = undefined,
|
||||
onSaved = undefined,
|
||||
isNew = false,
|
||||
view = false
|
||||
view = false,
|
||||
runBlockedReason = undefined
|
||||
}: Props = $props()
|
||||
|
||||
/** The one module the editor edits. Standalone (no `agent` key) so `initFlowState` loads a
|
||||
@@ -510,95 +513,101 @@
|
||||
{/snippet}
|
||||
|
||||
{#snippet runPane()}
|
||||
<div class="h-full min-h-0 flex flex-col">
|
||||
<!-- Laid out as the script editor's preview column is: what a run takes above what it
|
||||
produced, both alongside what is being edited. -->
|
||||
<div class="flex-1 min-h-0 {testMode === 'chat' ? 'hidden' : ''}">
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={40} minSize={15}>
|
||||
<div class="h-full overflow-auto">
|
||||
<ModulePreview
|
||||
mod={agentModule as FlowModule}
|
||||
schema={flowLocalAgentSchema(schema)}
|
||||
pickableProperties={stepPropPicker?.pickableProperties}
|
||||
runInputKeys={AGENT_EDITOR_RUN_INPUTS}
|
||||
bind:testJob
|
||||
bind:testIsLoading
|
||||
bind:scriptProgress
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={60} minSize={20}>
|
||||
<ModulePreviewResultViewer
|
||||
lang="deno"
|
||||
editor={undefined}
|
||||
diffEditor={undefined}
|
||||
mod={agentModule as FlowModule}
|
||||
{testJob}
|
||||
{testIsLoading}
|
||||
{scriptProgress}
|
||||
disableMock
|
||||
disableHistory
|
||||
/>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
{#if runBlockedReason}
|
||||
<div class="h-full flex items-center justify-center px-8 text-center text-xs text-secondary">
|
||||
{runBlockedReason}
|
||||
</div>
|
||||
{#if chatMounted}
|
||||
<div class={testMode === 'chat' ? 'flex flex-col flex-1 min-h-0' : 'hidden'}>
|
||||
{#if chatGap?.memory}
|
||||
<div class="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center">
|
||||
<MessageCircleOff size={48} class="text-tertiary opacity-50 mb-2" />
|
||||
<p class="text-sm font-semibold text-emphasis">Chat needs managed memory</p>
|
||||
<p class="text-xs text-secondary max-w-xs">
|
||||
{chatGap.memoryCanTurnOn
|
||||
? 'Without it, every message would be answered without the ones before it.'
|
||||
: 'This agent replays a fixed list of messages. Switch its memory to managed to chat with it.'}
|
||||
</p>
|
||||
{#if chatGap.memoryCanTurnOn && !readOnly}
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
btnClasses="bg-surface mt-2"
|
||||
onClick={turnOnMemory}
|
||||
>
|
||||
Turn on managed memory
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if chatGap?.noStream}
|
||||
<div
|
||||
class="shrink-0 flex items-center gap-2 px-4 py-1.5 border-b text-2xs text-secondary"
|
||||
>
|
||||
<Info size={12} class="shrink-0" />
|
||||
<span class="flex-1">
|
||||
{chatGap.noStream === 'image'
|
||||
? 'Image answers do not stream: each one shows once its run ends.'
|
||||
: 'Streaming is off: each answer shows once its run ends.'}
|
||||
</span>
|
||||
{#if chatGap.noStream === 'off' && !readOnly}
|
||||
<Button unifiedSize="2xs" variant="subtle" onClick={turnOnStreaming}>
|
||||
Turn on
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Hidden rather than unmounted while memory is off: switching it off mid-turn must
|
||||
{:else}
|
||||
<div class="h-full min-h-0 flex flex-col">
|
||||
<!-- Laid out as the script editor's preview column is: what a run takes above what it
|
||||
produced, both alongside what is being edited. -->
|
||||
<div class="flex-1 min-h-0 {testMode === 'chat' ? 'hidden' : ''}">
|
||||
<Splitpanes horizontal class="h-full">
|
||||
<Pane size={40} minSize={15}>
|
||||
<div class="h-full overflow-auto">
|
||||
<ModulePreview
|
||||
mod={agentModule as FlowModule}
|
||||
schema={flowLocalAgentSchema(schema)}
|
||||
pickableProperties={stepPropPicker?.pickableProperties}
|
||||
runInputKeys={AGENT_EDITOR_RUN_INPUTS}
|
||||
bind:testJob
|
||||
bind:testIsLoading
|
||||
bind:scriptProgress
|
||||
/>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={60} minSize={20}>
|
||||
<ModulePreviewResultViewer
|
||||
lang="deno"
|
||||
editor={undefined}
|
||||
diffEditor={undefined}
|
||||
mod={agentModule as FlowModule}
|
||||
{testJob}
|
||||
{testIsLoading}
|
||||
{scriptProgress}
|
||||
disableMock
|
||||
disableHistory
|
||||
/>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
{#if chatMounted}
|
||||
<div class={testMode === 'chat' ? 'flex flex-col flex-1 min-h-0' : 'hidden'}>
|
||||
{#if chatGap?.memory}
|
||||
<div class="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center">
|
||||
<MessageCircleOff size={48} class="text-tertiary opacity-50 mb-2" />
|
||||
<p class="text-sm font-semibold text-emphasis">Chat needs managed memory</p>
|
||||
<p class="text-xs text-secondary max-w-xs">
|
||||
{chatGap.memoryCanTurnOn
|
||||
? 'Without it, every message would be answered without the ones before it.'
|
||||
: 'This agent replays a fixed list of messages. Switch its memory to managed to chat with it.'}
|
||||
</p>
|
||||
{#if chatGap.memoryCanTurnOn && !readOnly}
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
btnClasses="bg-surface mt-2"
|
||||
onClick={turnOnMemory}
|
||||
>
|
||||
Turn on managed memory
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if chatGap?.noStream}
|
||||
<div
|
||||
class="shrink-0 flex items-center gap-2 px-4 py-1.5 border-b text-2xs text-secondary"
|
||||
>
|
||||
<Info size={12} class="shrink-0" />
|
||||
<span class="flex-1">
|
||||
{chatGap.noStream === 'image'
|
||||
? 'Image answers do not stream: each one shows once its run ends.'
|
||||
: 'Streaming is off: each answer shows once its run ends.'}
|
||||
</span>
|
||||
{#if chatGap.noStream === 'off' && !readOnly}
|
||||
<Button unifiedSize="2xs" variant="subtle" onClick={turnOnStreaming}>
|
||||
Turn on
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Hidden rather than unmounted while memory is off: switching it off mid-turn must
|
||||
not end the chat following that turn. Test chats, since what runs is the agent as
|
||||
edited. -->
|
||||
<div class={chatGap?.memory ? 'hidden' : 'flex flex-col flex-1 min-h-0'}>
|
||||
<FlowChat
|
||||
onRunFlow={runChatTurn}
|
||||
path={chatPath}
|
||||
conversationKind="test"
|
||||
subject="agent"
|
||||
frame="none"
|
||||
inputSchema={AGENT_CHAT_SCHEMA}
|
||||
flowModules={chatModules}
|
||||
/>
|
||||
<div class={chatGap?.memory ? 'hidden' : 'flex flex-col flex-1 min-h-0'}>
|
||||
<FlowChat
|
||||
onRunFlow={runChatTurn}
|
||||
path={chatPath}
|
||||
conversationKind="test"
|
||||
subject="agent"
|
||||
frame="none"
|
||||
inputSchema={AGENT_CHAT_SCHEMA}
|
||||
flowModules={chatModules}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<!-- A tool is a whole step editor, so it gets a surface of its own rather than a level of the
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
on:change={() => dispatch('reload')}
|
||||
agent={item}
|
||||
{shareModal}
|
||||
{moveDrawer}
|
||||
{deploymentDrawer}
|
||||
{depth}
|
||||
bind:menuOpen
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
enableAi={$copilotInfo.enabled}
|
||||
owns={(t) => t.host === undefined}
|
||||
onClose={(deployed) => goto(deployed ? `${base}/agents/get/${path}` : `${base}/?kind=agent`)}
|
||||
onDeployed={(saved) => goto(`${base}/agents/get/${saved}`)}
|
||||
onDeployed={(saved) =>
|
||||
// Replaced, not pushed: Back would otherwise reopen `new_draft` on an agent that now exists.
|
||||
goto(`${base}/agents/get/${saved}`, { replaceState: isNew })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state'
|
||||
import {
|
||||
Bot,
|
||||
FileUp,
|
||||
FolderInput,
|
||||
FormInput,
|
||||
MessageSquare,
|
||||
Pen,
|
||||
Shield,
|
||||
Trash
|
||||
} from 'lucide-svelte'
|
||||
import { Bot, FileUp, FormInput, MessageSquare, Pen, Shield, Trash } from 'lucide-svelte'
|
||||
import { base } from '$lib/base'
|
||||
import { goto } from '$lib/navigation'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
@@ -19,7 +10,6 @@
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import MoveDrawer from '$lib/components/MoveDrawer.svelte'
|
||||
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
import AgentEditorHost from '$lib/components/flows/content/AgentEditorHost.svelte'
|
||||
import { keepsManagedMemory } from '$lib/components/flows/agentFormFields'
|
||||
@@ -43,9 +33,19 @@
|
||||
let loaded = $derived(agent?.state != undefined)
|
||||
let chatAvailable = $derived(keepsManagedMemory(agent?.state?.args?.memory))
|
||||
let canEdit = $derived(loaded && (agent?.canWrite ?? false) && !$userStore?.operator)
|
||||
// An agent runs as a flow preview, and the server refuses a preview to an operator, and to a
|
||||
// non-admin under another user's namespace (`require_path_read_access_for_preview`).
|
||||
let runBlockedReason = $derived(
|
||||
$userStore?.operator
|
||||
? 'Operators cannot run agents yet.'
|
||||
: !$userStore?.is_admin &&
|
||||
path.startsWith('u/') &&
|
||||
!path.startsWith(`u/${$userStore?.username}/`)
|
||||
? `Only its owner and workspace admins can run an agent under ${path.split('/').slice(0, 2).join('/')}.`
|
||||
: undefined
|
||||
)
|
||||
|
||||
let shareModal: ShareModal | undefined = $state(undefined)
|
||||
let moveDrawer: MoveDrawer | undefined = $state(undefined)
|
||||
let deploymentDrawer: DeployWorkspaceDrawer | undefined = $state(undefined)
|
||||
let deleteOpen = $state(false)
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
|
||||
<ShareModal bind:this={shareModal} />
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
<MoveDrawer bind:this={moveDrawer} on:update={(e) => goto(`${base}/agents/get/${e.detail}`)} />
|
||||
<ConfirmationModal
|
||||
open={deleteOpen}
|
||||
title="Delete agent"
|
||||
@@ -91,7 +90,7 @@
|
||||
<div class="grow"></div>
|
||||
<!-- Only an agent that keeps the conversation can chat: without managed memory every message
|
||||
would be answered alone, so it is run from its inputs and nothing is offered to switch. -->
|
||||
{#if loaded && chatAvailable && testPane?.mode}
|
||||
{#if loaded && chatAvailable && !runBlockedReason && testPane?.mode}
|
||||
<ToggleButtonGroup
|
||||
bind:selected={
|
||||
() => testPane?.mode,
|
||||
@@ -131,12 +130,6 @@
|
||||
disabled: !canEdit,
|
||||
action: () => shareModal?.openDrawer?.(path, 'resource')
|
||||
},
|
||||
{
|
||||
displayName: 'Move/Rename',
|
||||
icon: FolderInput,
|
||||
disabled: !canEdit,
|
||||
action: () => moveDrawer?.openDrawer(path, agent?.state?.description, 'resource')
|
||||
},
|
||||
...(!agent?.state?.wsSpecific &&
|
||||
isDeployable('resource', path, await getDeployUiSettings())
|
||||
? [
|
||||
@@ -178,6 +171,7 @@
|
||||
{toolId}
|
||||
onSelectTool={(id) => (toolId = id)}
|
||||
view
|
||||
{runBlockedReason}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user