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 c3aeef410a..94ad31d1de 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 a5a3fbcac1..a153145c76 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 83c43d5b3a..99673b70f8 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 e81b35774e..1cc5b361ed 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)