mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 03:32:23 +08:00
Chore/remove python dependencies selector (#7494)
This commit is contained in:
parent
715eb8fa32
commit
784b11ce19
|
@ -1,15 +1,13 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Literal, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from httpx import Timeout, get, post
|
from httpx import Timeout, post
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from core.helper.code_executor.entities import CodeDependency
|
|
||||||
from core.helper.code_executor.javascript.javascript_transformer import NodeJsTemplateTransformer
|
from core.helper.code_executor.javascript.javascript_transformer import NodeJsTemplateTransformer
|
||||||
from core.helper.code_executor.jinja2.jinja2_transformer import Jinja2TemplateTransformer
|
from core.helper.code_executor.jinja2.jinja2_transformer import Jinja2TemplateTransformer
|
||||||
from core.helper.code_executor.python3.python3_transformer import Python3TemplateTransformer
|
from core.helper.code_executor.python3.python3_transformer import Python3TemplateTransformer
|
||||||
|
@ -66,8 +64,7 @@ class CodeExecutor:
|
||||||
def execute_code(cls,
|
def execute_code(cls,
|
||||||
language: CodeLanguage,
|
language: CodeLanguage,
|
||||||
preload: str,
|
preload: str,
|
||||||
code: str,
|
code: str) -> str:
|
||||||
dependencies: Optional[list[CodeDependency]] = None) -> str:
|
|
||||||
"""
|
"""
|
||||||
Execute code
|
Execute code
|
||||||
:param language: code language
|
:param language: code language
|
||||||
|
@ -87,9 +84,6 @@ class CodeExecutor:
|
||||||
'enable_network': True
|
'enable_network': True
|
||||||
}
|
}
|
||||||
|
|
||||||
if dependencies:
|
|
||||||
data['dependencies'] = [dependency.model_dump() for dependency in dependencies]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = post(str(url), json=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT)
|
response = post(str(url), json=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT)
|
||||||
if response.status_code == 503:
|
if response.status_code == 503:
|
||||||
|
@ -119,7 +113,7 @@ class CodeExecutor:
|
||||||
return response.data.stdout or ''
|
return response.data.stdout or ''
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict, dependencies: Optional[list[CodeDependency]] = None) -> dict:
|
def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Execute code
|
Execute code
|
||||||
:param language: code language
|
:param language: code language
|
||||||
|
@ -131,67 +125,12 @@ class CodeExecutor:
|
||||||
if not template_transformer:
|
if not template_transformer:
|
||||||
raise CodeExecutionException(f'Unsupported language {language}')
|
raise CodeExecutionException(f'Unsupported language {language}')
|
||||||
|
|
||||||
runner, preload, dependencies = template_transformer.transform_caller(code, inputs, dependencies)
|
runner, preload = template_transformer.transform_caller(code, inputs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = cls.execute_code(language, preload, runner, dependencies)
|
response = cls.execute_code(language, preload, runner)
|
||||||
except CodeExecutionException as e:
|
except CodeExecutionException as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
return template_transformer.transform_response(response)
|
return template_transformer.transform_response(response)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def list_dependencies(cls, language: str) -> list[CodeDependency]:
|
|
||||||
if language not in cls.supported_dependencies_languages:
|
|
||||||
return []
|
|
||||||
|
|
||||||
with cls.dependencies_cache_lock:
|
|
||||||
if language in cls.dependencies_cache:
|
|
||||||
# check expiration
|
|
||||||
dependencies = cls.dependencies_cache[language]
|
|
||||||
if dependencies['expiration'] > time.time():
|
|
||||||
return dependencies['data']
|
|
||||||
# remove expired cache
|
|
||||||
del cls.dependencies_cache[language]
|
|
||||||
|
|
||||||
dependencies = cls._get_dependencies(language)
|
|
||||||
with cls.dependencies_cache_lock:
|
|
||||||
cls.dependencies_cache[language] = {
|
|
||||||
'data': dependencies,
|
|
||||||
'expiration': time.time() + 60
|
|
||||||
}
|
|
||||||
|
|
||||||
return dependencies
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_dependencies(cls, language: Literal['python3']) -> list[CodeDependency]:
|
|
||||||
"""
|
|
||||||
List dependencies
|
|
||||||
"""
|
|
||||||
url = URL(CODE_EXECUTION_ENDPOINT) / 'v1' / 'sandbox' / 'dependencies'
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'X-Api-Key': CODE_EXECUTION_API_KEY
|
|
||||||
}
|
|
||||||
|
|
||||||
running_language = cls.code_language_to_running_language.get(language)
|
|
||||||
if isinstance(running_language, Enum):
|
|
||||||
running_language = running_language.value
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'language': running_language,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = get(str(url), params=data, headers=headers, timeout=CODE_EXECUTION_TIMEOUT)
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f'Failed to list dependencies, got status code {response.status_code}, please check if the sandbox service is running')
|
|
||||||
response = response.json()
|
|
||||||
dependencies = response.get('data', {}).get('dependencies', [])
|
|
||||||
return [
|
|
||||||
CodeDependency(**dependency) for dependency in dependencies
|
|
||||||
if dependency.get('name') not in Python3TemplateTransformer.get_standard_packages()
|
|
||||||
]
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f'Failed to list dependencies: {e}')
|
|
||||||
return []
|
|
|
@ -2,8 +2,6 @@ from abc import abstractmethod
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from core.helper.code_executor.code_executor import CodeExecutor
|
|
||||||
|
|
||||||
|
|
||||||
class CodeNodeProvider(BaseModel):
|
class CodeNodeProvider(BaseModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -23,10 +21,6 @@ class CodeNodeProvider(BaseModel):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_default_available_packages(cls) -> list[dict]:
|
|
||||||
return [p.model_dump() for p in CodeExecutor.list_dependencies(cls.get_language())]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_config(cls) -> dict:
|
def get_default_config(cls) -> dict:
|
||||||
return {
|
return {
|
||||||
|
@ -50,6 +44,5 @@ class CodeNodeProvider(BaseModel):
|
||||||
"children": None
|
"children": None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"available_dependencies": cls.get_default_available_packages(),
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class CodeDependency(BaseModel):
|
|
||||||
name: str
|
|
||||||
version: str
|
|
|
@ -3,7 +3,7 @@ from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
|
||||||
|
|
||||||
class Jinja2Formatter:
|
class Jinja2Formatter:
|
||||||
@classmethod
|
@classmethod
|
||||||
def format(cls, template: str, inputs: str) -> str:
|
def format(cls, template: str, inputs: dict) -> str:
|
||||||
"""
|
"""
|
||||||
Format template
|
Format template
|
||||||
:param template: template
|
:param template: template
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from core.helper.code_executor.python3.python3_transformer import Python3TemplateTransformer
|
|
||||||
from core.helper.code_executor.template_transformer import TemplateTransformer
|
from core.helper.code_executor.template_transformer import TemplateTransformer
|
||||||
|
|
||||||
|
|
||||||
class Jinja2TemplateTransformer(TemplateTransformer):
|
class Jinja2TemplateTransformer(TemplateTransformer):
|
||||||
@classmethod
|
|
||||||
def get_standard_packages(cls) -> set[str]:
|
|
||||||
return {'jinja2'} | Python3TemplateTransformer.get_standard_packages()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def transform_response(cls, response: str) -> dict:
|
def transform_response(cls, response: str) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,30 +4,6 @@ from core.helper.code_executor.template_transformer import TemplateTransformer
|
||||||
|
|
||||||
|
|
||||||
class Python3TemplateTransformer(TemplateTransformer):
|
class Python3TemplateTransformer(TemplateTransformer):
|
||||||
@classmethod
|
|
||||||
def get_standard_packages(cls) -> set[str]:
|
|
||||||
return {
|
|
||||||
'base64',
|
|
||||||
'binascii',
|
|
||||||
'collections',
|
|
||||||
'datetime',
|
|
||||||
'functools',
|
|
||||||
'hashlib',
|
|
||||||
'hmac',
|
|
||||||
'itertools',
|
|
||||||
'json',
|
|
||||||
'math',
|
|
||||||
'operator',
|
|
||||||
'os',
|
|
||||||
'random',
|
|
||||||
're',
|
|
||||||
'string',
|
|
||||||
'sys',
|
|
||||||
'time',
|
|
||||||
'traceback',
|
|
||||||
'uuid',
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_runner_script(cls) -> str:
|
def get_runner_script(cls) -> str:
|
||||||
runner_script = dedent(f"""
|
runner_script = dedent(f"""
|
||||||
|
|
|
@ -2,9 +2,6 @@ import json
|
||||||
import re
|
import re
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from core.helper.code_executor.entities import CodeDependency
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateTransformer(ABC):
|
class TemplateTransformer(ABC):
|
||||||
|
@ -13,12 +10,7 @@ class TemplateTransformer(ABC):
|
||||||
_result_tag: str = '<<RESULT>>'
|
_result_tag: str = '<<RESULT>>'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_standard_packages(cls) -> set[str]:
|
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||||
return set()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def transform_caller(cls, code: str, inputs: dict,
|
|
||||||
dependencies: Optional[list[CodeDependency]] = None) -> tuple[str, str, list[CodeDependency]]:
|
|
||||||
"""
|
"""
|
||||||
Transform code to python runner
|
Transform code to python runner
|
||||||
:param code: code
|
:param code: code
|
||||||
|
@ -28,14 +20,7 @@ class TemplateTransformer(ABC):
|
||||||
runner_script = cls.assemble_runner_script(code, inputs)
|
runner_script = cls.assemble_runner_script(code, inputs)
|
||||||
preload_script = cls.get_preload_script()
|
preload_script = cls.get_preload_script()
|
||||||
|
|
||||||
packages = dependencies or []
|
return runner_script, preload_script
|
||||||
standard_packages = cls.get_standard_packages()
|
|
||||||
for package in standard_packages:
|
|
||||||
if package not in packages:
|
|
||||||
packages.append(CodeDependency(name=package, version=''))
|
|
||||||
packages = list({dep.name: dep for dep in packages if dep.name}.values())
|
|
||||||
|
|
||||||
return runner_script, preload_script, packages
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extract_result_str_from_response(cls, response: str) -> str:
|
def extract_result_str_from_response(cls, response: str) -> str:
|
||||||
|
|
|
@ -67,7 +67,6 @@ class CodeNode(BaseNode):
|
||||||
language=code_language,
|
language=code_language,
|
||||||
code=code,
|
code=code,
|
||||||
inputs=variables,
|
inputs=variables,
|
||||||
dependencies=node_data.dependencies
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Transform result
|
# Transform result
|
||||||
|
|
|
@ -3,7 +3,6 @@ from typing import Literal, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from core.helper.code_executor.code_executor import CodeLanguage
|
from core.helper.code_executor.code_executor import CodeLanguage
|
||||||
from core.helper.code_executor.entities import CodeDependency
|
|
||||||
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
from core.workflow.entities.base_node_data_entities import BaseNodeData
|
||||||
from core.workflow.entities.variable_entities import VariableSelector
|
from core.workflow.entities.variable_entities import VariableSelector
|
||||||
|
|
||||||
|
@ -16,8 +15,12 @@ class CodeNodeData(BaseNodeData):
|
||||||
type: Literal['string', 'number', 'object', 'array[string]', 'array[number]', 'array[object]']
|
type: Literal['string', 'number', 'object', 'array[string]', 'array[number]', 'array[object]']
|
||||||
children: Optional[dict[str, 'Output']] = None
|
children: Optional[dict[str, 'Output']] = None
|
||||||
|
|
||||||
|
class Dependency(BaseModel):
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
|
||||||
variables: list[VariableSelector]
|
variables: list[VariableSelector]
|
||||||
code_language: Literal[CodeLanguage.PYTHON3, CodeLanguage.JAVASCRIPT]
|
code_language: Literal[CodeLanguage.PYTHON3, CodeLanguage.JAVASCRIPT]
|
||||||
code: str
|
code: str
|
||||||
outputs: dict[str, Output]
|
outputs: dict[str, Output]
|
||||||
dependencies: Optional[list[CodeDependency]] = None
|
dependencies: Optional[list[Dependency]] = None
|
|
@ -6,14 +6,13 @@ from _pytest.monkeypatch import MonkeyPatch
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
|
from core.helper.code_executor.code_executor import CodeExecutor, CodeLanguage
|
||||||
from core.helper.code_executor.entities import CodeDependency
|
|
||||||
|
|
||||||
MOCK = os.getenv('MOCK_SWITCH', 'false') == 'true'
|
MOCK = os.getenv('MOCK_SWITCH', 'false') == 'true'
|
||||||
|
|
||||||
class MockedCodeExecutor:
|
class MockedCodeExecutor:
|
||||||
@classmethod
|
@classmethod
|
||||||
def invoke(cls, language: Literal['python3', 'javascript', 'jinja2'],
|
def invoke(cls, language: Literal['python3', 'javascript', 'jinja2'],
|
||||||
code: str, inputs: dict, dependencies: Optional[list[CodeDependency]] = None) -> dict:
|
code: str, inputs: dict) -> dict:
|
||||||
# invoke directly
|
# invoke directly
|
||||||
match language:
|
match language:
|
||||||
case CodeLanguage.PYTHON3:
|
case CodeLanguage.PYTHON3:
|
||||||
|
@ -24,6 +23,8 @@ class MockedCodeExecutor:
|
||||||
return {
|
return {
|
||||||
"result": Template(code).render(inputs)
|
"result": Template(code).render(inputs)
|
||||||
}
|
}
|
||||||
|
case _:
|
||||||
|
raise Exception("Language not supported")
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def setup_code_executor_mock(request, monkeypatch: MonkeyPatch):
|
def setup_code_executor_mock(request, monkeypatch: MonkeyPatch):
|
||||||
|
|
|
@ -28,14 +28,6 @@ def test_javascript_with_code_template():
|
||||||
inputs={'arg1': 'Hello', 'arg2': 'World'})
|
inputs={'arg1': 'Hello', 'arg2': 'World'})
|
||||||
assert result == {'result': 'HelloWorld'}
|
assert result == {'result': 'HelloWorld'}
|
||||||
|
|
||||||
|
|
||||||
def test_javascript_list_default_available_packages():
|
|
||||||
packages = JavascriptCodeProvider.get_default_available_packages()
|
|
||||||
|
|
||||||
# no default packages available for javascript
|
|
||||||
assert len(packages) == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_javascript_get_runner_script():
|
def test_javascript_get_runner_script():
|
||||||
runner_script = NodeJsTemplateTransformer.get_runner_script()
|
runner_script = NodeJsTemplateTransformer.get_runner_script()
|
||||||
assert runner_script.count(NodeJsTemplateTransformer._code_placeholder) == 1
|
assert runner_script.count(NodeJsTemplateTransformer._code_placeholder) == 1
|
||||||
|
|
|
@ -29,15 +29,6 @@ def test_python3_with_code_template():
|
||||||
assert result == {'result': 'HelloWorld'}
|
assert result == {'result': 'HelloWorld'}
|
||||||
|
|
||||||
|
|
||||||
def test_python3_list_default_available_packages():
|
|
||||||
packages = Python3CodeProvider.get_default_available_packages()
|
|
||||||
assert len(packages) > 0
|
|
||||||
assert {'requests', 'httpx'}.issubset(p['name'] for p in packages)
|
|
||||||
|
|
||||||
# check JSON serializable
|
|
||||||
assert len(str(json.dumps(packages))) > 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_python3_get_runner_script():
|
def test_python3_get_runner_script():
|
||||||
runner_script = Python3TemplateTransformer.get_runner_script()
|
runner_script = Python3TemplateTransformer.get_runner_script()
|
||||||
assert runner_script.count(Python3TemplateTransformer._code_placeholder) == 1
|
assert runner_script.count(Python3TemplateTransformer._code_placeholder) == 1
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
import type { FC } from 'react'
|
|
||||||
import React, { useCallback, useState } from 'react'
|
|
||||||
import { t } from 'i18next'
|
|
||||||
import {
|
|
||||||
RiArrowDownSLine,
|
|
||||||
RiSearchLine,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import type { CodeDependency } from './types'
|
|
||||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
|
||||||
import { Check } 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>
|
|
||||||
<RiArrowDownSLine 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'
|
|
||||||
>
|
|
||||||
<RiSearchLine 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)
|
|
|
@ -1,36 +0,0 @@
|
||||||
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,7 +5,6 @@ import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confir
|
||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import type { CodeNodeType } from './types'
|
import type { CodeNodeType } from './types'
|
||||||
import { CodeLanguage } from './types'
|
import { CodeLanguage } from './types'
|
||||||
import Dependencies from './dependency'
|
|
||||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
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 OutputVarList from '@/app/components/workflow/nodes/_base/components/variable/output-var-list'
|
||||||
import AddButton from '@/app/components/base/button/add-button'
|
import AddButton from '@/app/components/base/button/add-button'
|
||||||
|
@ -60,11 +59,6 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||||
varInputs,
|
varInputs,
|
||||||
inputVarValues,
|
inputVarValues,
|
||||||
setInputVarValues,
|
setInputVarValues,
|
||||||
allowDependencies,
|
|
||||||
availableDependencies,
|
|
||||||
handleAddDependency,
|
|
||||||
handleRemoveDependency,
|
|
||||||
handleChangeDependency,
|
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -84,31 +78,6 @@ const Panel: FC<NodePanelProps<CodeNodeType>> = ({
|
||||||
filterVar={filterVar}
|
filterVar={filterVar}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</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 />
|
<Split />
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
isInNode
|
isInNode
|
||||||
|
|
|
@ -16,10 +16,4 @@ export type CodeNodeType = CommonNodeType & {
|
||||||
code_language: CodeLanguage
|
code_language: CodeLanguage
|
||||||
code: string
|
code: string
|
||||||
outputs: OutputVar
|
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 { BlockEnum, VarType } from '../../types'
|
||||||
import type { Var } from '../../types'
|
import type { Var } from '../../types'
|
||||||
import { useStore } from '../../store'
|
import { useStore } from '../../store'
|
||||||
import type { CodeDependency, CodeNodeType, OutputVar } from './types'
|
import type { CodeNodeType, OutputVar } from './types'
|
||||||
import { CodeLanguage } from './types'
|
import { CodeLanguage } from './types'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
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'
|
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||||
|
@ -21,19 +21,15 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||||
const appId = useAppStore.getState().appDetail?.id
|
const appId = useAppStore.getState().appDetail?.id
|
||||||
|
|
||||||
const [allLanguageDefault, setAllLanguageDefault] = useState<Record<CodeLanguage, CodeNodeType> | null>(null)
|
const [allLanguageDefault, setAllLanguageDefault] = useState<Record<CodeLanguage, CodeNodeType> | null>(null)
|
||||||
const [allLanguageDependencies, setAllLanguageDependencies] = useState<Record<CodeLanguage, CodeDependency[]> | null>(null)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const { config: javaScriptConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.javascript }) as any
|
const { config: javaScriptConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.javascript }) as any
|
||||||
const { config: pythonConfig, available_dependencies: pythonDependencies } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any
|
const { config: pythonConfig } = await fetchNodeDefault(appId, BlockEnum.Code, { code_language: CodeLanguage.python3 }) as any
|
||||||
setAllLanguageDefault({
|
setAllLanguageDefault({
|
||||||
[CodeLanguage.javascript]: javaScriptConfig as CodeNodeType,
|
[CodeLanguage.javascript]: javaScriptConfig as CodeNodeType,
|
||||||
[CodeLanguage.python3]: pythonConfig as CodeNodeType,
|
[CodeLanguage.python3]: pythonConfig as CodeNodeType,
|
||||||
} as any)
|
} as any)
|
||||||
setAllLanguageDependencies({
|
|
||||||
[CodeLanguage.python3]: pythonDependencies as CodeDependency[],
|
|
||||||
} as any)
|
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
}, [appId])
|
}, [appId])
|
||||||
|
@ -45,62 +41,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||||
setInputs,
|
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 [outputKeyOrders, setOutputKeyOrders] = useState<string[]>([])
|
||||||
const syncOutputKeyOrders = useCallback((outputs: OutputVar) => {
|
const syncOutputKeyOrders = useCallback((outputs: OutputVar) => {
|
||||||
setOutputKeyOrders(Object.keys(outputs))
|
setOutputKeyOrders(Object.keys(outputs))
|
||||||
|
@ -223,11 +163,6 @@ const useConfig = (id: string, payload: CodeNodeType) => {
|
||||||
inputVarValues,
|
inputVarValues,
|
||||||
setInputVarValues,
|
setInputVarValues,
|
||||||
runResult,
|
runResult,
|
||||||
availableDependencies,
|
|
||||||
allowDependencies,
|
|
||||||
handleAddDependency,
|
|
||||||
handleRemoveDependency,
|
|
||||||
handleChangeDependency,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user