mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-10 03:16:51 +08:00
improve: code upgrade (#4231)
This commit is contained in:
94
web/app/components/workflow/nodes/code/dependency-picker.tsx
Normal file
94
web/app/components/workflow/nodes/code/dependency-picker.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { t } from 'i18next'
|
||||
import type { CodeDependency } from './types'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Check, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
type Props = {
|
||||
value: CodeDependency
|
||||
available_dependencies: CodeDependency[]
|
||||
onChange: (dependency: CodeDependency) => void
|
||||
}
|
||||
|
||||
const DependencyPicker: FC<Props> = ({
|
||||
available_dependencies,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
const handleChange = useCallback((dependency: CodeDependency) => {
|
||||
return () => {
|
||||
setOpen(false)
|
||||
onChange(dependency)
|
||||
}
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='flex-grow cursor-pointer'>
|
||||
<div className='flex items-center h-8 justify-between px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px]'>
|
||||
<div className='grow w-0 truncate' title={value.name}>{value.name}</div>
|
||||
<ChevronDown className='shrink-0 w-3.5 h-3.5 text-gray-700' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{
|
||||
zIndex: 100,
|
||||
}}>
|
||||
<div className='p-1 bg-white rounded-lg shadow-sm' style={{
|
||||
width: 350,
|
||||
}}>
|
||||
<div
|
||||
className='shadow-sm bg-white mb-2 mx-1 flex items-center px-2 rounded-lg bg-gray-100'
|
||||
>
|
||||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
value={searchText}
|
||||
className='grow px-0.5 py-[7px] text-[13px] text-gray-700 bg-transparent appearance-none outline-none caret-primary-600 placeholder:text-gray-400'
|
||||
placeholder={t('workflow.nodes.code.searchDependencies') || ''}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
{
|
||||
searchText && (
|
||||
<div
|
||||
className='flex items-center justify-center ml-[5px] w-[18px] h-[18px] cursor-pointer'
|
||||
onClick={() => setSearchText('')}
|
||||
>
|
||||
<XCircle className='w-[14px] h-[14px] text-gray-400' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='max-h-[30vh] overflow-y-auto'>
|
||||
{available_dependencies.filter((v) => {
|
||||
if (!searchText)
|
||||
return true
|
||||
return v.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
}).map(dependency => (
|
||||
<div
|
||||
key={dependency.name}
|
||||
className='flex items-center h-[30px] justify-between pl-3 pr-2 rounded-lg hover:bg-gray-100 text-gray-900 text-[13px] cursor-pointer'
|
||||
onClick={handleChange(dependency)}
|
||||
>
|
||||
<div className='w-0 grow truncate'>{dependency.name}</div>
|
||||
{dependency.name === value.name && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DependencyPicker)
|
||||
36
web/app/components/workflow/nodes/code/dependency.tsx
Normal file
36
web/app/components/workflow/nodes/code/dependency.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import RemoveButton from '../_base/components/remove-button'
|
||||
import type { CodeDependency } from './types'
|
||||
import DependencyPicker from './dependency-picker'
|
||||
|
||||
type Props = {
|
||||
available_dependencies: CodeDependency[]
|
||||
dependencies: CodeDependency[]
|
||||
handleRemove: (index: number) => void
|
||||
handleChange: (index: number, dependency: CodeDependency) => void
|
||||
}
|
||||
|
||||
const Dependencies: FC<Props> = ({
|
||||
available_dependencies, dependencies, handleRemove, handleChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
{dependencies.map((dependency, index) => (
|
||||
<div className='flex items-center space-x-1' key={index}>
|
||||
<DependencyPicker
|
||||
value={dependency}
|
||||
available_dependencies={available_dependencies}
|
||||
onChange={dependency => handleChange(index, dependency)}
|
||||
/>
|
||||
<RemoveButton
|
||||
className='!p-2 !bg-gray-100 hover:!bg-gray-200'
|
||||
onClick={() => handleRemove(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Dependencies)
|
||||
@@ -5,6 +5,7 @@ import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confir
|
||||
import useConfig from './use-config'
|
||||
import type { CodeNodeType } from './types'
|
||||
import { CodeLanguage } from './types'
|
||||
import Dependencies from './dependency'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
@@ -59,6 +60,11 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
varInputs,
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
allowDependencies,
|
||||
availableDependencies,
|
||||
handleAddDependency,
|
||||
handleRemoveDependency,
|
||||
handleChangeDependency,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
@@ -78,6 +84,31 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
{
|
||||
allowDependencies
|
||||
? (
|
||||
<div>
|
||||
<Split />
|
||||
<div className='pt-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.advancedDependencies`)}
|
||||
operations={
|
||||
<AddButton onClick={() => handleAddDependency({ name: '', version: '' })} />
|
||||
}
|
||||
tooltip={t(`${i18nPrefix}.advancedDependenciesTip`)!}
|
||||
>
|
||||
<Dependencies
|
||||
available_dependencies={availableDependencies}
|
||||
dependencies={inputs.dependencies || []}
|
||||
handleRemove={index => handleRemoveDependency(index)}
|
||||
handleChange={(index, dependency) => handleChangeDependency(index, dependency)}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
<Split />
|
||||
<CodeEditor
|
||||
isInNode
|
||||
|
||||
@@ -16,4 +16,10 @@ export type CodeNodeType = CommonNodeType & {
|
||||
code_language: CodeLanguage
|
||||
code: string
|
||||
outputs: OutputVar
|
||||
dependencies?: CodeDependency[]
|
||||
}
|
||||
|
||||
export type CodeDependency = {
|
||||
name: string
|
||||
version: string
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import useOutputVarList from '../_base/hooks/use-output-var-list'
|
||||
import { BlockEnum, VarType } from '../../types'
|
||||
import type { Var } from '../../types'
|
||||
import { useStore } from '../../store'
|
||||
import type { CodeNodeType, OutputVar } from './types'
|
||||
import type { CodeDependency, CodeNodeType, OutputVar } from './types'
|
||||
import { CodeLanguage } from './types'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
@@ -21,15 +21,19 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
|
||||
const [allLanguageDefault, setAllLanguageDefault] = useState<Record<CodeLanguage, CodeNodeType> | null>(null)
|
||||
const [allLanguageDependencies, setAllLanguageDependencies] = useState<Record<CodeLanguage, CodeDependency[]> | null>(null)
|
||||
useEffect(() => {
|
||||
if (appId) {
|
||||
(async () => {
|
||||
const { config: javaScriptConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.javascript }) as any
|
||||
const { config: pythonConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any
|
||||
const { config: pythonConfig, available_dependencies: pythonDependencies } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any
|
||||
setAllLanguageDefault({
|
||||
[CodeLanguage.javascript]: javaScriptConfig as CodeNodeType,
|
||||
[CodeLanguage.python3]: pythonConfig as CodeNodeType,
|
||||
} as any)
|
||||
setAllLanguageDependencies({
|
||||
[CodeLanguage.python3]: pythonDependencies as CodeDependency[],
|
||||
} as any)
|
||||
})()
|
||||
}
|
||||
}, [appId])
|
||||
@@ -41,6 +45,62 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
setInputs,
|
||||
})
|
||||
|
||||
const handleAddDependency = useCallback((dependency: CodeDependency) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
if (!draft.dependencies)
|
||||
draft.dependencies = []
|
||||
draft.dependencies.push(dependency)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleRemoveDependency = useCallback((index: number) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
if (!draft.dependencies)
|
||||
draft.dependencies = []
|
||||
draft.dependencies.splice(index, 1)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleChangeDependency = useCallback((index: number, dependency: CodeDependency) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
if (!draft.dependencies)
|
||||
draft.dependencies = []
|
||||
draft.dependencies[index] = dependency
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const [allowDependencies, setAllowDependencies] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
if (!inputs.code_language)
|
||||
return
|
||||
if (!allLanguageDependencies)
|
||||
return
|
||||
|
||||
const newAllowDependencies = !!allLanguageDependencies[inputs.code_language]
|
||||
setAllowDependencies(newAllowDependencies)
|
||||
}, [allLanguageDependencies, inputs.code_language])
|
||||
|
||||
const [availableDependencies, setAvailableDependencies] = useState<CodeDependency[]>([])
|
||||
useEffect(() => {
|
||||
if (!inputs.code_language)
|
||||
return
|
||||
if (!allLanguageDependencies)
|
||||
return
|
||||
|
||||
const newAvailableDependencies = produce(allLanguageDependencies[inputs.code_language], (draft) => {
|
||||
const currentLanguage = inputs.code_language
|
||||
if (!currentLanguage || !draft || !inputs.dependencies)
|
||||
return []
|
||||
return draft.filter((dependency) => {
|
||||
return !inputs.dependencies?.find(d => d.name === dependency.name)
|
||||
})
|
||||
})
|
||||
setAvailableDependencies(newAvailableDependencies || [])
|
||||
}, [allLanguageDependencies, inputs.code_language, inputs.dependencies])
|
||||
|
||||
const [outputKeyOrders, setOutputKeyOrders] = useState<string[]>([])
|
||||
const syncOutputKeyOrders = useCallback((outputs: OutputVar) => {
|
||||
setOutputKeyOrders(Object.keys(outputs))
|
||||
@@ -163,6 +223,11 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||
inputVarValues,
|
||||
setInputVarValues,
|
||||
runResult,
|
||||
availableDependencies,
|
||||
allowDependencies,
|
||||
handleAddDependency,
|
||||
handleRemoveDependency,
|
||||
handleChangeDependency,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user