diff --git a/web/app/components/snippets/__tests__/index.spec.tsx b/web/app/components/snippets/__tests__/index.spec.tsx
index e0a2777f2a..3c9a831035 100644
--- a/web/app/components/snippets/__tests__/index.spec.tsx
+++ b/web/app/components/snippets/__tests__/index.spec.tsx
@@ -22,6 +22,7 @@ vi.mock('../hooks/use-configs-map', () => ({
vi.mock('../hooks/use-nodes-sync-draft', () => ({
useNodesSyncDraft: () => ({
doSyncWorkflowDraft: vi.fn(),
+ syncInputFieldsDraft: vi.fn(),
syncWorkflowDraftWhenPageClose: vi.fn(),
}),
}))
diff --git a/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx
new file mode 100644
index 0000000000..7550d7d045
--- /dev/null
+++ b/web/app/components/snippets/components/__tests__/snippet-main.spec.tsx
@@ -0,0 +1,216 @@
+import type { WorkflowProps } from '@/app/components/workflow'
+import type { SnippetDetailPayload, SnippetInputField, SnippetSection } from '@/models/snippet'
+import { fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { PipelineInputVarType } from '@/models/pipeline'
+import SnippetMain from '../snippet-main'
+
+const mockSetAppSidebarExpand = vi.fn()
+const mockSyncInputFieldsDraft = vi.fn()
+const mockCloseEditor = vi.fn()
+const mockOpenEditor = vi.fn()
+const mockReset = vi.fn()
+const mockSetInputPanelOpen = vi.fn()
+const mockToggleInputPanel = vi.fn()
+const mockTogglePublishMenu = vi.fn()
+
+vi.mock('@/hooks/use-breakpoints', () => ({
+ default: () => 'desktop',
+ MediaType: { mobile: 'mobile', desktop: 'desktop' },
+}))
+
+vi.mock('@/app/components/app/store', () => ({
+ useStore: (selector: (state: { setAppSidebarExpand: typeof mockSetAppSidebarExpand }) => unknown) => selector({
+ setAppSidebarExpand: mockSetAppSidebarExpand,
+ }),
+}))
+
+vi.mock('@/app/components/snippets/store', () => ({
+ useSnippetDetailStore: (selector: (state: {
+ editingField: SnippetInputField | null
+ isEditorOpen: boolean
+ isInputPanelOpen: boolean
+ isPublishMenuOpen: boolean
+ closeEditor: typeof mockCloseEditor
+ openEditor: typeof mockOpenEditor
+ reset: typeof mockReset
+ setInputPanelOpen: typeof mockSetInputPanelOpen
+ toggleInputPanel: typeof mockToggleInputPanel
+ togglePublishMenu: typeof mockTogglePublishMenu
+ }) => unknown) => selector({
+ editingField: null,
+ isEditorOpen: false,
+ isInputPanelOpen: true,
+ isPublishMenuOpen: false,
+ closeEditor: mockCloseEditor,
+ openEditor: mockOpenEditor,
+ reset: mockReset,
+ setInputPanelOpen: mockSetInputPanelOpen,
+ toggleInputPanel: mockToggleInputPanel,
+ togglePublishMenu: mockTogglePublishMenu,
+ }),
+}))
+
+vi.mock('@/app/components/snippets/hooks/use-configs-map', () => ({
+ useConfigsMap: () => ({
+ flowId: 'snippet-1',
+ flowType: 'snippet',
+ fileSettings: {},
+ }),
+}))
+
+vi.mock('@/app/components/snippets/hooks/use-nodes-sync-draft', () => ({
+ useNodesSyncDraft: () => ({
+ doSyncWorkflowDraft: vi.fn(),
+ syncInputFieldsDraft: mockSyncInputFieldsDraft,
+ syncWorkflowDraftWhenPageClose: vi.fn(),
+ }),
+}))
+
+vi.mock('@/app/components/snippets/hooks/use-snippet-refresh-draft', () => ({
+ useSnippetRefreshDraft: () => ({
+ handleRefreshWorkflowDraft: vi.fn(),
+ }),
+}))
+
+vi.mock('@/app/components/app-sidebar', () => ({
+ default: ({
+ renderHeader,
+ renderNavigation,
+ }: {
+ renderHeader?: (modeState: string) => React.ReactNode
+ renderNavigation?: (modeState: string) => React.ReactNode
+ }) => (
+
+
{renderHeader?.('expand')}
+
{renderNavigation?.('expand')}
+
+ ),
+}))
+
+vi.mock('@/app/components/app-sidebar/nav-link', () => ({
+ default: ({ name }: { name: string }) => {name}
,
+}))
+
+vi.mock('@/app/components/app-sidebar/snippet-info', () => ({
+ default: () => ,
+}))
+
+vi.mock('@/app/components/evaluation', () => ({
+ default: () => ,
+}))
+
+vi.mock('@/app/components/workflow', () => ({
+ WorkflowWithInnerContext: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}))
+
+vi.mock('@/app/components/snippets/components/snippet-children', () => ({
+ default: ({
+ onRemoveField,
+ onSubmitField,
+ }: {
+ onRemoveField: (index: number) => void
+ onSubmitField: (field: SnippetInputField) => void
+ }) => (
+
+
+
+
+ ),
+}))
+
+const payload: SnippetDetailPayload = {
+ snippet: {
+ id: 'snippet-1',
+ name: 'Snippet',
+ description: 'desc',
+ author: '',
+ updatedAt: '2026-03-29 10:00',
+ usage: '0',
+ icon: '',
+ iconBackground: '',
+ },
+ graph: {
+ nodes: [],
+ edges: [],
+ viewport: { x: 0, y: 0, zoom: 1 },
+ },
+ inputFields: [
+ {
+ type: PipelineInputVarType.textInput,
+ label: 'Blog URL',
+ variable: 'blog_url',
+ required: true,
+ },
+ ],
+ uiMeta: {
+ inputFieldCount: 1,
+ checklistCount: 0,
+ autoSavedAt: '2026-03-29 10:00',
+ },
+}
+
+const renderSnippetMain = (section: SnippetSection = 'orchestrate') => {
+ return render(
+ ,
+ )
+}
+
+describe('SnippetMain', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockSyncInputFieldsDraft.mockResolvedValue(undefined)
+ })
+
+ describe('Input Fields Sync', () => {
+ it('should sync draft input_fields when removing a field from the panel', async () => {
+ renderSnippetMain()
+
+ fireEvent.click(screen.getByRole('button', { name: 'remove' }))
+
+ await waitFor(() => {
+ expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith([], {
+ onRefresh: expect.any(Function),
+ })
+ })
+ })
+
+ it('should sync draft input_fields when submitting a field from the editor', async () => {
+ renderSnippetMain()
+
+ fireEvent.click(screen.getByRole('button', { name: 'submit' }))
+
+ await waitFor(() => {
+ expect(mockSyncInputFieldsDraft).toHaveBeenCalledWith([
+ payload.inputFields[0],
+ {
+ type: PipelineInputVarType.textInput,
+ label: 'New Field',
+ variable: 'new_field',
+ required: true,
+ },
+ ], {
+ onRefresh: expect.any(Function),
+ })
+ })
+ })
+ })
+})
diff --git a/web/app/components/snippets/components/__tests__/workflow-panel.spec.tsx b/web/app/components/snippets/components/__tests__/workflow-panel.spec.tsx
index e897216e6a..7b81a98494 100644
--- a/web/app/components/snippets/components/__tests__/workflow-panel.spec.tsx
+++ b/web/app/components/snippets/components/__tests__/workflow-panel.spec.tsx
@@ -35,8 +35,7 @@ describe('SnippetWorkflowPanel', () => {
onCloseEditor={vi.fn()}
onSubmitField={vi.fn()}
onRemoveField={vi.fn()}
- onPrimarySortChange={vi.fn()}
- onSecondarySortChange={vi.fn()}
+ onSortChange={vi.fn()}
/>,
)
diff --git a/web/app/components/snippets/components/panel/index.tsx b/web/app/components/snippets/components/panel/index.tsx
index c949a35f14..96de81d2ee 100644
--- a/web/app/components/snippets/components/panel/index.tsx
+++ b/web/app/components/snippets/components/panel/index.tsx
@@ -5,7 +5,6 @@ import type { SnippetInputField } from '@/models/snippet'
import { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
-import Divider from '@/app/components/base/divider'
import FieldListContainer from '@/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container'
type SnippetInputFieldPanelProps = {
@@ -14,8 +13,7 @@ type SnippetInputFieldPanelProps = {
onAdd: () => void
onEdit: (field: SnippetInputField) => void
onRemove: (index: number) => void
- onPrimarySortChange: (fields: SnippetInputField[]) => void
- onSecondarySortChange: (fields: SnippetInputField[]) => void
+ onSortChange: (fields: SnippetInputField[]) => void
}
const toInputFields = (list: SortableItem[]) => {
@@ -31,32 +29,19 @@ const SnippetInputFieldPanel = ({
onAdd,
onEdit,
onRemove,
- onPrimarySortChange,
- onSecondarySortChange,
+ onSortChange,
}: SnippetInputFieldPanelProps) => {
const { t } = useTranslation('snippet')
- const primaryFields = fields.slice(0, 2)
- const secondaryFields = fields.slice(2)
- const handlePrimaryRemove = useCallback((index: number) => {
+ const handleRemove = useCallback((index: number) => {
onRemove(index)
}, [onRemove])
- const handleSecondaryRemove = useCallback((index: number) => {
- onRemove(index + primaryFields.length)
- }, [onRemove, primaryFields.length])
-
- const handlePrimaryEdit = useCallback((id: string) => {
- const field = primaryFields.find(item => item.variable === id)
+ const handleEdit = useCallback((id: string) => {
+ const field = fields.find(item => item.variable === id)
if (field)
onEdit(field)
- }, [onEdit, primaryFields])
-
- const handleSecondaryEdit = useCallback((id: string) => {
- const field = secondaryFields.find(item => item.variable === id)
- if (field)
- onEdit(field)
- }, [onEdit, secondaryFields])
+ }, [fields, onEdit])
return (
@@ -79,37 +64,19 @@ const SnippetInputFieldPanel = ({
-
-
- {t('panelPrimaryGroup')}
-
onPrimarySortChange(toInputFields(list))}
- onRemoveField={handlePrimaryRemove}
- onEditField={handlePrimaryEdit}
- />
-
-
-
-
- {t('panelSecondaryGroup')}
-
- onSecondarySortChange(toInputFields(list))}
- onRemoveField={handleSecondaryRemove}
- onEditField={handleSecondaryEdit}
+ className="flex flex-col gap-y-1 px-4 py-4"
+ inputFields={fields}
+ onListSortChange={list => onSortChange(toInputFields(list))}
+ onRemoveField={handleRemove}
+ onEditField={handleEdit}
/>
diff --git a/web/app/components/snippets/components/snippet-children.tsx b/web/app/components/snippets/components/snippet-children.tsx
index b812e56a71..af5da29cb0 100644
--- a/web/app/components/snippets/components/snippet-children.tsx
+++ b/web/app/components/snippets/components/snippet-children.tsx
@@ -22,8 +22,7 @@ type SnippetChildrenProps = {
onCloseEditor: () => void
onSubmitField: (field: SnippetInputField) => void
onRemoveField: (index: number) => void
- onPrimarySortChange: (fields: SnippetInputField[]) => void
- onSecondarySortChange: (fields: SnippetInputField[]) => void
+ onSortChange: (fields: SnippetInputField[]) => void
}
const SnippetChildren = ({
@@ -41,8 +40,7 @@ const SnippetChildren = ({
onCloseEditor,
onSubmitField,
onRemoveField,
- onPrimarySortChange,
- onSecondarySortChange,
+ onSortChange,
}: SnippetChildrenProps) => {
return (
<>
@@ -66,8 +64,7 @@ const SnippetChildren = ({
onCloseEditor={onCloseEditor}
onSubmitField={onSubmitField}
onRemoveField={onRemoveField}
- onPrimarySortChange={onPrimarySortChange}
- onSecondarySortChange={onSecondarySortChange}
+ onSortChange={onSortChange}
/>
{isPublishMenuOpen && (
@@ -85,8 +82,7 @@ const SnippetChildren = ({
onAdd={() => onOpenEditor()}
onEdit={onOpenEditor}
onRemove={onRemoveField}
- onPrimarySortChange={onPrimarySortChange}
- onSecondarySortChange={onSecondarySortChange}
+ onSortChange={onSortChange}
/>
diff --git a/web/app/components/snippets/components/snippet-main.tsx b/web/app/components/snippets/components/snippet-main.tsx
index a5441b6df8..770404acca 100644
--- a/web/app/components/snippets/components/snippet-main.tsx
+++ b/web/app/components/snippets/components/snippet-main.tsx
@@ -61,6 +61,7 @@ const SnippetMain = ({
const [fields, setFields] = useState(payload.inputFields)
const {
doSyncWorkflowDraft,
+ syncInputFieldsDraft,
syncWorkflowDraftWhenPageClose,
} = useNodesSyncDraft(snippetId)
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
@@ -100,19 +101,16 @@ const SnippetMain = ({
setAppSidebarExpand(isMobile ? mode : localeMode)
}, [isMobile, setAppSidebarExpand])
- const primaryFields = useMemo(() => fields.slice(0, 2), [fields])
- const secondaryFields = useMemo(() => fields.slice(2), [fields])
-
- const handlePrimarySortChange = (newFields: SnippetInputField[]) => {
- setFields([...newFields, ...secondaryFields])
- }
-
- const handleSecondarySortChange = (newFields: SnippetInputField[]) => {
- setFields([...primaryFields, ...newFields])
+ const handleSortChange = (newFields: SnippetInputField[]) => {
+ setFields(newFields)
}
const handleRemoveField = (index: number) => {
- setFields(current => current.filter((_, currentIndex) => currentIndex !== index))
+ const nextFields = fields.filter((_, currentIndex) => currentIndex !== index)
+ setFields(nextFields)
+ void syncInputFieldsDraft(nextFields, {
+ onRefresh: setFields,
+ })
}
const handleSubmitField = (field: SnippetInputField) => {
@@ -124,10 +122,14 @@ const SnippetMain = ({
return
}
- if (originalVariable)
- setFields(current => current.map(item => item.variable === originalVariable ? field : item))
- else
- setFields(current => [...current, field])
+ const nextFields = originalVariable
+ ? fields.map(item => item.variable === originalVariable ? field : item)
+ : [...fields, field]
+
+ setFields(nextFields)
+ void syncInputFieldsDraft(nextFields, {
+ onRefresh: setFields,
+ })
closeEditor()
}
@@ -205,8 +207,7 @@ const SnippetMain = ({
onCloseEditor={closeEditor}
onSubmitField={handleSubmitField}
onRemoveField={handleRemoveField}
- onPrimarySortChange={handlePrimarySortChange}
- onSecondarySortChange={handleSecondarySortChange}
+ onSortChange={handleSortChange}
/>
)}
diff --git a/web/app/components/snippets/components/workflow-panel.tsx b/web/app/components/snippets/components/workflow-panel.tsx
index 47769899b8..80c41df8a0 100644
--- a/web/app/components/snippets/components/workflow-panel.tsx
+++ b/web/app/components/snippets/components/workflow-panel.tsx
@@ -18,8 +18,7 @@ type SnippetWorkflowPanelProps = {
onCloseEditor: () => void
onSubmitField: (field: SnippetInputField) => void
onRemoveField: (index: number) => void
- onPrimarySortChange: (fields: SnippetInputField[]) => void
- onSecondarySortChange: (fields: SnippetInputField[]) => void
+ onSortChange: (fields: SnippetInputField[]) => void
}
const SnippetPanelOnLeft = ({
@@ -32,8 +31,7 @@ const SnippetPanelOnLeft = ({
onCloseEditor,
onSubmitField,
onRemoveField,
- onPrimarySortChange,
- onSecondarySortChange,
+ onSortChange,
}: SnippetWorkflowPanelProps) => {
return (
@@ -51,8 +49,7 @@ const SnippetPanelOnLeft = ({
onAdd={() => onOpenEditor()}
onEdit={onOpenEditor}
onRemove={onRemoveField}
- onPrimarySortChange={onPrimarySortChange}
- onSecondarySortChange={onSecondarySortChange}
+ onSortChange={onSortChange}
/>
)}
@@ -70,8 +67,7 @@ const SnippetWorkflowPanel = ({
onCloseEditor,
onSubmitField,
onRemoveField,
- onPrimarySortChange,
- onSecondarySortChange,
+ onSortChange,
}: SnippetWorkflowPanelProps) => {
const versionHistoryPanelProps = useMemo(() => {
return {
@@ -98,8 +94,7 @@ const SnippetWorkflowPanel = ({
onCloseEditor={onCloseEditor}
onSubmitField={onSubmitField}
onRemoveField={onRemoveField}
- onPrimarySortChange={onPrimarySortChange}
- onSecondarySortChange={onSecondarySortChange}
+ onSortChange={onSortChange}
/>
),
},
@@ -113,10 +108,10 @@ const SnippetWorkflowPanel = ({
onCloseEditor,
onCloseInputPanel,
onOpenEditor,
- onPrimarySortChange,
onRemoveField,
- onSecondarySortChange,
+ onSortChange,
onSubmitField,
+ snippetId,
versionHistoryPanelProps,
])
diff --git a/web/app/components/snippets/hooks/__tests__/use-snippet-init.spec.ts b/web/app/components/snippets/hooks/__tests__/use-snippet-init.spec.ts
index e53f1c13fe..beb019b6a4 100644
--- a/web/app/components/snippets/hooks/__tests__/use-snippet-init.spec.ts
+++ b/web/app/components/snippets/hooks/__tests__/use-snippet-init.spec.ts
@@ -84,6 +84,68 @@ describe('useSnippetInit', () => {
expect(result.current.isLoading).toBe(false)
})
+ it('should use draft input_fields for snippet inputs', () => {
+ mockUseSnippetApiDetail.mockReturnValue({
+ data: {
+ id: 'snippet-1',
+ name: 'Tone Rewriter',
+ description: 'A static snippet mock.',
+ type: 'node',
+ is_published: false,
+ version: '1',
+ use_count: 0,
+ icon_info: {
+ icon_type: null,
+ icon: '🪄',
+ icon_background: '#E0EAFF',
+ },
+ input_fields: [
+ {
+ label: 'Published field',
+ variable: 'published_field',
+ type: 'text-input',
+ required: true,
+ },
+ ],
+ created_at: 1_712_300_000,
+ updated_at: 1_712_300_000,
+ author: 'Evan',
+ },
+ error: null,
+ isLoading: false,
+ })
+ mockUseSnippetDraftWorkflow.mockReturnValue({
+ data: {
+ id: 'draft-1',
+ graph: {},
+ features: {},
+ input_fields: [
+ {
+ label: 'Draft field',
+ variable: 'draft_field',
+ type: 'text-input',
+ required: true,
+ },
+ ],
+ hash: 'draft-hash',
+ created_at: 1_712_300_000,
+ updated_at: 1_712_345_678,
+ },
+ isLoading: false,
+ })
+
+ const { result } = renderHook(() => useSnippetInit('snippet-1'))
+
+ expect(result.current.data?.inputFields).toEqual([
+ {
+ label: 'Draft field',
+ variable: 'draft_field',
+ type: 'text-input',
+ required: true,
+ },
+ ])
+ })
+
it('should sync draft metadata into workflow store', () => {
mockUseSnippetDraftWorkflow.mockImplementation((_snippetId: string, onSuccess?: (data: { updated_at: number, hash: string }) => void) => {
onSuccess?.({
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 e216876979..169f1e6e9a 100644
--- a/web/app/components/snippets/hooks/use-nodes-sync-draft.ts
+++ b/web/app/components/snippets/hooks/use-nodes-sync-draft.ts
@@ -1,4 +1,6 @@
import type { SyncDraftCallback } from '@/app/components/workflow/hooks-store'
+import type { SnippetInputField } from '@/models/snippet'
+import type { SnippetDraftSyncPayload, SnippetWorkflow } from '@/types/snippet'
import { produce } from 'immer'
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
@@ -18,13 +20,17 @@ const isSyncConflictError = (error: unknown): error is { bodyUsed: boolean, json
&& typeof error.json === 'function'
}
+type SyncInputFieldsDraftCallback = SyncDraftCallback & {
+ onRefresh?: (inputFields: SnippetInputField[]) => void
+}
+
export const useNodesSyncDraft = (snippetId: string) => {
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const { getNodesReadOnly } = useNodesReadOnly()
const { handleRefreshWorkflowDraft } = useSnippetRefreshDraft(snippetId)
- const getPostParams = useCallback(() => {
+ const getGraphSyncPayload = useCallback(() => {
const {
getNodes,
edges,
@@ -32,8 +38,6 @@ export const useNodesSyncDraft = (snippetId: string) => {
} = store.getState()
const nodes = getNodes().filter(node => !node.data?._isTempNode)
const [x, y, zoom] = transform
- const { syncWorkflowDraftHash } = workflowStore.getState()
-
if (!snippetId)
return null
@@ -55,47 +59,39 @@ export const useNodesSyncDraft = (snippetId: string) => {
})
return {
- url: `/snippets/${snippetId}/workflows/draft`,
- params: {
- graph: {
- nodes: producedNodes,
- edges: producedEdges,
- viewport: { x, y, zoom },
- },
- hash: syncWorkflowDraftHash,
+ graph: {
+ nodes: producedNodes,
+ edges: producedEdges,
+ viewport: { x, y, zoom },
},
}
- }, [snippetId, store, workflowStore])
+ }, [snippetId, store])
- const syncWorkflowDraftWhenPageClose = useCallback(() => {
- if (getNodesReadOnly())
- return
-
- const postParams = getPostParams()
- if (postParams)
- postWithKeepalive(`${API_PREFIX}${postParams.url}`, postParams.params)
- }, [getNodesReadOnly, getPostParams])
-
- const performSync = useCallback(async (
+ const syncDraft = useCallback(async (
+ payload: Omit,
notRefreshWhenSyncError?: boolean,
callback?: SyncDraftCallback,
+ onRefresh?: (draftWorkflow: SnippetWorkflow) => void,
) => {
if (getNodesReadOnly())
return
- const postParams = getPostParams()
- if (!postParams)
+ if (!snippetId)
return
const {
setDraftUpdatedAt,
setSyncWorkflowDraftHash,
+ syncWorkflowDraftHash,
} = workflowStore.getState()
try {
const response = await consoleClient.snippets.syncDraftWorkflow({
params: { snippetId },
- body: postParams.params,
+ body: {
+ ...payload,
+ hash: syncWorkflowDraftHash || undefined,
+ },
})
setSyncWorkflowDraftHash(response.hash)
@@ -106,7 +102,7 @@ export const useNodesSyncDraft = (snippetId: string) => {
if (isSyncConflictError(error) && !error.bodyUsed) {
error.json().then((err) => {
if (err.code === 'draft_workflow_not_sync' && !notRefreshWhenSyncError)
- handleRefreshWorkflowDraft()
+ handleRefreshWorkflowDraft(onRefresh)
})
}
callback?.onError?.()
@@ -114,12 +110,57 @@ export const useNodesSyncDraft = (snippetId: string) => {
finally {
callback?.onSettled?.()
}
- }, [getNodesReadOnly, getPostParams, handleRefreshWorkflowDraft, snippetId, workflowStore])
+ }, [getNodesReadOnly, handleRefreshWorkflowDraft, snippetId, workflowStore])
+
+ const syncWorkflowDraftWhenPageClose = useCallback(() => {
+ if (getNodesReadOnly())
+ return
+
+ const graphPayload = getGraphSyncPayload()
+ if (!graphPayload)
+ return
+
+ const { syncWorkflowDraftHash } = workflowStore.getState()
+ postWithKeepalive(`${API_PREFIX}/snippets/${snippetId}/workflows/draft`, {
+ ...graphPayload,
+ hash: syncWorkflowDraftHash,
+ })
+ }, [getGraphSyncPayload, getNodesReadOnly, snippetId, workflowStore])
+
+ const performSync = useCallback(async (
+ notRefreshWhenSyncError?: boolean,
+ callback?: SyncDraftCallback,
+ ) => {
+ const graphPayload = getGraphSyncPayload()
+ if (!graphPayload)
+ return
+
+ await syncDraft(graphPayload, notRefreshWhenSyncError, callback)
+ }, [getGraphSyncPayload, syncDraft])
+
+ const performInputFieldsSync = useCallback(async (
+ inputFields: SnippetInputField[],
+ callback?: SyncInputFieldsDraftCallback,
+ ) => {
+ await syncDraft(
+ { input_fields: inputFields },
+ false,
+ callback,
+ (draftWorkflow) => {
+ const refreshedInputFields = Array.isArray(draftWorkflow.input_fields)
+ ? draftWorkflow.input_fields as SnippetInputField[]
+ : []
+ callback?.onRefresh?.(refreshedInputFields)
+ },
+ )
+ }, [syncDraft])
const doSyncWorkflowDraft = useSerialAsyncCallback(performSync, getNodesReadOnly)
+ const syncInputFieldsDraft = useSerialAsyncCallback(performInputFieldsSync)
return {
doSyncWorkflowDraft,
+ syncInputFieldsDraft,
syncWorkflowDraftWhenPageClose,
}
}
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 d37a338a37..270db76a00 100644
--- a/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts
+++ b/web/app/components/snippets/hooks/use-snippet-refresh-draft.ts
@@ -1,4 +1,5 @@
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
+import type { SnippetWorkflow } from '@/types/snippet'
import { useCallback } from 'react'
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
import { useWorkflowStore } from '@/app/components/workflow/store'
@@ -8,7 +9,7 @@ export const useSnippetRefreshDraft = (snippetId: string) => {
const workflowStore = useWorkflowStore()
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
- const handleRefreshWorkflowDraft = useCallback(() => {
+ const handleRefreshWorkflowDraft = useCallback((onSuccess?: (draftWorkflow: SnippetWorkflow) => void) => {
const {
setDraftUpdatedAt,
setIsSyncingWorkflowDraft,
@@ -30,6 +31,7 @@ export const useSnippetRefreshDraft = (snippetId: string) => {
} as WorkflowDataUpdater)
setSyncWorkflowDraftHash(response.hash)
setDraftUpdatedAt(response.updated_at)
+ onSuccess?.(response)
}).finally(() => {
setIsSyncingWorkflowDraft(false)
})
diff --git a/web/service/use-snippets.ts b/web/service/use-snippets.ts
index 6b5ddc780f..12df39b830 100644
--- a/web/service/use-snippets.ts
+++ b/web/service/use-snippets.ts
@@ -102,8 +102,8 @@ const toSnippetCanvasData = (workflow?: SnippetWorkflow): SnippetCanvasData => {
}
export const buildSnippetDetailPayload = (snippet: SnippetContract, workflow?: SnippetWorkflow): SnippetDetailPayload => {
- const inputFields = Array.isArray(snippet.input_fields)
- ? snippet.input_fields as SnippetInputFieldUIModel[]
+ const inputFields = Array.isArray(workflow?.input_fields)
+ ? workflow.input_fields as SnippetInputFieldUIModel[]
: []
return {
diff --git a/web/types/snippet.ts b/web/types/snippet.ts
index f962b20ab5..f0651c6ebf 100644
--- a/web/types/snippet.ts
+++ b/web/types/snippet.ts
@@ -77,6 +77,7 @@ export type SnippetWorkflow = {
id: string
graph: Record
features: Record
+ input_fields?: SnippetInputField[]
hash: string
created_at: number
updated_at: number
@@ -87,7 +88,7 @@ export type SnippetDraftSyncPayload = {
hash?: string
environment_variables?: Record[]
conversation_variables?: Record[]
- input_variables?: Record[]
+ input_fields?: SnippetInputField[]
}
export type SnippetDraftSyncResponse = {