mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 03:32:23 +08:00
Merge branch 'fix/workflow-start-form-default-value' into deploy/dev
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
This commit is contained in:
commit
653542197b
|
@ -0,0 +1,3 @@
|
|||
from .code_executor import CodeExecutor, CodeLanguage
|
||||
|
||||
__all__ = ["CodeExecutor", "CodeLanguage"]
|
|
@ -1,7 +1,8 @@
|
|||
import logging
|
||||
from collections.abc import Mapping
|
||||
from enum import Enum
|
||||
from threading import Lock
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from httpx import Timeout, post
|
||||
from pydantic import BaseModel
|
||||
|
@ -117,7 +118,7 @@ class CodeExecutor:
|
|||
return response.data.stdout or ""
|
||||
|
||||
@classmethod
|
||||
def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: dict) -> dict:
|
||||
def execute_workflow_code_template(cls, language: CodeLanguage, code: str, inputs: Mapping[str, Any]) -> dict:
|
||||
"""
|
||||
Execute code
|
||||
:param language: code language
|
||||
|
|
|
@ -2,6 +2,8 @@ import json
|
|||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from base64 import b64encode
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
|
||||
class TemplateTransformer(ABC):
|
||||
|
@ -10,7 +12,7 @@ class TemplateTransformer(ABC):
|
|||
_result_tag: str = "<<RESULT>>"
|
||||
|
||||
@classmethod
|
||||
def transform_caller(cls, code: str, inputs: dict) -> tuple[str, str]:
|
||||
def transform_caller(cls, code: str, inputs: Mapping[str, Any]) -> tuple[str, str]:
|
||||
"""
|
||||
Transform code to python runner
|
||||
:param code: code
|
||||
|
@ -48,13 +50,13 @@ class TemplateTransformer(ABC):
|
|||
pass
|
||||
|
||||
@classmethod
|
||||
def serialize_inputs(cls, inputs: dict) -> str:
|
||||
def serialize_inputs(cls, inputs: Mapping[str, Any]) -> str:
|
||||
inputs_json_str = json.dumps(inputs, ensure_ascii=False).encode()
|
||||
input_base64_encoded = b64encode(inputs_json_str).decode("utf-8")
|
||||
return input_base64_encoded
|
||||
|
||||
@classmethod
|
||||
def assemble_runner_script(cls, code: str, inputs: dict) -> str:
|
||||
def assemble_runner_script(cls, code: str, inputs: Mapping[str, Any]) -> str:
|
||||
# assemble runner script
|
||||
script = cls.get_runner_script()
|
||||
script = script.replace(cls._code_placeholder, code)
|
||||
|
|
|
@ -55,6 +55,7 @@ class JinaRerankModel(RerankModel):
|
|||
base_url + "/rerank",
|
||||
json={"model": model, "query": query, "documents": docs, "top_n": top_n},
|
||||
headers={"Authorization": f"Bearer {credentials.get('api_key')}"},
|
||||
timeout=20,
|
||||
)
|
||||
response.raise_for_status()
|
||||
results = response.json()
|
||||
|
|
|
@ -32,3 +32,15 @@ credentials_for_provider:
|
|||
placeholder:
|
||||
en_US: Enter your TTS service API key
|
||||
zh_Hans: 输入您的 TTS 服务 API 密钥
|
||||
openai_base_url:
|
||||
type: text-input
|
||||
required: false
|
||||
label:
|
||||
en_US: OpenAI base URL
|
||||
zh_Hans: OpenAI base URL
|
||||
help:
|
||||
en_US: Please input your OpenAI base URL
|
||||
zh_Hans: 请输入你的 OpenAI base URL
|
||||
placeholder:
|
||||
en_US: Please input your OpenAI base URL
|
||||
zh_Hans: 请输入你的 OpenAI base URL
|
||||
|
|
|
@ -5,6 +5,7 @@ import warnings
|
|||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
import openai
|
||||
from yarl import URL
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.errors import ToolParameterValidationError, ToolProviderCredentialValidationError
|
||||
|
@ -53,15 +54,24 @@ class PodcastAudioGeneratorTool(BuiltinTool):
|
|||
if not host1_voice or not host2_voice:
|
||||
raise ToolParameterValidationError("Host voices are required")
|
||||
|
||||
# Get OpenAI API key from credentials
|
||||
# Ensure runtime and credentials
|
||||
if not self.runtime or not self.runtime.credentials:
|
||||
raise ToolProviderCredentialValidationError("Tool runtime or credentials are missing")
|
||||
|
||||
# Get OpenAI API key from credentials
|
||||
api_key = self.runtime.credentials.get("api_key")
|
||||
if not api_key:
|
||||
raise ToolProviderCredentialValidationError("OpenAI API key is missing")
|
||||
|
||||
# Get OpenAI base URL
|
||||
openai_base_url = self.runtime.credentials.get("openai_base_url", None)
|
||||
openai_base_url = str(URL(openai_base_url) / "v1") if openai_base_url else None
|
||||
|
||||
# Initialize OpenAI client
|
||||
client = openai.OpenAI(api_key=api_key)
|
||||
client = openai.OpenAI(
|
||||
api_key=api_key,
|
||||
base_url=openai_base_url,
|
||||
)
|
||||
|
||||
# Create a thread pool
|
||||
max_workers = 5
|
||||
|
|
|
@ -49,8 +49,14 @@ class Limit(BaseModel):
|
|||
size: int = -1
|
||||
|
||||
|
||||
class ExtractConfig(BaseModel):
|
||||
enabled: bool = False
|
||||
serial: str = "1"
|
||||
|
||||
|
||||
class ListOperatorNodeData(BaseNodeData):
|
||||
variable: Sequence[str] = Field(default_factory=list)
|
||||
filter_by: FilterBy
|
||||
order_by: OrderBy
|
||||
limit: Limit
|
||||
extract_by: ExtractConfig
|
||||
|
|
|
@ -58,6 +58,10 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]):
|
|||
if self.node_data.filter_by.enabled:
|
||||
variable = self._apply_filter(variable)
|
||||
|
||||
# Extract
|
||||
if self.node_data.extract_by.enabled:
|
||||
variable = self._extract_slice(variable)
|
||||
|
||||
# Order
|
||||
if self.node_data.order_by.enabled:
|
||||
variable = self._apply_order(variable)
|
||||
|
@ -140,6 +144,16 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]):
|
|||
result = variable.value[: self.node_data.limit.size]
|
||||
return variable.model_copy(update={"value": result})
|
||||
|
||||
def _extract_slice(
|
||||
self, variable: Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]
|
||||
) -> Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]:
|
||||
value = int(self.graph_runtime_state.variable_pool.convert_template(self.node_data.extract_by.serial).text) - 1
|
||||
if len(variable.value) > int(value):
|
||||
result = variable.value[value]
|
||||
else:
|
||||
result = ""
|
||||
return variable.model_copy(update={"value": [result]})
|
||||
|
||||
|
||||
def _get_file_extract_number_func(*, key: str) -> Callable[[File], int]:
|
||||
match key:
|
||||
|
|
|
@ -4,7 +4,14 @@ import pytest
|
|||
|
||||
from core.file import File, FileTransferMethod, FileType
|
||||
from core.variables import ArrayFileSegment
|
||||
from core.workflow.nodes.list_operator.entities import FilterBy, FilterCondition, Limit, ListOperatorNodeData, OrderBy
|
||||
from core.workflow.nodes.list_operator.entities import (
|
||||
ExtractConfig,
|
||||
FilterBy,
|
||||
FilterCondition,
|
||||
Limit,
|
||||
ListOperatorNodeData,
|
||||
OrderBy,
|
||||
)
|
||||
from core.workflow.nodes.list_operator.exc import InvalidKeyError
|
||||
from core.workflow.nodes.list_operator.node import ListOperatorNode, _get_file_extract_string_func
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
|
@ -22,6 +29,7 @@ def list_operator_node():
|
|||
),
|
||||
"order_by": OrderBy(enabled=False, value="asc"),
|
||||
"limit": Limit(enabled=False, size=0),
|
||||
"extract_by": ExtractConfig(enabled=False, serial="1"),
|
||||
"title": "Test Title",
|
||||
}
|
||||
node_data = ListOperatorNodeData(**config)
|
||||
|
|
|
@ -173,7 +173,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||
const conversationInputs: Record<string, any> = {}
|
||||
|
||||
inputsForms.forEach((item: any) => {
|
||||
conversationInputs[item.variable] = item.default || ''
|
||||
conversationInputs[item.variable] = item.default || null
|
||||
})
|
||||
handleNewConversationInputsChange(conversationInputs)
|
||||
}, [handleNewConversationInputsChange, inputsForms])
|
||||
|
|
|
@ -159,7 +159,7 @@ export const useEmbeddedChatbot = () => {
|
|||
const conversationInputs: Record<string, any> = {}
|
||||
|
||||
inputsForms.forEach((item: any) => {
|
||||
conversationInputs[item.variable] = item.default || ''
|
||||
conversationInputs[item.variable] = item.default || null
|
||||
})
|
||||
handleNewConversationInputsChange(conversationInputs)
|
||||
}, [handleNewConversationInputsChange, inputsForms])
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
|
@ -32,20 +33,31 @@ const MarkdownForm = ({ node }: any) => {
|
|||
// </form>
|
||||
const { onSend } = useChatContext()
|
||||
|
||||
const getFormValues = (children: any) => {
|
||||
const formValues: { [key: string]: any } = {}
|
||||
children.forEach((child: any) => {
|
||||
if (child.tagName === SUPPORTED_TAGS.INPUT)
|
||||
formValues[child.properties.name] = child.properties.value
|
||||
if (child.tagName === SUPPORTED_TAGS.TEXTAREA)
|
||||
formValues[child.properties.name] = child.properties.value
|
||||
const [formValues, setFormValues] = useState<{ [key: string]: any }>({})
|
||||
|
||||
useEffect(() => {
|
||||
const initialValues: { [key: string]: any } = {}
|
||||
node.children.forEach((child: any) => {
|
||||
if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName))
|
||||
initialValues[child.properties.name] = child.properties.value
|
||||
})
|
||||
return formValues
|
||||
setFormValues(initialValues)
|
||||
}, [node.children])
|
||||
|
||||
const getFormValues = (children: any) => {
|
||||
const values: { [key: string]: any } = {}
|
||||
children.forEach((child: any) => {
|
||||
if ([SUPPORTED_TAGS.INPUT, SUPPORTED_TAGS.TEXTAREA].includes(child.tagName))
|
||||
values[child.properties.name] = formValues[child.properties.name]
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
const onSubmit = (e: any) => {
|
||||
e.preventDefault()
|
||||
const format = node.properties.dataFormat || DATA_FORMAT.TEXT
|
||||
const result = getFormValues(node.children)
|
||||
|
||||
if (format === DATA_FORMAT.JSON) {
|
||||
onSend?.(JSON.stringify(result))
|
||||
}
|
||||
|
@ -77,25 +89,22 @@ const MarkdownForm = ({ node }: any) => {
|
|||
</label>
|
||||
)
|
||||
}
|
||||
if (child.tagName === SUPPORTED_TAGS.INPUT) {
|
||||
if (Object.values(SUPPORTED_TYPES).includes(child.properties.type)) {
|
||||
return (
|
||||
<Input
|
||||
key={index}
|
||||
type={child.properties.type}
|
||||
name={child.properties.name}
|
||||
placeholder={child.properties.placeholder}
|
||||
value={child.properties.value}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
child.properties.value = e.target.value
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return <p key={index}>Unsupported input type: {child.properties.type}</p>
|
||||
}
|
||||
if (child.tagName === SUPPORTED_TAGS.INPUT && Object.values(SUPPORTED_TYPES).includes(child.properties.type)) {
|
||||
return (
|
||||
<Input
|
||||
key={index}
|
||||
type={child.properties.type}
|
||||
name={child.properties.name}
|
||||
placeholder={child.properties.placeholder}
|
||||
value={formValues[child.properties.name]}
|
||||
onChange={(e) => {
|
||||
setFormValues(prevValues => ({
|
||||
...prevValues,
|
||||
[child.properties.name]: e.target.value,
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (child.tagName === SUPPORTED_TAGS.TEXTAREA) {
|
||||
return (
|
||||
|
@ -103,10 +112,12 @@ const MarkdownForm = ({ node }: any) => {
|
|||
key={index}
|
||||
name={child.properties.name}
|
||||
placeholder={child.properties.placeholder}
|
||||
value={child.properties.value}
|
||||
value={formValues[child.properties.name]}
|
||||
onChange={(e) => {
|
||||
e.preventDefault()
|
||||
child.properties.value = e.target.value
|
||||
setFormValues(prevValues => ({
|
||||
...prevValues,
|
||||
[child.properties.name]: e.target.value,
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -15,7 +15,6 @@ import Category from './category'
|
|||
import Tools from './tools'
|
||||
import cn from '@/utils/classnames'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
|
@ -44,13 +43,15 @@ const AddToolModal: FC<Props> = ({
|
|||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const language = getLanguage(locale)
|
||||
const [currentType, setCurrentType] = useState('builtin')
|
||||
const [currentCategory, setCurrentCategory] = useState('')
|
||||
const [keywords, setKeywords] = useState<string>('')
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
}
|
||||
const isMatchingKeywords = (text: string, keywords: string) => {
|
||||
return text.toLowerCase().includes(keywords.toLowerCase())
|
||||
}
|
||||
const [toolList, setToolList] = useState<ToolWithProvider[]>([])
|
||||
const [listLoading, setListLoading] = useState(true)
|
||||
const getAllTools = async () => {
|
||||
|
@ -82,13 +83,16 @@ const AddToolModal: FC<Props> = ({
|
|||
else
|
||||
return toolWithProvider.labels.includes(currentCategory)
|
||||
}).filter((toolWithProvider) => {
|
||||
return toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return label.toLowerCase().includes(keywords.toLowerCase())
|
||||
return (
|
||||
isMatchingKeywords(toolWithProvider.name, keywords)
|
||||
|| toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return isMatchingKeywords(label, keywords)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
}, [currentType, currentCategory, toolList, keywords, language])
|
||||
}, [currentType, currentCategory, toolList, keywords])
|
||||
|
||||
const {
|
||||
modelConfig,
|
||||
|
|
|
@ -11,7 +11,6 @@ import { ToolTypeEnum } from './types'
|
|||
import Tools from './tools'
|
||||
import { useToolTabs } from './hooks'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
|
||||
type AllToolsProps = {
|
||||
searchText: string
|
||||
|
@ -21,13 +20,16 @@ const AllTools = ({
|
|||
searchText,
|
||||
onSelect,
|
||||
}: AllToolsProps) => {
|
||||
const language = useGetLanguage()
|
||||
const tabs = useToolTabs()
|
||||
const [activeTab, setActiveTab] = useState(ToolTypeEnum.All)
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
|
||||
const isMatchingKeywords = (text: string, keywords: string) => {
|
||||
return text.toLowerCase().includes(keywords.toLowerCase())
|
||||
}
|
||||
|
||||
const tools = useMemo(() => {
|
||||
let mergedTools: ToolWithProvider[] = []
|
||||
if (activeTab === ToolTypeEnum.All)
|
||||
|
@ -40,11 +42,14 @@ const AllTools = ({
|
|||
mergedTools = workflowTools
|
||||
|
||||
return mergedTools.filter((toolWithProvider) => {
|
||||
return toolWithProvider.tools.some((tool) => {
|
||||
return tool.label[language].toLowerCase().includes(searchText.toLowerCase())
|
||||
return isMatchingKeywords(toolWithProvider.name, searchText)
|
||||
|| toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return isMatchingKeywords(label, searchText)
|
||||
})
|
||||
})
|
||||
})
|
||||
}, [activeTab, buildInTools, customTools, workflowTools, searchText, language])
|
||||
}, [activeTab, buildInTools, customTools, workflowTools, searchText])
|
||||
return (
|
||||
<div>
|
||||
<div className='flex items-center px-3 h-8 space-x-1 bg-gray-25 border-b-[0.5px] border-black/[0.08] shadow-xs'>
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { VarType } from '../../../types'
|
||||
import type { Var } from '../../../types'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
readOnly: boolean
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
const ExtractInput: FC<Props> = ({
|
||||
nodeId,
|
||||
readOnly,
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isFocus, setIsFocus] = useState(false)
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.number].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='flex items-start space-x-1'>
|
||||
<Input
|
||||
instanceId='http-extract-number'
|
||||
className={cn(isFocus ? 'shadow-xs bg-gray-50 border-gray-300' : 'bg-gray-100 border-gray-100', 'w-0 grow rounded-lg px-3 py-[6px] border')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
readOnly={readOnly}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={setIsFocus}
|
||||
placeholder={!readOnly ? t('workflow.nodes.http.extractListPlaceholder')! : ''}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
export default React.memo(ExtractInput)
|
|
@ -12,6 +12,10 @@ const nodeDefault: NodeDefault<ListFilterNodeType> = {
|
|||
enabled: false,
|
||||
conditions: [],
|
||||
},
|
||||
extract_by: {
|
||||
enabled: false,
|
||||
serial: '1',
|
||||
},
|
||||
order_by: {
|
||||
enabled: false,
|
||||
key: '',
|
||||
|
|
|
@ -13,6 +13,7 @@ import FilterCondition from './components/filter-condition'
|
|||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import { type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import ExtractInput from '@/app/components/workflow/nodes/list-operator/components/extract-input'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.listFilter'
|
||||
|
||||
|
@ -32,6 +33,8 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||
filterVar,
|
||||
handleFilterEnabledChange,
|
||||
handleFilterChange,
|
||||
handleExtractsEnabledChange,
|
||||
handleExtractsChange,
|
||||
handleLimitChange,
|
||||
handleOrderByEnabledChange,
|
||||
handleOrderByKeyChange,
|
||||
|
@ -79,6 +82,41 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||
: null}
|
||||
</Field>
|
||||
<Split />
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.extractsCondition`)}
|
||||
operations={
|
||||
<Switch
|
||||
defaultValue={inputs.extract_by?.enabled}
|
||||
onChange={handleExtractsEnabledChange}
|
||||
size='md'
|
||||
disabled={readOnly}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{inputs.extract_by?.enabled
|
||||
? (
|
||||
<div className='flex items-center justify-between'>
|
||||
{hasSubVariable && (
|
||||
<div className='grow mr-2'>
|
||||
<ExtractInput
|
||||
value={inputs.extract_by.serial as string}
|
||||
onChange={handleExtractsChange}
|
||||
readOnly={readOnly}
|
||||
nodeId={id}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
</Field>
|
||||
<Split />
|
||||
<LimitConfig
|
||||
config={inputs.limit}
|
||||
onChange={handleLimitChange}
|
||||
readonly={readOnly}
|
||||
/>
|
||||
<Split />
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.orderBy`)}
|
||||
operations={
|
||||
|
@ -118,13 +156,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
|||
: null}
|
||||
</Field>
|
||||
<Split />
|
||||
<LimitConfig
|
||||
config={inputs.limit}
|
||||
onChange={handleLimitChange}
|
||||
readonly={readOnly}
|
||||
/>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<OutputVars>
|
||||
<>
|
||||
|
|
|
@ -25,6 +25,10 @@ export type ListFilterNodeType = CommonNodeType & {
|
|||
enabled: boolean
|
||||
conditions: Condition[]
|
||||
}
|
||||
extract_by: {
|
||||
enabled: boolean
|
||||
serial?: string
|
||||
}
|
||||
order_by: {
|
||||
enabled: boolean
|
||||
key: ValueSelector | string
|
||||
|
|
|
@ -119,6 +119,22 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
|
|||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleExtractsEnabledChange = useCallback((enabled: boolean) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.extract_by.enabled = enabled
|
||||
if (enabled)
|
||||
draft.extract_by.serial = '1'
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleExtractsChange = useCallback((value: string) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.extract_by.serial = value
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleOrderByEnabledChange = useCallback((enabled: boolean) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.order_by.enabled = enabled
|
||||
|
@ -162,6 +178,8 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
|
|||
handleOrderByEnabledChange,
|
||||
handleOrderByKeyChange,
|
||||
handleOrderByTypeChange,
|
||||
handleExtractsEnabledChange,
|
||||
handleExtractsChange,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -369,6 +369,7 @@ const translation = {
|
|||
inputVars: 'Input Variables',
|
||||
api: 'API',
|
||||
apiPlaceholder: 'Enter URL, type ‘/’ insert variable',
|
||||
extractListPlaceholder: 'Enter list item index, type ‘/’ insert variable',
|
||||
notStartWithHttp: 'API should start with http:// or https://',
|
||||
key: 'Key',
|
||||
type: 'Type',
|
||||
|
@ -605,6 +606,7 @@ const translation = {
|
|||
inputVar: 'Input Variable',
|
||||
filterCondition: 'Filter Condition',
|
||||
filterConditionKey: 'Filter Condition Key',
|
||||
extractsCondition: 'Extract the N item',
|
||||
filterConditionComparisonOperator: 'Filter Condition Comparison Operator',
|
||||
filterConditionComparisonValue: 'Filter Condition value',
|
||||
selectVariableKeyPlaceholder: 'Select sub variable key',
|
||||
|
|
|
@ -369,6 +369,7 @@ const translation = {
|
|||
inputVars: '输入变量',
|
||||
api: 'API',
|
||||
apiPlaceholder: '输入 URL,输入变量时请键入‘/’',
|
||||
extractListPlaceholder: '输入提取列表编号,输入变量时请键入‘/’',
|
||||
notStartWithHttp: 'API 应该以 http:// 或 https:// 开头',
|
||||
key: '键',
|
||||
type: '类型',
|
||||
|
@ -608,6 +609,7 @@ const translation = {
|
|||
filterConditionComparisonOperator: '过滤条件比较操作符',
|
||||
filterConditionComparisonValue: '过滤条件比较值',
|
||||
selectVariableKeyPlaceholder: '选择子变量的 Key',
|
||||
extractsCondition: '取第 N 项',
|
||||
limit: '取前 N 项',
|
||||
orderBy: '排序',
|
||||
asc: '升序',
|
||||
|
|
Loading…
Reference in New Issue
Block a user