This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
import { VarType } from '../../types'
import { extractFunctionParams, extractReturnType } from './code-parser'
import { CodeLanguage } from './types'
const SAMPLE_CODES = {
python3: {
noParams: 'def main():',
singleParam: 'def main(param1):',
multipleParams: `def main(param1, param2, param3):
return {"result": param1}`,
withTypes: `def main(param1: str, param2: int, param3: List[str]):
result = process_data(param1, param2)
return {"output": result}`,
withDefaults: `def main(param1: str = "default", param2: int = 0):
return {"data": param1}`,
},
javascript: {
noParams: 'function main() {',
singleParam: 'function main(param1) {',
multipleParams: `function main(param1, param2, param3) {
return { result: param1 }
}`,
withComments: `// Main function
function main(param1, param2) {
// Process data
return { output: process(param1, param2) }
}`,
withSpaces: 'function main( param1 , param2 ) {',
},
}
describe('extractFunctionParams', () => {
describe('Python3', () => {
test('handles no parameters', () => {
const result = extractFunctionParams(SAMPLE_CODES.python3.noParams, CodeLanguage.python3)
expect(result).toEqual([])
})
test('extracts single parameter', () => {
const result = extractFunctionParams(SAMPLE_CODES.python3.singleParam, CodeLanguage.python3)
expect(result).toEqual(['param1'])
})
test('extracts multiple parameters', () => {
const result = extractFunctionParams(SAMPLE_CODES.python3.multipleParams, CodeLanguage.python3)
expect(result).toEqual(['param1', 'param2', 'param3'])
})
test('handles type hints', () => {
const result = extractFunctionParams(SAMPLE_CODES.python3.withTypes, CodeLanguage.python3)
expect(result).toEqual(['param1', 'param2', 'param3'])
})
test('handles default values', () => {
const result = extractFunctionParams(SAMPLE_CODES.python3.withDefaults, CodeLanguage.python3)
expect(result).toEqual(['param1', 'param2'])
})
})
// JavaScript のテストケース
describe('JavaScript', () => {
test('handles no parameters', () => {
const result = extractFunctionParams(SAMPLE_CODES.javascript.noParams, CodeLanguage.javascript)
expect(result).toEqual([])
})
test('extracts single parameter', () => {
const result = extractFunctionParams(SAMPLE_CODES.javascript.singleParam, CodeLanguage.javascript)
expect(result).toEqual(['param1'])
})
test('extracts multiple parameters', () => {
const result = extractFunctionParams(SAMPLE_CODES.javascript.multipleParams, CodeLanguage.javascript)
expect(result).toEqual(['param1', 'param2', 'param3'])
})
test('handles comments in code', () => {
const result = extractFunctionParams(SAMPLE_CODES.javascript.withComments, CodeLanguage.javascript)
expect(result).toEqual(['param1', 'param2'])
})
test('handles whitespace', () => {
const result = extractFunctionParams(SAMPLE_CODES.javascript.withSpaces, CodeLanguage.javascript)
expect(result).toEqual(['param1', 'param2'])
})
})
})
const RETURN_TYPE_SAMPLES = {
python3: {
singleReturn: `
def main(param1):
return {"result": "value"}`,
multipleReturns: `
def main(param1, param2):
return {"result": "value", "status": "success"}`,
noReturn: `
def main():
print("Hello")`,
complexReturn: `
def main():
data = process()
return {"result": data, "count": 42, "messages": ["hello"]}`,
nestedObject: `
def main(name, age, city):
return {
'personal_info': {
'name': name,
'age': age,
'city': city
},
'timestamp': int(time.time()),
'status': 'active'
}`,
},
javascript: {
singleReturn: `
function main(param1) {
return { result: "value" }
}`,
multipleReturns: `
function main(param1) {
return { result: "value", status: "success" }
}`,
withParentheses: `
function main() {
return ({ result: "value", status: "success" })
}`,
noReturn: `
function main() {
console.log("Hello")
}`,
withQuotes: `
function main() {
return { "result": 'value', 'status': "success" }
}`,
nestedObject: `
function main(name, age, city) {
return {
personal_info: {
name: name,
age: age,
city: city
},
timestamp: Date.now(),
status: 'active'
}
}`,
withJSDoc: `
/**
* Creates a user profile with personal information and metadata
* @param {string} name - The user's name
* @param {number} age - The user's age
* @param {string} city - The user's city of residence
* @returns {Object} An object containing the user profile
*/
function main(name, age, city) {
return {
result: {
personal_info: {
name: name,
age: age,
city: city
},
timestamp: Date.now(),
status: 'active'
}
};
}`,
},
}
describe('extractReturnType', () => {
// Python3 のテスト
describe('Python3', () => {
test('extracts single return value', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.singleReturn, CodeLanguage.python3)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
})
})
test('extracts multiple return values', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.multipleReturns, CodeLanguage.python3)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
test('returns empty object when no return statement', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.noReturn, CodeLanguage.python3)
expect(result).toEqual({})
})
test('handles complex return statement', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.complexReturn, CodeLanguage.python3)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
count: {
type: VarType.string,
children: null,
},
messages: {
type: VarType.string,
children: null,
},
})
})
test('handles nested object structure', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.python3.nestedObject, CodeLanguage.python3)
expect(result).toEqual({
personal_info: {
type: VarType.string,
children: null,
},
timestamp: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
})
// JavaScript のテスト
describe('JavaScript', () => {
test('extracts single return value', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.singleReturn, CodeLanguage.javascript)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
})
})
test('extracts multiple return values', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.multipleReturns, CodeLanguage.javascript)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
test('handles return with parentheses', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withParentheses, CodeLanguage.javascript)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
test('returns empty object when no return statement', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.noReturn, CodeLanguage.javascript)
expect(result).toEqual({})
})
test('handles quoted keys', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.withQuotes, CodeLanguage.javascript)
expect(result).toEqual({
result: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
test('handles nested object structure', () => {
const result = extractReturnType(RETURN_TYPE_SAMPLES.javascript.nestedObject, CodeLanguage.javascript)
expect(result).toEqual({
personal_info: {
type: VarType.string,
children: null,
},
timestamp: {
type: VarType.string,
children: null,
},
status: {
type: VarType.string,
children: null,
},
})
})
})
})

View File

@@ -0,0 +1,86 @@
import { VarType } from '../../types'
import type { OutputVar } from './types'
import { CodeLanguage } from './types'
export const extractFunctionParams = (code: string, language: CodeLanguage) => {
if (language === CodeLanguage.json)
return []
const patterns: Record<Exclude<CodeLanguage, CodeLanguage.json>, RegExp> = {
[CodeLanguage.python3]: /def\s+main\s*\((.*?)\)/,
[CodeLanguage.javascript]: /function\s+main\s*\((.*?)\)/,
}
const match = code.match(patterns[language])
const params: string[] = []
if (match?.[1]) {
params.push(...match[1].split(',')
.map(p => p.trim())
.filter(Boolean)
.map(p => p.split(':')[0].trim()),
)
}
return params
}
export const extractReturnType = (code: string, language: CodeLanguage): OutputVar => {
const codeWithoutComments = code.replace(/\/\*\*[\s\S]*?\*\//, '')
// console.log(codeWithoutComments)
const returnIndex = codeWithoutComments.indexOf('return')
if (returnIndex === -1)
return {}
// return から始まる部分文字列を取得
const codeAfterReturn = codeWithoutComments.slice(returnIndex)
let bracketCount = 0
let startIndex = codeAfterReturn.indexOf('{')
if (language === CodeLanguage.javascript && startIndex === -1) {
const parenStart = codeAfterReturn.indexOf('(')
if (parenStart !== -1)
startIndex = codeAfterReturn.indexOf('{', parenStart)
}
if (startIndex === -1)
return {}
let endIndex = -1
for (let i = startIndex; i < codeAfterReturn.length; i++) {
if (codeAfterReturn[i] === '{')
bracketCount++
if (codeAfterReturn[i] === '}') {
bracketCount--
if (bracketCount === 0) {
endIndex = i + 1
break
}
}
}
if (endIndex === -1)
return {}
const returnContent = codeAfterReturn.slice(startIndex + 1, endIndex - 1)
// console.log(returnContent)
const result: OutputVar = {}
const keyRegex = /['"]?(\w+)['"]?\s*:(?![^{]*})/g
const matches = returnContent.matchAll(keyRegex)
for (const match of matches) {
// console.log(`Found key: "${match[1]}" from match: "${match[0]}"`)
const key = match[1]
result[key] = {
type: VarType.string,
children: null,
}
}
// console.log(result)
return result
}

View File

@@ -0,0 +1,40 @@
import type { NodeDefault } from '../../types'
import { CodeLanguage, type CodeNodeType } from './types'
import { genNodeMetaData } from '@/app/components/workflow/utils'
import { BlockEnum } from '@/app/components/workflow/types'
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
const i18nPrefix = 'workflow.errorMsg'
const metaData = genNodeMetaData({
classification: BlockClassificationEnum.Transform,
sort: 1,
type: BlockEnum.Code,
})
const nodeDefault: NodeDefault<CodeNodeType> = {
metaData,
defaultValue: {
code: '',
code_language: CodeLanguage.python3,
variables: [],
outputs: {},
},
checkValid(payload: CodeNodeType, t: any) {
let errorMessages = ''
const { code, variables } = payload
if (!errorMessages && variables.filter(v => !v.variable).length > 0)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) })
if (!errorMessages && variables.filter(v => !v.value_selector.length).length > 0)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
if (!errorMessages && !code)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.code`) })
return {
isValid: !errorMessages,
errorMessage: errorMessages,
}
},
}
export default nodeDefault

View File

@@ -0,0 +1,85 @@
import type { FC } from 'react'
import React, { useCallback, useState } from 'react'
import { t } from 'i18next'
import {
RiArrowDownSLine,
} from '@remixicon/react'
import type { CodeDependency } from './types'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import Input from '@/app/components/base/input'
import { Check } from '@/app/components/base/icons/src/vender/line/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='grow cursor-pointer'>
<div className='flex h-8 items-center justify-between rounded-lg border-0 bg-gray-100 px-2.5 text-[13px] text-gray-900'>
<div className='w-0 grow truncate' title={value.name}>{value.name}</div>
<RiArrowDownSLine className='h-3.5 w-3.5 shrink-0 text-gray-700' />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{
zIndex: 100,
}}>
<div className='rounded-lg bg-white p-1 shadow-sm' style={{
width: 350,
}}>
<div className='mx-1 mb-2'>
<Input
showLeftIcon
showClearIcon
value={searchText}
placeholder={t('workflow.nodes.code.searchDependencies') || ''}
onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')}
autoFocus
/>
</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 h-[30px] cursor-pointer items-center justify-between rounded-lg pl-3 pr-2 text-[13px] text-gray-900 hover:bg-gray-100'
onClick={handleChange(dependency)}
>
<div className='w-0 grow truncate'>{dependency.name}</div>
{dependency.name === value.name && <Check className='h-4 w-4 shrink-0 text-primary-600' />}
</div>
))}
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default React.memo(DependencyPicker)

View File

@@ -0,0 +1,13 @@
import type { FC } from 'react'
import React from 'react'
import type { CodeNodeType } from './types'
import type { NodeProps } from '@/app/components/workflow/types'
const Node: FC<NodeProps<CodeNodeType>> = () => {
return (
// No summary content
<div></div>
)
}
export default React.memo(Node)

View File

@@ -0,0 +1,136 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm'
import useConfig from './use-config'
import type { CodeNodeType } from './types'
import { CodeLanguage } from './types'
import { extractFunctionParams, extractReturnType } from './code-parser'
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'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import type { NodePanelProps } from '@/app/components/workflow/types'
import SyncButton from '@/app/components/base/button/sync-button'
const i18nPrefix = 'workflow.nodes.code'
const codeLanguages = [
{
label: 'Python3',
value: CodeLanguage.python3,
},
{
label: 'JavaScript',
value: CodeLanguage.javascript,
},
]
const Panel: FC<NodePanelProps<CodeNodeType>> = ({
id,
data,
}) => {
const { t } = useTranslation()
const {
readOnly,
inputs,
outputKeyOrders,
handleCodeAndVarsChange,
handleVarListChange,
handleAddVariable,
handleRemoveVariable,
handleSyncFunctionSignature,
handleCodeChange,
handleCodeLanguageChange,
handleVarsChange,
handleAddOutputVariable,
filterVar,
isShowRemoveVarConfirm,
hideRemoveVarConfirm,
onRemoveVarConfirm,
} = useConfig(id, data)
const handleGeneratedCode = (value: string) => {
const params = extractFunctionParams(value, inputs.code_language)
const codeNewInput = params.map((p) => {
return {
variable: p,
value_selector: [],
}
})
const returnTypes = extractReturnType(value, inputs.code_language)
handleCodeAndVarsChange(value, codeNewInput, returnTypes)
}
return (
<div className='mt-2'>
<div className='space-y-4 px-4 pb-4'>
<Field
title={t(`${i18nPrefix}.inputVars`)}
operations={
!readOnly ? (
<div className="flex gap-2">
<SyncButton popupContent={t(`${i18nPrefix}.syncFunctionSignature`)} onClick={handleSyncFunctionSignature} />
<AddButton onClick={handleAddVariable} />
</div>
) : undefined
}
>
<VarList
readonly={readOnly}
nodeId={id}
list={inputs.variables}
onChange={handleVarListChange}
filterVar={filterVar}
isSupportFileVar={false}
/>
</Field>
<Split />
<CodeEditor
nodeId={id}
isInNode
readOnly={readOnly}
title={
<TypeSelector
options={codeLanguages}
value={inputs.code_language}
onChange={handleCodeLanguageChange}
/>
}
language={inputs.code_language}
value={inputs.code}
onChange={handleCodeChange}
onGenerated={handleGeneratedCode}
showCodeGenerator={true}
/>
</div>
<Split />
<div className='px-4 pb-2 pt-4'>
<Field
title={t(`${i18nPrefix}.outputVars`)}
operations={
<AddButton onClick={handleAddOutputVariable} />
}
required
>
<OutputVarList
readonly={readOnly}
outputs={inputs.outputs}
outputKeyOrders={outputKeyOrders}
onChange={handleVarsChange}
onRemove={handleRemoveVariable}
/>
</Field>
</div>
<RemoveEffectVarConfirm
isShow={isShowRemoveVarConfirm}
onCancel={hideRemoveVarConfirm}
onConfirm={onRemoveVarConfirm}
/>
</div >
)
}
export default React.memo(Panel)

View File

@@ -0,0 +1,24 @@
import type { CommonNodeType, VarType, Variable } from '@/app/components/workflow/types'
export enum CodeLanguage {
python3 = 'python3',
javascript = 'javascript',
json = 'json',
}
export type OutputVar = Record<string, {
type: VarType
children: null // support nest in the future,
}>
export type CodeDependency = {
name: string
version?: string
}
export type CodeNodeType = CommonNodeType & {
variables: Variable[]
code_language: CodeLanguage
code: string
outputs: OutputVar
}

View File

@@ -0,0 +1,209 @@
import { useCallback, useEffect, useState } from 'react'
import { produce } from 'immer'
import useVarList from '../_base/hooks/use-var-list'
import useOutputVarList from '../_base/hooks/use-output-var-list'
import { BlockEnum, VarType } from '../../types'
import type { Var, Variable } from '../../types'
import { useStore } from '../../store'
import type { CodeNodeType, OutputVar } from './types'
import { CodeLanguage } from './types'
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
import {
fetchNodeDefault,
fetchPipelineNodeDefault,
} from '@/service/workflow'
import {
useNodesReadOnly,
} from '@/app/components/workflow/hooks'
const useConfig = (id: string, payload: CodeNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const appId = useStore(s => s.appId)
const pipelineId = useStore(s => s.pipelineId)
const [allLanguageDefault, setAllLanguageDefault] = useState<Record<CodeLanguage, CodeNodeType> | 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
setAllLanguageDefault({
[CodeLanguage.javascript]: javaScriptConfig as CodeNodeType,
[CodeLanguage.python3]: pythonConfig as CodeNodeType,
} as any)
})()
}
}, [appId])
useEffect(() => {
if (pipelineId) {
(async () => {
const { config: javaScriptConfig } = await fetchPipelineNodeDefault(pipelineId, BlockEnum.Code, { code_language: CodeLanguage.javascript }) as any
const { config: pythonConfig } = await fetchPipelineNodeDefault(pipelineId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any
setAllLanguageDefault({
[CodeLanguage.javascript]: javaScriptConfig as CodeNodeType,
[CodeLanguage.python3]: pythonConfig as CodeNodeType,
} as any)
})()
}
}, [pipelineId])
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
const { inputs, setInputs } = useNodeCrud<CodeNodeType>(id, payload)
const { handleVarListChange, handleAddVariable } = useVarList<CodeNodeType>({
inputs,
setInputs,
})
const [outputKeyOrders, setOutputKeyOrders] = useState<string[]>([])
const syncOutputKeyOrders = useCallback((outputs: OutputVar) => {
setOutputKeyOrders(Object.keys(outputs))
}, [])
useEffect(() => {
if (inputs.code) {
if (inputs.outputs && Object.keys(inputs.outputs).length > 0)
syncOutputKeyOrders(inputs.outputs)
return
}
const isReady = defaultConfig && Object.keys(defaultConfig).length > 0
if (isReady) {
setInputs({
...inputs,
...defaultConfig,
})
syncOutputKeyOrders(defaultConfig.outputs)
}
}, [defaultConfig])
const handleCodeChange = useCallback((code: string) => {
const newInputs = produce(inputs, (draft) => {
draft.code = code
})
setInputs(newInputs)
}, [inputs, setInputs])
const handleCodeLanguageChange = useCallback((codeLanguage: CodeLanguage) => {
const currDefaultConfig = allLanguageDefault?.[codeLanguage]
const newInputs = produce(inputs, (draft) => {
draft.code_language = codeLanguage
if (!currDefaultConfig)
return
draft.code = currDefaultConfig.code
draft.variables = currDefaultConfig.variables
draft.outputs = currDefaultConfig.outputs
})
setInputs(newInputs)
}, [allLanguageDefault, inputs, setInputs])
const handleSyncFunctionSignature = useCallback(() => {
const generateSyncSignatureCode = (code: string) => {
let mainDefRe
let newMainDef
if (inputs.code_language === CodeLanguage.javascript) {
mainDefRe = /function\s+main\b\s*\([\s\S]*?\)/g
newMainDef = 'function main({{var_list}})'
let param_list = inputs.variables?.map(item => item.variable).join(', ') || ''
param_list = param_list ? `{${param_list}}` : ''
newMainDef = newMainDef.replace('{{var_list}}', param_list)
}
else if (inputs.code_language === CodeLanguage.python3) {
mainDefRe = /def\s+main\b\s*\([\s\S]*?\)/g
const param_list = []
for (const item of inputs.variables) {
let param = item.variable
let param_type = ''
switch (item.value_type) {
case VarType.string:
param_type = ': str'
break
case VarType.number:
param_type = ': float'
break
case VarType.object:
param_type = ': dict'
break
case VarType.array:
param_type = ': list'
break
case VarType.arrayNumber:
param_type = ': list[float]'
break
case VarType.arrayString:
param_type = ': list[str]'
break
case VarType.arrayObject:
param_type = ': list[dict]'
break
}
param += param_type
param_list.push(`${param}`)
}
newMainDef = `def main(${param_list.join(', ')})`
}
else { return code }
const newCode = code.replace(mainDefRe, newMainDef)
return newCode
}
const newInputs = produce(inputs, (draft) => {
draft.code = generateSyncSignatureCode(draft.code)
})
setInputs(newInputs)
}, [inputs, setInputs])
const {
handleVarsChange,
handleAddVariable: handleAddOutputVariable,
handleRemoveVariable,
isShowRemoveVarConfirm,
hideRemoveVarConfirm,
onRemoveVarConfirm,
} = useOutputVarList<CodeNodeType>({
id,
inputs,
setInputs,
outputKeyOrders,
onOutputKeyOrdersChange: setOutputKeyOrders,
})
const filterVar = useCallback((varPayload: Var) => {
return [VarType.string, VarType.number, VarType.boolean, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayBoolean, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, [])
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {
const newInputs = produce(inputs, (draft) => {
draft.code = code
draft.variables = inputVariables
draft.outputs = outputVariables
})
setInputs(newInputs)
syncOutputKeyOrders(outputVariables)
}, [inputs, setInputs, syncOutputKeyOrders])
return {
readOnly,
inputs,
outputKeyOrders,
handleVarListChange,
handleAddVariable,
handleRemoveVariable,
handleSyncFunctionSignature,
handleCodeChange,
handleCodeLanguageChange,
handleVarsChange,
filterVar,
handleAddOutputVariable,
isShowRemoveVarConfirm,
hideRemoveVarConfirm,
onRemoveVarConfirm,
handleCodeAndVarsChange,
}
}
export default useConfig

View File

@@ -0,0 +1,65 @@
import type { RefObject } from 'react'
import type { InputVar, Variable } from '@/app/components/workflow/types'
import { useCallback, useMemo } from 'react'
import useNodeCrud from '../_base/hooks/use-node-crud'
import type { CodeNodeType } from './types'
type Params = {
id: string,
payload: CodeNodeType,
runInputData: Record<string, any>
runInputDataRef: RefObject<Record<string, any>>
getInputVars: (textList: string[]) => InputVar[]
setRunInputData: (data: Record<string, any>) => void
toVarInputs: (variables: Variable[]) => InputVar[]
}
const useSingleRunFormParams = ({
id,
payload,
runInputData,
toVarInputs,
setRunInputData,
}: Params) => {
const { inputs } = useNodeCrud<CodeNodeType>(id, payload)
const varInputs = toVarInputs(inputs.variables)
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
setRunInputData(newPayload)
}, [setRunInputData])
const inputVarValues = (() => {
const vars: Record<string, any> = {}
Object.keys(runInputData)
.forEach((key) => {
vars[key] = runInputData[key]
})
return vars
})()
const forms = useMemo(() => {
return [
{
inputs: varInputs,
values: inputVarValues,
onChange: setInputVarValues,
},
]
}, [inputVarValues, setInputVarValues, varInputs])
const getDependentVars = () => {
return payload.variables.map(v => v.value_selector)
}
const getDependentVar = (variable: string) => {
const varItem = payload.variables.find(v => v.variable === variable)
if (varItem)
return varItem.value_selector
}
return {
forms,
getDependentVars,
getDependentVar,
}
}
export default useSingleRunFormParams

View File

@@ -0,0 +1,3 @@
export const checkNodeValid = () => {
return true
}