Commit e3a3e07e authored by StyleZhang's avatar StyleZhang

tool

parent 8a906e29
import { groupBy } from 'lodash-es'
import type { Block } from '../types' import type { Block } from '../types'
import { BlockEnum } from '../types' import { BlockEnum } from '../types'
import { BlockClassificationEnum } from './types'
export const TABS = [
{
key: 'blocks',
name: 'Blocks',
},
{
key: 'built-in-tool',
name: 'Built-in Tool',
},
{
key: 'custom-tool',
name: 'Custom Tool',
},
]
export enum BlockClassificationEnum {
Default = '-',
QuestionUnderstand = 'question-understand',
Logic = 'logic',
Transform = 'transform',
Utilities = 'utilities',
}
export const BLOCKS: Block[] = [ export const BLOCKS: Block[] = [
{ {
...@@ -91,5 +68,3 @@ export const BLOCK_CLASSIFICATIONS: string[] = [ ...@@ -91,5 +68,3 @@ export const BLOCK_CLASSIFICATIONS: string[] = [
BlockClassificationEnum.Transform, BlockClassificationEnum.Transform,
BlockClassificationEnum.Utilities, BlockClassificationEnum.Utilities,
] ]
export const BLOCK_GROUP_BY_CLASSIFICATION = groupBy(BLOCKS, 'classification')
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { BLOCKS } from './constants' import { BLOCKS } from './constants'
import { TabsEnum } from './types'
export const useBlocks = () => { export const useBlocks = () => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -11,3 +12,22 @@ export const useBlocks = () => { ...@@ -11,3 +12,22 @@ export const useBlocks = () => {
} }
}) })
} }
export const useTabs = () => {
const { t } = useTranslation()
return [
{
key: TabsEnum.Blocks,
name: t('workflow.tabs.blocks'),
},
{
key: TabsEnum.BuiltInTool,
name: t('workflow.tabs.builtInTool'),
},
{
key: TabsEnum.CustomTool,
name: t('workflow.tabs.customTool'),
},
]
}
...@@ -11,7 +11,6 @@ import type { ...@@ -11,7 +11,6 @@ import type {
OffsetOptions, OffsetOptions,
Placement, Placement,
} from '@floating-ui/react' } from '@floating-ui/react'
import type { BlockEnum } from '../types'
import Tabs from './tabs' import Tabs from './tabs'
import { import {
PortalToFollowElem, PortalToFollowElem,
...@@ -22,11 +21,12 @@ import { ...@@ -22,11 +21,12 @@ import {
Plus02, Plus02,
SearchLg, SearchLg,
} from '@/app/components/base/icons/src/vender/line/general' } from '@/app/components/base/icons/src/vender/line/general'
import type { OnSelectBlock } from '@/app/components/workflow/types'
type NodeSelectorProps = { type NodeSelectorProps = {
open?: boolean open?: boolean
onOpenChange?: (open: boolean) => void onOpenChange?: (open: boolean) => void
onSelect: (type: BlockEnum) => void onSelect: OnSelectBlock
trigger?: (open: boolean) => React.ReactNode trigger?: (open: boolean) => React.ReactNode
placement?: Placement placement?: Placement
offset?: OffsetOptions offset?: OffsetOptions
...@@ -59,9 +59,9 @@ const NodeSelector: FC<NodeSelectorProps> = ({ ...@@ -59,9 +59,9 @@ const NodeSelector: FC<NodeSelectorProps> = ({
e.stopPropagation() e.stopPropagation()
setLocalOpen(v => !v) setLocalOpen(v => !v)
}, []) }, [])
const handleSelect = useCallback((type: BlockEnum) => { const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
handleOpenChange(false) handleOpenChange(false)
onSelect(type) onSelect(type, toolDefaultValue)
}, [handleOpenChange, onSelect]) }, [handleOpenChange, onSelect])
return ( return (
......
...@@ -6,30 +6,34 @@ import { ...@@ -6,30 +6,34 @@ import {
import { groupBy } from 'lodash-es' import { groupBy } from 'lodash-es'
import BlockIcon from '../block-icon' import BlockIcon from '../block-icon'
import type { BlockEnum } from '../types' import type { BlockEnum } from '../types'
import { BLOCK_CLASSIFICATIONS } from './constants'
import { import {
BLOCK_CLASSIFICATIONS, useBlocks,
TABS, useTabs,
} from './constants' } from './hooks'
import { useBlocks } from './hooks' import type { ToolDefaultValue } from './types'
import { TabsEnum } from './types'
import Tools from './tools'
export type TabsProps = { export type TabsProps = {
onSelect: (type: BlockEnum) => void onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
} }
const Tabs: FC<TabsProps> = ({ const Tabs: FC<TabsProps> = ({
onSelect, onSelect,
}) => { }) => {
const [activeTab, setActiveTab] = useState(TABS[0].key)
const blocks = useBlocks() const blocks = useBlocks()
const tabs = useTabs()
const [activeTab, setActiveTab] = useState(tabs[0].key)
return ( return (
<div> <div onClick={e => e.stopPropagation()}>
<div className='flex items-center justify-between px-3 h-[34px] border-b-[0.5px] border-b-black/5'> <div className='flex items-center px-3 h-[34px] border-b-[0.5px] border-b-black/5'>
{ {
TABS.map(tab => ( tabs.map(tab => (
<div <div
key={tab.key} key={tab.key}
className={` className={`
text-[13px] font-medium cursor-pointer mr-4 text-[13px] font-medium cursor-pointer
${activeTab === tab.key ? 'text-gray-700' : 'text-gray-500'} ${activeTab === tab.key ? 'text-gray-700' : 'text-gray-500'}
`} `}
onClick={() => setActiveTab(tab.key)} onClick={() => setActiveTab(tab.key)}
...@@ -39,42 +43,56 @@ const Tabs: FC<TabsProps> = ({ ...@@ -39,42 +43,56 @@ const Tabs: FC<TabsProps> = ({
)) ))
} }
</div> </div>
<div className='p-1'> {
{ activeTab === TabsEnum.Blocks && (
BLOCK_CLASSIFICATIONS.map(classification => ( <div className='p-1'>
<div {
key={classification} BLOCK_CLASSIFICATIONS.map(classification => (
className='mb-1 last-of-type:mb-0' <div
> key={classification}
{ className='mb-1 last-of-type:mb-0'
classification !== '-' && ( >
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'> {
{classification} classification !== '-' && (
</div> <div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
) {classification}
} </div>
{ )
groupBy(blocks, 'classification')[classification].map(block => ( }
<div {
key={block.type} groupBy(blocks, 'classification')[classification].map(block => (
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer' <div
onClick={(e) => { key={block.type}
e.stopPropagation() className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
onSelect(block.type) onClick={() => onSelect(block.type)}
}} >
> <BlockIcon
<BlockIcon className='mr-2'
className='mr-2' type={block.type}
type={block.type} />
/> <div className='text-sm text-gray-900'>{block.title}</div>
<div className='text-sm text-gray-900'>{block.title}</div> </div>
</div> ))
)) }
} </div>
</div> ))
)) }
} </div>
</div> )
}
{
activeTab === TabsEnum.BuiltInTool && (
<Tools onSelect={onSelect} />
)
}
{
activeTab === TabsEnum.CustomTool && (
<Tools
isCustom
onSelect={onSelect}
/>
)
}
</div> </div>
) )
} }
......
import {
memo,
useCallback,
} from 'react'
import produce from 'immer'
import { useStore } from '../../store'
import type { BlockEnum } from '../../types'
import type {
ToolDefaultValue,
ToolInWorkflow,
} from '../types'
import Item from './item'
type ToolsProps = {
isCustom?: boolean
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
}
const Tools = ({
isCustom,
onSelect,
}: ToolsProps) => {
const toolsets = useStore(state => state.toolsets).filter(toolset => toolset.type === (isCustom ? 'api' : 'builtin'))
const setToolsets = useStore(state => state.setToolsets)
const toolsMap = useStore(state => state.toolsMap)
const setToolsMap = useStore(state => state.setToolsMap)
const handleExpand = useCallback((toolId: string) => {
const currentToolset = toolsets.find(toolset => toolset.id === toolId)!
if (currentToolset.expanded) {
setToolsets(produce(toolsets, (draft) => {
const index = draft.findIndex(toolset => toolset.id === toolId)
draft[index].expanded = false
}))
return
}
if (!currentToolset.expanded) {
setToolsets(produce(toolsets, (draft) => {
const index = draft.findIndex(toolset => toolset.id === toolId)
if (!toolsMap[toolId].length && !currentToolset.fetching)
draft[index].fetching = true
draft[index].expanded = true
}))
}
}, [setToolsets, toolsets, toolsMap])
const handleAddTools = useCallback((toolsetId: string, tools: ToolInWorkflow[]) => {
setToolsMap(produce(toolsMap, (draft) => {
draft[toolsetId] = tools
}))
}, [setToolsMap, toolsMap])
const handleFetched = useCallback((toolsetId: string) => {
setToolsets(produce(toolsets, (draft) => {
const index = draft.findIndex(toolset => toolset.id === toolsetId)
draft[index].fetching = false
}))
}, [setToolsets, toolsets])
return (
<div className='p-1 max-h-[464px] overflow-y-auto'>
{
toolsets.map(toolset => (
<Item
key={toolset.id}
data={toolset}
tools={toolsMap[toolset.id]}
onExpand={handleExpand}
onAddTools={handleAddTools}
onFetched={handleFetched}
onSelect={onSelect}
/>
))
}
</div>
)
}
export default memo(Tools)
import {
memo,
useCallback,
useEffect,
useMemo,
} from 'react'
import { useContext } from 'use-context-selector'
import { BlockEnum } from '../../types'
import type {
CollectionWithExpanded,
ToolDefaultValue,
ToolInWorkflow,
} from '../types'
import AppIcon from '@/app/components/base/app-icon'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import {
fetchBuiltInToolList,
fetchCustomToolList,
} from '@/service/tools'
import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
import Loading from '@/app/components/base/loading'
type ItemProps = {
data: CollectionWithExpanded
tools: ToolInWorkflow[]
onExpand: (toolsetId: string) => void
onAddTools: (toolsetId: string, tools: ToolInWorkflow[]) => void
onFetched: (toolsetId: string) => void
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
}
const Item = ({
data,
tools,
onExpand,
onAddTools,
onFetched,
onSelect,
}: ItemProps) => {
const { locale } = useContext(I18n)
const language = getLanguage(locale)
const fetchToolList = useMemo(() => {
return data.type === 'api' ? fetchCustomToolList : fetchBuiltInToolList
}, [data.type])
const handleFetchToolList = useCallback(() => {
fetchToolList(data.name).then((list) => {
onAddTools(data.id, list)
}).finally(() => {
onFetched(data.id)
})
}, [data.id, data.name, fetchToolList, onAddTools, onFetched])
useEffect(() => {
if (data.fetching)
handleFetchToolList()
}, [data.fetching, handleFetchToolList])
return (
<>
<div
className='flex items-center pl-3 pr-2.5 h-8 cursor-pointer'
key={data.id}
onClick={() => onExpand(data.id)}
>
{
typeof data.icon === 'string'
? (
<div
className='shrink-0 mr-2 w-5 h-5 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${data.icon})`,
}}
></div>
)
: (
<AppIcon
className='shrink-0 mr-2 !w-5 !h-5 !text-sm'
size='tiny'
icon={data.icon.content}
background={data.icon.background}
/>
)
}
<div
className='grow mr-2 truncate text-sm text-gray-900'
title={data.name}
>
{data.name}
</div>
{
data.expanded
? <ChevronDown className='shrink-0 w-3 h-3 text-gray-500' />
: <ChevronDown className='shrink-0 w-3 h-3 text-gray-500 -rotate-90' />
}
</div>
{
data.expanded && !data.fetching && tools.map(tool => (
<div
key={tool.name}
className='relative flex items-center pl-10 pr-3 h-8 rounded-lg truncate cursor-pointer text-sm text-gray-900 hover:bg-black/5'
title={tool.label[language]}
onClick={() => onSelect(BlockEnum.Tool, {
provider_id: data.id,
provider_type: data.type,
tool_name: tool.name,
_icon: data.icon,
title: tool.label[language],
})}
>
<div className='absolute left-[22px] w-[1px] h-8 bg-black/5' />
{tool.label[language]}
</div>
))
}
{
data.expanded && data.fetching && (
<div className='felx items-center justify-center h-8'>
<Loading />
</div>
)
}
</>
)
}
export default memo(Item)
import type {
Collection,
Tool,
} from '@/app/components/tools/types'
export enum TabsEnum {
Blocks = 'blocks',
BuiltInTool = 'built-in-tool',
CustomTool = 'custom-tool',
}
export enum BlockClassificationEnum {
Default = '-',
QuestionUnderstand = 'question-understand',
Logic = 'logic',
Transform = 'transform',
Utilities = 'utilities',
}
export type CollectionWithExpanded = Collection & {
expanded?: boolean
fetching?: boolean
}
export type ToolInWorkflow = Tool
export type ToolsMap = Record<string, ToolInWorkflow[]>
export type ToolDefaultValue = {
provider_id: string
provider_type: string
tool_name: string
title: string
_icon: Collection['icon']
}
...@@ -22,6 +22,7 @@ import type { ...@@ -22,6 +22,7 @@ import type {
import { NODES_INITIAL_DATA } from './constants' import { NODES_INITIAL_DATA } from './constants'
import { getLayoutByDagre } from './utils' import { getLayoutByDagre } from './utils'
import { useStore } from './store' import { useStore } from './store'
import type { ToolDefaultValue } from './block-selector/types'
import { syncWorkflowDraft } from '@/service/workflow' import { syncWorkflowDraft } from '@/service/workflow'
import { useFeaturesStore } from '@/app/components/base/features/hooks' import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
...@@ -197,6 +198,12 @@ export const useWorkflow = () => { ...@@ -197,6 +198,12 @@ export const useWorkflow = () => {
setNodes, setNodes,
} = store.getState() } = store.getState()
const nodes = getNodes()
const selectedNode = nodes.find(node => node.data._selected)
if (!cancelSelection && selectedNode?.id === nodeId)
return
const newNodes = produce(getNodes(), (draft) => { const newNodes = produce(getNodes(), (draft) => {
draft.forEach(node => node.data._selected = false) draft.forEach(node => node.data._selected = false)
const selectedNode = draft.find(node => node.id === nodeId)! const selectedNode = draft.find(node => node.id === nodeId)!
...@@ -280,7 +287,12 @@ export const useWorkflow = () => { ...@@ -280,7 +287,12 @@ export const useWorkflow = () => {
setNodes(newNodes) setNodes(newNodes)
}, [store]) }, [store])
const handleNodeAddNext = useCallback((currentNodeId: string, nodeType: BlockEnum, sourceHandle: string) => { const handleNodeAddNext = useCallback((
currentNodeId: string,
nodeType: BlockEnum,
sourceHandle: string,
toolDefaultValue?: ToolDefaultValue,
) => {
const { const {
getNodes, getNodes,
setNodes, setNodes,
...@@ -294,6 +306,7 @@ export const useWorkflow = () => { ...@@ -294,6 +306,7 @@ export const useWorkflow = () => {
type: 'custom', type: 'custom',
data: { data: {
...nodesInitialData[nodeType], ...nodesInitialData[nodeType],
...(toolDefaultValue || {}),
_selected: true, _selected: true,
}, },
position: { position: {
...@@ -323,7 +336,12 @@ export const useWorkflow = () => { ...@@ -323,7 +336,12 @@ export const useWorkflow = () => {
handleSyncWorkflowDraft() handleSyncWorkflowDraft()
}, [store, nodesInitialData, handleSyncWorkflowDraft]) }, [store, nodesInitialData, handleSyncWorkflowDraft])
const handleNodeChange = useCallback((currentNodeId: string, nodeType: BlockEnum, sourceHandle?: string) => { const handleNodeChange = useCallback((
currentNodeId: string,
nodeType: BlockEnum,
sourceHandle: string,
toolDefaultValue?: ToolDefaultValue,
) => {
const { const {
getNodes, getNodes,
setNodes, setNodes,
...@@ -339,6 +357,7 @@ export const useWorkflow = () => { ...@@ -339,6 +357,7 @@ export const useWorkflow = () => {
type: 'custom', type: 'custom',
data: { data: {
...nodesInitialData[nodeType], ...nodesInitialData[nodeType],
...(toolDefaultValue || {}),
_selected: currentNode.data._selected, _selected: currentNode.data._selected,
}, },
position: { position: {
...@@ -359,7 +378,7 @@ export const useWorkflow = () => { ...@@ -359,7 +378,7 @@ export const useWorkflow = () => {
id: `${parentNodeId}-${newCurrentNode.id}`, id: `${parentNodeId}-${newCurrentNode.id}`,
type: 'custom', type: 'custom',
source: parentNodeId, source: parentNodeId,
sourceHandle: sourceHandle || 'source', sourceHandle,
target: newCurrentNode.id, target: newCurrentNode.id,
targetHandle: 'target', targetHandle: 'target',
} }
......
import type { FC } from 'react' import type { FC } from 'react'
import { import {
memo, memo,
useEffect,
useMemo, useMemo,
} from 'react' } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
...@@ -14,6 +15,7 @@ import ReactFlow, { ...@@ -14,6 +15,7 @@ import ReactFlow, {
} from 'reactflow' } from 'reactflow'
import type { Viewport } from 'reactflow' import type { Viewport } from 'reactflow'
import 'reactflow/dist/style.css' import 'reactflow/dist/style.css'
import type { ToolsMap } from './block-selector/types'
import type { import type {
Edge, Edge,
Node, Node,
...@@ -38,6 +40,7 @@ import { ...@@ -38,6 +40,7 @@ import {
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { FeaturesProvider } from '@/app/components/base/features' import { FeaturesProvider } from '@/app/components/base/features'
import { fetchCollectionList } from '@/service/tools'
const nodeTypes = { const nodeTypes = {
custom: CustomNode, custom: CustomNode,
...@@ -162,6 +165,22 @@ const WorkflowWrap: FC<WorkflowProps> = ({ ...@@ -162,6 +165,22 @@ const WorkflowWrap: FC<WorkflowProps> = ({
return [] return []
}, [data, nodes]) }, [data, nodes])
const handleFetchCollectionList = async () => {
const toolsets = await fetchCollectionList()
useStore.setState({
toolsets,
toolsMap: toolsets.reduce((acc, toolset) => {
acc[toolset.id] = []
return acc
}, {} as ToolsMap),
})
}
useEffect(() => {
handleFetchCollectionList()
}, [])
if (error && appDetail) { if (error && appDetail) {
syncWorkflowDraft({ syncWorkflowDraft({
url: `/apps/${appDetail.id}/workflows/draft`, url: `/apps/${appDetail.id}/workflows/draft`,
......
...@@ -2,10 +2,10 @@ import { ...@@ -2,10 +2,10 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import BlockSelector from '../../../../block-selector' import { useWorkflow } from '@/app/components/workflow/hooks'
import { useWorkflow } from '../../../../hooks' import BlockSelector from '@/app/components/workflow/block-selector'
import type { BlockEnum } from '../../../../types'
import { Plus } from '@/app/components/base/icons/src/vender/line/general' import { Plus } from '@/app/components/base/icons/src/vender/line/general'
import type { OnSelectBlock } from '@/app/components/workflow/types'
type AddProps = { type AddProps = {
nodeId: string nodeId: string
...@@ -19,8 +19,8 @@ const Add = ({ ...@@ -19,8 +19,8 @@ const Add = ({
}: AddProps) => { }: AddProps) => {
const { handleNodeAddNext } = useWorkflow() const { handleNodeAddNext } = useWorkflow()
const handleSelect = useCallback((type: BlockEnum) => { const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
handleNodeAddNext(nodeId, type, sourceHandle) handleNodeAddNext(nodeId, type, sourceHandle, toolDefaultValue)
}, [nodeId, sourceHandle, handleNodeAddNext]) }, [nodeId, sourceHandle, handleNodeAddNext])
const renderTrigger = useCallback((open: boolean) => { const renderTrigger = useCallback((open: boolean) => {
......
...@@ -35,6 +35,7 @@ const NextStep = ({ ...@@ -35,6 +35,7 @@ const NextStep = ({
<Item <Item
nodeId={outgoers[0].id} nodeId={outgoers[0].id}
data={outgoers[0].data} data={outgoers[0].data}
sourceHandle='source'
/> />
) )
} }
......
...@@ -3,17 +3,17 @@ import { ...@@ -3,17 +3,17 @@ import {
useCallback, useCallback,
} from 'react' } from 'react'
import type { import type {
BlockEnum,
CommonNodeType, CommonNodeType,
} from '../../../../types' OnSelectBlock,
import BlockIcon from '../../../../block-icon' } from '@/app/components/workflow/types'
import BlockSelector from '../../../../block-selector' import BlockIcon from '@/app/components/workflow/block-icon'
import { useWorkflow } from '../../../../hooks' import BlockSelector from '@/app/components/workflow/block-selector'
import { useWorkflow } from '@/app/components/workflow/hooks'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
type ItemProps = { type ItemProps = {
nodeId: string nodeId: string
sourceHandle?: string sourceHandle: string
branchName?: string branchName?: string
data: CommonNodeType data: CommonNodeType
} }
...@@ -24,8 +24,8 @@ const Item = ({ ...@@ -24,8 +24,8 @@ const Item = ({
data, data,
}: ItemProps) => { }: ItemProps) => {
const { handleNodeChange } = useWorkflow() const { handleNodeChange } = useWorkflow()
const handleSelect = useCallback((type: BlockEnum) => { const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
handleNodeChange(nodeId, type, sourceHandle) handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
}, [nodeId, sourceHandle, handleNodeChange]) }, [nodeId, sourceHandle, handleNodeChange])
const renderTrigger = useCallback((open: boolean) => { const renderTrigger = useCallback((open: boolean) => {
return ( return (
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
import { BlockEnum } from '../../../types' import { BlockEnum } from '../../../types'
import type { Node } from '../../../types' import type { Node } from '../../../types'
import BlockSelector from '../../../block-selector' import BlockSelector from '../../../block-selector'
import type { ToolDefaultValue } from '../../../block-selector/types'
import { useWorkflow } from '../../../hooks' import { useWorkflow } from '../../../hooks'
type NodeHandleProps = { type NodeHandleProps = {
...@@ -100,8 +101,8 @@ export const NodeSourceHandle = ({ ...@@ -100,8 +101,8 @@ export const NodeSourceHandle = ({
if (!connected) if (!connected)
setOpen(v => !v) setOpen(v => !v)
}, [connected]) }, [connected])
const handleSelect = useCallback((type: BlockEnum) => { const handleSelect = useCallback((type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => {
handleNodeAddNext(id, type, handleId) handleNodeAddNext(id, type, handleId, toolDefaultValue)
}, [handleNodeAddNext, id, handleId]) }, [handleNodeAddNext, id, handleId])
return ( return (
......
...@@ -2,21 +2,23 @@ import { ...@@ -2,21 +2,23 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import BlockSelector from '../../../../block-selector' import BlockSelector from '@/app/components/workflow/block-selector'
import { useWorkflow } from '../../../../hooks' import { useWorkflow } from '@/app/components/workflow/hooks'
import type { BlockEnum } from '../../../../types' import type { OnSelectBlock } from '@/app/components/workflow/types'
type ChangeBlockProps = { type ChangeBlockProps = {
nodeId: string nodeId: string
sourceHandle: string
} }
const ChangeBlock = ({ const ChangeBlock = ({
nodeId, nodeId,
sourceHandle,
}: ChangeBlockProps) => { }: ChangeBlockProps) => {
const { handleNodeChange } = useWorkflow() const { handleNodeChange } = useWorkflow()
const handleSelect = useCallback((type: BlockEnum) => { const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
handleNodeChange(nodeId, type) handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
}, [handleNodeChange, nodeId]) }, [handleNodeChange, nodeId, sourceHandle])
const renderTrigger = useCallback(() => { const renderTrigger = useCallback(() => {
return ( return (
......
...@@ -2,8 +2,9 @@ import { ...@@ -2,8 +2,9 @@ import {
memo, memo,
useState, useState,
} from 'react' } from 'react'
import { useWorkflow } from '../../../../hooks' import { useEdges } from 'reactflow'
import ChangeBlock from './change-block' import ChangeBlock from './change-block'
import { useWorkflow } from '@/app/components/workflow/hooks'
import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general' import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
import { import {
PortalToFollowElem, PortalToFollowElem,
...@@ -17,9 +18,12 @@ type PanelOperatorProps = { ...@@ -17,9 +18,12 @@ type PanelOperatorProps = {
const PanelOperator = ({ const PanelOperator = ({
nodeId, nodeId,
}: PanelOperatorProps) => { }: PanelOperatorProps) => {
const edges = useEdges()
const { handleNodeDelete } = useWorkflow() const { handleNodeDelete } = useWorkflow()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const edge = edges.find(edge => edge.target === nodeId)
return ( return (
<PortalToFollowElem <PortalToFollowElem
placement='bottom-end' placement='bottom-end'
...@@ -44,7 +48,10 @@ const PanelOperator = ({ ...@@ -44,7 +48,10 @@ const PanelOperator = ({
<PortalToFollowElemContent className='z-[11]'> <PortalToFollowElemContent className='z-[11]'>
<div className='w-[240px] border-[0.5px] border-gray-200 rounded-2xl shadow-xl bg-white'> <div className='w-[240px] border-[0.5px] border-gray-200 rounded-2xl shadow-xl bg-white'>
<div className='p-1'> <div className='p-1'>
<ChangeBlock nodeId={nodeId} /> <ChangeBlock
nodeId={nodeId}
sourceHandle={edge?.sourceHandle || 'source'}
/>
<div className='flex items-center px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'>Help Link</div> <div className='flex items-center px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'>Help Link</div>
</div> </div>
<div className='h-[1px] bg-gray-100'></div> <div className='h-[1px] bg-gray-100'></div>
......
...@@ -7,7 +7,9 @@ import { ...@@ -7,7 +7,9 @@ import {
memo, memo,
} from 'react' } from 'react'
import type { NodeProps } from '../../types' import type { NodeProps } from '../../types'
import BlockIcon from '../../block-icon' import { BlockEnum } from '@/app/components/workflow/types'
import BlockIcon from '@/app/components/workflow/block-icon'
import AppIcon from '@/app/components/base/app-icon'
type BaseNodeProps = { type BaseNodeProps = {
children: ReactElement children: ReactElement
...@@ -18,6 +20,8 @@ const BaseNode: FC<BaseNodeProps> = ({ ...@@ -18,6 +20,8 @@ const BaseNode: FC<BaseNodeProps> = ({
data, data,
children, children,
}) => { }) => {
const type = data.type
return ( return (
<div <div
className={` className={`
...@@ -27,14 +31,43 @@ const BaseNode: FC<BaseNodeProps> = ({ ...@@ -27,14 +31,43 @@ const BaseNode: FC<BaseNodeProps> = ({
`} `}
> >
<div className='flex items-center px-3 pt-3 pb-2'> <div className='flex items-center px-3 pt-3 pb-2'>
<BlockIcon {
className='shrink-0 mr-2' type !== BlockEnum.Tool && (
type={data.type} <BlockIcon
size='md' className='shrink-0 mr-2'
/> type={data.type}
size='md'
/>
)
}
{
type === BlockEnum.Tool && (
<>
{
typeof data._icon === 'string'
? (
<div
className='shrink-0 mr-2 w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${data._icon})`,
}}
></div>
)
: (
<AppIcon
className='shrink-0 mr-2'
size='tiny'
icon={data._icon?.content}
background={data._icon?.background}
/>
)
}
</>
)
}
<div <div
title={data.title} title={data.title}
className='text-[13px] font-semibold text-gray-700 truncate' className='grow text-[13px] font-semibold text-gray-700 truncate'
> >
{data.title} {data.title}
</div> </div>
......
...@@ -7,11 +7,6 @@ import { ...@@ -7,11 +7,6 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import { type Node } from '../../types'
import { BlockEnum } from '../../types'
import BlockIcon from '../../block-icon'
import { useWorkflow } from '../../hooks'
import { canRunBySingle } from '../../utils'
import NextStep from './components/next-step' import NextStep from './components/next-step'
import PanelOperator from './components/panel-operator' import PanelOperator from './components/panel-operator'
import { import {
...@@ -21,9 +16,15 @@ import { ...@@ -21,9 +16,15 @@ import {
import { import {
XClose, XClose,
} from '@/app/components/base/icons/src/vender/line/general' } from '@/app/components/base/icons/src/vender/line/general'
import BlockIcon from '@/app/components/workflow/block-icon'
import { useWorkflow } from '@/app/components/workflow/hooks'
import { canRunBySingle } from '@/app/components/workflow/utils'
import { GitBranch01 } from '@/app/components/base/icons/src/vender/line/development' import { GitBranch01 } from '@/app/components/base/icons/src/vender/line/development'
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import TooltipPlus from '@/app/components/base/tooltip-plus' import TooltipPlus from '@/app/components/base/tooltip-plus'
import type { Node } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
import AppIcon from '@/app/components/base/app-icon'
type BasePanelProps = { type BasePanelProps = {
children: ReactElement children: ReactElement
...@@ -34,6 +35,7 @@ const BasePanel: FC<BasePanelProps> = ({ ...@@ -34,6 +35,7 @@ const BasePanel: FC<BasePanelProps> = ({
data, data,
children, children,
}) => { }) => {
const type = data.type
const { const {
handleNodeSelect, handleNodeSelect,
handleNodeDataUpdate, handleNodeDataUpdate,
...@@ -49,11 +51,40 @@ const BasePanel: FC<BasePanelProps> = ({ ...@@ -49,11 +51,40 @@ const BasePanel: FC<BasePanelProps> = ({
<div className='mr-2 w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'> <div className='mr-2 w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl overflow-y-auto'>
<div className='sticky top-0 bg-white border-b-[0.5px] border-black/5 z-10'> <div className='sticky top-0 bg-white border-b-[0.5px] border-black/5 z-10'>
<div className='flex items-center px-4 pt-4 pb-1'> <div className='flex items-center px-4 pt-4 pb-1'>
<BlockIcon {
className='shrink-0 mr-1' type !== BlockEnum.Tool && (
type={data.type} <BlockIcon
size='md' className='shrink-0 mr-1'
/> type={data.type}
size='md'
/>
)
}
{
type === BlockEnum.Tool && (
<>
{
typeof data._icon === 'string'
? (
<div
className='shrink-0 mr-2 w-6 h-6 bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${data._icon})`,
}}
></div>
)
: (
<AppIcon
className='shrink-0 mr-2'
size='tiny'
icon={data._icon?.content}
background={data._icon?.background}
/>
)
}
</>
)
}
<TitleInput <TitleInput
value={data.title || ''} value={data.title || ''}
onChange={handleTitleChange} onChange={handleTitleChange}
......
import { create } from 'zustand' import { create } from 'zustand'
import type { HelpLinePosition } from './help-line/types' import type { HelpLinePosition } from './help-line/types'
import type {
CollectionWithExpanded,
ToolInWorkflow,
ToolsMap,
} from './block-selector/types'
type State = { type State = {
mode: string mode: string
...@@ -8,6 +13,8 @@ type State = { ...@@ -8,6 +13,8 @@ type State = {
runStaus: string runStaus: string
isDragging: boolean isDragging: boolean
helpLine?: HelpLinePosition helpLine?: HelpLinePosition
toolsets: CollectionWithExpanded[]
toolsMap: ToolsMap
} }
type Action = { type Action = {
...@@ -16,6 +23,8 @@ type Action = { ...@@ -16,6 +23,8 @@ type Action = {
setRunStaus: (runStaus: string) => void setRunStaus: (runStaus: string) => void
setIsDragging: (isDragging: boolean) => void setIsDragging: (isDragging: boolean) => void
setHelpLine: (helpLine?: HelpLinePosition) => void setHelpLine: (helpLine?: HelpLinePosition) => void
setToolsets: (toolsets: CollectionWithExpanded[]) => void
setToolsMap: (toolsMap: Record<string, ToolInWorkflow[]>) => void
} }
export const useStore = create<State & Action>(set => ({ export const useStore = create<State & Action>(set => ({
...@@ -30,4 +39,8 @@ export const useStore = create<State & Action>(set => ({ ...@@ -30,4 +39,8 @@ export const useStore = create<State & Action>(set => ({
setIsDragging: isDragging => set(() => ({ isDragging })), setIsDragging: isDragging => set(() => ({ isDragging })),
helpLine: undefined, helpLine: undefined,
setHelpLine: helpLine => set(() => ({ helpLine })), setHelpLine: helpLine => set(() => ({ helpLine })),
toolsets: [],
setToolsets: toolsets => set(() => ({ toolsets })),
toolsMap: {},
setToolsMap: toolsMap => set(() => ({ toolsMap })),
})) }))
...@@ -2,6 +2,8 @@ import type { ...@@ -2,6 +2,8 @@ import type {
Edge as ReactFlowEdge, Edge as ReactFlowEdge,
Node as ReactFlowNode, Node as ReactFlowNode,
} from 'reactflow' } from 'reactflow'
import type { Collection } from '@/app/components/tools/types'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
export enum BlockEnum { export enum BlockEnum {
Start = 'start', Start = 'start',
...@@ -28,6 +30,7 @@ export type CommonNodeType<T = {}> = { ...@@ -28,6 +30,7 @@ export type CommonNodeType<T = {}> = {
_hovering?: boolean _hovering?: boolean
_targetBranches?: Branch[] _targetBranches?: Branch[]
_isSingleRun?: boolean _isSingleRun?: boolean
_icon?: Collection['icon']
title: string title: string
desc: string desc: string
type: BlockEnum type: BlockEnum
...@@ -134,3 +137,5 @@ export type NodeDefault<T> = { ...@@ -134,3 +137,5 @@ export type NodeDefault<T> = {
getAvailablePrevNodes: () => BlockEnum[] getAvailablePrevNodes: () => BlockEnum[]
getAvailableNextNodes: () => BlockEnum[] getAvailableNextNodes: () => BlockEnum[]
} }
export type OnSelectBlock = (type: BlockEnum, toolDefaultValue?: ToolDefaultValue) => void
const translation = { const translation = {
tabs: {
blocks: 'Blocks',
builtInTool: 'Built-in Tool',
customTool: 'Custom Tool',
},
blocks: { blocks: {
'start': 'Start', 'start': 'Start',
'end': 'End', 'end': 'End',
......
const translation = { const translation = {
tabs: {
blocks: 'Blocks',
builtInTool: '内置工具',
customTool: '自定义工具',
},
blocks: { blocks: {
'start': '开始', 'start': '开始',
'end': '结束', 'end': '结束',
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment