mirror of
https://github.com/langgenius/dify.git
synced 2026-01-19 11:45:05 +08:00
refactor(skill): migrate skill editor store to workflow store slice injection
Refactor the skill editor state management from a standalone Zustand store with Context provider pattern to a slice injection pattern that integrates with the existing workflow store. This aligns with how rag-pipeline already injects its slice. - Remove SkillEditorProvider and SkillEditorContext - Export createSkillEditorSlice for injection into workflow store - Update all components to use useStore/useWorkflowStore from workflow store - Add SkillEditorSliceShape to SliceFromInjection union type - Use type-safe slice creator args without any types
This commit is contained in:
@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { WorkflowSliceShape } from './store/workflow/workflow-slice'
|
||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||
import type { SkillEditorSliceShape } from '@/app/components/workflow/skill/store'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import {
|
||||
@ -17,8 +18,8 @@ import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import {
|
||||
WorkflowContextProvider,
|
||||
} from '@/app/components/workflow/context'
|
||||
import { SkillEditorProvider } from '@/app/components/workflow/skill/context'
|
||||
import SkillMain from '@/app/components/workflow/skill/main'
|
||||
import { createSkillEditorSlice } from '@/app/components/workflow/skill/store'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
|
||||
import {
|
||||
@ -247,14 +248,31 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
)
|
||||
}
|
||||
|
||||
type WorkflowAppInjectedSlice = WorkflowSliceShape & SkillEditorSliceShape
|
||||
type SliceCreatorArgs = Parameters<import('zustand').StateCreator<WorkflowAppInjectedSlice>>
|
||||
|
||||
const injectWorkflowStoreSliceFn: import('@/app/components/workflow/store').InjectWorkflowStoreSliceFn = (
|
||||
set,
|
||||
get,
|
||||
store,
|
||||
) => {
|
||||
const args: SliceCreatorArgs = [
|
||||
set as SliceCreatorArgs[0],
|
||||
get as SliceCreatorArgs[1],
|
||||
store as SliceCreatorArgs[2],
|
||||
]
|
||||
return {
|
||||
...createWorkflowSlice(...args),
|
||||
...createSkillEditorSlice(...args),
|
||||
}
|
||||
}
|
||||
|
||||
const WorkflowAppWrapper = () => {
|
||||
return (
|
||||
<WorkflowContextProvider
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice as InjectWorkflowStoreSliceFn}
|
||||
injectWorkflowStoreSliceFn={injectWorkflowStoreSliceFn}
|
||||
>
|
||||
<SkillEditorProvider>
|
||||
<WorkflowAppWithAdditionalContext />
|
||||
</SkillEditorProvider>
|
||||
<WorkflowAppWithAdditionalContext />
|
||||
</WorkflowContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { SkillEditorStore } from './store'
|
||||
import { useRef } from 'react'
|
||||
import { createSkillEditorStore, SkillEditorContext } from './store'
|
||||
|
||||
type SkillEditorProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function SkillEditorProvider({ children }: SkillEditorProviderProps): React.ReactElement {
|
||||
const storeRef = useRef<SkillEditorStore | undefined>(undefined)
|
||||
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createSkillEditorStore()
|
||||
|
||||
return (
|
||||
<SkillEditorContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</SkillEditorContext.Provider>
|
||||
)
|
||||
}
|
||||
@ -5,33 +5,33 @@ import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import EditorTabItem from './editor-tab-item'
|
||||
import { useSkillAssetNodeMap } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
|
||||
const EditorTabs: FC = () => {
|
||||
const { t } = useTranslation('workflow')
|
||||
const openTabIds = useSkillEditorStore(s => s.openTabIds)
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const previewTabId = useSkillEditorStore(s => s.previewTabId)
|
||||
const dirtyContents = useSkillEditorStore(s => s.dirtyContents)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const openTabIds = useStore(s => s.openTabIds!)
|
||||
const activeTabId = useStore(s => s.activeTabId!)
|
||||
const previewTabId = useStore(s => s.previewTabId!)
|
||||
const dirtyContents = useStore(s => s.dirtyContents!)
|
||||
const storeApi = useWorkflowStore()
|
||||
const { data: nodeMap } = useSkillAssetNodeMap()
|
||||
|
||||
const [pendingCloseId, setPendingCloseId] = useState<string | null>(null)
|
||||
|
||||
const handleTabClick = useCallback((fileId: string) => {
|
||||
storeApi.getState().activateTab(fileId)
|
||||
storeApi.getState().activateTab?.(fileId)
|
||||
}, [storeApi])
|
||||
|
||||
const handleTabDoubleClick = useCallback((fileId: string) => {
|
||||
storeApi.getState().pinTab(fileId)
|
||||
storeApi.getState().pinTab?.(fileId)
|
||||
}, [storeApi])
|
||||
|
||||
const closeTab = useCallback((fileId: string) => {
|
||||
storeApi.getState().closeTab(fileId)
|
||||
storeApi.getState().clearDraftContent(fileId)
|
||||
storeApi.getState().closeTab?.(fileId)
|
||||
storeApi.getState().clearDraftContent?.(fileId)
|
||||
}, [storeApi])
|
||||
|
||||
const handleTabClose = useCallback((fileId: string) => {
|
||||
|
||||
@ -13,10 +13,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useRenameAppAssetNode } from '@/service/use-app-asset'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from '../store'
|
||||
import { getAncestorIds } from '../utils/tree-utils'
|
||||
import TreeContextMenu from './tree-context-menu'
|
||||
import TreeNode from './tree-node'
|
||||
@ -49,9 +49,9 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
const { data: treeData, isLoading, error } = useSkillAssetTreeData()
|
||||
const isMutating = useIsMutating() > 0
|
||||
|
||||
const expandedFolderIds = useSkillEditorStore(s => s.expandedFolderIds)
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const expandedFolderIds = useStore(s => s.expandedFolderIds!)
|
||||
const activeTabId = useStore(s => s.activeTabId!)
|
||||
const storeApi = useWorkflowStore()
|
||||
|
||||
const renameNode = useRenameAppAssetNode()
|
||||
|
||||
@ -62,12 +62,12 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
}, [expandedFolderIds])
|
||||
|
||||
const handleToggle = useCallback((id: string) => {
|
||||
storeApi.getState().toggleFolder(id)
|
||||
storeApi.getState().toggleFolder?.(id)
|
||||
}, [storeApi])
|
||||
|
||||
const handleActivate = useCallback((node: NodeApi<TreeNodeData>) => {
|
||||
if (node.data.node_type === 'file')
|
||||
storeApi.getState().openTab(node.data.id, { pinned: true })
|
||||
storeApi.getState().openTab?.(node.data.id, { pinned: true })
|
||||
else
|
||||
node.toggle()
|
||||
}, [storeApi])
|
||||
@ -95,7 +95,7 @@ const FileTree: React.FC<FileTreeProps> = ({ className }) => {
|
||||
|
||||
const ancestors = getAncestorIds(activeTabId, treeData.children)
|
||||
if (ancestors.length > 0)
|
||||
storeApi.getState().revealFile(ancestors)
|
||||
storeApi.getState().revealFile?.(ancestors)
|
||||
requestAnimationFrame(() => {
|
||||
const node = tree.get(activeTabId)
|
||||
if (node) {
|
||||
|
||||
@ -6,8 +6,8 @@ import type { TreeNodeData } from '../type'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useSkillAssetTreeData } from '../hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from '../store'
|
||||
import { findNodeById } from '../utils/tree-utils'
|
||||
import NodeMenu from './node-menu'
|
||||
|
||||
@ -17,12 +17,12 @@ type TreeContextMenuProps = {
|
||||
|
||||
const TreeContextMenu: FC<TreeContextMenuProps> = ({ treeRef }) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const contextMenu = useSkillEditorStore(s => s.contextMenu)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const contextMenu = useStore(s => s.contextMenu)
|
||||
const storeApi = useWorkflowStore()
|
||||
const { data: treeData } = useSkillAssetTreeData()
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
storeApi.getState().setContextMenu(null)
|
||||
storeApi.getState().setContextMenu?.(null)
|
||||
}, [storeApi])
|
||||
|
||||
useClickAway(() => {
|
||||
|
||||
@ -14,9 +14,9 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useDelayedClick } from '../hooks/use-delayed-click'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from '../store'
|
||||
import { getFileIconType } from '../utils/file-utils'
|
||||
import NodeMenu from './node-menu'
|
||||
import TreeEditInput from './tree-edit-input'
|
||||
@ -26,10 +26,10 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeData>)
|
||||
const { t } = useTranslation('workflow')
|
||||
const isFolder = node.data.node_type === 'folder'
|
||||
const isSelected = node.isSelected
|
||||
const isDirty = useSkillEditorStore(s => s.dirtyContents.has(node.data.id))
|
||||
const contextMenuNodeId = useSkillEditorStore(s => s.contextMenu?.nodeId)
|
||||
const isDirty = useStore(s => s.dirtyContents?.has(node.data.id) ?? false)
|
||||
const contextMenuNodeId = useStore(s => s.contextMenu?.nodeId)
|
||||
const hasContextMenu = contextMenuNodeId === node.data.id
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const storeApi = useWorkflowStore()
|
||||
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
|
||||
@ -41,11 +41,11 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeData>)
|
||||
)
|
||||
|
||||
const openFilePreview = useCallback(() => {
|
||||
storeApi.getState().openTab(node.data.id, { pinned: false })
|
||||
storeApi.getState().openTab?.(node.data.id, { pinned: false })
|
||||
}, [node.data.id, storeApi])
|
||||
|
||||
const openFilePinned = useCallback(() => {
|
||||
storeApi.getState().openTab(node.data.id, { pinned: true })
|
||||
storeApi.getState().openTab?.(node.data.id, { pinned: true })
|
||||
}, [node.data.id, storeApi])
|
||||
|
||||
const { handleClick: handleFileClick, handleDoubleClick: handleFileDoubleClick } = useDelayedClick({
|
||||
@ -79,7 +79,7 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeData>)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
storeApi.getState().setContextMenu({
|
||||
storeApi.getState().setContextMenu?.({
|
||||
top: e.clientY,
|
||||
left: e.clientX,
|
||||
nodeId: node.data.id,
|
||||
@ -97,7 +97,7 @@ const TreeNode = ({ node, style, dragHandle }: NodeRendererProps<TreeNodeData>)
|
||||
if (isFolder)
|
||||
node.toggle()
|
||||
else
|
||||
storeApi.getState().openTab(node.data.id, { pinned: true })
|
||||
storeApi.getState().openTab?.(node.data.id, { pinned: true })
|
||||
}
|
||||
}, [isFolder, node, storeApi])
|
||||
|
||||
|
||||
@ -6,12 +6,12 @@ import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
useCreateAppAssetFile,
|
||||
useCreateAppAssetFolder,
|
||||
useDeleteAppAssetNode,
|
||||
} from '@/service/use-app-asset'
|
||||
import { useSkillEditorStoreApi } from '../store'
|
||||
import { getAllDescendantFileIds } from '../utils/tree-utils'
|
||||
import { useSkillAssetTreeData } from './use-skill-asset-tree'
|
||||
|
||||
@ -36,7 +36,7 @@ export function useFileOperations({
|
||||
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const storeApi = useWorkflowStore()
|
||||
|
||||
const createFolder = useCreateAppAssetFolder()
|
||||
const createFile = useCreateAppAssetFile()
|
||||
@ -250,14 +250,14 @@ export function useFileOperations({
|
||||
await deleteNode.mutateAsync({ appId, nodeId })
|
||||
|
||||
descendantFileIds.forEach((fileId) => {
|
||||
storeApi.getState().closeTab(fileId)
|
||||
storeApi.getState().clearDraftContent(fileId)
|
||||
storeApi.getState().closeTab?.(fileId)
|
||||
storeApi.getState().clearDraftContent?.(fileId)
|
||||
})
|
||||
|
||||
// Also close and clear the node itself if it's a file
|
||||
if (!isFolder) {
|
||||
storeApi.getState().closeTab(nodeId)
|
||||
storeApi.getState().clearDraftContent(nodeId)
|
||||
storeApi.getState().closeTab?.(nodeId)
|
||||
storeApi.getState().clearDraftContent?.(nodeId)
|
||||
}
|
||||
|
||||
Toast.notify({
|
||||
|
||||
@ -18,10 +18,10 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useFileOperations } from './hooks/use-file-operations'
|
||||
import { useSkillAssetTreeData } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore } from './store'
|
||||
import { getTargetFolderIdFromSelection } from './utils/tree-utils'
|
||||
|
||||
type MenuItemProps = {
|
||||
@ -55,7 +55,7 @@ const SidebarSearchAdd: FC = () => {
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
const { data: treeData } = useSkillAssetTreeData()
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const activeTabId = useStore(s => s.activeTabId!)
|
||||
|
||||
const targetFolderId = useMemo(() => {
|
||||
if (!treeData?.children)
|
||||
|
||||
@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { useGetAppAssetFileContent, useUpdateAppAssetFileContent } from '@/service/use-app-asset'
|
||||
import { Theme } from '@/types/app'
|
||||
@ -19,7 +20,6 @@ import MediaFilePreview from './editor/media-file-preview'
|
||||
import OfficeFilePlaceholder from './editor/office-file-placeholder'
|
||||
import UnsupportedFileDownload from './editor/unsupported-file-download'
|
||||
import { useSkillAssetNodeMap } from './hooks/use-skill-asset-tree'
|
||||
import { useSkillEditorStore, useSkillEditorStoreApi } from './store'
|
||||
import { getFileExtension, getFileLanguage, isCodeOrTextFile, isImageFile, isMarkdownFile, isOfficeFile, isVideoFile } from './utils/file-utils'
|
||||
|
||||
if (typeof window !== 'undefined')
|
||||
@ -34,9 +34,9 @@ const SkillDocEditor: FC = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appId = appDetail?.id || ''
|
||||
|
||||
const activeTabId = useSkillEditorStore(s => s.activeTabId)
|
||||
const dirtyContents = useSkillEditorStore(s => s.dirtyContents)
|
||||
const storeApi = useSkillEditorStoreApi()
|
||||
const activeTabId = useStore(s => s.activeTabId!)
|
||||
const dirtyContents = useStore(s => s.dirtyContents!)
|
||||
const storeApi = useWorkflowStore()
|
||||
const { data: nodeMap } = useSkillAssetNodeMap()
|
||||
|
||||
const currentFileNode = activeTabId ? nodeMap?.get(activeTabId) : undefined
|
||||
@ -68,8 +68,8 @@ const SkillDocEditor: FC = () => {
|
||||
const handleEditorChange = useCallback((value: string | undefined) => {
|
||||
if (!activeTabId || !isEditable)
|
||||
return
|
||||
storeApi.getState().setDraftContent(activeTabId, value ?? '')
|
||||
storeApi.getState().pinTab(activeTabId)
|
||||
storeApi.getState().setDraftContent?.(activeTabId, value ?? '')
|
||||
storeApi.getState().pinTab?.(activeTabId)
|
||||
}, [activeTabId, isEditable, storeApi])
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
@ -86,7 +86,7 @@ const SkillDocEditor: FC = () => {
|
||||
nodeId: activeTabId,
|
||||
payload: { content },
|
||||
})
|
||||
storeApi.getState().clearDraftContent(activeTabId)
|
||||
storeApi.getState().clearDraftContent?.(activeTabId)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('api.saved', { ns: 'common' }),
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import type { StateCreator, StoreApi } from 'zustand'
|
||||
import * as React from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { useStore as useZustandStore } from 'zustand'
|
||||
import { createStore } from 'zustand/vanilla'
|
||||
import type { StateCreator } from 'zustand'
|
||||
|
||||
export type OpenTabOptions = {
|
||||
/** true = Pinned (permanent), false/undefined = Preview (temporary) */
|
||||
@ -211,24 +207,24 @@ export const createFileOperationsMenuSlice: StateCreator<FileOperationsMenuSlice
|
||||
},
|
||||
})
|
||||
|
||||
export type SkillEditorShape
|
||||
export type SkillEditorSliceShape
|
||||
= TabSliceShape
|
||||
& FileTreeSliceShape
|
||||
& DirtySliceShape
|
||||
& FileOperationsMenuSliceShape
|
||||
& {
|
||||
reset: () => void
|
||||
resetSkillEditor: () => void
|
||||
}
|
||||
|
||||
export const createSkillEditorStore = (): StoreApi<SkillEditorShape> => {
|
||||
return createStore<SkillEditorShape>((...args) => ({
|
||||
export const createSkillEditorSlice: StateCreator<SkillEditorSliceShape, [], [], SkillEditorSliceShape> = (set, get, store) => {
|
||||
const args = [set, get, store] as Parameters<StateCreator<SkillEditorSliceShape>>
|
||||
return {
|
||||
...createTabSlice(...args),
|
||||
...createFileTreeSlice(...args),
|
||||
...createDirtySlice(...args),
|
||||
...createFileOperationsMenuSlice(...args),
|
||||
|
||||
reset: () => {
|
||||
const [set] = args
|
||||
resetSkillEditor: () => {
|
||||
set({
|
||||
openTabIds: [],
|
||||
activeTabId: null,
|
||||
@ -238,25 +234,5 @@ export const createSkillEditorStore = (): StoreApi<SkillEditorShape> => {
|
||||
contextMenu: null,
|
||||
})
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export type SkillEditorStore = StoreApi<SkillEditorShape>
|
||||
|
||||
export const SkillEditorContext = React.createContext<SkillEditorStore | null>(null)
|
||||
|
||||
export function useSkillEditorStore<T>(selector: (state: SkillEditorShape) => T): T {
|
||||
const store = useContext(SkillEditorContext)
|
||||
if (!store)
|
||||
throw new Error('Missing SkillEditorContext.Provider in the tree')
|
||||
|
||||
return useZustandStore(store, selector)
|
||||
}
|
||||
|
||||
export const useSkillEditorStoreApi = (): SkillEditorStore => {
|
||||
const store = useContext(SkillEditorContext)
|
||||
if (!store)
|
||||
throw new Error('Missing SkillEditorContext.Provider in the tree')
|
||||
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import type { WorkflowDraftSliceShape } from './workflow-draft-slice'
|
||||
import type { WorkflowSliceShape } from './workflow-slice'
|
||||
import type { RagPipelineSliceShape } from '@/app/components/rag-pipeline/store'
|
||||
import type { WorkflowSliceShape as WorkflowAppSliceShape } from '@/app/components/workflow-app/store/workflow/workflow-slice'
|
||||
import type { SkillEditorSliceShape } from '@/app/components/workflow/skill/store'
|
||||
import { useContext } from 'react'
|
||||
import {
|
||||
useStore as useZustandStore,
|
||||
@ -40,6 +41,7 @@ import { createWorkflowSlice } from './workflow-slice'
|
||||
export type SliceFromInjection
|
||||
= Partial<WorkflowAppSliceShape>
|
||||
& Partial<RagPipelineSliceShape>
|
||||
& Partial<SkillEditorSliceShape>
|
||||
|
||||
export type Shape
|
||||
= ChatVariableSliceShape
|
||||
|
||||
Reference in New Issue
Block a user