Files
dify/web/app/components/workflow/header/header-in-normal.tsx
T
Charles YaoandClaude Opus 4.8 22dc64d6e6 feat(web): surface Workflow Copilot inside the workflow editor
Add a copilot toggle button next to Test Run in the workflow header and
a left-mounted panel that runs the copilot Fix flow (start fix, live
SSE progress log, approve_repair/run_verify/provide_testdata/publish/
undo/re_fix actions, and a message box), auto-resolving the app id
(workflow store `appId`), workspace id (`currentWorkspaceAtom`), and
the most recent failed run (`useWorkflowRunHistory`) instead of
requiring manual entry.

Extracts the session/SSE request logic from the standalone
`/workflow-copilot-debug` page into a shared
`app/components/workflow-copilot` module (`use-copilot-session`) so
both the debug page and the new panel drive the same implementation.

Adds a `showWorkflowCopilotPanel` store flag (defaults false) mirroring
the existing `showEnvPanel` pattern, so the feature is fully additive
and does not affect rag-pipeline/snippet editors that reuse the same
header/panel shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 21:45:04 -07:00

112 lines
4.7 KiB
TypeScript

import type { StartNodeType } from '../nodes/start/types'
import type { RunAndHistoryProps } from './run-and-history'
import { useCallback } from 'react'
import { useNodes } from 'reactflow'
import { useInputFieldPanel } from '@/app/components/rag-pipeline/hooks/use-input-field-panel'
import Divider from '../../base/divider'
import { useHooksStore } from '../hooks-store'
import { useNodesInteractions } from '../hooks/use-nodes-interactions'
import { useNodesReadOnly } from '../hooks/use-workflow'
import { useWorkflowRun } from '../hooks/use-workflow-run'
import { useStore, useWorkflowStore } from '../store'
import EditingTitle from './editing-title'
import EnvButton from './env-button'
import GlobalVariableButton from './global-variable-button'
import OnlineUsers from './online-users'
import RunAndHistory from './run-and-history'
import ScrollToSelectedNodeButton from './scroll-to-selected-node-button'
import { VersionHistoryButton } from './version-history-button'
import WorkflowCopilotButton from './workflow-copilot-button'
export type HeaderInNormalProps = {
components?: {
title?: React.ReactNode
left?: React.ReactNode
middle?: React.ReactNode
chatVariableTrigger?: React.ReactNode
}
controls?: {
showEnvButton?: boolean
showGlobalVariableButton?: boolean
showWorkflowCopilotButton?: boolean
}
runAndHistoryProps?: RunAndHistoryProps
}
const HeaderInNormal = ({ components, controls, runAndHistoryProps }: HeaderInNormalProps) => {
const workflowStore = useWorkflowStore()
const { nodesReadOnly } = useNodesReadOnly()
const canReleaseAndVersion = useHooksStore((s) => s.accessControl.canReleaseAndVersion)
const { handleNodeSelect } = useNodesInteractions()
const setShowWorkflowVersionHistoryPanel = useStore((s) => s.setShowWorkflowVersionHistoryPanel)
const setShowEnvPanel = useStore((s) => s.setShowEnvPanel)
const setShowDebugAndPreviewPanel = useStore((s) => s.setShowDebugAndPreviewPanel)
const setShowVariableInspectPanel = useStore((s) => s.setShowVariableInspectPanel)
const setShowChatVariablePanel = useStore((s) => s.setShowChatVariablePanel)
const setShowGlobalVariablePanel = useStore((s) => s.setShowGlobalVariablePanel)
const setShowWorkflowCopilotPanel = useStore((s) => s.setShowWorkflowCopilotPanel)
const nodes = useNodes<StartNodeType>()
const selectedNode = nodes.find((node) => node.data.selected)
const { handleBackupDraft } = useWorkflowRun()
const { closeAllInputFieldPanels } = useInputFieldPanel()
const showEnvButton = controls?.showEnvButton !== false
const showGlobalVariableButton = controls?.showGlobalVariableButton !== false
const showWorkflowCopilotButton = controls?.showWorkflowCopilotButton === true
const showContextButtons =
!!components?.chatVariableTrigger || showEnvButton || showGlobalVariableButton
const onStartRestoring = useCallback(() => {
workflowStore.setState({ isRestoring: true })
handleBackupDraft()
// clear right panel
if (selectedNode) handleNodeSelect(selectedNode.id, true)
setShowWorkflowVersionHistoryPanel(true)
setShowEnvPanel(false)
setShowDebugAndPreviewPanel(false)
setShowVariableInspectPanel(false)
setShowChatVariablePanel(false)
setShowGlobalVariablePanel(false)
setShowWorkflowCopilotPanel(false)
closeAllInputFieldPanels()
}, [
workflowStore,
handleBackupDraft,
selectedNode,
handleNodeSelect,
setShowWorkflowVersionHistoryPanel,
setShowEnvPanel,
setShowDebugAndPreviewPanel,
setShowVariableInspectPanel,
setShowChatVariablePanel,
setShowGlobalVariablePanel,
setShowWorkflowCopilotPanel,
closeAllInputFieldPanels,
])
return (
<div className="flex w-full items-center justify-between">
<div>{components?.title ?? <EditingTitle />}</div>
<div>
<ScrollToSelectedNodeButton />
</div>
<div className="flex items-center gap-2">
<OnlineUsers />
{components?.left}
<Divider type="vertical" className="mx-auto h-3.5" />
<RunAndHistory {...runAndHistoryProps} />
{showWorkflowCopilotButton && <WorkflowCopilotButton disabled={nodesReadOnly} />}
{showContextButtons && (
<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]">
{components?.chatVariableTrigger}
{showEnvButton && <EnvButton disabled={nodesReadOnly} />}
{showGlobalVariableButton && <GlobalVariableButton disabled={nodesReadOnly} />}
</div>
)}
{components?.middle}
{canReleaseAndVersion && <VersionHistoryButton onClick={onStartRestoring} />}
</div>
</div>
)
}
export default HeaderInNormal