From 772dbe620d9e642fee04ecb0e398c7d9d324e042 Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 27 Jan 2026 01:31:22 +0800 Subject: [PATCH] fix(workflow): disable view switch during preview run instead of mounted guard Simpler approach: disable the view picker toggle when preview is running, preventing users from switching views during active runs. This replaces the previous mounted ref guard approach (commits a0188bd9b5, b7f1eb9b7b, 8332f0de2b) which added complexity to handle post-unmount operations. Disabling the toggle is more direct and follows KISS principle. Changes: - Add disabled prop to ViewPicker based on isResponding state - Revert mounted ref guards in use-chat-flow-control.ts - Revert isMountedRef parameter in use-nodes/edges-interactions-without-sync.ts - Revert defensive type check in markdown-utils.ts (no longer needed) --- .../components/base/markdown/markdown-utils.ts | 5 +---- web/app/components/workflow-app/index.tsx | 4 +++- .../use-edges-interactions-without-sync.ts | 8 ++------ .../use-nodes-interactions-without-sync.ts | 18 ++++-------------- .../hooks/use-chat-flow-control.ts | 15 +++------------ web/app/components/workflow/view-picker.tsx | 8 +++++--- 6 files changed, 18 insertions(+), 40 deletions(-) diff --git a/web/app/components/base/markdown/markdown-utils.ts b/web/app/components/base/markdown/markdown-utils.ts index c3aeef410a5..94ad31d1ded 100644 --- a/web/app/components/base/markdown/markdown-utils.ts +++ b/web/app/components/base/markdown/markdown-utils.ts @@ -8,7 +8,7 @@ import { ALLOW_UNSAFE_DATA_SCHEME } from '@/config' export const preprocessLaTeX = (content: string) => { if (typeof content !== 'string') - return '' + return content const codeBlockRegex = /```[\s\S]*?```/g const codeBlocks = content.match(codeBlockRegex) || [] @@ -32,9 +32,6 @@ export const preprocessLaTeX = (content: string) => { } export const preprocessThinkTag = (content: string) => { - if (typeof content !== 'string') - return '' - const thinkOpenTagRegex = /(\s*)+/g const thinkCloseTagRegex = /(\s*<\/think>)+/g return flow([ diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index a5a3fbcac10..a153145c766 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -23,7 +23,7 @@ import { WorkflowContextProvider, } from '@/app/components/workflow/context' import { HeaderShell } from '@/app/components/workflow/header' -import { useWorkflowStore } from '@/app/components/workflow/store' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status' import { SupportUploadFileTypes, @@ -63,6 +63,7 @@ const WorkflowViewContent = ({ }: WorkflowViewContentProps) => { const features = useFeatures(s => s.features) const isSupportSandbox = !!features.sandbox?.enabled + const isResponding = useStore(s => s.isResponding) const [viewType, doSetViewType] = useQueryState(WORKFLOW_VIEW_PARAM_KEY, parseAsViewType) const { syncWorkflowDraftImmediately } = useNodesSyncDraft() const pendingSyncRef = useRef | null>(null) @@ -101,6 +102,7 @@ const WorkflowViewContent = ({ ) const viewPickerDock = ( diff --git a/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts index 83c43d5b3aa..99673b70f84 100644 --- a/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts +++ b/web/app/components/workflow/hooks/use-edges-interactions-without-sync.ts @@ -1,15 +1,11 @@ -import type { RefObject } from 'react' import { produce } from 'immer' import { useCallback } from 'react' import { useStoreApi } from 'reactflow' -export const useEdgesInteractionsWithoutSync = (isMountedRef?: RefObject) => { +export const useEdgesInteractionsWithoutSync = () => { const store = useStoreApi() const handleEdgeCancelRunningStatus = useCallback(() => { - if (isMountedRef && isMountedRef.current === false) - return - const { edges, setEdges, @@ -23,7 +19,7 @@ export const useEdgesInteractionsWithoutSync = (isMountedRef?: RefObject) => { +export const useNodesInteractionsWithoutSync = () => { const store = useStoreApi() const handleNodeCancelRunningStatus = useCallback(() => { - if (isMountedRef && isMountedRef.current === false) - return - const { getNodes, setNodes, @@ -24,12 +20,9 @@ export const useNodesInteractionsWithoutSync = (isMountedRef?: RefObject { - if (isMountedRef && isMountedRef.current === false) - return - const { getNodes, setNodes, @@ -43,12 +36,9 @@ export const useNodesInteractionsWithoutSync = (isMountedRef?: RefObject { - if (isMountedRef && isMountedRef.current === false) - return - const { getNodes, setNodes, @@ -62,7 +52,7 @@ export const useNodesInteractionsWithoutSync = (isMountedRef?: RefObject s.setHasStopResponded) const setSuggestedQuestionsAbortController = useStore(s => s.setSuggestedQuestionsAbortController) const invalidateRun = useStore(s => s.invalidateRun) - - const isMountedRef = useRef(true) - useEffect(() => { - isMountedRef.current = true - return () => { - isMountedRef.current = false - } - }, []) - - const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync(isMountedRef) - const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync(isMountedRef) + const { handleNodeCancelRunningStatus } = useNodesInteractionsWithoutSync() + const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() const { setIterTimes, setLoopTimes } = workflowStore.getState() diff --git a/web/app/components/workflow/view-picker.tsx b/web/app/components/workflow/view-picker.tsx index e81b35774e9..1cc5b361ed1 100644 --- a/web/app/components/workflow/view-picker.tsx +++ b/web/app/components/workflow/view-picker.tsx @@ -12,18 +12,20 @@ type ViewPickerProps = { value: ViewType onChange: (value: ViewType) => void className?: string + disabled?: boolean } const ViewPicker: FC = ({ value, onChange, className, + disabled, }) => { const { t } = useTranslation() const options = useMemo(() => ([ - { value: ViewType.graph, text: t('viewPicker.graph', { ns: 'workflow' }) }, - { value: ViewType.skill, text: t('viewPicker.skill', { ns: 'workflow' }) }, - ]), [t]) + { value: ViewType.graph, text: t('viewPicker.graph', { ns: 'workflow' }), disabled: disabled && value !== ViewType.graph }, + { value: ViewType.skill, text: t('viewPicker.skill', { ns: 'workflow' }), disabled: disabled && value !== ViewType.skill }, + ]), [t, disabled, value]) const handleChange = useCallback((nextValue: string | number | symbol) => { if (nextValue === value)