mirror of
https://github.com/langgenius/dify.git
synced 2026-10-16 07:20:34 +08:00
fix(workflow): preserve shared App cache across editor teardown
This commit is contained in:
@@ -49,7 +49,7 @@ const RagPipelineWrapper = () => {
|
||||
if (!pipelineId) return <Conversion />
|
||||
|
||||
return (
|
||||
<EditorResourceScope identity={{ kind: 'knowledge-pipeline', pipelineId }}>
|
||||
<EditorResourceScope resource={{ kind: 'knowledge-pipeline', pipelineId }}>
|
||||
<WorkflowContextProvider injectWorkflowStoreSliceFn={createRagPipelineSliceSlice}>
|
||||
<RagPipeline />
|
||||
</WorkflowContextProvider>
|
||||
|
||||
@@ -80,7 +80,7 @@ const SnippetPage = () => {
|
||||
|
||||
const SnippetPageWrapper = ({ snippetId }: SnippetPageProps) => {
|
||||
return (
|
||||
<EditorResourceScope identity={{ kind: 'snippet', snippetId }}>
|
||||
<EditorResourceScope resource={{ kind: 'snippet', snippetId }}>
|
||||
<WorkflowContextProvider>
|
||||
<SnippetPage />
|
||||
</WorkflowContextProvider>
|
||||
|
||||
@@ -150,7 +150,7 @@ const WorkflowAppWrapper = () => {
|
||||
if (!isWorkflowAppMode(mode)) throw new Error('This app does not have a workflow editor')
|
||||
|
||||
return (
|
||||
<EditorResourceScope identity={{ kind: 'app', appId }}>
|
||||
<EditorResourceScope resource={{ kind: 'app', appId, mode }}>
|
||||
<WorkflowContextProvider
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice as InjectWorkflowStoreSliceFn}
|
||||
>
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { EditorIdentity } from './state'
|
||||
import type { EditorResource } from './resource'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { editorIdentityStateAtom } from './state'
|
||||
import { getEditorResourceId } from './resource'
|
||||
import { editorResourceAtom } from './state'
|
||||
|
||||
export function EditorResourceScope({
|
||||
identity,
|
||||
resource,
|
||||
children,
|
||||
}: {
|
||||
identity: EditorIdentity
|
||||
resource: EditorResource
|
||||
children: ReactNode
|
||||
}) {
|
||||
const id =
|
||||
identity.kind === 'app'
|
||||
? identity.appId
|
||||
: identity.kind === 'knowledge-pipeline'
|
||||
? identity.pipelineId
|
||||
: identity.snippetId
|
||||
|
||||
return (
|
||||
<ScopeProvider
|
||||
key={`${identity.kind}:${id}`}
|
||||
atoms={[[editorIdentityStateAtom, identity]]}
|
||||
key={`${resource.kind}:${getEditorResourceId(resource)}`}
|
||||
atoms={[[editorResourceAtom, resource]]}
|
||||
name="WorkflowEditor"
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,68 +1,39 @@
|
||||
import type { EditorResource } from './resource'
|
||||
import { atom } from 'jotai'
|
||||
import { atomWithLazy } from 'jotai/utils'
|
||||
import { isWorkflowAppMode } from '@/app/components/apps/app-type'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { atomWithResolvedSuspenseQuery } from '@/utils/query-atoms'
|
||||
import { isChatflow } from './resource'
|
||||
|
||||
export type EditorIdentity =
|
||||
| Readonly<{ kind: 'app'; appId: string }>
|
||||
| Readonly<{ kind: 'knowledge-pipeline'; pipelineId: string }>
|
||||
| Readonly<{ kind: 'snippet'; snippetId: string }>
|
||||
|
||||
export const editorIdentityStateAtom = atomWithLazy<EditorIdentity>(() => {
|
||||
export const editorResourceAtom = atomWithLazy<EditorResource>(() => {
|
||||
throw new Error('EditorResourceScope is required for the workflow editor')
|
||||
})
|
||||
|
||||
export const editorResourceKindAtom = atom((get) => get(editorIdentityStateAtom).kind)
|
||||
export const editorResourceKindAtom = atom((get) => get(editorResourceAtom).kind)
|
||||
export const editorAppIdAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
return identity.kind === 'app' ? identity.appId : undefined
|
||||
const resource = get(editorResourceAtom)
|
||||
return resource.kind === 'app' ? resource.appId : undefined
|
||||
})
|
||||
export const editorPipelineIdAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
return identity.kind === 'knowledge-pipeline' ? identity.pipelineId : undefined
|
||||
const resource = get(editorResourceAtom)
|
||||
return resource.kind === 'knowledge-pipeline' ? resource.pipelineId : undefined
|
||||
})
|
||||
|
||||
// The app entrypoint resolves this query before mounting its editor session.
|
||||
const editorAppQueryAtom = atomWithResolvedSuspenseQuery((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'app') throw new Error('App details require an app editor')
|
||||
return consoleQuery.apps.byAppId.get.queryOptions({
|
||||
input: { params: { app_id: identity.appId } },
|
||||
})
|
||||
})
|
||||
const appModeAtom = atom((get) => get(editorAppQueryAtom).data.mode)
|
||||
|
||||
export const editorAppNameAtom = atom((get) =>
|
||||
get(editorResourceKindAtom) === 'app' ? get(editorAppQueryAtom).data.name : undefined,
|
||||
)
|
||||
|
||||
export const editorResourceAtom = atom<EditorResource>((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'app') return identity
|
||||
const mode = get(appModeAtom)
|
||||
if (!isWorkflowAppMode(mode)) throw new Error('This app does not have a workflow editor')
|
||||
return { ...identity, mode }
|
||||
})
|
||||
export const isChatflowAtom = atom((get) => isChatflow(get(editorResourceAtom)))
|
||||
|
||||
export const workflowAppIdentityAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'app') throw new Error('This operation requires a workflow app editor')
|
||||
return identity
|
||||
const resource = get(editorResourceAtom)
|
||||
if (resource.kind !== 'app') throw new Error('This operation requires a workflow app editor')
|
||||
return resource
|
||||
})
|
||||
|
||||
export const knowledgePipelineIdentityAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'knowledge-pipeline')
|
||||
const resource = get(editorResourceAtom)
|
||||
if (resource.kind !== 'knowledge-pipeline')
|
||||
throw new Error('This operation requires a knowledge pipeline editor')
|
||||
return identity
|
||||
return resource
|
||||
})
|
||||
|
||||
export const snippetIdentityAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'snippet') throw new Error('This operation requires a snippet editor')
|
||||
return identity
|
||||
const resource = get(editorResourceAtom)
|
||||
if (resource.kind !== 'snippet') throw new Error('This operation requires a snippet editor')
|
||||
return resource
|
||||
})
|
||||
|
||||
@@ -577,17 +577,16 @@ export const Workflow: FC<WorkflowProps> = memo(
|
||||
const dataSourceList = useStore((s) => s.dataSourceList)
|
||||
// buildInTools, customTools, workflowTools, mcpTools, dataSourceList
|
||||
const resource = useAtomValue(editorResourceAtom)
|
||||
const resourceId = getEditorResourceId(resource)
|
||||
const [isLoadedVars, setIsLoadedVars] = useState(false)
|
||||
const [vars, setVars] = useState<VarInInspect[]>([])
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (!resource.kind || !resourceId) return
|
||||
if (!getEditorResourceId(resource)) return
|
||||
const data = await fetchAllInspectVars(resource)
|
||||
setVars(data)
|
||||
setIsLoadedVars(true)
|
||||
})()
|
||||
}, [resource.kind, resourceId])
|
||||
}, [resource])
|
||||
useEffect(() => {
|
||||
if (schemaTypeDefinitions && isLoadedVars) {
|
||||
fetchInspectVars({
|
||||
|
||||
@@ -8,14 +8,16 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { skipToken, useQuery } from '@tanstack/react-query'
|
||||
import { toJpeg, toPng, toSvg } from 'html-to-image'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getNodesBounds, useReactFlow } from 'reactflow'
|
||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||
import { editorAppNameAtom } from '@/app/components/workflow/editor-resource/state'
|
||||
import { editorAppIdAtom } from '@/app/components/workflow/editor-resource/state'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { downloadUrl } from '@/utils/download'
|
||||
import { useNodesReadOnly } from '../hooks/use-workflow'
|
||||
import TipPopup from './tip-popup'
|
||||
@@ -28,7 +30,13 @@ const ExportImage = memo(() => {
|
||||
const [previewUrl, setPreviewUrl] = useState('')
|
||||
const [previewTitle, setPreviewTitle] = useState('')
|
||||
const knowledgeName = useStore((s) => s.knowledgeName)
|
||||
const appName = useAtomValue(editorAppNameAtom)
|
||||
const appId = useAtomValue(editorAppIdAtom)
|
||||
const { data: appName } = useQuery(
|
||||
consoleQuery.apps.byAppId.get.queryOptions({
|
||||
input: appId ? { params: { app_id: appId } } : skipToken,
|
||||
select: (app) => app.name,
|
||||
}),
|
||||
)
|
||||
const isReadOnly = getNodesReadOnly()
|
||||
|
||||
const handleExportImage = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user