fix: inline agent panel not always show

This commit is contained in:
Joel 2026-06-23 14:49:27 +08:00
parent 31ee7bb467
commit ce5033c554
6 changed files with 76 additions and 26 deletions

View File

@ -89,6 +89,7 @@ const CandidateNodeMain: FC<Props> = ({
}
if (shouldCreateInlineAgentBinding) {
workflowStore.getState().setOpenInlineAgentPanelNodeId(candidateNode.id)
createInlineAgentBinding(candidateNode.id, {
onError: () => {
const { nodes, setNodes } = collaborativeWorkflow.getState()
@ -105,7 +106,6 @@ const CandidateNodeMain: FC<Props> = ({
delete node.data._isTempNode
}
}))
workflowStore.getState().setOpenInlineAgentPanelNodeId(candidateNode.id)
handleSyncWorkflowDraft(true, true)
},
})

View File

@ -193,6 +193,7 @@ export const useNodesInteractions = () => {
const createInlineAgentBindingForNode = useCallback((nodeId: string, options?: {
onError?: () => void
}) => {
workflowStore.getState().setOpenInlineAgentPanelNodeId(nodeId)
createInlineAgentBinding(nodeId, {
onError: () => {
options?.onError?.()
@ -208,7 +209,6 @@ export const useNodesInteractions = () => {
delete node.data._isTempNode
}
}))
workflowStore.getState().setOpenInlineAgentPanelNodeId(nodeId)
handleSyncWorkflowDraft(true, true)
},
})

View File

