mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 09:57:03 +08:00
fix knowledge retrieval node add freeze
Knowledge Retrieval panel effects depend on setInputs. In the collaboration branch the callback chain started changing identity on every render, so adding this node could repeatedly write node data and freeze the browser. Stabilize the collaborative workflow API returned by useCollaborativeWorkflow and keep useNodeCrud.setInputs stable while forwarding to the latest updater. Add a regression test for the stable setInputs contract.
This commit is contained in:
parent
d32bc1a364
commit
2f7336b5d9
@ -1,5 +1,5 @@
|
||||
import type { Edge, Node } from '../types'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { collaborationManager } from '../collaboration/core/collaboration-manager'
|
||||
|
||||
@ -77,9 +77,9 @@ export const useCollaborativeWorkflow = () => {
|
||||
}
|
||||
}, [store, setNodes, setEdges])
|
||||
|
||||
return {
|
||||
return useMemo(() => ({
|
||||
getState: collaborativeStore,
|
||||
setNodes,
|
||||
setEdges,
|
||||
}
|
||||
}), [collaborativeStore, setEdges, setNodes])
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import useNodeCrud from '../use-node-crud'
|
||||
|
||||
const mockHandleNodeDataUpdateWithSyncDraft = vi.hoisted(() => ({
|
||||
current: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useNodeDataUpdate: () => ({
|
||||
handleNodeDataUpdateWithSyncDraft: mockHandleNodeDataUpdateWithSyncDraft.current,
|
||||
}),
|
||||
}))
|
||||
|
||||
type TestNodeData = CommonNodeType<{
|
||||
value: string
|
||||
}>
|
||||
|
||||
const createData = (value = 'initial'): TestNodeData => ({
|
||||
type: BlockEnum.LLM,
|
||||
title: 'Test Node',
|
||||
desc: '',
|
||||
value,
|
||||
})
|
||||
|
||||
describe('useNodeCrud', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockHandleNodeDataUpdateWithSyncDraft.current = vi.fn()
|
||||
})
|
||||
|
||||
it('keeps setInputs stable across rerenders when id does not change', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ id, data }) => useNodeCrud(id, data),
|
||||
{
|
||||
initialProps: {
|
||||
id: 'node-1',
|
||||
data: createData(),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const firstSetInputs = result.current.setInputs
|
||||
|
||||
rerender({
|
||||
id: 'node-1',
|
||||
data: createData('updated'),
|
||||
})
|
||||
|
||||
expect(result.current.setInputs).toBe(firstSetInputs)
|
||||
})
|
||||
|
||||
it('forwards node data updates with the current node id and latest updater', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ id, data }) => useNodeCrud(id, data),
|
||||
{
|
||||
initialProps: {
|
||||
id: 'node-1',
|
||||
data: createData(),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
result.current.setInputs(createData('changed'))
|
||||
|
||||
expect(mockHandleNodeDataUpdateWithSyncDraft.current).toHaveBeenCalledWith({
|
||||
id: 'node-1',
|
||||
data: createData('changed'),
|
||||
})
|
||||
|
||||
const nextUpdater = vi.fn()
|
||||
mockHandleNodeDataUpdateWithSyncDraft.current = nextUpdater
|
||||
|
||||
rerender({
|
||||
id: 'node-1',
|
||||
data: createData('changed'),
|
||||
})
|
||||
|
||||
result.current.setInputs(createData('latest'))
|
||||
|
||||
expect(nextUpdater).toHaveBeenCalledWith({
|
||||
id: 'node-1',
|
||||
data: createData('latest'),
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,15 +1,21 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||
|
||||
const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
const updateRef = useRef(handleNodeDataUpdateWithSyncDraft)
|
||||
|
||||
const setInputs = (newInputs: CommonNodeType<T>) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
useEffect(() => {
|
||||
updateRef.current = handleNodeDataUpdateWithSyncDraft
|
||||
}, [handleNodeDataUpdateWithSyncDraft])
|
||||
|
||||
const setInputs = useCallback((newInputs: CommonNodeType<T>) => {
|
||||
updateRef.current({
|
||||
id,
|
||||
data: newInputs,
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
|
||||
return {
|
||||
inputs: data,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user