diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index 0494d4de1bf..4a67158c27e 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -28,13 +28,12 @@ import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage' const activeEdgeClassName = 'before:pointer-events-none' type SnippetNavigationTestState = { - fields: SnippetInputField[] onFieldsChange?: (fields: SnippetInputField[]) => void readonly: boolean snippet?: SnippetDetail } -const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations, snippetNavigationState } = vi.hoisted(() => ({ +const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mockToastSuccess, hotkeyRegistrations, snippetDraftState, snippetNavigationState } = vi.hoisted(() => ({ mockSwitchWorkspace: vi.fn(), mockSnippetFieldsChange: vi.fn(), mockToastSuccess: vi.fn(), @@ -43,8 +42,10 @@ const { mockIsAgentV2Enabled, mockSnippetFieldsChange, mockSwitchWorkspace, mock handler: (event: { preventDefault: () => void }) => void options?: { ignoreInputs?: boolean } }>(), + snippetDraftState: { + inputFields: [], + } as { inputFields: SnippetInputField[] }, snippetNavigationState: { - fields: [], readonly: true, snippet: undefined, onFieldsChange: undefined, @@ -204,6 +205,10 @@ vi.mock('@/app/components/snippets/store', () => ({ useSnippetDetailStore: (selector: (state: SnippetNavigationTestState) => unknown) => selector(snippetNavigationState), })) +vi.mock('@/app/components/snippets/draft-store', () => ({ + useSnippetDraftStore: (selector: (state: typeof snippetDraftState) => unknown) => selector(snippetDraftState), +})) + vi.mock('@/app/components/snippets/components/snippet-sidebar', () => ({ SnippetSidebarContent: ({ fields, @@ -423,7 +428,7 @@ describe('MainNav', () => { }) mockSwitchWorkspace.mockReturnValue(new Promise(() => {})) hotkeyRegistrations.clear() - snippetNavigationState.fields = [] + snippetDraftState.inputFields = [] snippetNavigationState.onFieldsChange = undefined snippetNavigationState.readonly = true snippetNavigationState.snippet = undefined @@ -634,7 +639,7 @@ describe('MainNav', () => { it('replaces global navigation with snippet detail navigation on snippet routes', () => { mockPathname = '/snippets/snippet-1/orchestrate' - snippetNavigationState.fields = snippetFields + snippetDraftState.inputFields = snippetFields snippetNavigationState.onFieldsChange = mockSnippetFieldsChange snippetNavigationState.readonly = false snippetNavigationState.snippet = snippet @@ -661,7 +666,7 @@ describe('MainNav', () => { it('collapses snippet detail navigation from the top-right toggle', () => { mockPathname = '/snippets/snippet-1/orchestrate' - snippetNavigationState.fields = snippetFields + snippetDraftState.inputFields = snippetFields snippetNavigationState.onFieldsChange = mockSnippetFieldsChange snippetNavigationState.snippet = snippet diff --git a/web/app/components/main-nav/index.tsx b/web/app/components/main-nav/index.tsx index 11934d8605d..a629498a7ed 100644 --- a/web/app/components/main-nav/index.tsx +++ b/web/app/components/main-nav/index.tsx @@ -15,6 +15,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import DifyLogo from '@/app/components/base/logo/dify-logo' import EnvNav from '@/app/components/header/env-nav' import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar' +import { useSnippetDraftStore } from '@/app/components/snippets/draft-store' import { useSnippetDetailStore } from '@/app/components/snippets/store' import { useAppContext } from '@/context/app-context' import { AgentDetailSection, AgentDetailTop } from '@/features/agent-v2/agent-detail/navigation' @@ -101,11 +102,11 @@ const MainNav = ({ const showSnippetDetailNavigation = isSnippetDetailPathname(pathname) const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation || showSnippetDetailNavigation const snippetNavigation = useSnippetDetailStore(useShallow(state => ({ - fields: state.fields, onFieldsChange: state.onFieldsChange, readonly: state.readonly, snippet: state.snippet, }))) + const snippetInputFields = useSnippetDraftStore(state => state.inputFields) const { hasAppDetail, setAppDetail } = useAppStore(useShallow(state => ({ hasAppDetail: !!state.appDetail, setAppDetail: state.setAppDetail, @@ -307,7 +308,7 @@ const MainNav = ({ ? ( diff --git a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx index 7e0cf7494e5..3deef732ca1 100644 --- a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx +++ b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx @@ -6,13 +6,13 @@ import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { renderWorkflowComponent } from '@/app/components/workflow/__tests__/workflow-test-env' import { BlockEnum } from '@/app/components/workflow/types' import { PipelineInputVarType } from '@/models/pipeline' +import { useSnippetDraftStore } from '../../draft-store' import SnippetMain from '../snippet-main' const mockSyncInputFieldsDraft = vi.fn() const mockDoSyncWorkflowDraft = vi.fn() const mockSyncWorkflowDraftWhenPageClose = vi.fn() const mockReset = vi.fn() -const mockSetFields = vi.fn() const mockSetNavigationState = vi.fn() const mockPublishSnippetMutateAsync = vi.fn() const mockFetchInspectVars = vi.fn() @@ -52,11 +52,9 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ let capturedHooksStore: Record | undefined let capturedWorkflowNodes: WorkflowProps['nodes'] | undefined let snippetDetailStoreState: { - fields: SnippetInputField[] onFieldsChange?: (fields: SnippetInputField[]) => void readonly: boolean reset: typeof mockReset - setFields: typeof mockSetFields setNavigationState: typeof mockSetNavigationState snippet?: SnippetDetail snippetId?: string @@ -277,11 +275,10 @@ describe('SnippetMain', () => { }) capturedHooksStore = undefined capturedWorkflowNodes = undefined + useSnippetDraftStore.getState().reset() snippetDetailStoreState = { - fields: [...payload.inputFields], readonly: true, reset: mockReset, - setFields: mockSetFields, setNavigationState: mockSetNavigationState, } }) diff --git a/web/app/components/snippets/components/hooks/__tests__/use-snippet-input-field-actions.spec.ts b/web/app/components/snippets/components/hooks/__tests__/use-snippet-input-field-actions.spec.ts index d879042338b..ae21558f2ab 100644 --- a/web/app/components/snippets/components/hooks/__tests__/use-snippet-input-field-actions.spec.ts +++ b/web/app/components/snippets/components/hooks/__tests__/use-snippet-input-field-actions.spec.ts @@ -1,15 +1,10 @@ import type { SnippetInputField } from '@/models/snippet' import { act, renderHook } from '@testing-library/react' import { PipelineInputVarType } from '@/models/pipeline' +import { useSnippetDraftStore } from '../../../draft-store' import { useSnippetInputFieldActions } from '../use-snippet-input-field-actions' const mockSyncInputFieldsDraft = vi.fn() -const mockSetFields = vi.fn() - -let snippetDetailStoreState: { - fields: SnippetInputField[] - setFields: typeof mockSetFields -} vi.mock('../../../hooks/use-nodes-sync-draft', () => ({ useNodesSyncDraft: () => ({ @@ -17,10 +12,6 @@ vi.mock('../../../hooks/use-nodes-sync-draft', () => ({ }), })) -vi.mock('../../../store', () => ({ - useSnippetDetailStore: (selector: (state: typeof snippetDetailStoreState) => unknown) => selector(snippetDetailStoreState), -})) - const createField = (overrides: Partial = {}): SnippetInputField => ({ type: PipelineInputVarType.textInput, label: 'Blog URL', @@ -32,19 +23,13 @@ const createField = (overrides: Partial = {}): SnippetInputFi describe('useSnippetInputFieldActions', () => { beforeEach(() => { vi.clearAllMocks() - snippetDetailStoreState = { - fields: [], - setFields: mockSetFields, - } - mockSetFields.mockImplementation((fields: SnippetInputField[]) => { - snippetDetailStoreState.fields = fields - }) + useSnippetDraftStore.getState().reset() mockSyncInputFieldsDraft.mockResolvedValue(undefined) }) describe('Field sync', () => { it('should update fields and sync the draft', () => { - snippetDetailStoreState.fields = [createField()] + useSnippetDraftStore.getState().setInputFields([createField()]) const { result } = renderHook(() => useSnippetInputFieldActions({ snippetId: 'snippet-1', })) @@ -60,8 +45,8 @@ describe('useSnippetInputFieldActions', () => { result.current.handleFieldsChange(nextFields) }) - expect(result.current.fields).toEqual([createField()]) - expect(mockSetFields).toHaveBeenCalledWith(nextFields) + expect(result.current.fields).toEqual(nextFields) + expect(useSnippetDraftStore.getState().inputFields).toEqual(nextFields) expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith(nextFields, { onRefresh: expect.any(Function), }) diff --git a/web/app/components/snippets/components/hooks/use-snippet-input-field-actions.ts b/web/app/components/snippets/components/hooks/use-snippet-input-field-actions.ts index 862cb3cfc29..853b96663c1 100644 --- a/web/app/components/snippets/components/hooks/use-snippet-input-field-actions.ts +++ b/web/app/components/snippets/components/hooks/use-snippet-input-field-actions.ts @@ -1,8 +1,8 @@ import type { SnippetInputField } from '@/models/snippet' import { useCallback } from 'react' import { useShallow } from 'zustand/react/shallow' +import { useSnippetDraftStore } from '../../draft-store' import { useNodesSyncDraft } from '../../hooks/use-nodes-sync-draft' -import { useSnippetDetailStore } from '../../store' type UseSnippetInputFieldActionsOptions = { canEdit?: boolean @@ -15,25 +15,25 @@ export const useSnippetInputFieldActions = ({ }: UseSnippetInputFieldActionsOptions) => { const { syncInputFieldsDraft } = useNodesSyncDraft(snippetId) const { - fields, - setFields, - } = useSnippetDetailStore(useShallow(state => ({ - fields: state.fields, - setFields: state.setFields, + inputFields, + setInputFields, + } = useSnippetDraftStore(useShallow(state => ({ + inputFields: state.inputFields, + setInputFields: state.setInputFields, }))) const handleFieldsChange = useCallback((newFields: SnippetInputField[]) => { if (!canEdit) return - setFields(newFields) + setInputFields(newFields) void syncInputFieldsDraft(newFields, { - onRefresh: setFields, + onRefresh: setInputFields, }) - }, [canEdit, setFields, syncInputFieldsDraft]) + }, [canEdit, setInputFields, syncInputFieldsDraft]) return { - fields, + fields: inputFields, handleFieldsChange, } } diff --git a/web/app/components/snippets/components/snippet-main.tsx b/web/app/components/snippets/components/snippet-main.tsx index 19327b5faad..770d3e2e0c3 100644 --- a/web/app/components/snippets/components/snippet-main.tsx +++ b/web/app/components/snippets/components/snippet-main.tsx @@ -8,8 +8,8 @@ import { toast } from '@langgenius/dify-ui/toast' import { useCallback, useEffect, + useLayoutEffect, useMemo, - useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' @@ -23,6 +23,7 @@ import { initialEdges, initialNodes, } from '@/app/components/workflow/utils' +import { useSnippetDraftStore } from '../draft-store' import { useConfigsMap } from '../hooks/use-configs-map' import { useGetRunAndTraceUrl } from '../hooks/use-get-run-and-trace-url' import { useInspectVarsCrud } from '../hooks/use-inspect-vars-crud' @@ -136,16 +137,12 @@ const SnippetMain = ({ const effectiveDraftNodes = localDraftState?.nodes ?? draftNodes const effectiveDraftEdges = localDraftState?.edges ?? draftEdges const effectiveDraftViewport = localDraftState?.viewport ?? draftViewport - const currentInputFieldsRef = useRef(effectiveDraftPayload.inputFields) const { graph, snippet } = effectiveDraftPayload const canSave = currentCanvasNodeCount > 0 - const getCurrentInputFields = useCallback(() => currentInputFieldsRef.current, []) const { doSyncWorkflowDraft: syncWorkflowDraft, syncWorkflowDraftWhenPageClose, - } = useNodesSyncDraft(snippetId, { - getInputFields: getCurrentInputFields, - }) + } = useNodesSyncDraft(snippetId) const workflowStore = useWorkflowStore() const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId) const { @@ -197,13 +194,18 @@ const SnippetMain = ({ }, [workflowAvailableNodesMetaData]) const { reset, - setFields, setNavigationState, } = useSnippetDetailStore(useShallow(state => ({ reset: state.reset, - setFields: state.setFields, setNavigationState: state.setNavigationState, }))) + const { + hydrateDraft, + setInputFields, + } = useSnippetDraftStore(useShallow(state => ({ + hydrateDraft: state.hydrateDraft, + setInputFields: state.setInputFields, + }))) const { fields, handleFieldsChange: handleSnippetFieldsChange, @@ -224,10 +226,12 @@ const SnippetMain = ({ return () => reset() }, [reset, snippetId]) - useEffect(() => { - currentInputFieldsRef.current = effectiveDraftPayload.inputFields - setFields(effectiveDraftPayload.inputFields) - }, [effectiveDraftPayload.inputFields, setFields, snippetId]) + useLayoutEffect(() => { + hydrateDraft({ + snippetId, + inputFields: effectiveDraftPayload.inputFields, + }) + }, [effectiveDraftPayload.inputFields, hydrateDraft, snippetId]) useEffect(() => { workflowStore.setState({ canvasReadOnly: false }) @@ -254,7 +258,6 @@ const SnippetMain = ({ ) => syncWorkflowDraft(...args), [syncWorkflowDraft]) const handleFieldsChange = useCallback((nextFields: SnippetInputField[]) => { - currentInputFieldsRef.current = nextFields handleSnippetFieldsChange(nextFields) }, [handleSnippetFieldsChange]) @@ -286,7 +289,6 @@ const SnippetMain = ({ ? syncedDraftPayload.input_fields as SnippetInputField[] : fields - currentInputFieldsRef.current = inputFields setLocalDraftState({ payload: { ...draftPayload, @@ -297,8 +299,8 @@ const SnippetMain = ({ edges: initialEdges(draftGraph.edges, draftGraph.nodes), viewport: draftGraph.viewport, }) - setFields(inputFields) - }, [draftPayload, fields, setFields]) + setInputFields(inputFields) + }, [draftPayload, fields, setInputFields]) const hooksStore = useMemo(() => { return { diff --git a/web/app/components/snippets/draft-store/__tests__/index.spec.ts b/web/app/components/snippets/draft-store/__tests__/index.spec.ts new file mode 100644 index 00000000000..d50067f2033 --- /dev/null +++ b/web/app/components/snippets/draft-store/__tests__/index.spec.ts @@ -0,0 +1,36 @@ +import type { SnippetInputField } from '@/models/snippet' +import { PipelineInputVarType } from '@/models/pipeline' +import { useSnippetDraftStore } from '..' + +const createField = (variable: string): SnippetInputField => ({ + label: variable, + variable, + type: PipelineInputVarType.textInput, + required: true, +}) + +describe('useSnippetDraftStore', () => { + beforeEach(() => { + useSnippetDraftStore.getState().reset() + }) + + it('should store and reset snippet input fields', () => { + const inputFields = [ + createField('topic'), + createField('audience'), + ] + + useSnippetDraftStore.getState().hydrateDraft({ + snippetId: 'snippet-1', + inputFields, + }) + + expect(useSnippetDraftStore.getState().snippetId).toBe('snippet-1') + expect(useSnippetDraftStore.getState().inputFields).toEqual(inputFields) + + useSnippetDraftStore.getState().reset() + + expect(useSnippetDraftStore.getState().snippetId).toBeUndefined() + expect(useSnippetDraftStore.getState().inputFields).toEqual([]) + }) +}) diff --git a/web/app/components/snippets/draft-store/index.ts b/web/app/components/snippets/draft-store/index.ts new file mode 100644 index 00000000000..2cc9081b016 --- /dev/null +++ b/web/app/components/snippets/draft-store/index.ts @@ -0,0 +1,24 @@ +'use client' + +import type { SnippetInputField } from '@/models/snippet' +import { create } from 'zustand' + +type SnippetDraftState = { + snippetId?: string + inputFields: SnippetInputField[] + hydrateDraft: (payload: { snippetId: string, inputFields: SnippetInputField[] }) => void + setInputFields: (inputFields: SnippetInputField[]) => void + reset: () => void +} + +const initialState = { + snippetId: undefined, + inputFields: [] as SnippetInputField[], +} + +export const useSnippetDraftStore = create(set => ({ + ...initialState, + hydrateDraft: ({ snippetId, inputFields }) => set({ snippetId, inputFields }), + setInputFields: inputFields => set({ inputFields }), + reset: () => set(initialState), +})) diff --git a/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts b/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts index df75ec1714b..dc955e7f6cb 100644 --- a/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts +++ b/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts @@ -1,7 +1,7 @@ import type { SnippetInputField } from '@/models/snippet' import { act, renderHook } from '@testing-library/react' import { PipelineInputVarType } from '@/models/pipeline' -import { useSnippetDetailStore } from '../../store' +import { useSnippetDraftStore } from '../../draft-store' import { useNodesSyncDraft } from '../use-nodes-sync-draft' const mockGetNodes = vi.fn() @@ -112,9 +112,7 @@ describe('snippet/use-nodes-sync-draft', () => { mockSetSyncWorkflowDraftHash.mockImplementation((hash: string) => { workflowStoreState.syncWorkflowDraftHash = hash }) - useSnippetDetailStore.setState({ - fields: [createInputField('topic')], - }) + useSnippetDraftStore.getState().setInputFields([createInputField('topic')]) }) it('should include current input_fields when syncing the draft graph', async () => { @@ -139,25 +137,16 @@ describe('snippet/use-nodes-sync-draft', () => { expect(mockUseNodesReadOnlyByCanEdit).toHaveBeenCalledWith(true) }) - it('should use provided input_fields when the snippet store is not initialized yet', async () => { - useSnippetDetailStore.setState({ - fields: [], + it('should keep draft input_fields when the navigation store is reset during route leave', () => { + const { result } = renderHook(() => useNodesSyncDraft('snippet-1')) + + act(() => { + result.current.syncWorkflowDraftWhenPageClose() }) - const inputFields = [createInputField('topic')] - const { result } = renderHook(() => useNodesSyncDraft('snippet-1', { - getInputFields: () => inputFields, + + expect(mockPostWithKeepalive).toHaveBeenCalledWith('/api/snippets/snippet-1/workflows/draft', expect.objectContaining({ + input_fields: [createInputField('topic')], })) - - await act(async () => { - await result.current.doSyncWorkflowDraft() - }) - - expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-1' }, - body: expect.objectContaining({ - input_fields: inputFields, - }), - }) }) it('should snapshot graph before queued draft sync executes', async () => { @@ -267,22 +256,4 @@ describe('snippet/use-nodes-sync-draft', () => { hash: 'draft-hash', }) }) - - it('should use provided input_fields when syncing on page close before the snippet store initializes', () => { - useSnippetDetailStore.setState({ - fields: [], - }) - const inputFields = [createInputField('topic')] - const { result } = renderHook(() => useNodesSyncDraft('snippet-1', { - getInputFields: () => inputFields, - })) - - act(() => { - result.current.syncWorkflowDraftWhenPageClose() - }) - - expect(mockPostWithKeepalive).toHaveBeenCalledWith('/api/snippets/snippet-1/workflows/draft', expect.objectContaining({ - input_fields: inputFields, - })) - }) }) diff --git a/web/app/components/snippets/hooks/__tests__/use-snippet-refresh-draft.spec.ts b/web/app/components/snippets/hooks/__tests__/use-snippet-refresh-draft.spec.ts index c85bfe3bc5d..eb500839efb 100644 --- a/web/app/components/snippets/hooks/__tests__/use-snippet-refresh-draft.spec.ts +++ b/web/app/components/snippets/hooks/__tests__/use-snippet-refresh-draft.spec.ts @@ -31,8 +31,8 @@ vi.mock('@/app/components/workflow/store', () => ({ }), })) -vi.mock('../../store', () => ({ - useSnippetDetailStore: { +vi.mock('../../draft-store', () => ({ + useSnippetDraftStore: { setState: (...args: unknown[]) => mockSnippetSetState(...args), }, })) @@ -75,7 +75,7 @@ describe('useSnippetRefreshDraft', () => { }) expect(mockFetchSnippetDraftWorkflow).toHaveBeenCalledWith('snippet-1') expect(mockSnippetSetState).toHaveBeenCalledWith({ - fields: [], + inputFields: [], }) expect(mockSetSyncWorkflowDraftHash).toHaveBeenCalledWith('draft-hash') expect(mockSetDraftUpdatedAt).toHaveBeenCalledWith(1_712_345_678) diff --git a/web/app/components/snippets/hooks/__tests__/use-snippet-start-run.spec.ts b/web/app/components/snippets/hooks/__tests__/use-snippet-start-run.spec.ts index 58edaccbbfb..cc8cbb9f536 100644 --- a/web/app/components/snippets/hooks/__tests__/use-snippet-start-run.spec.ts +++ b/web/app/components/snippets/hooks/__tests__/use-snippet-start-run.spec.ts @@ -4,7 +4,7 @@ import { act } from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import { PipelineInputVarType } from '@/models/pipeline' -import { useSnippetDetailStore } from '../../store' +import { useSnippetDraftStore } from '../../draft-store' import { useSnippetStartRun } from '../use-snippet-start-run' const mockWorkflowStoreGetState = vi.fn() @@ -39,7 +39,7 @@ const inputFields: SnippetInputField[] = [ describe('useSnippetStartRun', () => { beforeEach(() => { vi.clearAllMocks() - useSnippetDetailStore.getState().reset() + useSnippetDraftStore.getState().reset() mockWorkflowStoreGetState.mockReturnValue({ workflowRunningData: undefined, showDebugAndPreviewPanel: false, @@ -51,7 +51,7 @@ describe('useSnippetStartRun', () => { }) it('should open the debug panel and input form when snippet has input fields', () => { - useSnippetDetailStore.setState({ fields: inputFields }) + useSnippetDraftStore.getState().setInputFields(inputFields) const { result } = renderHook(() => useSnippetStartRun({ handleRun: mockHandleRun, @@ -83,7 +83,7 @@ describe('useSnippetStartRun', () => { }) it('should use current snippet input fields from the store before starting a run', () => { - useSnippetDetailStore.setState({ fields: inputFields }) + useSnippetDraftStore.getState().setInputFields(inputFields) const { result } = renderHook(() => useSnippetStartRun({ handleRun: mockHandleRun, @@ -99,7 +99,7 @@ describe('useSnippetStartRun', () => { }) it('should close the panel when debug panel is already open', () => { - useSnippetDetailStore.setState({ fields: inputFields }) + useSnippetDraftStore.getState().setInputFields(inputFields) mockWorkflowStoreGetState.mockReturnValue({ workflowRunningData: undefined, @@ -122,7 +122,7 @@ describe('useSnippetStartRun', () => { }) it('should do nothing when workflow is already running', () => { - useSnippetDetailStore.setState({ fields: inputFields }) + useSnippetDraftStore.getState().setInputFields(inputFields) mockWorkflowStoreGetState.mockReturnValue({ workflowRunningData: { diff --git a/web/app/components/snippets/hooks/use-nodes-sync-draft.ts b/web/app/components/snippets/hooks/use-nodes-sync-draft.ts index 8550a5200c2..3cdab0254b2 100644 --- a/web/app/components/snippets/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/snippets/hooks/use-nodes-sync-draft.ts @@ -10,7 +10,7 @@ import { API_PREFIX } from '@/config' import { consoleClient } from '@/service/client' // eslint-disable-next-line no-restricted-imports import { postWithKeepalive } from '@/service/fetch' -import { useSnippetDetailStore } from '../store' +import { useSnippetDraftStore } from '../draft-store' import { useSnippetRefreshDraft } from './use-snippet-refresh-draft' const isSyncConflictError = (error: unknown): error is { bodyUsed: boolean, json: () => Promise<{ code?: string }> } => { @@ -25,10 +25,6 @@ type SyncInputFieldsDraftCallback = SyncDraftCallback & { onRefresh?: (inputFields: SnippetInputField[]) => void } -type UseNodesSyncDraftOptions = { - getInputFields?: () => SnippetInputField[] -} - const snippetDraftSyncQueues = new Map>() const enqueueSnippetDraftSync = ( @@ -48,18 +44,17 @@ const enqueueSnippetDraftSync = ( return nextTask } -export const useNodesSyncDraft = (snippetId: string, options: UseNodesSyncDraftOptions = {}) => { +export const useNodesSyncDraft = (snippetId: string) => { const store = useStoreApi() const workflowStore = useWorkflowStore() const { getNodesReadOnly } = useNodesReadOnlyByCanEdit(true) const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId) - const { getInputFields } = options const getInputFieldsSyncPayload = useCallback((inputFields?: SnippetInputField[]) => { return { - input_fields: inputFields ?? getInputFields?.() ?? useSnippetDetailStore.getState().fields, + input_fields: inputFields ?? useSnippetDraftStore.getState().inputFields, } - }, [getInputFields]) + }, []) const getDraftSyncPayload = useCallback((inputFields?: SnippetInputField[]) => { const { diff --git a/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts b/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts index 12174e731ea..e310c919fba 100644 --- a/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts +++ b/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts @@ -5,7 +5,7 @@ import { useCallback } from 'react' import { useWorkflowUpdate } from '@/app/components/workflow/hooks' import { useWorkflowStore } from '@/app/components/workflow/store' import { fetchSnippetDraftWorkflow } from '@/service/use-snippet-workflows' -import { useSnippetDetailStore } from '../store' +import { useSnippetDraftStore } from '../draft-store' export const useSnippetRefreshDraft = (snippetId: string) => { const workflowStore = useWorkflowStore() @@ -36,8 +36,8 @@ export const useSnippetRefreshDraft = (snippetId: string) => { edges: response.graph?.edges || [], viewport: response.graph?.viewport || { x: 0, y: 0, zoom: 1 }, } as WorkflowDataUpdater) - useSnippetDetailStore.setState({ - fields: inputFields, + useSnippetDraftStore.setState({ + inputFields, }) setSyncWorkflowDraftHash(response.hash) setDraftUpdatedAt(response.updated_at) diff --git a/web/app/components/snippets/hooks/use-snippet-start-run.ts b/web/app/components/snippets/hooks/use-snippet-start-run.ts index 9657391bf2a..3e7ec5f4ac2 100644 --- a/web/app/components/snippets/hooks/use-snippet-start-run.ts +++ b/web/app/components/snippets/hooks/use-snippet-start-run.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react' import { useWorkflowInteractions } from '@/app/components/workflow/hooks' import { useWorkflowStore } from '@/app/components/workflow/store' import { WorkflowRunningStatus } from '@/app/components/workflow/types' -import { useSnippetDetailStore } from '../store' +import { useSnippetDraftStore } from '../draft-store' type UseSnippetStartRunOptions = { handleRun: (params: SnippetDraftRunPayload) => void @@ -38,7 +38,7 @@ export const useSnippetStartRun = ({ setShowDebugAndPreviewPanel(true) - const currentInputFields = useSnippetDetailStore.getState().fields + const currentInputFields = useSnippetDraftStore.getState().inputFields if (currentInputFields.length > 0) { setShowInputsPanel(true) diff --git a/web/app/components/snippets/store/__tests__/index.spec.ts b/web/app/components/snippets/store/__tests__/index.spec.ts index 06005c97a57..aa7611bae2a 100644 --- a/web/app/components/snippets/store/__tests__/index.spec.ts +++ b/web/app/components/snippets/store/__tests__/index.spec.ts @@ -1,14 +1,6 @@ -import type { SnippetDetail, SnippetInputField } from '@/models/snippet' -import { PipelineInputVarType } from '@/models/pipeline' +import type { SnippetDetail } from '@/models/snippet' import { useSnippetDetailStore } from '..' -const createField = (variable: string): SnippetInputField => ({ - label: variable, - variable, - type: PipelineInputVarType.textInput, - required: true, -}) - const snippet: SnippetDetail = { id: 'snippet-1', name: 'Snippet', @@ -23,21 +15,6 @@ describe('useSnippetDetailStore', () => { useSnippetDetailStore.getState().reset() }) - it('should store and reset snippet input fields', () => { - const fields = [ - createField('topic'), - createField('audience'), - ] - - useSnippetDetailStore.getState().setFields(fields) - - expect(useSnippetDetailStore.getState().fields).toEqual(fields) - - useSnippetDetailStore.getState().reset() - - expect(useSnippetDetailStore.getState().fields).toEqual([]) - }) - it('should store and reset snippet navigation state', () => { const onFieldsChange = vi.fn() @@ -58,7 +35,6 @@ describe('useSnippetDetailStore', () => { useSnippetDetailStore.getState().reset() expect(useSnippetDetailStore.getState()).toMatchObject({ - fields: [], readonly: true, snippet: undefined, snippetId: undefined, diff --git a/web/app/components/snippets/store/index.ts b/web/app/components/snippets/store/index.ts index ee4006f6d7a..09ec732aae1 100644 --- a/web/app/components/snippets/store/index.ts +++ b/web/app/components/snippets/store/index.ts @@ -11,14 +11,11 @@ type SnippetNavigationState = { } type SnippetDetailUIState = { - fields: SnippetInputField[] - setFields: (fields: SnippetInputField[]) => void setNavigationState: (state: SnippetNavigationState) => void reset: () => void } & SnippetNavigationState const initialState = { - fields: [] as SnippetInputField[], readonly: true, snippet: undefined, snippetId: undefined, @@ -27,7 +24,6 @@ const initialState = { export const useSnippetDetailStore = create(set => ({ ...initialState, - setFields: fields => set({ fields }), setNavigationState: state => set(state), reset: () => set(initialState), }))