@ -27,7 +27,7 @@ const {
mockHandleNodeDataUpdateWithSyncDraft: vi.fn((_payload, options) => options?.callback?.onSuccess?.()),
mockInsertNodes: vi.fn(),
mockOrchestrateDrawerPanelProps: [] as Array<{
agentId: string
agentId?: string
inlineComposerState?: unknown
isInline: boolean
nodeId: string
@ -150,7 +150,7 @@ vi.mock('../hooks', () => ({
vi.mock('../components/agent-orchestrate-drawer-panel', () => ({
AgentOrchestrateDrawerPanel: (props: {
agentId: string
agentId?: string
appId?: string
inlineComposerState?: unknown
isInline: boolean
@ -321,6 +321,7 @@ describe('agent/panel', () => {
})
it('renders a pending inline agent state while the binding is being created', () => {
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
const { container } = render(
<AgentV2Panel
id="agent-node"
@ -337,6 +338,16 @@ describe('agent/panel', () => {
expect(screen.queryByText(/^workflow\.errorMsg\.fieldRequired/)).not.toBeInTheDocument()
expect(container.querySelector('[aria-busy="true"]')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'workflow.nodes.agent.roster.change' })).toBeDisabled()
expect(screen.getByRole('dialog', { name: 'workflow.nodes.agent.roster.inlineSetup.name' })).toBeInTheDocument()
expect(screen.getByRole('region', { name: 'inline-orchestrate-panel' })).toBeInTheDocument()
expect(screen.queryByText('workflow.nodes.agent.roster.editInConsole')).not.toBeInTheDocument()
expect(screen.queryByText('workflow.nodes.agent.roster.makeCopy')).not.toBeInTheDocument()
expect(mockOrchestrateDrawerPanelProps.at(-1)).toMatchObject({
agentId: undefined,
isInline: true,
nodeId: 'agent-node',
open: true,
})
expect(screen.getByText('workflow.nodes.agent.task.label')).toBeInTheDocument()
expect(screen.getByText('workflow.nodes.agent.outputVars.text')).toBeInTheDocument()
expect(container.querySelector('[inert]')).toBeInTheDocument()
@ -570,10 +581,30 @@ describe('agent/panel', () => {
fireEvent.click(screen.getByRole('button', { name: 'workflow.nodes.agent.roster.change' }))
fireEvent.click(screen.getByRole('button', { name: 'Start from Scratch' }))
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith('agent-node')
expect(mockCreateInlineAgentBinding).toHaveBeenCalledWith('agent-node', expect.objectContaining({
onSuccess: expect.any(Function),
}))
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith('agent-node')
expect(mockHandleNodeDataUpdateWithSyncDraft).toHaveBeenCalledWith(
{
id: 'agent-node',
data: expect.objectContaining({
agent_binding: {
binding_type: 'inline_agent',
},
agent_task: 'Keep this task',
agent_declared_outputs: [{
name: 'summary',
type: 'string',
}],
_openInlineAgentPanel: true,
}),
},
expect.objectContaining({
sync: true,
notRefreshWhenSyncError: true,
}),
)
expect(mockHandleNodeDataUpdateWithSyncDraft).toHaveBeenCalledWith(
{
id: 'agent-node',

View File

@ -15,7 +15,7 @@ import { consoleQuery } from '@/service/client'
import { useWorkflowInlineAgentConfigureSync } from '../agent-soul-config'
type AgentOrchestrateDrawerPanelProps = {
agentId: string
agentId?: string
appId?: string
inlineComposerState?: WorkflowAgentComposerResponse
isInline: boolean
@ -40,7 +40,7 @@ function AgentOrchestrateDrawerPanelContent({
open,
}: AgentOrchestrateDrawerPanelProps) {
const rosterComposerQuery = useQuery(consoleQuery.agent.byAgentId.composer.get.queryOptions({
input: open && !isInline
input: open && !isInline && agentId
? {
params: {
agent_id: agentId,
@ -62,12 +62,12 @@ function AgentOrchestrateDrawerPanelContent({
})
useHydrateAgentSoulConfigDraft({
agentId: isInline ? `${nodeId}:${agentId}` : agentId,
agentId: isInline ? `${nodeId}:${agentId ?? 'pending'}` : agentId ?? nodeId,
activeVersionId: activeConfigSnapshot?.id,
config: agentSoulConfig as AgentSoulConfig | undefined,
})
if (!agentSoulConfig) {
if (!agentId || !agentSoulConfig) {
return (
<div className="flex h-full min-h-80 items-center justify-center bg-components-panel-bg">
<Loading type="app" />

View File

@ -40,18 +40,18 @@ export function AgentV2Panel({
const inlineAgentId = inputs.agent_binding?.binding_type === 'inline_agent' ? inputs.agent_binding.agent_id : undefined
const isInlineAgentReady = hasValidInlineAgentBinding(inputs)
const isInlineAgentPending = inputs.agent_binding?.binding_type === 'inline_agent' && !isInlineAgentReady
const isInlineAgentPanelOpen = isInlineAgentReady && openInlineAgentPanelNodeId === id
const isInlineAgentPanelOpen = (isInlineAgentReady || isInlineAgentPending) && openInlineAgentPanelNodeId === id
const rosterAgentQuery = useAgentRosterDetail(rosterAgentId)
const inlineAgentQuery = useWorkflowInlineAgentDetail(id, inlineAgentId)
const { createInlineAgentBinding, isCreatingInlineAgent } = useCreateInlineAgentBinding()
const inlineAgent = inlineAgentQuery.data?.agent
const isAgentPanelOpen = isInlineAgentReady ? isInlineAgentPanelOpen : isRosterAgentPanelOpen
const isInlineAgentLoading = isInlineAgentReady && !inlineAgent
const isAgentPanelOpen = isInlineAgentReady || isInlineAgentPending ? isInlineAgentPanelOpen : isRosterAgentPanelOpen
const isInlineAgentLoading = isInlineAgentPending || (isInlineAgentReady && !inlineAgent)
const isAgentBindingPending = isInlineAgentPending || isCreatingInlineAgent
const canStartFromScratch = inputs.agent_binding?.binding_type !== 'inline_agent'
const displayedAgent = rosterAgentQuery.data ?? (inlineAgentId && isInlineAgentReady
const displayedAgent = rosterAgentQuery.data ?? (isInlineAgentPending || isInlineAgentReady
? {
id: inlineAgentId,
id: inlineAgentId ?? id,
name: inlineAgent?.name || t('nodes.agent.roster.inlineSetup.name', { ns: 'workflow' }),
description: inlineAgent?.description,
role: t('nodes.agent.roster.inlineSetup.type', { ns: 'workflow' }),
@ -114,12 +114,31 @@ export function AgentV2Panel({
}, [handleNodeDataUpdateWithSyncDraft, id, inputs, setOpenInlineAgentPanelNodeId])
const handleStartFromScratch = useCallback(() => {
setIsRosterAgentPanelOpen(false)
setIsInlineAgentPanelOpenedFromTrigger(false)
setOpenInlineAgentPanelNodeId(id)
const pendingInputs = produce(inputsRef.current, (draft) => {
delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
draft.agent_binding = {
binding_type: 'inline_agent',
}
draft._openInlineAgentPanel = true
})
inputsRef.current = pendingInputs
handleNodeDataUpdateWithSyncDraft(
{
id,
data: pendingInputs,
},
{
sync: true,
notRefreshWhenSyncError: true,
},
)
createInlineAgentBinding(id, {
onSuccess: (binding) => {
setIsRosterAgentPanelOpen(false)
setIsInlineAgentPanelOpenedFromTrigger(false)
setOpenInlineAgentPanelNodeId(id)
const newInputs = produce(inputsRef.current, (draft) => {
delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
draft.agent_binding = binding
@ -196,27 +215,27 @@ export function AgentV2Panel({
<div className="border-b border-divider-subtle">
<AgentRosterField
agent={displayedAgent}
agentId={rosterAgentId ?? inlineAgentId ?? undefined}
canOpenPanel={!isInlineAgentPending}
isInlineSetup={isInlineAgentReady}
agentId={rosterAgentId ?? inlineAgentId ?? (isInlineAgentPending ? id : undefined)}
canOpenPanel
isInlineSetup={isInlineAgentReady || isInlineAgentPending}
isLoading={isInlineAgentLoading}
isPanelOpen={isAgentPanelOpen}
isPending={isAgentBindingPending}
panelBody={isAgentPanelOpen && displayedAgent
? (
<AgentOrchestrateDrawerPanel
agentId={displayedAgent.id}
agentId={inlineAgentId ?? rosterAgentId}
appId={appId}
inlineComposerState={inlineAgentQuery.data}
isInline={isInlineAgentReady}
isInline={isInlineAgentReady || isInlineAgentPending}
nodeId={id}
open={isAgentPanelOpen}
/>
)
: undefined}
panelMode={isInlineAgentReady && !isInlineAgentPanelOpenedFromTrigger ? 'setup' : 'detail'}
panelMode={isInlineAgentPending || (isInlineAgentReady && !isInlineAgentPanelOpenedFromTrigger) ? 'setup' : 'detail'}
portalContainerRef={drawerPortalContainerRef}
showPanelDetailActions={!isInlineAgentReady}
showPanelDetailActions={!isInlineAgentReady && !isInlineAgentPending}
onChange={handleRosterChange}
onPanelOpenChange={handleAgentPanelOpenChange}
onStartFromScratch={canStartFromScratch ? handleStartFromScratch : undefined}

View File

@ -125,6 +125,7 @@ const AddBlock = ({
workflowStore.setState({
candidateNode: undefined,
})
workflowStore.getState().setOpenInlineAgentPanelNodeId(newNode.id)
saveStateToHistory(WorkflowHistoryEvent.NodeAdd, { nodeId: newNode.id })
createInlineAgentBinding(newNode.id, {
onSuccess: (binding) => {
@ -138,7 +139,6 @@ const AddBlock = ({
delete node.data._isTempNode
}
}))
workflowStore.getState().setOpenInlineAgentPanelNodeId(newNode.id)
handleSyncWorkflowDraft(true, true)
},
})