mirror of
https://github.com/langgenius/dify.git
synced 2026-06-26 14:51:13 +08:00
chore: inline agent in agent node
This commit is contained in:
parent
0d7d1704f7
commit
855ec62fb2
@ -348,4 +348,52 @@ describe('useWorkflowInlineAgentConfigureSync', () => {
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
it('still saves manually when inline agent autosave is disabled', async () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
mutations: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
const { result } = renderWorkflowHook(() => useWorkflowInlineAgentConfigureSync({
|
||||
nodeId: 'node-1',
|
||||
baseConfig: {
|
||||
schema_version: 1,
|
||||
},
|
||||
autoSaveEnabled: false,
|
||||
enabled: true,
|
||||
}), {
|
||||
queryClient,
|
||||
hooksStoreProps: {
|
||||
configsMap: {
|
||||
flowId: 'app-1',
|
||||
flowType: FlowType.appFlow,
|
||||
fileSettings: {} as never,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
await result.current.saveDraft()
|
||||
})
|
||||
|
||||
expect(mockComposerMutationFn).toHaveBeenCalledWith({
|
||||
params: {
|
||||
app_id: 'app-1',
|
||||
node_id: 'node-1',
|
||||
},
|
||||
body: expect.objectContaining({
|
||||
variant: 'workflow',
|
||||
save_strategy: 'node_job_only',
|
||||
agent_soul: expect.objectContaining({
|
||||
schema_version: 1,
|
||||
}),
|
||||
}),
|
||||
}, expect.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
@ -20,6 +20,7 @@ const {
|
||||
mockSetInputs,
|
||||
mockStoreState,
|
||||
mockUseAgentRosterDetail,
|
||||
mockWorkflowInlineAgentDetailRefetch,
|
||||
mockUseWorkflowInlineAgentDetail,
|
||||
mockUseNodeCrud,
|
||||
} = vi.hoisted(() => ({
|
||||
@ -48,6 +49,7 @@ const {
|
||||
setOpenInlineAgentPanelNodeId: vi.fn(),
|
||||
},
|
||||
mockUseAgentRosterDetail: vi.fn(),
|
||||
mockWorkflowInlineAgentDetailRefetch: vi.fn(),
|
||||
mockUseWorkflowInlineAgentDetail: vi.fn(),
|
||||
mockUseNodeCrud: vi.fn(),
|
||||
}))
|
||||
@ -188,12 +190,39 @@ vi.mock('../components/agent-orchestrate-drawer-panel', () => ({
|
||||
inlineComposerState?: unknown
|
||||
isInline: boolean
|
||||
nodeId: string
|
||||
onClose?: () => void
|
||||
onSaved?: (binding: {
|
||||
agent_id?: string | null
|
||||
binding_type: 'inline_agent' | 'roster_agent'
|
||||
current_snapshot_id?: string | null
|
||||
}) => void
|
||||
onSaveInlineToRoster?: () => void
|
||||
open: boolean
|
||||
}) => {
|
||||
mockOrchestrateDrawerPanelProps.push(props)
|
||||
|
||||
return (
|
||||
<div role="region" aria-label="inline-orchestrate-panel" />
|
||||
<div role="region" aria-label="inline-orchestrate-panel">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onSaveInlineToRoster}
|
||||
>
|
||||
Inline workspace more
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
props.onSaved?.({
|
||||
binding_type: 'inline_agent',
|
||||
agent_id: 'inline-agent-1',
|
||||
current_snapshot_id: 'latest-snapshot-2',
|
||||
})
|
||||
props.onClose?.()
|
||||
}}
|
||||
>
|
||||
Save inline modal
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
@ -339,6 +368,8 @@ describe('agent/panel', () => {
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
isFetching: false,
|
||||
refetch: mockWorkflowInlineAgentDetailRefetch,
|
||||
}))
|
||||
})
|
||||
|
||||
@ -586,6 +617,7 @@ describe('agent/panel', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /^workflow\.nodes\.agent\.roster\.openPanel/ }))
|
||||
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith('agent-node')
|
||||
expect(mockWorkflowInlineAgentDetailRefetch).toHaveBeenCalled()
|
||||
|
||||
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
|
||||
rerender(
|
||||
@ -612,7 +644,7 @@ describe('agent/panel', () => {
|
||||
expect(screen.getByRole('region', { name: 'inline-orchestrate-panel' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens save-to-roster action from the inline drawer menu and rebinds to the saved roster agent', () => {
|
||||
it('opens save-to-roster action from the inline workspace menu and rebinds to the saved roster agent', () => {
|
||||
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
|
||||
render(
|
||||
<AgentV2Panel
|
||||
@ -629,8 +661,8 @@ describe('agent/panel', () => {
|
||||
)
|
||||
|
||||
const panel = screen.getByRole('dialog', { name: 'Workflow Agent 1' })
|
||||
fireEvent.click(within(panel).getByRole('button', { name: 'workflow.nodes.agent.roster.more' }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'agentV2.roster.saveToRoster' }))
|
||||
expect(within(panel).queryByRole('button', { name: 'workflow.nodes.agent.roster.more' })).not.toBeInTheDocument()
|
||||
fireEvent.click(within(panel).getByRole('button', { name: 'Inline workspace more' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save inline agent to roster', hidden: true }))
|
||||
|
||||
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith(undefined)
|
||||
@ -651,6 +683,43 @@ describe('agent/panel', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('updates the inline binding snapshot from the modal save response before closing', () => {
|
||||
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
|
||||
render(
|
||||
<AgentV2Panel
|
||||
id="agent-node"
|
||||
data={createData({
|
||||
agent_binding: {
|
||||
binding_type: 'inline_agent',
|
||||
agent_id: 'inline-agent-1',
|
||||
current_snapshot_id: 'snapshot-1',
|
||||
},
|
||||
})}
|
||||
panelProps={panelProps}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save inline modal' }))
|
||||
|
||||
expect(mockHandleNodeDataUpdateWithSyncDraft).toHaveBeenCalledWith(
|
||||
{
|
||||
id: 'agent-node',
|
||||
data: expect.objectContaining({
|
||||
agent_binding: {
|
||||
binding_type: 'inline_agent',
|
||||
agent_id: 'inline-agent-1',
|
||||
current_snapshot_id: 'latest-snapshot-2',
|
||||
},
|
||||
}),
|
||||
},
|
||||
expect.objectContaining({
|
||||
sync: true,
|
||||
notRefreshWhenSyncError: true,
|
||||
}),
|
||||
)
|
||||
expect(mockStoreState.setOpenInlineAgentPanelNodeId).toHaveBeenCalledWith(undefined)
|
||||
})
|
||||
|
||||
it('does not show start from scratch for an existing inline agent binding', () => {
|
||||
render(
|
||||
<AgentV2Panel
|
||||
@ -673,7 +742,11 @@ describe('agent/panel', () => {
|
||||
|
||||
it('opens the inline panel while workflow composer state is still loading', () => {
|
||||
mockStoreState.openInlineAgentPanelNodeId = 'agent-node'
|
||||
mockUseWorkflowInlineAgentDetail.mockReturnValue({ data: undefined })
|
||||
mockUseWorkflowInlineAgentDetail.mockReturnValue({
|
||||
data: undefined,
|
||||
isFetching: true,
|
||||
refetch: mockWorkflowInlineAgentDetailRefetch,
|
||||
})
|
||||
|
||||
const { container } = render(
|
||||
<AgentV2Panel
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { AgentSoulConfig } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { AgentSoulConfig, WorkflowAgentComposerResponse } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { DefaultModelResponse } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
@ -54,6 +54,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
nodeId,
|
||||
baseConfig,
|
||||
currentModel,
|
||||
autoSaveEnabled = true,
|
||||
enabled,
|
||||
}: {
|
||||
nodeId: string
|
||||
@ -63,6 +64,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
model: string
|
||||
plugin_id?: string
|
||||
}
|
||||
autoSaveEnabled?: boolean
|
||||
enabled: boolean
|
||||
}) {
|
||||
const queryClient = useQueryClient()
|
||||
@ -89,7 +91,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
currentModel: currentModelRef.current,
|
||||
}), [store])
|
||||
|
||||
const saveComposer = useSerialAsyncCallback(async (configSnapshot: AgentSoulConfig) => {
|
||||
const saveComposer = useSerialAsyncCallback(async (configSnapshot: AgentSoulConfig): Promise<WorkflowAgentComposerResponse | undefined> => {
|
||||
if (!configsMap?.flowId || configsMap.flowType !== FlowType.appFlow)
|
||||
return
|
||||
|
||||
@ -121,6 +123,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
setOriginalDraft(agentSoulConfigToFormState(composerState.agent_soul))
|
||||
setDraftSavedAt(Date.now())
|
||||
lastAutosavedDraftKeyRef.current = savedDraftKey
|
||||
return composerState
|
||||
})
|
||||
|
||||
const latestDraftSaveRef = useRef<() => void>(() => undefined)
|
||||
@ -137,7 +140,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
return
|
||||
|
||||
debouncedSaveDraft.cancel?.()
|
||||
await saveComposer(getAgentSoulDraft())
|
||||
return saveComposer(getAgentSoulDraft())
|
||||
}, [debouncedSaveDraft, getAgentSoulDraft, saveComposer])
|
||||
|
||||
useEffect(() => {
|
||||
@ -147,6 +150,7 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
|
||||
if (
|
||||
!enabledRef.current
|
||||
|| !autoSaveEnabled
|
||||
|| !store.get(isAgentComposerDirtyAtom)
|
||||
|| lastAutosavedDraftKeyRef.current === agentSoulDraftKey
|
||||
) {
|
||||
@ -155,13 +159,14 @@ export function useWorkflowInlineAgentConfigureSync({
|
||||
|
||||
debouncedSaveDraft()
|
||||
})
|
||||
}, [debouncedSaveDraft, getAgentSoulDraft, store])
|
||||
}, [autoSaveEnabled, debouncedSaveDraft, getAgentSoulDraft, store])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedSaveDraft.flush?.()
|
||||
if (autoSaveEnabled)
|
||||
debouncedSaveDraft.flush?.()
|
||||
}
|
||||
}, [debouncedSaveDraft])
|
||||
}, [autoSaveEnabled, debouncedSaveDraft])
|
||||
|
||||
return {
|
||||
draftSavedAt,
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import type { AgentConfigSnapshotSummaryResponse, AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { WorkflowAgentComposerResponse } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { AgentComposerBindingResponse, WorkflowAgentComposerResponse } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
@ -29,6 +35,8 @@ type AgentOrchestrateDrawerPanelProps = {
|
||||
isInline: boolean
|
||||
nodeId: string
|
||||
onClose?: () => void
|
||||
onSaved?: (binding: AgentComposerBindingResponse) => void
|
||||
onSaveInlineToRoster?: () => void
|
||||
open: boolean
|
||||
}
|
||||
|
||||
@ -122,8 +130,11 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
inlineComposerState,
|
||||
nodeId,
|
||||
onClose,
|
||||
onSaved,
|
||||
onSaveInlineToRoster,
|
||||
open,
|
||||
}: AgentOrchestrateDrawerPanelProps) {
|
||||
const { t } = useTranslation()
|
||||
const [clearChatList, setClearChatList] = useState(false)
|
||||
const [conversationId, setConversationId] = useState<string | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
@ -137,6 +148,7 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
nodeId,
|
||||
baseConfig: agentSoulConfig,
|
||||
currentModel,
|
||||
autoSaveEnabled: false,
|
||||
enabled: open && !!agentSoulConfig,
|
||||
})
|
||||
const previewAgentSoulConfig = useAgentPreviewSoulConfig(agentSoulConfig as AgentSoulConfig | undefined)
|
||||
@ -161,7 +173,17 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
|
||||
setIsSaving(true)
|
||||
try {
|
||||
await saveDraft()
|
||||
const composerState = await saveDraft()
|
||||
const binding = composerState?.binding
|
||||
if (
|
||||
binding?.binding_type !== 'inline_agent'
|
||||
|| !binding.agent_id
|
||||
|| !binding.current_snapshot_id
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
onSaved?.(binding)
|
||||
onClose?.()
|
||||
}
|
||||
finally {
|
||||
@ -188,6 +210,7 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
<WorkflowInlineAgentConfigureActionBar
|
||||
isSaving={isSaving}
|
||||
onCancel={() => onClose?.()}
|
||||
onSaveInlineToRoster={onSaveInlineToRoster}
|
||||
onSave={() => {
|
||||
void handleSave()
|
||||
}}
|
||||
@ -217,6 +240,16 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
setClearChatList(true)
|
||||
}}
|
||||
showChatFeaturesAction={false}
|
||||
trailingAction={(
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex size-8 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
aria-label={t('operation.close', { ns: 'common' })}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-4" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
chat={(
|
||||
@ -232,7 +265,6 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
draftType="debug_build"
|
||||
onClearChatListChange={setClearChatList}
|
||||
onConversationIdChange={setConversationId}
|
||||
onSaveDraftBeforeRun={saveDraft}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@ -244,10 +276,12 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
|
||||
function WorkflowInlineAgentConfigureActionBar({
|
||||
isSaving,
|
||||
onCancel,
|
||||
onSaveInlineToRoster,
|
||||
onSave,
|
||||
}: {
|
||||
isSaving: boolean
|
||||
onCancel: () => void
|
||||
onSaveInlineToRoster?: () => void
|
||||
onSave: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('common')
|
||||
@ -268,16 +302,28 @@ function WorkflowInlineAgentConfigureActionBar({
|
||||
<div className="flex h-4 items-start px-1">
|
||||
<div className="h-full w-px bg-divider-regular" />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
className="px-2"
|
||||
disabled={isSaving}
|
||||
aria-label={t('operation.more')}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</Button>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={(
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="medium"
|
||||
className="px-2"
|
||||
disabled={isSaving || !onSaveInlineToRoster}
|
||||
aria-label={t('operation.more')}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
<DropdownMenuContent placement="top" sideOffset={4} popupClassName="min-w-44 w-max">
|
||||
<DropdownMenuItem className="gap-2 whitespace-nowrap" onClick={onSaveInlineToRoster}>
|
||||
<span aria-hidden className="i-ri-inbox-archive-line size-4 shrink-0 text-text-tertiary" />
|
||||
<span>{t('roster.saveToRoster', { ns: 'agentV2' })}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
|
||||
@ -3,7 +3,7 @@ import type { AgentRosterNodeData } from '@/app/components/workflow/block-select
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerCloseButton,
|
||||
@ -247,13 +247,11 @@ function AgentRosterDrawer({
|
||||
function AgentRosterInlineConfigureDialog({
|
||||
agent,
|
||||
children,
|
||||
onSaveInlineToRoster,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
agent: AgentRosterDisplayData
|
||||
children?: ReactNode
|
||||
onSaveInlineToRoster?: () => void
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}) {
|
||||
@ -269,23 +267,6 @@ function AgentRosterInlineConfigureDialog({
|
||||
disablePointerDismissal
|
||||
>
|
||||
<DialogContent className="h-[min(760px,calc(100dvh-32px))] w-[min(1120px,calc(100vw-32px))] max-w-none overflow-hidden p-0">
|
||||
<DialogCloseButton className="z-10" />
|
||||
{onSaveInlineToRoster && (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(`${i18nPrefix}.roster.more`, { ns: 'workflow' })}
|
||||
className="absolute top-3 right-12 z-10 flex size-6 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="min-w-44 w-max">
|
||||
<DropdownMenuItem className="gap-2 whitespace-nowrap" onClick={onSaveInlineToRoster}>
|
||||
<span aria-hidden className="i-ri-inbox-archive-line size-4 shrink-0 text-text-tertiary" />
|
||||
<span>{t('roster.saveToRoster', { ns: 'agentV2' })}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<DialogTitle className="sr-only">
|
||||
{agent.name}
|
||||
</DialogTitle>
|
||||
@ -441,7 +422,6 @@ export function AgentRosterField({
|
||||
? (
|
||||
<AgentRosterInlineConfigureDialog
|
||||
agent={agent}
|
||||
onSaveInlineToRoster={onSaveInlineToRoster}
|
||||
open={panelOpen}
|
||||
onClose={() => setPanelOpen(false)}
|
||||
>
|
||||
|
||||
@ -58,10 +58,12 @@ export function AgentV2Panel({
|
||||
consoleQuery.apps.byAppId.workflows.draft.nodes.byNodeId.agentComposer.copyFromRoster.post.mutationOptions(),
|
||||
)
|
||||
const isAgentPanelOpen = isInlineAgentReady || isInlineAgentPending ? isInlineAgentPanelOpen : isRosterAgentPanelOpen
|
||||
const isInlineAgentLoading = isInlineAgentPending || (isInlineAgentReady && !inlineAgent)
|
||||
const isInlineAgentDetailFetchingForPanel = isInlineAgentPanelOpen && inlineAgentQuery.isFetching
|
||||
const isInlineAgentLoading = isInlineAgentPending || (isInlineAgentReady && (!inlineAgent || isInlineAgentDetailFetchingForPanel))
|
||||
const isAgentBindingPending = isInlineAgentPending || isCreatingInlineAgent
|
||||
const canStartFromScratch = inputs.agent_binding?.binding_type !== 'inline_agent'
|
||||
const canSaveInlineToRoster = isInlineAgentReady && !!inlineAgent
|
||||
const inlineComposerStateForPanel = isInlineAgentDetailFetchingForPanel ? undefined : inlineAgentQuery.data
|
||||
const displayedAgent = rosterAgentQuery.data ?? (isInlineAgentPending || isInlineAgentReady
|
||||
? {
|
||||
id: inlineAgentId ?? id,
|
||||
@ -234,6 +236,37 @@ export function AgentV2Panel({
|
||||
)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||
|
||||
const handleInlineAgentSaved = useCallback((binding: AgentComposerBindingResponse) => {
|
||||
if (
|
||||
binding.binding_type !== 'inline_agent'
|
||||
|| !binding.agent_id
|
||||
|| !binding.current_snapshot_id
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const newInputs = produce(inputsRef.current, (draft) => {
|
||||
delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
|
||||
delete draft._openInlineAgentPanel
|
||||
draft.agent_binding = {
|
||||
binding_type: 'inline_agent',
|
||||
agent_id: binding.agent_id,
|
||||
current_snapshot_id: binding.current_snapshot_id,
|
||||
}
|
||||
})
|
||||
inputsRef.current = newInputs
|
||||
handleNodeDataUpdateWithSyncDraft(
|
||||
{
|
||||
id,
|
||||
data: newInputs,
|
||||
},
|
||||
{
|
||||
sync: true,
|
||||
notRefreshWhenSyncError: true,
|
||||
},
|
||||
)
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||
|
||||
const handleStartFromScratch = useCallback(() => {
|
||||
setIsRosterAgentPanelOpen(false)
|
||||
setIsInlineAgentPanelOpenedFromTrigger(false)
|
||||
@ -269,6 +302,9 @@ export function AgentV2Panel({
|
||||
setIsInlineAgentPanelOpenedFromTrigger(true)
|
||||
|
||||
setOpenInlineAgentPanelNodeId(open ? id : undefined)
|
||||
if (open && isInlineAgentReady)
|
||||
void inlineAgentQuery.refetch()
|
||||
|
||||
if (open && isInlineAgentPending && !isCreatingInlineAgent) {
|
||||
createInlineAgentBinding(id, {
|
||||
onSuccess: handleInlineAgentBindingCreated,
|
||||
@ -278,7 +314,7 @@ export function AgentV2Panel({
|
||||
}
|
||||
|
||||
setIsRosterAgentPanelOpen(open)
|
||||
}, [createInlineAgentBinding, handleInlineAgentBindingCreated, id, isCreatingInlineAgent, isInlineAgentPending, isInlineAgentReady, setOpenInlineAgentPanelNodeId])
|
||||
}, [createInlineAgentBinding, handleInlineAgentBindingCreated, id, inlineAgentQuery, isCreatingInlineAgent, isInlineAgentPending, isInlineAgentReady, setOpenInlineAgentPanelNodeId])
|
||||
|
||||
const handleDeclaredOutputsChange = useCallback((outputs: ReturnType<typeof getAgentV2DeclaredOutputs>, agentTask?: string) => {
|
||||
const previousOutputs = getAgentV2DeclaredOutputs(inputsRef.current)
|
||||
@ -338,10 +374,12 @@ export function AgentV2Panel({
|
||||
<WorkflowInlineAgentConfigureWorkspace
|
||||
agentId={inlineAgentId ?? undefined}
|
||||
appId={appId}
|
||||
inlineComposerState={inlineAgentQuery.data}
|
||||
inlineComposerState={inlineComposerStateForPanel}
|
||||
isInline
|
||||
nodeId={id}
|
||||
onClose={() => handleAgentPanelOpenChange(false)}
|
||||
onSaved={handleInlineAgentSaved}
|
||||
onSaveInlineToRoster={canSaveInlineToRoster ? handleSaveInlineToRosterOpen : undefined}
|
||||
open={isAgentPanelOpen}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { SegmentedControl, SegmentedControlDivider, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -36,6 +37,7 @@ export function AgentPreviewHeader({
|
||||
onRefresh,
|
||||
refreshDisabled,
|
||||
showChatFeaturesAction = true,
|
||||
trailingAction,
|
||||
}: {
|
||||
mode: AgentConfigureRightPanelMode
|
||||
previewEnabled: boolean
|
||||
@ -46,6 +48,7 @@ export function AgentPreviewHeader({
|
||||
onRefresh: () => void
|
||||
refreshDisabled?: boolean
|
||||
showChatFeaturesAction?: boolean
|
||||
trailingAction?: ReactNode
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const docLink = useDocLink()
|
||||
@ -149,6 +152,12 @@ export function AgentPreviewHeader({
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{trailingAction && (
|
||||
<>
|
||||
<SegmentedControlDivider className="mx-3" />
|
||||
{trailingAction}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user