From 8e4989ed0350109af3d971abbb1d67a79c8253c5 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Sun, 28 Apr 2024 17:09:56 +0800 Subject: [PATCH] feat: workflow remove preview mode (#3941) --- .../line/communication/message-play.svg | 5 + .../line/communication/MessagePlay.json | 39 ++++++ .../vender/line/communication/MessagePlay.tsx | 16 +++ .../src/vender/line/communication/index.ts | 1 + .../components/workflow/header/checklist.tsx | 17 ++- .../workflow/header/editing-title.tsx | 4 - web/app/components/workflow/header/index.tsx | 75 ++++++----- .../workflow/header/run-and-history.tsx | 80 ++++++----- .../workflow/header/running-title.tsx | 22 +-- .../workflow/header/view-history.tsx | 71 +++++++--- web/app/components/workflow/hooks/index.ts | 2 + .../workflow/hooks/use-edges-interactions.ts | 15 +++ .../workflow/hooks/use-nodes-interactions.ts | 42 ++++-- .../workflow/hooks/use-nodes-sync-draft.ts | 8 +- .../hooks/use-workflow-interactions.ts | 50 +++++++ .../workflow/hooks/use-workflow-mode.ts | 14 ++ .../workflow/hooks/use-workflow-run.ts | 125 ++++++------------ .../components/workflow/hooks/use-workflow.ts | 29 +--- .../components/before-run-form/form-item.tsx | 9 +- .../components/workflow/nodes/_base/node.tsx | 27 +++- .../components/workflow/nodes/_base/panel.tsx | 11 +- .../workflow/panel/chat-record/index.tsx | 18 ++- .../panel/debug-and-preview/index.tsx | 58 ++++++-- .../panel/debug-and-preview/user-input.tsx | 3 +- web/app/components/workflow/panel/index.tsx | 44 ++---- .../workflow/panel/inputs-panel.tsx | 5 +- web/app/components/workflow/panel/record.tsx | 19 ++- .../workflow/panel/workflow-preview.tsx | 22 +-- web/app/components/workflow/store.ts | 8 +- web/app/components/workflow/types.ts | 7 + web/app/components/workflow/utils.ts | 8 +- web/i18n/en-US/workflow.ts | 2 + web/i18n/zh-Hans/workflow.ts | 2 + 33 files changed, 549 insertions(+), 309 deletions(-) create mode 100644 web/app/components/base/icons/assets/vender/line/communication/message-play.svg create mode 100644 web/app/components/base/icons/src/vender/line/communication/MessagePlay.json create mode 100644 web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx create mode 100644 web/app/components/workflow/hooks/use-workflow-interactions.ts create mode 100644 web/app/components/workflow/hooks/use-workflow-mode.ts diff --git a/web/app/components/base/icons/assets/vender/line/communication/message-play.svg b/web/app/components/base/icons/assets/vender/line/communication/message-play.svg new file mode 100644 index 0000000000..79460f5654 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/communication/message-play.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/src/vender/line/communication/MessagePlay.json b/web/app/components/base/icons/src/vender/line/communication/MessagePlay.json new file mode 100644 index 0000000000..5624650604 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/communication/MessagePlay.json @@ -0,0 +1,39 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "17", + "height": "16", + "viewBox": "0 0 17 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Left Icon" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M7.83333 2.66683H5.7C4.5799 2.66683 4.01984 2.66683 3.59202 2.88482C3.21569 3.07656 2.90973 3.38252 2.71799 3.75885C2.5 4.18667 2.5 4.74672 2.5 5.86683V9.3335C2.5 9.95348 2.5 10.2635 2.56815 10.5178C2.75308 11.208 3.29218 11.7471 3.98236 11.932C4.2367 12.0002 4.54669 12.0002 5.16667 12.0002V13.5572C5.16667 13.9124 5.16667 14.09 5.23949 14.1812C5.30282 14.2606 5.39885 14.3067 5.50036 14.3066C5.61708 14.3065 5.75578 14.1955 6.03317 13.9736L7.62348 12.7014C7.94834 12.4415 8.11078 12.3115 8.29166 12.2191C8.45213 12.1371 8.62295 12.0772 8.79948 12.041C8.99845 12.0002 9.20646 12.0002 9.6225 12.0002H10.6333C11.7534 12.0002 12.3135 12.0002 12.7413 11.7822C13.1176 11.5904 13.4236 11.2845 13.6153 10.9081C13.8333 10.4803 13.8333 9.92027 13.8333 8.80016V8.66683M11.6551 6.472L14.8021 4.44889C15.0344 4.29958 15.1505 4.22493 15.1906 4.13C15.2257 4.04706 15.2257 3.95347 15.1906 3.87052C15.1505 3.7756 15.0344 3.70094 14.8021 3.55163L11.6551 1.52852C11.3874 1.35646 11.2536 1.27043 11.1429 1.27833C11.0465 1.28522 10.9578 1.33365 10.8998 1.41105C10.8333 1.49987 10.8333 1.65896 10.8333 1.97715V6.02337C10.8333 6.34156 10.8333 6.50066 10.8998 6.58948C10.9578 6.66688 11.0465 6.71531 11.1429 6.72219C11.2536 6.7301 11.3874 6.64407 11.6551 6.472Z", + "stroke": "currentColor", + "stroke-width": "1.5", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + } + ] + }, + "name": "MessagePlay" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx b/web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx new file mode 100644 index 0000000000..e570e49c52 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/communication/MessagePlay.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './MessagePlay.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'MessagePlay' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/communication/index.ts b/web/app/components/base/icons/src/vender/line/communication/index.ts index 3ab20e8bb4..27e646ef98 100644 --- a/web/app/components/base/icons/src/vender/line/communication/index.ts +++ b/web/app/components/base/icons/src/vender/line/communication/index.ts @@ -4,3 +4,4 @@ export { default as ChatBot } from './ChatBot' export { default as CuteRobot } from './CuteRobot' export { default as MessageCheckRemove } from './MessageCheckRemove' export { default as MessageFastPlus } from './MessageFastPlus' +export { default as MessagePlay } from './MessagePlay' diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index 21e8869857..d30516d92d 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -7,6 +7,7 @@ import { useEdges, useNodes, } from 'reactflow' +import cn from 'classnames' import BlockIcon from '../block-icon' import { useChecklist, @@ -28,7 +29,12 @@ import { } from '@/app/components/base/icons/src/vender/line/general' import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' -const WorkflowChecklist = () => { +type WorkflowChecklistProps = { + disabled: boolean +} +const WorkflowChecklist = ({ + disabled, +}: WorkflowChecklistProps) => { const { t } = useTranslation() const [open, setOpen] = useState(false) const nodes = useNodes() @@ -46,8 +52,13 @@ const WorkflowChecklist = () => { open={open} onOpenChange={setOpen} > - setOpen(v => !v)}> -
+ !disabled && setOpen(v => !v)}> +
{ @@ -13,12 +12,9 @@ const EditingTitle = () => { return (
- - {t('workflow.common.editing')} { !!draftUpdatedAt && ( <> - · {t('workflow.common.autoSaved')} {dayjs(draftUpdatedAt).format('HH:mm:ss')} ) diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index c72bca3317..7340aa6e50 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -13,6 +13,7 @@ import { useChecklistBeforePublish, useNodesReadOnly, useNodesSyncDraft, + useWorkflowMode, useWorkflowRun, } from '../hooks' import AppPublisher from '../../app/app-publisher' @@ -21,12 +22,13 @@ import RunAndHistory from './run-and-history' import EditingTitle from './editing-title' import RunningTitle from './running-title' import RestoringTitle from './restoring-title' +import ViewHistory from './view-history' import Checklist from './checklist' import { Grid01 } from '@/app/components/base/icons/src/vender/line/layout' import Button from '@/app/components/base/button' -import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' import { useStore as useAppStore } from '@/app/components/app/store' import { publishWorkflow } from '@/service/workflow' +import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows' const Header: FC = () => { const { t } = useTranslation() @@ -38,18 +40,21 @@ const Header: FC = () => { nodesReadOnly, getNodesReadOnly, } = useNodesReadOnly() - const isRestoring = useStore(s => s.isRestoring) const publishedAt = useStore(s => s.publishedAt) const draftUpdatedAt = useStore(s => s.draftUpdatedAt) const { handleLoadBackupDraft, - handleRunSetting, handleBackupDraft, handleRestoreFromPublishedWorkflow, } = useWorkflowRun() const { handleCheckBeforePublish } = useChecklistBeforePublish() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { notify } = useContext(ToastContext) + const { + normal, + restoring, + viewHistory, + } = useWorkflowMode() const handleShowFeatures = useCallback(() => { const { @@ -62,10 +67,6 @@ const Header: FC = () => { setShowFeaturesPanel(true) }, [workflowStore, getNodesReadOnly]) - const handleGoBackToEdit = useCallback(() => { - handleRunSetting(true) - }, [handleRunSetting]) - const handleCancelRestore = useCallback(() => { handleLoadBackupDraft() workflowStore.setState({ isRestoring: false }) @@ -102,6 +103,11 @@ const Header: FC = () => { handleSyncWorkflowDraft(true) }, [handleSyncWorkflowDraft]) + const handleGoBackToEdit = useCallback(() => { + handleLoadBackupDraft() + workflowStore.setState({ historyWorkflowData: undefined }) + }, [workflowStore, handleLoadBackupDraft]) + return (
{ ) } { - !nodesReadOnly && !isRestoring && + normal && } { - nodesReadOnly && !isRestoring && + viewHistory && } { - isRestoring && + restoring && }
{ - !isRestoring && ( + normal && (
- { - nodesReadOnly && ( - - ) - }
) } { - isRestoring && ( + viewHistory && ( +
+ +
+ +
+ ) + } + { + restoring && (
+
+ +
+
+ +
+
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 08a7708fc0..819208054b 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 @@ -56,12 +56,13 @@ const UserInput = () => { expanded && (
{ - variables.map(variable => ( + variables.map((variable, index) => (
handleValueChange(variable.variable, v)} diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index 58eebfcea8..6614362c37 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -1,9 +1,7 @@ import type { FC } from 'react' -import { - memo, - useMemo, -} from 'react' +import { memo } from 'react' import { useNodes } from 'reactflow' +import cn from 'classnames' import { useShallow } from 'zustand/react/shallow' import type { CommonNodeType } from '../types' import { Panel as NodePanel } from '../nodes' @@ -23,9 +21,8 @@ const Panel: FC = () => { const nodes = useNodes() const isChatMode = useIsChatMode() const selectedNode = nodes.find(node => node.data.selected) - const showInputsPanel = useStore(s => s.showInputsPanel) - const workflowRunningData = useStore(s => s.workflowRunningData) const historyWorkflowData = useStore(s => s.historyWorkflowData) + const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel) const isRestoring = useStore(s => s.isRestoring) const { enableShortcuts, @@ -37,28 +34,13 @@ const Panel: FC = () => { showMessageLogModal: state.showMessageLogModal, setShowMessageLogModal: state.setShowMessageLogModal, }))) - const { - showNodePanel, - showDebugAndPreviewPanel, - showWorkflowPreview, - } = useMemo(() => { - return { - showNodePanel: !!selectedNode && !workflowRunningData && !historyWorkflowData && !showInputsPanel, - showDebugAndPreviewPanel: isChatMode && workflowRunningData && !historyWorkflowData, - showWorkflowPreview: !isChatMode && !historyWorkflowData && (workflowRunningData || showInputsPanel), - } - }, [ - showInputsPanel, - selectedNode, - isChatMode, - workflowRunningData, - historyWorkflowData, - ]) return (
{ /> ) } + { + !!selectedNode && ( + + ) + } { historyWorkflowData && !isChatMode && ( @@ -87,20 +74,15 @@ const Panel: FC = () => { ) } { - showDebugAndPreviewPanel && ( + showDebugAndPreviewPanel && isChatMode && ( ) } { - showWorkflowPreview && ( + showDebugAndPreviewPanel && !isChatMode && ( ) } - { - showNodePanel && ( - - ) - }
) } diff --git a/web/app/components/workflow/panel/inputs-panel.tsx b/web/app/components/workflow/panel/inputs-panel.tsx index 35a7ea2393..9a1241e0b9 100644 --- a/web/app/components/workflow/panel/inputs-panel.tsx +++ b/web/app/components/workflow/panel/inputs-panel.tsx @@ -34,7 +34,6 @@ const InputsPanel = ({ onRun }: Props) => { const workflowRunningData = useStore(s => s.workflowRunningData) const { handleRun, - handleRunSetting, } = useWorkflowRun() const startNode = nodes.find(node => node.data.type === BlockEnum.Start) const startVariables = startNode?.data.variables @@ -72,7 +71,6 @@ const InputsPanel = ({ onRun }: Props) => { const doRun = () => { onRun() - handleRunSetting() handleRun({ inputs, files }) } @@ -87,12 +85,13 @@ const InputsPanel = ({ onRun }: Props) => { <>
{ - variables.map(variable => ( + variables.map((variable, index) => (
{ const historyWorkflowData = useStore(s => s.historyWorkflowData) + const { handleUpdateWorkflowCanvas } = useWorkflowInteractions() + + const handleResultCallback = useCallback((res: any) => { + const graph: WorkflowDataUpdator = res.graph + handleUpdateWorkflowCanvas({ + nodes: graph.nodes, + edges: graph.edges, + viewport: graph.viewport, + }) + }, [handleUpdateWorkflowCanvas]) return (
{`Test Run#${historyWorkflowData?.sequence_number}`}
- +
) } diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index 186577f9f6..e51a728e22 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -10,7 +10,7 @@ import OutputPanel from '../run/output-panel' import ResultPanel from '../run/result-panel' import TracingPanel from '../run/tracing-panel' import { - useWorkflowRun, + useWorkflowInteractions, } from '../hooks' import { useStore } from '../store' import { @@ -22,9 +22,10 @@ import { XClose } from '@/app/components/base/icons/src/vender/line/general' const WorkflowPreview = () => { const { t } = useTranslation() - const { handleRunSetting } = useWorkflowRun() - const showInputsPanel = useStore(s => s.showInputsPanel) + const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions() const workflowRunningData = useStore(s => s.workflowRunningData) + const showInputsPanel = useStore(s => s.showInputsPanel) + const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel) const [currentTab, setCurrentTab] = useState(showInputsPanel ? 'INPUT' : 'TRACING') const switchTab = async (tab: string) => { @@ -34,6 +35,11 @@ const WorkflowPreview = () => { const [height, setHieght] = useState(0) const ref = useRef(null) + useEffect(() => { + if (showDebugAndPreviewPanel && showInputsPanel) + setCurrentTab('INPUT') + }, [showDebugAndPreviewPanel, showInputsPanel]) + const adjustResultHeight = () => { if (ref.current) setHieght(ref.current?.clientHeight - 16 - 16 - 2 - 1) @@ -49,11 +55,9 @@ const WorkflowPreview = () => { `}>
{`Test Run${!workflowRunningData?.result.sequence_number ? '' : `#${workflowRunningData?.result.sequence_number}`}`} - {showInputsPanel && workflowRunningData?.result?.status !== WorkflowRunningStatus.Running && ( -
handleRunSetting(true)}> - -
- )} +
handleCancelDebugAndPreviewPanel()}> + +
@@ -107,7 +111,7 @@ const WorkflowPreview = () => { 'grow bg-white h-0 overflow-y-auto rounded-b-2xl', (currentTab === 'RESULT' || currentTab === 'TRACING') && '!bg-gray-50', )}> - {currentTab === 'INPUT' && ( + {currentTab === 'INPUT' && showInputsPanel && ( switchTab('RESULT')} /> )} {currentTab === 'RESULT' && ( diff --git a/web/app/components/workflow/store.ts b/web/app/components/workflow/store.ts index 9186a610f7..e7db357fb7 100644 --- a/web/app/components/workflow/store.ts +++ b/web/app/components/workflow/store.ts @@ -23,9 +23,9 @@ type Shape = { appId: string panelWidth: number workflowRunningData?: WorkflowRunningData - setWorkflowRunningData: (workflowData: WorkflowRunningData) => void + setWorkflowRunningData: (workflowData?: WorkflowRunningData) => void historyWorkflowData?: HistoryWorkflowData - setHistoryWorkflowData: (historyWorkflowData: HistoryWorkflowData) => void + setHistoryWorkflowData: (historyWorkflowData?: HistoryWorkflowData) => void showRunHistory: boolean setShowRunHistory: (showRunHistory: boolean) => void showFeaturesPanel: boolean @@ -68,6 +68,8 @@ type Shape = { setClipboardElements: (clipboardElements: Node[]) => void shortcutsDisabled: boolean setShortcutsDisabled: (shortcutsDisabled: boolean) => void + showDebugAndPreviewPanel: boolean + setShowDebugAndPreviewPanel: (showDebugAndPreviewPanel: boolean) => void } export const createWorkflowStore = () => { @@ -117,6 +119,8 @@ export const createWorkflowStore = () => { setClipboardElements: clipboardElements => set(() => ({ clipboardElements })), shortcutsDisabled: false, setShortcutsDisabled: shortcutsDisabled => set(() => ({ shortcutsDisabled })), + showDebugAndPreviewPanel: false, + setShowDebugAndPreviewPanel: showDebugAndPreviewPanel => set(() => ({ showDebugAndPreviewPanel })), })) } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index aa4238b27c..50d6922c9c 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -1,6 +1,7 @@ import type { Edge as ReactFlowEdge, Node as ReactFlowNode, + Viewport, } from 'reactflow' import type { TransferMethod } from '@/types/app' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' @@ -60,6 +61,12 @@ export type NodePanelProps = { } export type Edge = ReactFlowEdge +export type WorkflowDataUpdator = { + nodes: Node[] + edges: Edge[] + viewport: Viewport +} + export type ValueSelector = string[] // [nodeId, key | obj key path] export type Variable = { diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 40867e4637..6a5f0d9e47 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -79,7 +79,9 @@ const getCycleEdges = (nodes: Node[], edges: Edge[]) => { return cycleEdges } -export const initialNodes = (nodes: Node[], edges: Edge[]) => { +export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { + const nodes = cloneDeep(originNodes) + const edges = cloneDeep(originEdges) const firstNode = nodes[0] if (!firstNode?.position) { @@ -121,7 +123,9 @@ export const initialNodes = (nodes: Node[], edges: Edge[]) => { }) } -export const initialEdges = (edges: Edge[], nodes: Node[]) => { +export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => { + const nodes = cloneDeep(originNodes) + const edges = cloneDeep(originEdges) let selectedNode: Node | null = null const nodesMap = nodes.reduce((acc, node) => { acc[node.id] = node diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 0d1ced6120..d6997164aa 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -49,6 +49,8 @@ const translation = { processData: 'Process Data', input: 'Input', output: 'Output', + viewOnly: 'View Only', + showRunHistory: 'Show Run History', }, errorMsg: { fieldRequired: '{{field}} is required', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 2771f8c1ca..4bb75bf3bd 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -49,6 +49,8 @@ const translation = { processData: '数据处理', input: '输入', output: '输出', + viewOnly: '只读', + showRunHistory: '显示运行历史', }, errorMsg: { fieldRequired: '{{field}} 不能为空',