mirror of
https://github.com/langgenius/dify.git
synced 2026-10-16 07:20:34 +08:00
refactor(workflow): establish scoped editor identity lifecycles
Bind each editor session to its resource kind and ID. Resolve App query facts at the entry boundary and derive mode/name from the shared cache. Resource switches remount the canvas session; legacy consumers are migrated in the following commit.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useMemo } from 'react'
|
||||
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
|
||||
import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
import { EditorResourceScope } from '@/app/components/workflow/editor-resource/provider'
|
||||
import { initialEdges, initialNodes } from '@/app/components/workflow/utils'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import Conversion from './components/conversion'
|
||||
@@ -48,9 +49,11 @@ const RagPipelineWrapper = () => {
|
||||
if (!pipelineId) return <Conversion />
|
||||
|
||||
return (
|
||||
<WorkflowContextProvider injectWorkflowStoreSliceFn={createRagPipelineSliceSlice}>
|
||||
<RagPipeline />
|
||||
</WorkflowContextProvider>
|
||||
<EditorResourceScope identity={{ kind: 'knowledge-pipeline', pipelineId }}>
|
||||
<WorkflowContextProvider injectWorkflowStoreSliceFn={createRagPipelineSliceSlice}>
|
||||
<RagPipeline />
|
||||
</WorkflowContextProvider>
|
||||
</EditorResourceScope>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useMemo } from 'react'
|
||||
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
|
||||
import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
import { EditorResourceScope } from '@/app/components/workflow/editor-resource/provider'
|
||||
import { initialEdges, initialNodes } from '@/app/components/workflow/utils'
|
||||
import SnippetLayout from './components/snippet-layout'
|
||||
import SnippetMain from './components/snippet-main'
|
||||
@@ -78,9 +79,11 @@ const SnippetPage = ({ snippetId }: SnippetPageProps) => {
|
||||
|
||||
const SnippetPageWrapper = ({ snippetId }: SnippetPageProps) => {
|
||||
return (
|
||||
<WorkflowContextProvider key={snippetId}>
|
||||
<SnippetPage snippetId={snippetId} />
|
||||
</WorkflowContextProvider>
|
||||
<EditorResourceScope identity={{ kind: 'snippet', snippetId }}>
|
||||
<WorkflowContextProvider>
|
||||
<SnippetPage snippetId={snippetId} />
|
||||
</WorkflowContextProvider>
|
||||
</EditorResourceScope>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { isWorkflowAppMode } from '@/app/components/apps/app-type'
|
||||
import { FeaturesProvider } from '@/app/components/base/features'
|
||||
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
|
||||
import WorkflowWithDefaultContext from '@/app/components/workflow'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
import { EditorResourceScope } from '@/app/components/workflow/editor-resource/provider'
|
||||
import { workflowAppIdentityAtom } from '@/app/components/workflow/editor-resource/state'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
|
||||
import { initialEdges, initialNodes } from '@/app/components/workflow/utils'
|
||||
@@ -39,7 +42,7 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
|
||||
// Initialize trigger status at application level
|
||||
const { setTriggerStatuses } = useTriggerStatusStore()
|
||||
const { appId } = useParams<{ appId: string }>()
|
||||
const { appId } = useAtomValue(workflowAppIdentityAtom)
|
||||
const { data: appDetail } = useSuspenseQuery(
|
||||
consoleQuery.apps.byAppId.get.queryOptions({ input: { params: { app_id: appId } } }),
|
||||
)
|
||||
@@ -137,12 +140,23 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
}
|
||||
|
||||
const WorkflowAppWrapper = () => {
|
||||
const { appId } = useParams<{ appId: string }>()
|
||||
const { data: mode } = useSuspenseQuery(
|
||||
consoleQuery.apps.byAppId.get.queryOptions({
|
||||
input: { params: { app_id: appId } },
|
||||
select: (app) => app.mode,
|
||||
}),
|
||||
)
|
||||
if (!isWorkflowAppMode(mode)) throw new Error('This app does not have a workflow editor')
|
||||
|
||||
return (
|
||||
<WorkflowContextProvider
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice as InjectWorkflowStoreSliceFn}
|
||||
>
|
||||
<WorkflowAppWithAdditionalContext />
|
||||
</WorkflowContextProvider>
|
||||
<EditorResourceScope identity={{ kind: 'app', appId }}>
|
||||
<WorkflowContextProvider
|
||||
injectWorkflowStoreSliceFn={createWorkflowSlice as InjectWorkflowStoreSliceFn}
|
||||
>
|
||||
<WorkflowAppWithAdditionalContext />
|
||||
</WorkflowContextProvider>
|
||||
</EditorResourceScope>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { EditorIdentity } from './state'
|
||||
import { ScopeProvider } from 'jotai-scope'
|
||||
import { editorIdentityStateAtom } from './state'
|
||||
|
||||
export function EditorResourceScope({
|
||||
identity,
|
||||
children,
|
||||
}: {
|
||||
identity: EditorIdentity
|
||||
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]]}
|
||||
name="WorkflowEditor"
|
||||
>
|
||||
{children}
|
||||
</ScopeProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { WorkflowAppMode } from '@/app/components/apps/app-type'
|
||||
|
||||
export type EditorResource =
|
||||
| Readonly<{ kind: 'app'; appId: string; mode: WorkflowAppMode }>
|
||||
| Readonly<{ kind: 'knowledge-pipeline'; pipelineId: string }>
|
||||
| Readonly<{ kind: 'snippet'; snippetId: string }>
|
||||
|
||||
export function getEditorResourceId(resource: EditorResource): string {
|
||||
switch (resource.kind) {
|
||||
case 'app':
|
||||
return resource.appId
|
||||
case 'knowledge-pipeline':
|
||||
return resource.pipelineId
|
||||
case 'snippet':
|
||||
return resource.snippetId
|
||||
}
|
||||
}
|
||||
|
||||
export function isChatflow(resource: EditorResource): boolean {
|
||||
return resource.kind === 'app' && resource.mode === 'advanced-chat'
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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>(() => {
|
||||
throw new Error('EditorResourceScope is required for the workflow editor')
|
||||
})
|
||||
|
||||
export const editorResourceKindAtom = atom((get) => get(editorIdentityStateAtom).kind)
|
||||
export const editorAppIdAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
return identity.kind === 'app' ? identity.appId : undefined
|
||||
})
|
||||
export const editorPipelineIdAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
return identity.kind === 'knowledge-pipeline' ? identity.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
|
||||
})
|
||||
|
||||
export const knowledgePipelineIdentityAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'knowledge-pipeline')
|
||||
throw new Error('This operation requires a knowledge pipeline editor')
|
||||
return identity
|
||||
})
|
||||
|
||||
export const snippetIdentityAtom = atom((get) => {
|
||||
const identity = get(editorIdentityStateAtom)
|
||||
if (identity.kind !== 'snippet') throw new Error('This operation requires a snippet editor')
|
||||
return identity
|
||||
})
|
||||
Reference in New Issue
Block a user