From 31ee7bb4670db3082c37bee138b14098bc1920be Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 23 Jun 2026 14:37:41 +0800 Subject: [PATCH 001/103] fix: public and Knowledge no mask --- .../orchestrate/__tests__/publish-bar.spec.tsx | 13 +++++++++++++ .../components/orchestrate/knowledge/dialog.tsx | 6 +++++- .../components/orchestrate/publish-bar/index.tsx | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx index 6beb7cc649c..e72d19d7a0e 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/__tests__/publish-bar.spec.tsx @@ -306,6 +306,19 @@ describe('AgentConfigurePublishBar', () => { expect(onPublish).not.toHaveBeenCalled() }) + it('should keep published state when the published detail updates before the active snapshot is refreshed', () => { + renderPublishBar({ + activeConfigIsPublished: true, + activeConfigSnapshot: null, + }) + + expect(screen.getByText('agentV2.agentDetail.configure.publishBar.upToDate')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'agentV2.agentDetail.configure.publishBar.published' })).toBeDisabled() + expect(hotkeyRegistrations.get('Mod+Shift+P')?.options).toEqual( + expect.objectContaining({ enabled: false, ignoreInputs: false }), + ) + }) + it('should initialize unpublished state when active config is not published', async () => { const { onPublish } = renderPublishBar({ activeConfigIsPublished: false, diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx index 200313ca813..6c6b25f3380 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx @@ -227,7 +227,11 @@ export function AgentKnowledgeRetrievalDialog({ return ( - + {t('agentDetail.configure.knowledgeRetrieval.dialog.title')} diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/publish-bar/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/publish-bar/index.tsx index 44a4e9fdf8f..bfb2ad47f60 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/publish-bar/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/publish-bar/index.tsx @@ -63,7 +63,7 @@ function getPublishState({ return 'publishing' if (!activeConfigSnapshot) - return 'draft' + return activeConfigIsPublished ? 'published' : 'draft' if (!activeConfigIsPublished || isDirty) return 'unpublished' From ce5033c554f4ef4979f230ebe90ec85c4f28ee20 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 23 Jun 2026 14:49:27 +0800 Subject: [PATCH 002/103] fix: inline agent panel not always show --- .../workflow/candidate-node-main.tsx | 2 +- .../workflow/hooks/use-nodes-interactions.ts | 2 +- .../nodes/agent-v2/__tests__/panel.spec.tsx | 37 ++++++++++++-- .../agent-orchestrate-drawer-panel.tsx | 8 +-- .../workflow/nodes/agent-v2/panel.tsx | 51 +++++++++++++------ .../workflow/operator/add-block.tsx | 2 +- 6 files changed, 76 insertions(+), 26 deletions(-) diff --git a/web/app/components/workflow/candidate-node-main.tsx b/web/app/components/workflow/candidate-node-main.tsx index d7473023891..50a46f89155 100644 --- a/web/app/components/workflow/candidate-node-main.tsx +++ b/web/app/components/workflow/candidate-node-main.tsx @@ -89,6 +89,7 @@ const CandidateNodeMain: FC = ({ } if (shouldCreateInlineAgentBinding) { + workflowStore.getState().setOpenInlineAgentPanelNodeId(candidateNode.id) createInlineAgentBinding(candidateNode.id, { onError: () => { const { nodes, setNodes } = collaborativeWorkflow.getState() @@ -105,7 +106,6 @@ const CandidateNodeMain: FC = ({ delete node.data._isTempNode } })) - workflowStore.getState().setOpenInlineAgentPanelNodeId(candidateNode.id) handleSyncWorkflowDraft(true, true) }, }) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 538c270692c..afd0bff2acc 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -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) }, }) diff --git a/web/app/components/workflow/nodes/agent-v2/__tests__/panel.spec.tsx b/web/app/components/workflow/nodes/agent-v2/__tests__/panel.spec.tsx index 1b824eb7510..723c6569ec7 100644 --- a/web/app/components/workflow/nodes/agent-v2/__tests__/panel.spec.tsx +++ b/web/app/components/workflow/nodes/agent-v2/__tests__/panel.spec.tsx @@ -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( { 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', diff --git a/web/app/components/workflow/nodes/agent-v2/components/agent-orchestrate-drawer-panel.tsx b/web/app/components/workflow/nodes/agent-v2/components/agent-orchestrate-drawer-panel.tsx index 0b174d02280..0dff1fe9583 100644 --- a/web/app/components/workflow/nodes/agent-v2/components/agent-orchestrate-drawer-panel.tsx +++ b/web/app/components/workflow/nodes/agent-v2/components/agent-orchestrate-drawer-panel.tsx @@ -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 (
diff --git a/web/app/components/workflow/nodes/agent-v2/panel.tsx b/web/app/components/workflow/nodes/agent-v2/panel.tsx index c80c021407d..1a377d69341 100644 --- a/web/app/components/workflow/nodes/agent-v2/panel.tsx +++ b/web/app/components/workflow/nodes/agent-v2/panel.tsx @@ -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({
) : 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} diff --git a/web/app/components/workflow/operator/add-block.tsx b/web/app/components/workflow/operator/add-block.tsx index e2937f28642..0628f225e8a 100644 --- a/web/app/components/workflow/operator/add-block.tsx +++ b/web/app/components/workflow/operator/add-block.tsx @@ -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) }, }) From 6c05219af8fe1cb07304171a482578bd513e6ddd Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 23 Jun 2026 15:50:08 +0800 Subject: [PATCH 003/103] fix: knowledge modal not anim --- .../orchestrate/knowledge/dialog.tsx | 150 ++++++++++++++---- .../orchestrate/knowledge/index.tsx | 1 - 2 files changed, 123 insertions(+), 28 deletions(-) diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx index 6c6b25f3380..a41915a7895 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/knowledge/dialog.tsx @@ -1,7 +1,7 @@ 'use client' import type { AgentKnowledgeDatasetConfig } from '@dify/contracts/api/console/agent/types.gen' -import type { ReactNode } from 'react' +import type { ReactNode, SetStateAction } from 'react' import type { MetadataFilteringCondition, MetadataFilteringModeEnum, @@ -37,6 +37,25 @@ import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datase import { AppModeEnum, RETRIEVE_METHOD, RETRIEVE_TYPE } from '@/types/app' type KnowledgeRetrievalQueryMode = 'agent' | 'custom' +type MetadataFilteringConditions = { + logical_operator: LogicalOperator + conditions: MetadataFilteringCondition[] +} + +type KnowledgeRetrievalDialogState = { + customQuery: string + hydratedKey: string | null + isEditingName: boolean + metadataFilterMode: MetadataFilteringModeEnum + metadataFilteringConditions: MetadataFilteringConditions + metadataModelConfig?: ModelConfig + multipleRetrievalConfig: MultipleRetrievalConfig + name: string + queryMode: KnowledgeRetrievalQueryMode + rerankModelOpen: boolean + retrievalMode: typeof RETRIEVE_TYPE[keyof typeof RETRIEVE_TYPE] + selectedDatasets: DataSet[] +} const queryModeOptions: KnowledgeRetrievalQueryMode[] = ['agent', 'custom'] @@ -75,6 +94,11 @@ const createDefaultRetrievalConfig = (): MultipleRetrievalConfig => ({ reranking_enable: false, }) +const createDefaultMetadataFilteringConditions = (): MetadataFilteringConditions => ({ + logical_operator: LogicalOperator.and, + conditions: [], +}) + const createDatasetFromRef = (dataset: AgentKnowledgeDatasetConfig, index: number): DataSet => { const id = dataset.id ?? dataset.name ?? `dataset-${index}` const name = dataset.name ?? id @@ -150,6 +174,32 @@ const getSelectedDatasets = (item?: AgentKnowledgeRetrievalItem) => ( item?.selectedDatasets ?? item?.datasetRefs?.map(createDatasetFromRef) ?? [] ) +const getDialogName = ( + item: AgentKnowledgeRetrievalItem | undefined, + initialName: string | undefined, + fallbackName: string, +) => item?.name ?? initialName ?? fallbackName + +const createDialogState = ( + item: AgentKnowledgeRetrievalItem | undefined, + initialName: string | undefined, + fallbackName: string, + hydrationKey: string, +): KnowledgeRetrievalDialogState => ({ + customQuery: item?.customQuery ?? '', + hydratedKey: hydrationKey, + isEditingName: false, + metadataFilterMode: item?.metadataFilterMode ?? WorkflowMetadataFilteringModeEnum.disabled, + metadataFilteringConditions: item?.metadataFilteringConditions ?? createDefaultMetadataFilteringConditions(), + metadataModelConfig: item?.metadataModelConfig, + multipleRetrievalConfig: item?.multipleRetrievalConfig ?? createDefaultRetrievalConfig(), + name: getDialogName(item, initialName, fallbackName), + queryMode: item?.queryMode ?? 'agent', + rerankModelOpen: false, + retrievalMode: item?.retrievalMode ?? RETRIEVE_TYPE.multiWay, + selectedDatasets: getSelectedDatasets(item), +}) + const createMetadataCondition = ({ id, name, type }: MetadataInDoc): MetadataFilteringCondition => ({ id: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`, metadata_id: id, @@ -174,22 +224,55 @@ export function AgentKnowledgeRetrievalDialog({ }) { const { t } = useTranslation('agentV2') const docLink = useDocLink() - const [name, setName] = useState(() => item?.name ?? initialName ?? t('agentDetail.configure.knowledgeRetrieval.retrievalOne')) - const [isEditingName, setIsEditingName] = useState(false) + const fallbackName = t('agentDetail.configure.knowledgeRetrieval.retrievalOne') + const hydrationKey = open ? (item?.id ?? initialName ?? 'new') : null + const [dialogState, setDialogState] = useState(() => createDialogState(item, initialName, fallbackName, hydrationKey ?? 'new')) const nameInputRef = useRef(null) - const [queryMode, setQueryMode] = useState(item?.queryMode ?? 'agent') - const [customQuery, setCustomQuery] = useState(item?.customQuery ?? '') - const [selectedDatasets, setSelectedDatasets] = useState(() => getSelectedDatasets(item)) - const [retrievalMode, setRetrievalMode] = useState(item?.retrievalMode ?? RETRIEVE_TYPE.multiWay) - const [multipleRetrievalConfig, setMultipleRetrievalConfig] = useState(item?.multipleRetrievalConfig ?? createDefaultRetrievalConfig) - const [rerankModelOpen, setRerankModelOpen] = useState(false) - const [metadataFilterMode, setMetadataFilterMode] = useState(item?.metadataFilterMode ?? WorkflowMetadataFilteringModeEnum.disabled) - const [metadataFilteringConditions, setMetadataFilteringConditions] = useState(item?.metadataFilteringConditions ?? { - logical_operator: LogicalOperator.and, - conditions: [] as MetadataFilteringCondition[], - }) - const [metadataModelConfig, setMetadataModelConfig] = useState(item?.metadataModelConfig) const queryModeLabelId = 'agent-knowledge-retrieval-query-mode-label' + const { + customQuery, + hydratedKey, + isEditingName, + metadataFilterMode, + metadataFilteringConditions, + metadataModelConfig, + multipleRetrievalConfig, + name, + queryMode, + rerankModelOpen, + retrievalMode, + selectedDatasets, + } = dialogState + const patchDialogState = (patch: Partial) => { + setDialogState(current => ({ + ...current, + ...patch, + })) + } + const setMetadataFilteringConditions = (update: SetStateAction) => { + setDialogState((current) => { + const nextMetadataFilteringConditions = typeof update === 'function' + ? update(current.metadataFilteringConditions) + : update + + return { + ...current, + metadataFilteringConditions: nextMetadataFilteringConditions, + } + }) + } + const setMetadataModelConfig = (update: SetStateAction) => { + setDialogState((current) => { + const nextMetadataModelConfig = typeof update === 'function' + ? update(current.metadataModelConfig) + : update + + return { + ...current, + metadataModelConfig: nextMetadataModelConfig, + } + }) + } const updateItem = (patch: Partial) => { if (!item) return @@ -217,6 +300,19 @@ export function AgentKnowledgeRetrievalDialog({ return intersectionBy(...datasetsWithMetadata.map(dataset => dataset.doc_metadata!), 'name') }, [selectedDatasets]) + useEffect(() => { + if (hydratedKey !== hydrationKey) { + // eslint-disable-next-line react/set-state-in-effect + setDialogState(current => hydrationKey + ? createDialogState(item, initialName, fallbackName, hydrationKey) + : { + ...current, + hydratedKey: null, + isEditingName: false, + }) + } + }, [hydrationKey]) + useEffect(() => { if (!isEditingName) return @@ -244,15 +340,15 @@ export function AgentKnowledgeRetrievalDialog({ aria-label={t('agentDetail.configure.knowledgeRetrieval.dialog.nameLabel')} className="h-7 min-w-0 flex-1 rounded-md px-1 py-0 system-xl-semibold text-text-primary" value={name} - onBlur={() => setIsEditingName(false)} + onBlur={() => patchDialogState({ isEditingName: false })} onChange={(event) => { const nextName = event.currentTarget.value - setName(nextName) + patchDialogState({ name: nextName }) updateItem({ name: nextName }) }} onKeyDown={(event) => { if (event.key === 'Enter') - setIsEditingName(false) + patchDialogState({ isEditingName: false }) }} /> ) @@ -260,7 +356,7 @@ export function AgentKnowledgeRetrievalDialog({ + ), })) vi.mock('../components/preview/versions-panel', () => ({ @@ -125,6 +132,7 @@ describe('AgentConfigurePage', () => { icon_background: '#E0F2FE', icon_type: 'emoji', name: 'Research Agent', + debug_conversation_id: 'debug-conversation-old', }, isFetching: false, isPending: false, diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx index 2598949ba21..20d9b500d2f 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx @@ -1,18 +1,41 @@ 'use client' +import type { AgentAppDetailWithSite } from '@dify/contracts/api/console/agent/types.gen' import { cn } from '@langgenius/dify-ui/cn' +import { useMutation, useQueryClient } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' +import { consoleQuery } from '@/service/client' export function AgentPreviewHeader({ + agentId, isChatFeaturesOpen, onToggleChatFeatures, onRestart, }: { + agentId: string isChatFeaturesOpen: boolean onToggleChatFeatures: () => void onRestart: () => void }) { const { t } = useTranslation('agentV2') + const queryClient = useQueryClient() + const refreshDebugConversationMutation = useMutation(consoleQuery.agent.byAgentId.debugConversation.refresh.post.mutationOptions({ + onSuccess: ({ debug_conversation_id }) => { + queryClient.setQueryData( + consoleQuery.agent.byAgentId.get.queryKey({ input: { params: { agent_id: agentId } } }), + (agentDetail) => { + if (!agentDetail) + return agentDetail + + return { + ...agentDetail, + debug_conversation_id, + } + }, + ) + onRestart() + }, + })) return (
@@ -22,8 +45,13 @@ export function AgentPreviewHeader({