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

View File

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

View File

@ -27,7 +27,7 @@ const {
mockHandleNodeDataUpdateWithSyncDraft: vi.fn((_payload, options) => options?.callback?.onSuccess?.()), mockHandleNodeDataUpdateWithSyncDraft: vi.fn((_payload, options) => options?.callback?.onSuccess?.()),
mockInsertNodes: vi.fn(), mockInsertNodes: vi.fn(),
mockOrchestrateDrawerPanelProps: [] as Array<{ mockOrchestrateDrawerPanelProps: [] as Array<{
agentId: string agentId?: string
inlineComposerState?: unknown inlineComposerState?: unknown
isInline: boolean isInline: boolean
nodeId: string nodeId: string
@ -150,7 +150,7 @@ vi.mock('../hooks', () => ({
vi.mock('../components/agent-orchestrate-drawer-panel', () => ({ vi.mock('../components/agent-orchestrate-drawer-panel', () => ({
AgentOrchestrateDrawerPanel: (props: { AgentOrchestrateDrawerPanel: (props: {
agentId: string agentId?: string
appId?: string appId?: string
inlineComposerState?: unknown inlineComposerState?: unknown
isInline: boolean isInline: boolean
@ -321,6 +321,7 @@ describe('agent/panel', () => {
}) })
it('renders a pending inline agent state while the binding is being created', () => { it('renders a pending inline agent state while the binding is being created', () => {
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
const { container } = render( const { container } = render(
<AgentV2Panel <AgentV2Panel
id="agent-node" id="agent-node"
@ -337,6 +338,16 @@ describe('agent/panel', () => {
expect(screen.queryByText(/^workflow\.errorMsg\.fieldRequired/)).not.toBeInTheDocument() expect(screen.queryByText(/^workflow\.errorMsg\.fieldRequired/)).not.toBeInTheDocument()
expect(container.querySelector('[aria-busy="true"]')).toBeInTheDocument() expect(container.querySelector('[aria-busy="true"]')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'workflow.nodes.agent.roster.change' })).toBeDisabled() 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.task.label')).toBeInTheDocument()
expect(screen.getByText('workflow.nodes.agent.outputVars.text')).toBeInTheDocument() expect(screen.getByText('workflow.nodes.agent.outputVars.text')).toBeInTheDocument()
expect(container.querySelector('[inert]')).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: 'workflow.nodes.agent.roster.change' }))
fireEvent.click(screen.getByRole('button', { name: 'Start from Scratch' })) fireEvent.click(screen.getByRole('button', { name: 'Start from Scratch' }))
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith('agent-node')
expect(mockCreateInlineAgentBinding).toHaveBeenCalledWith('agent-node', expect.objectContaining({ expect(mockCreateInlineAgentBinding).toHaveBeenCalledWith('agent-node', expect.objectContaining({
onSuccess: expect.any(Function), 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( expect(mockHandleNodeDataUpdateWithSyncDraft).toHaveBeenCalledWith(
{ {
id: 'agent-node', id: 'agent-node',

View File

@ -15,7 +15,7 @@ import { consoleQuery } from '@/service/client'
import { useWorkflowInlineAgentConfigureSync } from '../agent-soul-config' import { useWorkflowInlineAgentConfigureSync } from '../agent-soul-config'
type AgentOrchestrateDrawerPanelProps = { type AgentOrchestrateDrawerPanelProps = {
agentId: string agentId?: string
appId?: string appId?: string
inlineComposerState?: WorkflowAgentComposerResponse inlineComposerState?: WorkflowAgentComposerResponse
isInline: boolean isInline: boolean
@ -40,7 +40,7 @@ function AgentOrchestrateDrawerPanelContent({
open, open,
}: AgentOrchestrateDrawerPanelProps) { }: AgentOrchestrateDrawerPanelProps) {
const rosterComposerQuery = useQuery(consoleQuery.agent.byAgentId.composer.get.queryOptions({ const rosterComposerQuery = useQuery(consoleQuery.agent.byAgentId.composer.get.queryOptions({
input: open && !isInline input: open && !isInline && agentId
? { ? {
params: { params: {
agent_id: agentId, agent_id: agentId,
@ -62,12 +62,12 @@ function AgentOrchestrateDrawerPanelContent({
}) })
useHydrateAgentSoulConfigDraft({ useHydrateAgentSoulConfigDraft({
agentId: isInline ? `${nodeId}:${agentId}` : agentId, agentId: isInline ? `${nodeId}:${agentId ?? 'pending'}` : agentId ?? nodeId,
activeVersionId: activeConfigSnapshot?.id, activeVersionId: activeConfigSnapshot?.id,
config: agentSoulConfig as AgentSoulConfig | undefined, config: agentSoulConfig as AgentSoulConfig | undefined,
}) })
if (!agentSoulConfig) { if (!agentId || !agentSoulConfig) {
return ( return (
<div className="flex h-full min-h-80 items-center justify-center bg-components-panel-bg"> <div className="flex h-full min-h-80 items-center justify-center bg-components-panel-bg">
<Loading type="app" /> <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 inlineAgentId = inputs.agent_binding?.binding_type === 'inline_agent' ? inputs.agent_binding.agent_id : undefined
const isInlineAgentReady = hasValidInlineAgentBinding(inputs) const isInlineAgentReady = hasValidInlineAgentBinding(inputs)
const isInlineAgentPending = inputs.agent_binding?.binding_type === 'inline_agent' && !isInlineAgentReady 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 rosterAgentQuery = useAgentRosterDetail(rosterAgentId)
const inlineAgentQuery = useWorkflowInlineAgentDetail(id, inlineAgentId) const inlineAgentQuery = useWorkflowInlineAgentDetail(id, inlineAgentId)
const { createInlineAgentBinding, isCreatingInlineAgent } = useCreateInlineAgentBinding() const { createInlineAgentBinding, isCreatingInlineAgent } = useCreateInlineAgentBinding()
const inlineAgent = inlineAgentQuery.data?.agent const inlineAgent = inlineAgentQuery.data?.agent
const isAgentPanelOpen = isInlineAgentReady ? isInlineAgentPanelOpen : isRosterAgentPanelOpen const isAgentPanelOpen = isInlineAgentReady || isInlineAgentPending ? isInlineAgentPanelOpen : isRosterAgentPanelOpen
const isInlineAgentLoading = isInlineAgentReady && !inlineAgent const isInlineAgentLoading = isInlineAgentPending || (isInlineAgentReady && !inlineAgent)
const isAgentBindingPending = isInlineAgentPending || isCreatingInlineAgent const isAgentBindingPending = isInlineAgentPending || isCreatingInlineAgent
const canStartFromScratch = inputs.agent_binding?.binding_type !== 'inline_agent' 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' }), name: inlineAgent?.name || t('nodes.agent.roster.inlineSetup.name', { ns: 'workflow' }),
description: inlineAgent?.description, description: inlineAgent?.description,
role: t('nodes.agent.roster.inlineSetup.type', { ns: 'workflow' }), role: t('nodes.agent.roster.inlineSetup.type', { ns: 'workflow' }),
@ -114,12 +114,31 @@ export function AgentV2Panel({
}, [handleNodeDataUpdateWithSyncDraft, id, inputs, setOpenInlineAgentPanelNodeId]) }, [handleNodeDataUpdateWithSyncDraft, id, inputs, setOpenInlineAgentPanelNodeId])
const handleStartFromScratch = useCallback(() => { 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, { createInlineAgentBinding(id, {
onSuccess: (binding) => { onSuccess: (binding) => {
setIsRosterAgentPanelOpen(false)
setIsInlineAgentPanelOpenedFromTrigger(false)
setOpenInlineAgentPanelNodeId(id)
const newInputs = produce(inputsRef.current, (draft) => { const newInputs = produce(inputsRef.current, (draft) => {
delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
draft.agent_binding = binding draft.agent_binding = binding
@ -196,27 +215,27 @@ export function AgentV2Panel({
<div className="border-b border-divider-subtle"> <div className="border-b border-divider-subtle">
<AgentRosterField <AgentRosterField
agent={displayedAgent} agent={displayedAgent}
agentId={rosterAgentId ?? inlineAgentId ?? undefined} agentId={rosterAgentId ?? inlineAgentId ?? (isInlineAgentPending ? id : undefined)}
canOpenPanel={!isInlineAgentPending} canOpenPanel
isInlineSetup={isInlineAgentReady} isInlineSetup={isInlineAgentReady || isInlineAgentPending}
isLoading={isInlineAgentLoading} isLoading={isInlineAgentLoading}
isPanelOpen={isAgentPanelOpen} isPanelOpen={isAgentPanelOpen}
isPending={isAgentBindingPending} isPending={isAgentBindingPending}
panelBody={isAgentPanelOpen && displayedAgent panelBody={isAgentPanelOpen && displayedAgent
? ( ? (
<AgentOrchestrateDrawerPanel <AgentOrchestrateDrawerPanel
agentId={displayedAgent.id} agentId={inlineAgentId ?? rosterAgentId}
appId={appId} appId={appId}
inlineComposerState={inlineAgentQuery.data} inlineComposerState={inlineAgentQuery.data}
isInline={isInlineAgentReady} isInline={isInlineAgentReady || isInlineAgentPending}
nodeId={id} nodeId={id}
open={isAgentPanelOpen} open={isAgentPanelOpen}
/> />
) )
: undefined} : undefined}
panelMode={isInlineAgentReady && !isInlineAgentPanelOpenedFromTrigger ? 'setup' : 'detail'} panelMode={isInlineAgentPending || (isInlineAgentReady && !isInlineAgentPanelOpenedFromTrigger) ? 'setup' : 'detail'}
portalContainerRef={drawerPortalContainerRef} portalContainerRef={drawerPortalContainerRef}
showPanelDetailActions={!isInlineAgentReady} showPanelDetailActions={!isInlineAgentReady && !isInlineAgentPending}
onChange={handleRosterChange} onChange={handleRosterChange}
onPanelOpenChange={handleAgentPanelOpenChange} onPanelOpenChange={handleAgentPanelOpenChange}
onStartFromScratch={canStartFromScratch ? handleStartFromScratch : undefined} onStartFromScratch={canStartFromScratch ? handleStartFromScratch : undefined}

View File

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