diff --git a/web/app/components/workflow/custom-connection-line.tsx b/web/app/components/workflow/custom-connection-line.tsx index 4d519e930d3f5e..a411370b05f3fc 100644 --- a/web/app/components/workflow/custom-connection-line.tsx +++ b/web/app/components/workflow/custom-connection-line.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import { memo } from 'react' import type { ConnectionLineComponentProps } from 'reactflow' import { Position, @@ -36,4 +36,4 @@ const CustomConnectionLine = ({ fromX, fromY, toX, toY }: ConnectionLineComponen ) } -export default CustomConnectionLine +export default memo(CustomConnectionLine) diff --git a/web/app/components/workflow/header/publish.tsx b/web/app/components/workflow/header/publish.tsx index 08a29084c6cd76..5d904ab470d28d 100644 --- a/web/app/components/workflow/header/publish.tsx +++ b/web/app/components/workflow/header/publish.tsx @@ -1,6 +1,12 @@ -import { useState } from 'react' +import { + memo, + useState, +} from 'react' import { useTranslation } from 'react-i18next' -import { useStore } from '../store' +import { + useStore, + useWorkflowStore, +} from '../store' import Button from '@/app/components/base/button' import { PortalToFollowElem, @@ -12,13 +18,17 @@ import { useStore as useAppStore } from '@/app/components/app/store' const Publish = () => { const { t } = useTranslation() + const workflowStore = useWorkflowStore() const runningStatus = useStore(s => s.runningStatus) const [open, setOpen] = useState(false) const handlePublish = async () => { const appId = useAppStore.getState().appDetail?.id try { - await publishWorkflow(`/apps/${appId}/workflows/publish`) + const res = await publishWorkflow(`/apps/${appId}/workflows/publish`) + + if (res) + workflowStore.setState({ publishedAt: res.created_at }) } catch (e) { } @@ -86,4 +96,4 @@ const Publish = () => { ) } -export default Publish +export default memo(Publish) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index d13f0f89e3f490..589ed0dae631c2 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -44,6 +44,7 @@ export const useNodesInteractions = () => { const connectingNodeRef = useRef<{ nodeId: string; handleType: HandleType } | null>(null) const handleNodeDragStart = useCallback((_, node) => { + workflowStore.setState({ nodeAnimation: false }) const { runningStatus, } = workflowStore.getState() diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 32c767735e7a7d..e5503c68b5d3ee 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -9,7 +9,6 @@ import { NodeRunningStatus, WorkflowRunningStatus, } from '../types' -import { NODE_WIDTH } from '../constants' import { useStore as useAppStore } from '@/app/components/app/store' import type { IOtherOptions } from '@/service/base' import { ssePost } from '@/service/base' @@ -135,19 +134,17 @@ export const useWorkflowRun = () => { onNodeStarted: ({ data }) => { const nodes = getNodes() const { - getViewport, setViewport, } = reactflow - - const viewport = getViewport() const currentNodeIndex = nodes.findIndex(node => node.id === data.node_id) const currentNode = nodes[currentNodeIndex] const position = currentNode.position - const zoom = 0.5 + const zoom = 1 + setViewport({ + x: (clientWidth - 400 - currentNode.width!) / 2 - position.x, + y: (clientHeight - currentNode.height!) / 2 - position.y, zoom, - x: (((clientWidth - 400) / 2 - NODE_WIDTH / 2) / viewport.zoom - position.x) * zoom, - y: ((clientHeight / 2 - currentNode.height! / 2) / viewport.zoom - position.y) * zoom, }) const newNodes = produce(nodes, (draft) => { draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index f95ede2390b67c..6d4a74dbdcf11d 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -47,10 +47,12 @@ export const useIsChatMode = () => { export const useWorkflow = () => { const store = useStoreApi() const reactflow = useReactFlow() + const workflowStore = useWorkflowStore() const nodesExtraData = useNodesExtraData() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const handleLayout = useCallback(async () => { + workflowStore.setState({ nodeAnimation: true }) const { getNodes, edges, @@ -70,13 +72,16 @@ export const useWorkflow = () => { }) }) setNodes(newNodes) + const zoom = 0.7 setViewport({ x: 0, y: 0, - zoom: 0.8, + zoom, + }) + setTimeout(() => { + handleSyncWorkflowDraft() }) - setTimeout(() => handleSyncWorkflowDraft()) - }, [store, reactflow, handleSyncWorkflowDraft]) + }, [store, reactflow, handleSyncWorkflowDraft, workflowStore]) const getTreeLeafNodes = useCallback((nodeId: string) => { const { diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 26da3b90ae07ca..e4d9ae21a09993 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -13,6 +13,7 @@ import ReactFlow, { } from 'reactflow' import type { Viewport } from 'reactflow' import 'reactflow/dist/style.css' +import './style.css' import type { Edge, Node, @@ -61,6 +62,7 @@ const Workflow: FC = memo(({ }) => { const showFeaturesPanel = useStore(state => state.showFeaturesPanel) const runningStatus = useStore(s => s.runningStatus) + const nodeAnimation = useStore(s => s.nodeAnimation) const { handleSyncWorkflowDraft } = useNodesSyncDraft() useEffect(() => { @@ -99,7 +101,11 @@ const Workflow: FC = memo(({ return (
@@ -109,7 +115,6 @@ const Workflow: FC = memo(({ } { branchName && ( -
+
{branchName.toLocaleUpperCase()}
) diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx index f0cb6802aff7a9..de2ea1c824b5a7 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx @@ -22,7 +22,7 @@ const NextStep = ({ selectedNode, }: NextStepProps) => { const store = useStoreApi() - const branches = selectedNode.data._targetBranches + const branches = selectedNode.data._targetBranches || [] const nodeWithBranches = selectedNode.data.type === BlockEnum.IfElse || selectedNode.data.type === BlockEnum.QuestionClassifier const edges = useEdges() const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges) @@ -36,7 +36,7 @@ const NextStep = ({ toolProviderId={selectedNode!.data.provider_id} />
- +
{ !nodeWithBranches && !!outgoers.length && ( diff --git a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx index 3c620268982216..837cac1bbb5ea4 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/item.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/item.tsx @@ -55,7 +55,10 @@ const Item = ({ > { branchName && ( -
+
{branchName.toLocaleUpperCase()}
) diff --git a/web/app/components/workflow/nodes/_base/components/node-control.tsx b/web/app/components/workflow/nodes/_base/components/node-control.tsx index 0dd5836c6ba2e8..a31a197992824e 100644 --- a/web/app/components/workflow/nodes/_base/components/node-control.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-control.tsx @@ -5,7 +5,10 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import { useNodeDataUpdate } from '../../../hooks' +import { + useNodeDataUpdate, + useNodesInteractions, +} from '../../../hooks' import type { Node } from '../../../types' import { canRunBySingle } from '../../../utils' import PanelOperator from './panel-operator' @@ -23,6 +26,7 @@ const NodeControl: FC = ({ const { t } = useTranslation() const [open, setOpen] = useState(false) const { handleNodeDataUpdate } = useNodeDataUpdate() + const { handleNodeSelect } = useNodesInteractions() const handleOpenChange = useCallback((newOpen: boolean) => { setOpen(newOpen) @@ -51,6 +55,7 @@ const NodeControl: FC = ({ _isSingleRun: !data._isSingleRun, }, }) + handleNodeSelect(id) }} > { diff --git a/web/app/components/workflow/nodes/constants.ts b/web/app/components/workflow/nodes/constants.ts index c77fcffc0ac09d..734093eb7b9205 100644 --- a/web/app/components/workflow/nodes/constants.ts +++ b/web/app/components/workflow/nodes/constants.ts @@ -40,7 +40,7 @@ export const NodeComponentMap: Record> = { [BlockEnum.VariableAssigner]: VariableAssignerNode, } -export const PanelComponentMap: Record = { +export const PanelComponentMap: Record> = { [BlockEnum.Start]: StartPanel, [BlockEnum.End]: EndPanel, [BlockEnum.Answer]: AnswerPanel, diff --git a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx index f1804157552eb6..ea4f463c478daa 100644 --- a/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/chat-wrapper.tsx @@ -10,7 +10,7 @@ import { useChat } from './hooks' import Chat from '@/app/components/base/chat/chat' import type { OnSend } from '@/app/components/base/chat/types' import { useFeaturesStore } from '@/app/components/base/features/hooks' -import { fetchConvesationMessages } from '@/service/debug' +import { fetchSuggestedQuestions } from '@/service/debug' import { useStore as useAppStore } from '@/app/components/app/store' const ChatWrapper = () => { @@ -49,10 +49,10 @@ const ChatWrapper = () => { query, files, inputs: workflowStore.getState().inputs, - conversationId, + conversation_id: conversationId, }, { - onGetSuggestedQuestions: (conversationId, getAbortController) => fetchConvesationMessages(appId, conversationId, getAbortController), + onGetSuggestedQuestions: (messageId, getAbortController) => fetchSuggestedQuestions(appId, messageId, getAbortController), }, ) } diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index 85185d8e8e59ad..de88f8c30ad861 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -1,9 +1,9 @@ -import type { FC } from 'react' +import { memo } from 'react' import { useTranslation } from 'react-i18next' import { useStore } from '../../store' import ChatWrapper from './chat-wrapper' -const DebugAndPreview: FC = () => { +const DebugAndPreview = () => { const { t } = useTranslation() const showRunHistory = useStore(s => s.showRunHistory) @@ -28,4 +28,4 @@ const DebugAndPreview: FC = () => { ) } -export default DebugAndPreview +export default memo(DebugAndPreview) diff --git a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx index 7c3ee3da9f7f71..19959cef2d9a7f 100644 --- a/web/app/components/workflow/panel/debug-and-preview/user-input.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/user-input.tsx @@ -29,6 +29,9 @@ const UserInput = () => { }) } + if (!variables.length) + return null + return (
{ - const { currentSequenceNumber, workflowRunId } = useStore() + const currentSequenceNumber = useStore(s => s.currentSequenceNumber) + const workflowRunId = useStore(s => s.workflowRunId) + return (
diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 4b10dc372e715e..224d5343f238c7 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -13,7 +13,6 @@ import type { ToolInWorkflow, ToolsMap, } from './block-selector/types' -import { Mode } from './types' import type { Edge, Node, @@ -22,7 +21,6 @@ import type { import { WorkflowContext } from './context' type State = { - mode: Mode taskId: string currentSequenceNumber: number workflowRunId: string @@ -44,10 +42,10 @@ type State = { } notInitialWorkflow: boolean nodesDefaultConfigs: Record + nodeAnimation: boolean } type Action = { - setMode: (mode: Mode) => void setTaskId: (taskId: string) => void setCurrentSequenceNumber: (currentSequenceNumber: number) => void setWorkflowRunId: (workflowRunId: string) => void @@ -65,18 +63,17 @@ type Action = { setBackupDraft: (backupDraft?: State['backupDraft']) => void setNotInitialWorkflow: (notInitialWorkflow: boolean) => void setNodesDefaultConfigs: (nodesDefaultConfigs: Record) => void + setNodeAnimation: (nodeAnimation: boolean) => void } export const createWorkflowStore = () => { return create(set => ({ - mode: Mode.Editing, taskId: '', setTaskId: taskId => set(() => ({ taskId })), currentSequenceNumber: 0, setCurrentSequenceNumber: currentSequenceNumber => set(() => ({ currentSequenceNumber })), workflowRunId: '', setWorkflowRunId: workflowRunId => set(() => ({ workflowRunId })), - setMode: mode => set(() => ({ mode })), showRunHistory: false, setShowRunHistory: showRunHistory => set(() => ({ showRunHistory })), showFeaturesPanel: false, @@ -105,6 +102,8 @@ export const createWorkflowStore = () => { setNotInitialWorkflow: notInitialWorkflow => set(() => ({ notInitialWorkflow })), nodesDefaultConfigs: {}, setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })), + nodeAnimation: false, + setNodeAnimation: nodeAnimation => set(() => ({ nodeAnimation })), })) } diff --git a/web/app/components/workflow/style.css b/web/app/components/workflow/style.css new file mode 100644 index 00000000000000..fc6130cc02ce73 --- /dev/null +++ b/web/app/components/workflow/style.css @@ -0,0 +1,7 @@ +.workflow-panel-animation .react-flow__viewport { + transition: transform 0.3s ease-in-out; +} + +.workflow-node-animation .react-flow__node { + transition: transform 0.2s ease-in-out; +} \ No newline at end of file diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 91d72a1486d7f4..024f16a498ea45 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -173,11 +173,6 @@ export type NodeDefault = { export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void -export enum Mode { - Editing = 'editing', - Running = 'running', -} - export enum WorkflowRunningStatus { Waiting = 'waiting', Running = 'running', diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 1082fef195173f..4e16619cd3dfd0 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -59,7 +59,7 @@ export const getLayoutByDagre = (originNodes: Node[], originEdges: Edge[]) => { rankdir: 'LR', align: 'UL', nodesep: 64, - ranksep: 64, + ranksep: 40, }) nodes.forEach((node) => { dagreGraph.setNode(node.id, { width: node.width, height: node.height }) diff --git a/web/service/workflow.ts b/web/service/workflow.ts index 6828e5fcfd5655..503098ae333fd1 100644 --- a/web/service/workflow.ts +++ b/web/service/workflow.ts @@ -28,7 +28,7 @@ export const singleNodeRun = (appId: string, nodeId: string, params: object) => } export const publishWorkflow = (url: string) => { - return post(url) + return post(url) } export const stopWorkflowRun = (url: string) => {