This commit is contained in:
StyleZhang
2024-03-11 14:43:50 +08:00
parent 405e99d27f
commit 049e858ef7
11 changed files with 360 additions and 17 deletions

View File

@ -7,7 +7,6 @@ import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevice
import { ClockPlay } from '@/app/components/base/icons/src/vender/line/time'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
import { Mode } from '@/app/components/workflow/types'
const RunAndHistory: FC = () => {
const { t } = useTranslation()
@ -15,6 +14,11 @@ const RunAndHistory: FC = () => {
const mode = useStore(state => state.mode)
const showRunHistory = useStore(state => state.showRunHistory)
const handleClick = () => {
if (!isChatMode)
useStore.setState({ showInputsPanel: true })
}
return (
<div className='flex items-center px-0.5 h-8 rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xs'>
<div
@ -24,7 +28,7 @@ const RunAndHistory: FC = () => {
${mode === 'running' && 'bg-primary-50 !cursor-not-allowed'}
${mode === 'running' && isChatMode && 'opacity-50'}
`}
onClick={() => mode !== 'running' && useStore.setState({ mode: Mode.Running })}
onClick={() => mode !== 'running' && handleClick()}
>
{
mode === 'running'

View File

@ -21,6 +21,10 @@ import type {
BlockEnum,
Node,
} from './types'
import {
NodeRunningStatus,
WorkflowRunningStatus,
} from './types'
import {
NODES_EXTRA_DATA,
NODES_INITIAL_DATA,
@ -31,6 +35,7 @@ import type { ToolDefaultValue } from './block-selector/types'
import { syncWorkflowDraft } from '@/service/workflow'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ssePost } from '@/service/base'
export const useIsChatMode = () => {
const appDetail = useAppStore(s => s.appDetail)
@ -560,3 +565,51 @@ export const useWorkflow = () => {
handleEdgesChange,
}
}
export const useWorkflowRun = () => {
const store = useStoreApi()
return (params: any) => {
const {
getNodes,
setNodes,
} = store.getState()
const appDetail = useAppStore.getState().appDetail
let url = ''
if (appDetail?.mode === 'advanced-chat')
url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run`
if (appDetail?.mode === 'workflow')
url = `/apps/${appDetail.id}/workflows/draft/run`
ssePost(
url,
params,
{
onWorkflowStarted: () => {
useStore.setState({ runningStatus: WorkflowRunningStatus.Running })
},
onWorkflowFinished: ({ data }) => {
useStore.setState({ runningStatus: data.status as WorkflowRunningStatus })
},
onNodeStarted: ({ data }) => {
const newNodes = produce(getNodes(), (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._running = NodeRunningStatus.Running
})
setNodes(newNodes)
},
onNodeFinished: ({ data }) => {
const newNodes = produce(getNodes(), (draft) => {
const currentNode = draft.find(node => node.id === data.node_id)!
currentNode.data._running = data.status
})
setNodes(newNodes)
},
},
)
}
}

View File

@ -15,12 +15,14 @@ type Props = {
payload: InputVar
value: any
onChange: (value: any) => void
className?: string
}
const FormItem: FC<Props> = ({
payload,
value,
onChange,
className,
}) => {
const { type } = payload
const handleContextItemChange = useCallback((index: number) => {
@ -41,9 +43,9 @@ const FormItem: FC<Props> = ({
}
}, [value, onChange])
return (
<div className='flex justify-between items-start'>
<div className={`flex justify-between items-start ${className}`}>
{type !== InputVarType.contexts && <div className='shrink-0 w-[96px] pr-1 h-8 leading-8 text-[13px] font-medium text-gray-700 truncate'>{payload.label}</div>}
<div className='w-0 grow'>
<div className='grow'>
{
type === InputVarType.textInput && (
<input

View File

@ -7,13 +7,21 @@ import {
memo,
} from 'react'
import type { NodeProps } from '../../types'
import { BlockEnum } from '../../types'
import {
BlockEnum,
NodeRunningStatus,
} from '../../types'
import {
NodeSourceHandle,
NodeTargetHandle,
} from './components/node-handle'
import NodeControl from './components/node-control'
import BlockIcon from '@/app/components/workflow/block-icon'
import {
CheckCircle,
Loading02,
} from '@/app/components/base/icons/src/vender/line/general'
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
type BaseNodeProps = {
children: ReactElement
@ -28,7 +36,7 @@ const BaseNode: FC<BaseNodeProps> = ({
<div
className={`
flex border-[2px] rounded-2xl
${data.selected ? 'border-primary-600' : 'border-transparent'}
${(data.selected && !data._runningStatus) ? 'border-primary-600' : 'border-transparent'}
`}
>
<div
@ -36,6 +44,9 @@ const BaseNode: FC<BaseNodeProps> = ({
group relative w-[240px] bg-[#fcfdff] shadow-xs
border border-transparent rounded-[15px]
hover:shadow-lg
${data._runningStatus === NodeRunningStatus.Running && 'border-primary-500'}
${data._runningStatus === NodeRunningStatus.Succeeded && 'border-[#12B76A]'}
${data._runningStatus === NodeRunningStatus.Failed && 'border-[#F04438]'}
`}
>
{
@ -71,10 +82,25 @@ const BaseNode: FC<BaseNodeProps> = ({
/>
<div
title={data.title}
className='grow text-[13px] font-semibold text-gray-700 truncate'
className='grow mr-1 text-[13px] font-semibold text-gray-700 truncate'
>
{data.title}
</div>
{
data._runningStatus === NodeRunningStatus.Running && (
<Loading02 className='w-3.5 h-3.5 text-primary-600 animate-spin' />
)
}
{
data._runningStatus === NodeRunningStatus.Succeeded && (
<CheckCircle className='w-3.5 h-3.5 text-[#12B76A]' />
)
}
{
data._runningStatus === NodeRunningStatus.Failed && (
<AlertCircle className='w-3.5 h-3.5 text-[#F04438]' />
)
}
</div>
<div className='mb-1'>
{cloneElement(children, { id, data })}

View File

@ -1,5 +1,6 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { MiniMap } from 'reactflow'
import ZoomInOut from './zoom-in-out'
import { OrganizeGrid } from '@/app/components/base/icons/src/vender/line/layout'
import TooltipPlus from '@/app/components/base/tooltip-plus'
@ -9,15 +10,24 @@ const Operator = () => {
return (
<div className={`
absolute left-6 bottom-6 flex items-center p-0.5
rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500 z-10
absolute left-6 bottom-6 z-10
`}>
<ZoomInOut />
<TooltipPlus popupContent={t('workflow.panel.organizeBlocks')}>
<div className='ml-[1px] flex items-center justify-center w-8 h-8 cursor-pointer hover:bg-black/5 rounded-lg'>
<OrganizeGrid className='w-4 h-4' />
</div>
</TooltipPlus>
<MiniMap
style={{
width: 128,
height: 80,
}}
className='!static !m-0 !w-[128px] !h-[80px] border-[0.5px] border-black/[0.08]'
pannable
/>
<div className='flex items-center mt-1 p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
<ZoomInOut />
<TooltipPlus popupContent={t('workflow.panel.organizeBlocks')}>
<div className='ml-[1px] flex items-center justify-center w-8 h-8 cursor-pointer hover:bg-black/5 rounded-lg'>
<OrganizeGrid className='w-4 h-4' />
</div>
</TooltipPlus>
</div>
</div>
)
}

View File

@ -12,6 +12,7 @@ import WorkflowInfo from './workflow-info'
import DebugAndPreview from './debug-and-preview'
import RunHistory from './run-history'
import Record from './record'
import InputsPanel from './inputs-panel'
const Panel: FC = () => {
const isChatMode = useIsChatMode()
@ -19,6 +20,7 @@ const Panel: FC = () => {
const nodes = useNodes<CommonNodeType>()
const selectedNode = nodes.find(node => node.data.selected)
const showRunHistory = useStore(state => state.showRunHistory)
const showInputsPanel = useStore(s => s.showInputsPanel)
const {
showWorkflowInfoPanel,
showNodePanel,
@ -33,6 +35,11 @@ const Panel: FC = () => {
return (
<div className='absolute top-14 right-0 bottom-2 flex z-10'>
{
showInputsPanel && (
<InputsPanel />
)
}
{
runTaskId && (
<Record />

View File

@ -0,0 +1,79 @@
import {
memo,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import FormItem from '../nodes/_base/components/before-run-form/form-item'
import { BlockEnum } from '../types'
import { useStore } from '../store'
import { useWorkflowRun } from '../hooks'
import type { StartNodeType } from '../nodes/start/types'
import Button from '@/app/components/base/button'
const InputsPanel = () => {
const { t } = useTranslation()
const nodes = useNodes<StartNodeType>()
const run = useWorkflowRun()
const [inputs, setInputs] = useState<Record<string, string>>({})
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || []
const handleValueChange = (variable: string, v: string) => {
setInputs({
...inputs,
[variable]: v,
})
}
const handleCancel = useCallback(() => {
useStore.setState({ showInputsPanel: false })
}, [])
const handleRun = () => {
run(inputs)
}
return (
<div className='absolute top-0 right-2 w-[420px] pb-2 rounded-2xl border-[0.5px] border-gray-200 bg-white shadow-xl z-[11]'>
<div className='flex items-center pt-3 px-4 h-[44px] text-base font-semibold text-gray-900'>
{t('workflow.singleRun.testRun')}
</div>
<div className='px-4 pb-2'>
{
variables.map(variable => (
<div
key={variable.variable}
className='mb-2 last-of-type:mb-0'
>
<FormItem
className='!block'
payload={variable}
value={inputs[variable.variable]}
onChange={v => handleValueChange(variable.variable, v)}
/>
</div>
))
}
</div>
<div className='flex items-center justify-between px-4 py-2'>
<Button
className='py-0 w-[190px] h-8 rounded-lg border-[0.5px] border-gray-200 shadow-xs text-[13px] font-medium text-gray-700'
onClick={handleCancel}
>
{t('common.operation.cancel')}
</Button>
<Button
type='primary'
className='py-0 w-[190px] h-8 rounded-lg text-[13px] font-medium'
onClick={handleRun}
>
{t('workflow.singleRun.startRun')}
</Button>
</div>
</div>
)
}
export default memo(InputsPanel)

View File

@ -9,6 +9,7 @@ import type {
ToolsMap,
} from './block-selector/types'
import { Mode } from './types'
import type { WorkflowRunningStatus } from './types'
type State = {
mode: Mode
@ -22,6 +23,8 @@ type State = {
toolsMap: ToolsMap
draftUpdatedAt: number
publishedAt: number
runningStatus?: WorkflowRunningStatus
showInputsPanel: boolean
}
type Action = {
@ -36,6 +39,8 @@ type Action = {
setToolsMap: (toolsMap: Record<string, ToolInWorkflow[]>) => void
setDraftUpdatedAt: (draftUpdatedAt: number) => void
setPublishedAt: (publishedAt: number) => void
setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void
setShowInputsPanel: (showInputsPanel: boolean) => void
}
export const useStore = create<State & Action>(set => ({
@ -61,4 +66,8 @@ export const useStore = create<State & Action>(set => ({
setDraftUpdatedAt: draftUpdatedAt => set(() => ({ draftUpdatedAt })),
publishedAt: 0,
setPublishedAt: publishedAt => set(() => ({ publishedAt })),
runningStatus: undefined,
setRunningStatus: runningStatus => set(() => ({ runningStatus })),
showInputsPanel: false,
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
}))

View File

@ -27,6 +27,7 @@ export type Branch = {
export type CommonNodeType<T = {}> = {
_targetBranches?: Branch[]
_isSingleRun?: boolean
_runningStatus?: NodeRunningStatus
selected?: boolean
title: string
desc: string
@ -38,7 +39,7 @@ export type CommonEdgeType = {
_connectedNodeIsHovering: boolean
}
export type Node = ReactFlowNode<CommonNodeType>
export type Node<T = {}> = ReactFlowNode<CommonNodeType<T>>
export type SelectedNode = Pick<Node, 'id' | 'data'>
export type NodeProps<T = unknown> = { id: string; data: CommonNodeType<T> }
export type NodePanelProps<T> = {
@ -147,3 +148,16 @@ export enum Mode {
Editing = 'editing',
Running = 'running',
}
export enum WorkflowRunningStatus {
Running = 'running',
Succeeded = 'succeeded',
Failed = 'failed',
Stopped = 'stopped',
}
export enum NodeRunningStatus {
Running = 'running',
Succeeded = 'succeeded',
Failed = 'failed',
}