From 4a88ffdf2a2188ec18543681d0ce0c42d036acab Mon Sep 17 00:00:00 2001 From: yyh Date: Thu, 22 Jan 2026 14:45:14 +0800 Subject: [PATCH] feat: align workflow view picker layout --- .../components/workflow-children.tsx | 9 +++- .../components/workflow-header/index.tsx | 10 +++- .../workflow-app/components/workflow-main.tsx | 8 ++- web/app/components/workflow-app/index.tsx | 52 +++++++++++++------ .../workflow/header/header-in-normal.tsx | 4 +- web/app/components/workflow/header/index.tsx | 37 +++++++++---- web/app/components/workflow/view-picker.tsx | 2 +- 7 files changed, 86 insertions(+), 36 deletions(-) diff --git a/web/app/components/workflow-app/components/workflow-children.tsx b/web/app/components/workflow-app/components/workflow-children.tsx index 2634e8da2a..c0efd5dc3c 100644 --- a/web/app/components/workflow-app/components/workflow-children.tsx +++ b/web/app/components/workflow-app/components/workflow-children.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react' import type { PluginDefaultValue, TriggerDefaultValue, @@ -65,7 +66,11 @@ const getTriggerPluginNodeData = ( } } -const WorkflowChildren = () => { +type WorkflowChildrenProps = { + headerLeftSlot?: ReactNode +} + +const WorkflowChildren = ({ headerLeftSlot }: WorkflowChildrenProps) => { const { eventEmitter } = useEventEmitterContextContext() const [secretEnvList, setSecretEnvList] = useState([]) const showFeaturesPanel = useStore(s => s.showFeaturesPanel) @@ -188,7 +193,7 @@ const WorkflowChildren = () => { /> ) } - + ) diff --git a/web/app/components/workflow-app/components/workflow-header/index.tsx b/web/app/components/workflow-app/components/workflow-header/index.tsx index 3fe679925a..17fb747f7a 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react' import type { HeaderProps } from '@/app/components/workflow/header' import { memo, @@ -12,7 +13,11 @@ import { useIsChatMode } from '../../hooks' import ChatVariableTrigger from './chat-variable-trigger' import FeaturesTrigger from './features-trigger' -const WorkflowHeader = () => { +type WorkflowHeaderProps = { + leftSlot?: ReactNode +} + +const WorkflowHeader = ({ leftSlot }: WorkflowHeaderProps) => { const { appDetail, setCurrentLogItem, setShowMessageLogModal } = useAppStore(useShallow(state => ({ appDetail: state.appDetail, setCurrentLogItem: state.setCurrentLogItem, @@ -37,6 +42,7 @@ const WorkflowHeader = () => { return { normal: { components: { + left: leftSlot, middle: , chatVariableTrigger: , }, @@ -53,7 +59,7 @@ const WorkflowHeader = () => { onRestoreSettled: resetWorkflowVersionHistory, }, } - }, [resetWorkflowVersionHistory, isChatMode, viewHistoryProps]) + }, [leftSlot, resetWorkflowVersionHistory, isChatMode, viewHistoryProps]) return (
) diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 825ffebc20..f890b2a4f7 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react' import type { WorkflowProps } from '@/app/components/workflow' import { useCallback, @@ -21,11 +22,14 @@ import { } from '../hooks' import WorkflowChildren from './workflow-children' -type WorkflowMainProps = Pick +type WorkflowMainProps = Pick & { + headerLeftSlot?: ReactNode +} const WorkflowMain = ({ nodes, edges, viewport, + headerLeftSlot, }: WorkflowMainProps) => { const sandboxEnabled = useFeatures(state => state.features.sandbox?.enabled) ?? false const featuresStore = useFeaturesStore() @@ -186,7 +190,7 @@ const WorkflowMain = ({ hooksStore={hooksStore as any} > - + ) diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx index 08972bad5b..0b721109d0 100644 --- a/web/app/components/workflow-app/index.tsx +++ b/web/app/components/workflow-app/index.tsx @@ -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 { @@ -51,12 +52,12 @@ const SkillMain = dynamic(() => import('@/app/components/workflow/skill/main'), }) type WorkflowViewContentProps = { - graphContent: ReactNode + renderGraph: (headerLeftSlot: ReactNode) => ReactNode reload: () => Promise } const WorkflowViewContent = ({ - graphContent, + renderGraph, reload, }: WorkflowViewContentProps) => { const features = useFeatures(s => s.features) @@ -92,27 +93,45 @@ const WorkflowViewContent = ({ } }, [doSetViewType, refreshGraph, syncWorkflowDraftImmediately, viewType]) - if (!isSupportSandbox) { - return graphContent - } + if (!isSupportSandbox) + return renderGraph(null) + + const viewPicker = ( + + ) + const viewPickerDock = ( + +
+
+ {viewPicker} +
+
+
+ ) + return (
- {viewType === ViewType.graph ? ( isGraphRefreshing ? ( -
- -
+ <> + {viewPickerDock} +
+ +
+ ) - : graphContent + : renderGraph(viewPicker) ) : ( - + <> + {viewPickerDock} + + )}
) @@ -243,7 +262,7 @@ const WorkflowAppWithAdditionalContext = () => { }, [replayRunId, workflowStore, getWorkflowRunAndTraceUrl]) const isDataReady = !(!data || isLoading || isLoadingCurrentWorkspace || !currentWorkspace.id) - const GraphMain = useMemo(() => { + const renderGraph = useCallback((headerLeftSlot: ReactNode) => { if (!isDataReady) return null @@ -252,6 +271,7 @@ const WorkflowAppWithAdditionalContext = () => { nodes={nodesData} edges={edgesData} viewport={data.graph.viewport} + headerLeftSlot={headerLeftSlot} /> ) }, [isDataReady, nodesData, edgesData, data]) @@ -299,7 +319,7 @@ const WorkflowAppWithAdditionalContext = () => { > diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx index be4985fb83..b00edfa02e 100644 --- a/web/app/components/workflow/header/header-in-normal.tsx +++ b/web/app/components/workflow/header/header-in-normal.tsx @@ -65,14 +65,14 @@ const HeaderInNormal = ({ return (
-
+
+ {components?.left}
- {components?.left}
diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index 0590c016f2..ee1546906c 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -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' @@ -21,19 +22,15 @@ export type HeaderProps = { viewHistory?: HeaderInHistoryProps restoring?: HeaderInRestoringProps } -const Header = ({ - normal: normalProps, - viewHistory: viewHistoryProps, - restoring: restoringProps, -}: HeaderProps) => { + +type HeaderShellProps = { + children: ReactNode +} + +export const HeaderShell = ({ children }: HeaderShellProps) => { 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 ( @@ -41,6 +38,24 @@ const Header = ({ 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 &&
} + {children} +
+ ) +} + +const Header = ({ + normal: normalProps, + viewHistory: viewHistoryProps, + restoring: restoringProps, +}: HeaderProps) => { + const { + normal, + restoring, + viewHistory, + } = useWorkflowMode() + + return ( + { normal && ( ) } -
+ ) } diff --git a/web/app/components/workflow/view-picker.tsx b/web/app/components/workflow/view-picker.tsx index 43b20d52fb..e81b35774e 100644 --- a/web/app/components/workflow/view-picker.tsx +++ b/web/app/components/workflow/view-picker.tsx @@ -33,7 +33,7 @@ const ViewPicker: FC = ({ return (