Files
dify/web/app/components/workflow/variable-inspect/trigger.tsx
T
twwu 3ce404de2a perf(workflow): reduce canvas drag rendering and sync overhead
Isolate node data and search subscriptions to avoid unnecessary renders
of the workflow root and floating controls during position updates.

Reuse unchanged node references and broadcast incremental CRDT updates,
with catch-up after session reauthorization.

Add regression coverage for node data synchronization, toolbar behavior,
and collaborative graph updates.
2026-10-16 11:56:40 +08:00

169 lines
7.1 KiB
TypeScript

import type { FC } from 'react'
import type { CommonNodeType } from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useReactFlowStore } from 'reactflow'
import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import useCurrentVars from '../hooks/use-inspect-vars-crud'
import { useNodesReadOnly } from '../hooks/use-workflow'
import { useStore } from '../store'
const VariableInspectTrigger: FC = () => {
const { t } = useTranslation(['workflowDebug'])
const { eventEmitter } = useEventEmitterContextContext()
const showVariableInspectPanel = useStore((s) => s.showVariableInspectPanel)
const setShowVariableInspectPanel = useStore((s) => s.setShowVariableInspectPanel)
const environmentVariables = useStore((s) => s.environmentVariables)
const setCurrentFocusNodeId = useStore((s) => s.setCurrentFocusNodeId)
const { conversationVars, systemVars, nodesWithInspectVars, deleteAllInspectorVars } =
useCurrentVars()
const currentVars = useMemo(() => {
const allVars = [
...environmentVariables,
...conversationVars,
...systemVars,
...nodesWithInspectVars,
]
return allVars
}, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars])
const { nodesReadOnly, getNodesReadOnly } = useNodesReadOnly()
const workflowRunningData = useStore((s) => s.workflowRunningData)
const isStepRunning = useReactFlowStore((state) => {
for (const node of state.nodeInternals.values()) {
if ((node.data as CommonNodeType)._singleRunningStatus === NodeRunningStatus.Running)
return true
}
return false
})
const isPreviewRunning = useMemo(() => {
if (!workflowRunningData) return false
return workflowRunningData.result.status === WorkflowRunningStatus.Running
}, [workflowRunningData])
const isRunning = useMemo(
() => isPreviewRunning || isStepRunning,
[isPreviewRunning, isStepRunning],
)
const handleStop = () => {
eventEmitter?.emit({
type: EVENT_WORKFLOW_STOP,
} as any)
}
const handleClearAll = () => {
deleteAllInspectorVars()
setCurrentFocusNodeId('')
}
if (showVariableInspectPanel) return null
// React Flow reserves Space for panning unless the target is inside .nokey.
return (
<div className={cn('nokey flex shrink-0 flex-nowrap items-center gap-1 whitespace-nowrap')}>
{!isRunning && !currentVars.length && (
<Button
size="small"
variant="secondary"
disabled={nodesReadOnly}
className={cn(
'flex h-6 shrink-0 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 system-2xs-semibold-uppercase text-text-tertiary shadow-lg backdrop-blur-xs hover:bg-background-default-hover',
nodesReadOnly &&
'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled',
)}
onClick={() => {
if (getNodesReadOnly()) return
setShowVariableInspectPanel(true)
}}
>
{t(($) => $['debug.variableInspect.trigger.normal'], { ns: 'workflowDebug' })}
</Button>
)}
{!isRunning && currentVars.length > 0 && (
<>
<Button
size="small"
variant="secondary"
disabled={nodesReadOnly}
className={cn(
'flex h-6 shrink-0 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 system-xs-medium text-text-accent shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent',
nodesReadOnly &&
'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled',
)}
onClick={() => {
if (getNodesReadOnly()) return
setShowVariableInspectPanel(true)
}}
>
{t(($) => $['debug.variableInspect.trigger.cached'], { ns: 'workflowDebug' })}
</Button>
<Button
size="small"
variant="secondary"
disabled={nodesReadOnly}
className={cn(
'flex h-6 shrink-0 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 system-xs-medium text-text-tertiary shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent hover:text-text-accent',
nodesReadOnly &&
'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled',
)}
onClick={() => {
if (getNodesReadOnly()) return
handleClearAll()
}}
>
{t(($) => $['debug.variableInspect.trigger.clear'], { ns: 'workflowDebug' })}
</Button>
</>
)}
{isRunning && (
<>
<Button
size="small"
variant="secondary"
className="flex h-6 shrink-0 cursor-pointer items-center gap-1 rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-2 system-xs-medium text-text-accent shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent"
onClick={() => setShowVariableInspectPanel(true)}
>
<RiLoader2Line aria-hidden="true" className="size-4 shrink-0 animate-spin" />
<span className="text-text-accent">
{t(($) => $['debug.variableInspect.trigger.running'], { ns: 'workflowDebug' })}
</span>
</Button>
{isPreviewRunning && (
<Tooltip>
<TooltipTrigger
render={
<IconButton
aria-label={t(($) => $['debug.variableInspect.trigger.stop'], {
ns: 'workflowDebug',
})}
className="flex h-6 shrink-0 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent"
onClick={handleStop}
>
<RiStopCircleFill
aria-hidden="true"
className="size-4 shrink-0 text-text-accent"
/>
</IconButton>
}
/>
<TooltipContent>
{t(($) => $['debug.variableInspect.trigger.stop'], { ns: 'workflowDebug' })}
</TooltipContent>
</Tooltip>
)}
</>
)}
</div>
)
}
export default VariableInspectTrigger