fix workflow view switch refresh

This commit is contained in:
yyh
2026-01-22 13:14:01 +08:00
parent 62ec464d91
commit ee35f72861
7 changed files with 123 additions and 44 deletions

View File

@ -11,6 +11,7 @@ import { useResetWorkflowVersionHistory } from '@/service/use-workflow'
import { useIsChatMode } from '../../hooks'
import ChatVariableTrigger from './chat-variable-trigger'
import FeaturesTrigger from './features-trigger'
import ViewPickerTrigger from './view-picker-trigger'
const WorkflowHeader = () => {
const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({
@ -37,6 +38,7 @@ const WorkflowHeader = () => {
return {
normal: {
components: {
left: <ViewPickerTrigger />,
middle: <FeaturesTrigger />,
chatVariableTrigger: <ChatVariableTrigger />,
},

View File

@ -0,0 +1,28 @@
'use client'
import { useQueryState } from 'nuqs'
import { useCallback } from 'react'
import { useFeatures } from '@/app/components/base/features/hooks'
import { ViewType } from '@/app/components/workflow/types'
import ViewPicker from '@/app/components/workflow/view-picker'
import { parseAsViewType, WORKFLOW_VIEW_PARAM_KEY } from '../../search-params'
const ViewPickerTrigger = () => {
const isSupportSandbox = useFeatures(s => !!s.features.sandbox?.enabled)
const [viewType, doSetViewType] = useQueryState(WORKFLOW_VIEW_PARAM_KEY, parseAsViewType)
const handleViewTypeChange = useCallback((type: ViewType) => {
doSetViewType(type)
}, [doSetViewType])
if (!isSupportSandbox)
return null
return (
<ViewPicker
value={viewType}
onChange={handleViewTypeChange}
/>
)
}
export default ViewPickerTrigger

View File

@ -21,8 +21,9 @@ export const useWorkflowRefreshDraft = () => {
debouncedSyncWorkflowDraft,
} = workflowStore.getState()
if (debouncedSyncWorkflowDraft && typeof (debouncedSyncWorkflowDraft as any).cancel === 'function')
(debouncedSyncWorkflowDraft as any).cancel()
const { cancel } = debouncedSyncWorkflowDraft
if (typeof cancel === 'function')
cancel()
const wasLoaded = isWorkflowDataLoaded
if (wasLoaded)

View File

@ -10,8 +10,8 @@ import {
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState,
} from 'react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { FeaturesProvider } from '@/app/components/base/features'
@ -21,6 +21,7 @@ import WorkflowWithDefaultContext from '@/app/components/workflow'
import {
WorkflowContextProvider,
} from '@/app/components/workflow/context'
import { HeaderShell } from '@/app/components/workflow/header'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
import {
@ -36,7 +37,7 @@ import { fetchRunDetail } from '@/service/log'
import { useAppTriggers } from '@/service/use-tools'
import { AppModeEnum } from '@/types/app'
import { useFeatures } from '../base/features/hooks'
import ViewPicker from '../workflow/view-picker'
import ViewPickerTrigger from './components/workflow-header/view-picker-trigger'
import WorkflowAppMain from './components/workflow-main'
import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url'
import { useNodesSyncDraft } from './hooks/use-nodes-sync-draft'
@ -55,52 +56,85 @@ type WorkflowViewContentProps = {
reload: () => Promise<void>
}
const WorkflowViewPickerDock = () => {
return (
<HeaderShell>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<ViewPickerTrigger />
</div>
</div>
</HeaderShell>
)
}
const WorkflowViewContent = ({
graphContent,
reload,
}: WorkflowViewContentProps) => {
const features = useFeatures(s => s.features)
const isSupportSandbox = !!features.sandbox?.enabled
const [viewType, doSetViewType] = useQueryState(WORKFLOW_VIEW_PARAM_KEY, parseAsViewType)
const [viewType] = useQueryState(WORKFLOW_VIEW_PARAM_KEY, parseAsViewType)
const { syncWorkflowDraftImmediately } = useNodesSyncDraft()
const [isGraphRefreshing, setGraphRefreshing] = useReducer((_: boolean, next: boolean) => next, false)
const pendingSyncRef = useRef<Promise<void> | null>(null)
const [isGraphRefreshing, setIsGraphRefreshing] = useState(false)
const refreshInFlightRef = useRef<Promise<void> | null>(null)
const previousViewTypeRef = useRef<ViewType | null>(viewType)
const viewTypeRef = useRef(viewType)
const refreshGraph = useCallback(() => {
setIsGraphRefreshing(true)
return reload().finally(() => {
setIsGraphRefreshing(false)
})
const refreshGraph = useCallback((waitFor?: Promise<void>) => {
if (refreshInFlightRef.current)
return refreshInFlightRef.current
setGraphRefreshing(true)
const runRefresh = () => {
if (viewTypeRef.current !== ViewType.graph) {
refreshInFlightRef.current = null
setGraphRefreshing(false)
return Promise.resolve()
}
return reload().finally(() => {
refreshInFlightRef.current = null
setGraphRefreshing(false)
})
}
refreshInFlightRef.current = waitFor ? waitFor.then(runRefresh, runRefresh) : runRefresh()
return refreshInFlightRef.current
}, [reload])
const handleViewTypeChange = useCallback((type: ViewType) => {
if (viewType === ViewType.graph && type !== viewType)
pendingSyncRef.current = syncWorkflowDraftImmediately(true).catch(() => { })
useEffect(() => {
viewTypeRef.current = viewType
if (!isSupportSandbox) {
previousViewTypeRef.current = viewType
return
}
doSetViewType(type)
if (type === ViewType.graph) {
const pending = pendingSyncRef.current
if (pending) {
const previousView = previousViewTypeRef.current
if (previousView === ViewType.graph && viewType !== ViewType.graph) {
if (!pendingSyncRef.current) {
const pending = syncWorkflowDraftImmediately(true).catch(() => { })
pendingSyncRef.current = pending
pending.finally(() => {
refreshGraph()
if (pendingSyncRef.current === pending)
pendingSyncRef.current = null
})
pendingSyncRef.current = null
}
else {
refreshGraph()
}
}
}, [doSetViewType, refreshGraph, syncWorkflowDraftImmediately, viewType])
if (!isSupportSandbox) {
if (previousView !== ViewType.graph && viewType === ViewType.graph) {
refreshGraph(pendingSyncRef.current ?? undefined)
}
previousViewTypeRef.current = viewType
}, [isSupportSandbox, refreshGraph, syncWorkflowDraftImmediately, viewType])
if (!isSupportSandbox)
return graphContent
}
return (
<div className="relative h-full w-full">
<ViewPicker
value={viewType}
onChange={handleViewTypeChange}
/>
<div className="relative h-full w-full min-w-[960px]">
{viewType !== ViewType.graph ? <WorkflowViewPickerDock /> : null}
{viewType === ViewType.graph
? (
isGraphRefreshing

View File

@ -61,18 +61,18 @@ const HeaderInNormal = ({
setShowChatVariablePanel(false)
setShowGlobalVariablePanel(false)
closeAllInputFieldPanels()
}, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel, setShowGlobalVariablePanel])
}, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel, setShowGlobalVariablePanel, closeAllInputFieldPanels])
return (
<div className="flex w-full items-center justify-between">
<div className="relative top-[30px]">
<div className="flex items-center gap-2">
{components?.left}
<EditingTitle />
</div>
<div>
<ScrollToSelectedNodeButton />
</div>
<div className="flex items-center gap-2">
{components?.left}
<Divider type="vertical" className="mx-auto h-3.5" />
<RunAndHistory {...runAndHistoryProps} />
<div className="shrink-0 cursor-pointer rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs backdrop-blur-[10px]">

View File

@ -1,3 +1,4 @@
import type { ReactNode } from 'react'
import type { HeaderInNormalProps } from './header-in-normal'
import type { HeaderInRestoringProps } from './header-in-restoring'
import type { HeaderInHistoryProps } from './header-in-view-history'
@ -16,6 +17,26 @@ const HeaderInRestoring = dynamic(() => import('./header-in-restoring'), {
ssr: false,
})
type HeaderShellProps = {
children: ReactNode
}
export const HeaderShell = ({ children }: HeaderShellProps) => {
const pathname = usePathname()
const inWorkflowCanvas = pathname.endsWith('/workflow')
const isPipelineCanvas = pathname.endsWith('/pipeline')
const maximizeCanvas = useStore(s => s.maximizeCanvas)
return (
<div
className="absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3"
>
{(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className="h-14 w-[52px]" />}
{children}
</div>
)
}
export type HeaderProps = {
normal?: HeaderInNormalProps
viewHistory?: HeaderInHistoryProps
@ -26,21 +47,14 @@ const Header = ({
viewHistory: viewHistoryProps,
restoring: restoringProps,
}: HeaderProps) => {
const pathname = usePathname()
const inWorkflowCanvas = pathname.endsWith('/workflow')
const isPipelineCanvas = pathname.endsWith('/pipeline')
const {
normal,
restoring,
viewHistory,
} = useWorkflowMode()
const maximizeCanvas = useStore(s => s.maximizeCanvas)
return (
<div
className="absolute left-0 top-7 z-10 flex h-0 w-full items-center justify-between bg-mask-top2bottom-gray-50-to-transparent px-3"
>
{(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas && <div className="h-14 w-[52px]" />}
<HeaderShell>
{
normal && (
<HeaderInNormal
@ -62,7 +76,7 @@ const Header = ({
/>
)
}
</div>
</HeaderShell>
)
}

View File

@ -33,7 +33,7 @@ const ViewPicker: FC<ViewPickerProps> = ({
return (
<SegmentedControl
className={cn('absolute left-3 top-3 z-[12] text-text-accent-light-mode-only', className)}
className={cn('text-text-accent-light-mode-only', className)}
options={options}
value={value}
onChange={handleChange}