block icon

This commit is contained in:
StyleZhang 2024-03-18 21:02:40 +08:00
parent 202492e5ac
commit a89287bf20
10 changed files with 109 additions and 32 deletions

View File

@ -1,3 +1,5 @@
import type { TransferMethod } from '@/types/app'
export type EnabledOrDisabled = {
enabled: boolean
}
@ -26,7 +28,7 @@ export type SensitiveWordAvoidance = EnabledOrDisabled & {
export type FileUpload = {
image: EnabledOrDisabled & {
number_limits: number
transfer_methods: string[]
transfer_methods: TransferMethod[]
}
}

View File

@ -1,7 +1,6 @@
import type { FC } from 'react'
import { memo } from 'react'
import { BlockEnum } from './types'
import { useStore } from './store'
import {
Answer,
Code,
@ -21,7 +20,7 @@ type BlockIconProps = {
type: BlockEnum
size?: string
className?: string
toolProviderId?: string
toolIcon?: string | { content: string; background: string }
}
const ICON_CONTAINER_CLASSNAME_SIZE_MAP: Record<string, string> = {
sm: 'w-5 h-5 rounded-md shadow-xs',
@ -60,17 +59,14 @@ const BlockIcon: FC<BlockIconProps> = ({
type,
size = 'sm',
className,
toolProviderId,
toolIcon,
}) => {
const toolsets = useStore(s => s.toolsets)
const icon = toolsets.find(toolset => toolset.id === toolProviderId)?.icon
return (
<div className={`
flex items-center justify-center border-[0.5px] border-white/[0.02] text-white
${ICON_CONTAINER_CLASSNAME_SIZE_MAP[size]}
${ICON_CONTAINER_BG_COLOR_MAP[type]}
${icon && '!shadow-none'}
${toolIcon && '!shadow-none'}
${className}
`}
>
@ -80,15 +76,15 @@ const BlockIcon: FC<BlockIconProps> = ({
)
}
{
type === BlockEnum.Tool && icon && (
type === BlockEnum.Tool && toolIcon && (
<>
{
typeof icon === 'string'
typeof toolIcon === 'string'
? (
<div
className='shrink-0 w-full h-full bg-cover bg-center rounded-md'
style={{
backgroundImage: `url(${icon})`,
backgroundImage: `url(${toolIcon})`,
}}
></div>
)
@ -96,8 +92,8 @@ const BlockIcon: FC<BlockIconProps> = ({
<AppIcon
className='shrink-0 !w-full !h-full'
size='tiny'
icon={icon?.content}
background={icon?.background}
icon={toolIcon?.content}
background={toolIcon?.background}
/>
)
}

View File

@ -8,8 +8,9 @@ import CodeEditor from '../editor/code-editor'
import { CodeLanguage } from '../../../code/types'
import Select from '@/app/components/base/select'
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import { Resolution, TransferMethod } from '@/types/app'
import { Resolution } from '@/types/app'
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
import { useFeatures } from '@/app/components/base/features/hooks'
type Props = {
payload: InputVar
@ -25,6 +26,7 @@ const FormItem: FC<Props> = ({
className,
}) => {
const { type } = payload
const fileSettings = useFeatures(s => s.features.file)
const handleContextItemChange = useCallback((index: number) => {
return (newValue: any) => {
const newValues = produce(value, (draft: any) => {
@ -105,10 +107,8 @@ const FormItem: FC<Props> = ({
type === InputVarType.files && (
<TextGenerationImageUploader
settings={{
enabled: true,
number_limits: 3,
...fileSettings.image,
detail: Resolution.high,
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
}}
onFilesChange={files => onChange(files.filter(file => file.progress !== -1).map(fileItem => ({
type: 'image',

View File

@ -1,4 +1,7 @@
import { memo } from 'react'
import {
memo,
useMemo,
} from 'react'
import {
getConnectedEdges,
getOutgoers,
@ -6,6 +9,7 @@ import {
useStoreApi,
} from 'reactflow'
import BlockIcon from '../../../../block-icon'
import { useStore } from '../../../../store'
import type {
Branch,
Node,
@ -21,9 +25,15 @@ type NextStepProps = {
const NextStep = ({
selectedNode,
}: NextStepProps) => {
const data = selectedNode.data
const toolsets = useStore(s => s.toolsets)
const toolIcon = useMemo(() => {
if (data.type === BlockEnum.Tool)
return toolsets.find(toolset => toolset.id === data.provider_id)?.icon
}, [data, toolsets])
const store = useStoreApi()
const branches = selectedNode.data._targetBranches || []
const nodeWithBranches = selectedNode.data.type === BlockEnum.IfElse || selectedNode.data.type === BlockEnum.QuestionClassifier
const branches = data._targetBranches || []
const nodeWithBranches = data.type === BlockEnum.IfElse || data.type === BlockEnum.QuestionClassifier
const edges = useEdges()
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
@ -33,7 +43,7 @@ const NextStep = ({
<div className='shrink-0 relative flex items-center justify-center w-9 h-9 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'>
<BlockIcon
type={selectedNode!.data.type}
toolProviderId={selectedNode!.data.provider_id}
toolIcon={toolIcon}
/>
</div>
<Line linesNumber={nodeWithBranches ? branches.length : 1} />

View File

@ -1,6 +1,7 @@
import {
memo,
useCallback,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import { union } from 'lodash-es'
@ -15,6 +16,8 @@ import {
useNodesInteractions,
} from '@/app/components/workflow/hooks'
import Button from '@/app/components/base/button'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
type ItemProps = {
nodeId: string
@ -31,6 +34,11 @@ const Item = ({
const { t } = useTranslation()
const { handleNodeChange } = useNodesInteractions()
const nodesExtraData = useNodesExtraData()
const toolsets = useStore(s => s.toolsets)
const toolIcon = useMemo(() => {
if (data.type === BlockEnum.Tool)
return toolsets.find(toolset => toolset.id === data.provider_id)?.icon
}, [data, toolsets])
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
@ -65,7 +73,7 @@ const Item = ({
}
<BlockIcon
type={data.type}
toolProviderId={data.provider_id}
toolIcon={toolIcon}
className='shrink-0 mr-1.5'
/>
<div className='grow'>{data.title}</div>

View File

@ -5,12 +5,14 @@ import type {
import {
cloneElement,
memo,
useMemo,
} from 'react'
import type { NodeProps } from '../../types'
import {
BlockEnum,
NodeRunningStatus,
} from '../../types'
import { useStore } from '../../store'
import {
NodeSourceHandle,
NodeTargetHandle,
@ -32,6 +34,12 @@ const BaseNode: FC<BaseNodeProps> = ({
data,
children,
}) => {
const toolsets = useStore(s => s.toolsets)
const toolIcon = useMemo(() => {
if (data.type === BlockEnum.Tool)
return toolsets.find(toolset => toolset.id === data.provider_id)?.icon
}, [data, toolsets])
return (
<div
className={`
@ -84,7 +92,7 @@ const BaseNode: FC<BaseNodeProps> = ({
className='shrink-0 mr-2'
type={data.type}
size='md'
toolProviderId={data.provider_id}
toolIcon={toolIcon}
/>
<div
title={data.title}

View File

@ -6,6 +6,7 @@ import {
cloneElement,
memo,
useCallback,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import NextStep from './components/next-step'
@ -23,11 +24,13 @@ import {
useNodesExtraData,
useNodesInteractions,
} from '@/app/components/workflow/hooks'
import { useStore } from '@/app/components/workflow/store'
import { canRunBySingle } from '@/app/components/workflow/utils'
import { GitBranch01 } from '@/app/components/base/icons/src/vender/line/development'
import { Play } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import type { Node } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
type BasePanelProps = {
children: ReactElement
@ -43,6 +46,12 @@ const BasePanel: FC<BasePanelProps> = ({
const nodesExtraData = useNodesExtraData()
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
const toolsets = useStore(s => s.toolsets)
const toolIcon = useMemo(() => {
if (data.type === BlockEnum.Tool)
return toolsets.find(toolset => toolset.id === data.provider_id)?.icon
}, [data, toolsets])
const {
handleNodeDataUpdate,
handleNodeDataUpdateWithSyncDraft,
@ -62,7 +71,7 @@ const BasePanel: FC<BasePanelProps> = ({
<BlockIcon
className='shrink-0 mr-1'
type={data.type}
toolProviderId={data.provider_id}
toolIcon={toolIcon}
size='md'
/>
<TitleInput

View File

@ -1,11 +1,15 @@
import {
memo,
useCallback,
useMemo,
} 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 {
BlockEnum,
InputVarType,
} from '../types'
import {
useStore,
useWorkflowStore,
@ -13,24 +17,51 @@ import {
import { useWorkflowRun } from '../hooks'
import type { StartNodeType } from '../nodes/start/types'
import Button from '@/app/components/base/button'
import { useFeatures } from '@/app/components/base/features/hooks'
const InputsPanel = () => {
const { t } = useTranslation()
const workflowStore = useWorkflowStore()
const fileSettings = useFeatures(s => s.features.file)
const nodes = useNodes<StartNodeType>()
const inputs = useStore(s => s.inputs)
const files = useStore(s => s.files)
const {
handleRun,
handleRunSetting,
} = useWorkflowRun()
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
const variables = startNode?.data.variables || []
const startVariables = startNode?.data.variables
const handleValueChange = (variable: string, v: string) => {
workflowStore.getState().setInputs({
...inputs,
[variable]: v,
})
const variables = useMemo(() => {
const data = startVariables || []
if (fileSettings.image.enabled) {
return [
...data,
{
type: InputVarType.files,
variable: '__image',
required: true,
label: 'files',
},
]
}
return data
}, [fileSettings.image.enabled, startVariables])
const handleValueChange = (variable: string, v: any) => {
if (variable === '__image') {
workflowStore.setState({
files: v,
})
}
else {
workflowStore.getState().setInputs({
...inputs,
[variable]: v,
})
}
}
const handleCancel = useCallback(() => {
@ -40,7 +71,7 @@ const InputsPanel = () => {
const doRun = () => {
handleCancel()
handleRunSetting()
handleRun({ inputs })
handleRun({ inputs, files })
}
return (

View File

@ -16,6 +16,7 @@ import type {
import type {
Edge,
Node,
RunFile,
WorkflowRunningStatus,
} from './types'
import { WorkflowContext } from './context'
@ -36,6 +37,7 @@ type State = {
runningStatus?: WorkflowRunningStatus
showInputsPanel: boolean
inputs: Record<string, string>
files: RunFile[]
backupDraft?: {
nodes: Node[]
edges: Edge[]
@ -63,6 +65,7 @@ type Action = {
setRunningStatus: (runningStatus?: WorkflowRunningStatus) => void
setShowInputsPanel: (showInputsPanel: boolean) => void
setInputs: (inputs: Record<string, string>) => void
setFiles: (files: RunFile[]) => void
setBackupDraft: (backupDraft?: State['backupDraft']) => void
setNotInitialWorkflow: (notInitialWorkflow: boolean) => void
setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void
@ -102,6 +105,8 @@ export const createWorkflowStore = () => {
setShowInputsPanel: showInputsPanel => set(() => ({ showInputsPanel })),
inputs: {},
setInputs: inputs => set(() => ({ inputs })),
files: [],
setFiles: files => set(() => ({ files })),
backupDraft: undefined,
setBackupDraft: backupDraft => set(() => ({ backupDraft })),
notInitialWorkflow: false,

View File

@ -2,6 +2,7 @@ import type {
Edge as ReactFlowEdge,
Node as ReactFlowNode,
} from 'reactflow'
import type { TransferMethod } from '@/types/app'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
@ -211,3 +212,10 @@ export type CheckValidRes = {
isValid: boolean
errorMessage?: string
}
export type RunFile = {
type: string
transfer_method: TransferMethod[]
url?: string
upload_file_id?: string
}