mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
Compare commits
7 Commits
5b0d7b34aa
...
21f785a2f2
Author | SHA1 | Date | |
---|---|---|---|
|
21f785a2f2 | ||
|
a16a1fffb1 | ||
|
024579febc | ||
|
1c252e3127 | ||
|
a7b175ce09 | ||
|
074746cfd2 | ||
|
ad00cc8ca1 |
|
@ -173,7 +173,7 @@ class BaseAgentRunner(AppRunner):
|
||||||
}:
|
}:
|
||||||
continue
|
continue
|
||||||
enum = []
|
enum = []
|
||||||
if parameter.type == ToolParameter.ToolParameterType.SELECT:
|
if parameter.type in {ToolParameter.ToolParameterType.SELECT, ToolParameter.ToolParameterType.MULTI_SELECT}:
|
||||||
enum = [option.value for option in parameter.options]
|
enum = [option.value for option in parameter.options]
|
||||||
|
|
||||||
message_tool.parameters["properties"][parameter.name] = {
|
message_tool.parameters["properties"][parameter.name] = {
|
||||||
|
@ -264,7 +264,7 @@ class BaseAgentRunner(AppRunner):
|
||||||
}:
|
}:
|
||||||
continue
|
continue
|
||||||
enum = []
|
enum = []
|
||||||
if parameter.type == ToolParameter.ToolParameterType.SELECT:
|
if parameter.type in {ToolParameter.ToolParameterType.SELECT, ToolParameter.ToolParameterType.MULTI_SELECT}:
|
||||||
enum = [option.value for option in parameter.options]
|
enum = [option.value for option in parameter.options]
|
||||||
|
|
||||||
prompt_tool.parameters["properties"][parameter.name] = {
|
prompt_tool.parameters["properties"][parameter.name] = {
|
||||||
|
|
|
@ -142,12 +142,13 @@ class ToolParameter(BaseModel):
|
||||||
NUMBER = "number"
|
NUMBER = "number"
|
||||||
BOOLEAN = "boolean"
|
BOOLEAN = "boolean"
|
||||||
SELECT = "select"
|
SELECT = "select"
|
||||||
|
MULTI_SELECT = "multi-select"
|
||||||
SECRET_INPUT = "secret-input"
|
SECRET_INPUT = "secret-input"
|
||||||
FILE = "file"
|
FILE = "file"
|
||||||
FILES = "files"
|
FILES = "files"
|
||||||
|
|
||||||
# deprecated, should not use.
|
# deprecated, should not use.
|
||||||
SYSTEM_FILES = "systme-files"
|
SYSTEM_FILES = "system-files"
|
||||||
|
|
||||||
def as_normal_type(self):
|
def as_normal_type(self):
|
||||||
if self in {
|
if self in {
|
||||||
|
@ -155,6 +156,8 @@ class ToolParameter(BaseModel):
|
||||||
ToolParameter.ToolParameterType.SELECT,
|
ToolParameter.ToolParameterType.SELECT,
|
||||||
}:
|
}:
|
||||||
return "string"
|
return "string"
|
||||||
|
elif self == ToolParameter.ToolParameterType.MULTI_SELECT:
|
||||||
|
return "array"
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def cast_value(self, value: Any, /):
|
def cast_value(self, value: Any, /):
|
||||||
|
@ -198,6 +201,7 @@ class ToolParameter(BaseModel):
|
||||||
ToolParameter.ToolParameterType.SYSTEM_FILES
|
ToolParameter.ToolParameterType.SYSTEM_FILES
|
||||||
| ToolParameter.ToolParameterType.FILE
|
| ToolParameter.ToolParameterType.FILE
|
||||||
| ToolParameter.ToolParameterType.FILES
|
| ToolParameter.ToolParameterType.FILES
|
||||||
|
| ToolParameter.ToolParameterType.MULTI_SELECT
|
||||||
):
|
):
|
||||||
return value
|
return value
|
||||||
case _:
|
case _:
|
||||||
|
|
|
@ -29,7 +29,7 @@ class CrawlTool(BuiltinTool):
|
||||||
payload["allowExternalLinks"] = tool_parameters.get("allowExternalLinks", False)
|
payload["allowExternalLinks"] = tool_parameters.get("allowExternalLinks", False)
|
||||||
payload["webhook"] = tool_parameters.get("webhook")
|
payload["webhook"] = tool_parameters.get("webhook")
|
||||||
|
|
||||||
scrapeOptions["formats"] = get_array_params(tool_parameters, "formats")
|
scrapeOptions["formats"] = tool_parameters.get("formats")
|
||||||
scrapeOptions["headers"] = get_json_params(tool_parameters, "headers")
|
scrapeOptions["headers"] = get_json_params(tool_parameters, "headers")
|
||||||
scrapeOptions["includeTags"] = get_array_params(tool_parameters, "includeTags")
|
scrapeOptions["includeTags"] = get_array_params(tool_parameters, "includeTags")
|
||||||
scrapeOptions["excludeTags"] = get_array_params(tool_parameters, "excludeTags")
|
scrapeOptions["excludeTags"] = get_array_params(tool_parameters, "excludeTags")
|
||||||
|
|
|
@ -123,18 +123,35 @@ parameters:
|
||||||
form: form
|
form: form
|
||||||
############## Scrape Options #######################
|
############## Scrape Options #######################
|
||||||
- name: formats
|
- name: formats
|
||||||
type: string
|
type: multi-select
|
||||||
label:
|
label:
|
||||||
en_US: Formats
|
en_US: Output Formats
|
||||||
zh_Hans: 结果的格式
|
zh_Hans: 结果的格式
|
||||||
placeholder:
|
options:
|
||||||
en_US: Use commas to separate multiple tags
|
- value: markdown
|
||||||
zh_Hans: 多个标签时使用半角逗号分隔
|
label:
|
||||||
|
en_US: markdown
|
||||||
|
- value: html
|
||||||
|
label:
|
||||||
|
en_US: html
|
||||||
|
- value: rawHtml
|
||||||
|
label:
|
||||||
|
en_US: rawHtml
|
||||||
|
- value: links
|
||||||
|
label:
|
||||||
|
en_US: links
|
||||||
|
- value: screenshot
|
||||||
|
label:
|
||||||
|
en_US: screenshot
|
||||||
|
- value: extract
|
||||||
|
label:
|
||||||
|
en_US: extract
|
||||||
|
- value: screenshot@fullPage
|
||||||
|
label:
|
||||||
|
en_US: screenshot@fullPage
|
||||||
human_description:
|
human_description:
|
||||||
en_US: |
|
en_US: Formats to include in the output. Multiple selections possible.
|
||||||
Formats to include in the output. Available options: markdown, html, rawHtml, links, screenshot
|
zh_Hans: 输出中应包含的格式。可多选。
|
||||||
zh_Hans: |
|
|
||||||
输出中应包含的格式。可以填入: markdown, html, rawHtml, links, screenshot
|
|
||||||
form: form
|
form: form
|
||||||
- name: headers
|
- name: headers
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ScrapeTool(BuiltinTool):
|
||||||
payload = {}
|
payload = {}
|
||||||
extract = {}
|
extract = {}
|
||||||
|
|
||||||
payload["formats"] = get_array_params(tool_parameters, "formats")
|
payload["formats"] = tool_parameters.get("formats")
|
||||||
payload["onlyMainContent"] = tool_parameters.get("onlyMainContent", True)
|
payload["onlyMainContent"] = tool_parameters.get("onlyMainContent", True)
|
||||||
payload["includeTags"] = get_array_params(tool_parameters, "includeTags")
|
payload["includeTags"] = get_array_params(tool_parameters, "includeTags")
|
||||||
payload["excludeTags"] = get_array_params(tool_parameters, "excludeTags")
|
payload["excludeTags"] = get_array_params(tool_parameters, "excludeTags")
|
||||||
|
|
|
@ -23,18 +23,35 @@ parameters:
|
||||||
form: llm
|
form: llm
|
||||||
############## Payload #######################
|
############## Payload #######################
|
||||||
- name: formats
|
- name: formats
|
||||||
type: string
|
type: multi-select
|
||||||
label:
|
label:
|
||||||
en_US: Formats
|
en_US: Output Formats
|
||||||
zh_Hans: 结果的格式
|
zh_Hans: 结果的格式
|
||||||
placeholder:
|
options:
|
||||||
en_US: Use commas to separate multiple tags
|
- value: markdown
|
||||||
zh_Hans: 多个标签时使用半角逗号分隔
|
label:
|
||||||
|
en_US: markdown
|
||||||
|
- value: html
|
||||||
|
label:
|
||||||
|
en_US: html
|
||||||
|
- value: rawHtml
|
||||||
|
label:
|
||||||
|
en_US: rawHtml
|
||||||
|
- value: links
|
||||||
|
label:
|
||||||
|
en_US: links
|
||||||
|
- value: screenshot
|
||||||
|
label:
|
||||||
|
en_US: screenshot
|
||||||
|
- value: extract
|
||||||
|
label:
|
||||||
|
en_US: extract
|
||||||
|
- value: screenshot@fullPage
|
||||||
|
label:
|
||||||
|
en_US: screenshot@fullPage
|
||||||
human_description:
|
human_description:
|
||||||
en_US: |
|
en_US: Formats to include in the output. Multiple selections possible.
|
||||||
Formats to include in the output. Available options: markdown, html, rawHtml, links, screenshot, extract, screenshot@fullPage
|
zh_Hans: 输出中应包含的格式。可多选。
|
||||||
zh_Hans: |
|
|
||||||
输出中应包含的格式。可以填入: markdown, html, rawHtml, links, screenshot, extract, screenshot@fullPage
|
|
||||||
form: form
|
form: form
|
||||||
- name: onlyMainContent
|
- name: onlyMainContent
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
|
@ -7,7 +7,6 @@ from core.tools.entities.tool_entities import ToolParameter, ToolProviderCredent
|
||||||
from core.tools.entities.values import ToolLabelEnum, default_tool_label_dict
|
from core.tools.entities.values import ToolLabelEnum, default_tool_label_dict
|
||||||
from core.tools.errors import (
|
from core.tools.errors import (
|
||||||
ToolNotFoundError,
|
ToolNotFoundError,
|
||||||
ToolParameterValidationError,
|
|
||||||
ToolProviderNotFoundError,
|
ToolProviderNotFoundError,
|
||||||
)
|
)
|
||||||
from core.tools.provider.tool_provider import ToolProviderController
|
from core.tools.provider.tool_provider import ToolProviderController
|
||||||
|
@ -146,70 +145,6 @@ class BuiltinToolProviderController(ToolProviderController):
|
||||||
"""
|
"""
|
||||||
return self.identity.tags or []
|
return self.identity.tags or []
|
||||||
|
|
||||||
def validate_parameters(self, tool_id: int, tool_name: str, tool_parameters: dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
validate the parameters of the tool and set the default value if needed
|
|
||||||
|
|
||||||
:param tool_name: the name of the tool, defined in `get_tools`
|
|
||||||
:param tool_parameters: the parameters of the tool
|
|
||||||
"""
|
|
||||||
tool_parameters_schema = self.get_parameters(tool_name)
|
|
||||||
|
|
||||||
tool_parameters_need_to_validate: dict[str, ToolParameter] = {}
|
|
||||||
for parameter in tool_parameters_schema:
|
|
||||||
tool_parameters_need_to_validate[parameter.name] = parameter
|
|
||||||
|
|
||||||
for parameter in tool_parameters:
|
|
||||||
if parameter not in tool_parameters_need_to_validate:
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} not found in tool {tool_name}")
|
|
||||||
|
|
||||||
# check type
|
|
||||||
parameter_schema = tool_parameters_need_to_validate[parameter]
|
|
||||||
if parameter_schema.type == ToolParameter.ToolParameterType.STRING:
|
|
||||||
if not isinstance(tool_parameters[parameter], str):
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be string")
|
|
||||||
|
|
||||||
elif parameter_schema.type == ToolParameter.ToolParameterType.NUMBER:
|
|
||||||
if not isinstance(tool_parameters[parameter], int | float):
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be number")
|
|
||||||
|
|
||||||
if parameter_schema.min is not None and tool_parameters[parameter] < parameter_schema.min:
|
|
||||||
raise ToolParameterValidationError(
|
|
||||||
f"parameter {parameter} should be greater than {parameter_schema.min}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if parameter_schema.max is not None and tool_parameters[parameter] > parameter_schema.max:
|
|
||||||
raise ToolParameterValidationError(
|
|
||||||
f"parameter {parameter} should be less than {parameter_schema.max}"
|
|
||||||
)
|
|
||||||
|
|
||||||
elif parameter_schema.type == ToolParameter.ToolParameterType.BOOLEAN:
|
|
||||||
if not isinstance(tool_parameters[parameter], bool):
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be boolean")
|
|
||||||
|
|
||||||
elif parameter_schema.type == ToolParameter.ToolParameterType.SELECT:
|
|
||||||
if not isinstance(tool_parameters[parameter], str):
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be string")
|
|
||||||
|
|
||||||
options = parameter_schema.options
|
|
||||||
if not isinstance(options, list):
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} options should be list")
|
|
||||||
|
|
||||||
if tool_parameters[parameter] not in [x.value for x in options]:
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be one of {options}")
|
|
||||||
|
|
||||||
tool_parameters_need_to_validate.pop(parameter)
|
|
||||||
|
|
||||||
for parameter in tool_parameters_need_to_validate:
|
|
||||||
parameter_schema = tool_parameters_need_to_validate[parameter]
|
|
||||||
if parameter_schema.required:
|
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} is required")
|
|
||||||
|
|
||||||
# the parameter is not set currently, set the default value if needed
|
|
||||||
if parameter_schema.default is not None:
|
|
||||||
default_value = parameter_schema.type.cast_value(parameter_schema.default)
|
|
||||||
tool_parameters[parameter] = default_value
|
|
||||||
|
|
||||||
def validate_credentials(self, credentials: dict[str, Any]) -> None:
|
def validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
validate the credentials of the provider
|
validate the credentials of the provider
|
||||||
|
|
|
@ -117,6 +117,18 @@ class ToolProviderController(BaseModel, ABC):
|
||||||
if tool_parameters[parameter] not in [x.value for x in options]:
|
if tool_parameters[parameter] not in [x.value for x in options]:
|
||||||
raise ToolParameterValidationError(f"parameter {parameter} should be one of {options}")
|
raise ToolParameterValidationError(f"parameter {parameter} should be one of {options}")
|
||||||
|
|
||||||
|
elif parameter_schema.type == ToolParameter.ToolParameterType.MULTI_SELECT:
|
||||||
|
if not isinstance(tool_parameters[parameter], list):
|
||||||
|
raise ToolParameterValidationError(f"parameter {parameter} should be list")
|
||||||
|
|
||||||
|
options = parameter_schema.options
|
||||||
|
if not isinstance(options, list):
|
||||||
|
raise ToolParameterValidationError(f"parameter {parameter} options should be list")
|
||||||
|
|
||||||
|
for item in tool_parameters[parameter]:
|
||||||
|
if item not in [x.value for x in options]:
|
||||||
|
raise ToolParameterValidationError(f"parameter {parameter} should be one of {options}")
|
||||||
|
|
||||||
tool_parameters_need_to_validate.pop(parameter)
|
tool_parameters_need_to_validate.pop(parameter)
|
||||||
|
|
||||||
for parameter in tool_parameters_need_to_validate:
|
for parameter in tool_parameters_need_to_validate:
|
||||||
|
|
|
@ -220,6 +220,11 @@ class ToolManager:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"tool parameter {parameter_rule.name} value {parameter_value} not in options {options}"
|
f"tool parameter {parameter_rule.name} value {parameter_value} not in options {options}"
|
||||||
)
|
)
|
||||||
|
elif parameter_rule.type == ToolParameter.ToolParameterType.MULTI_SELECT and parameter_value is not None:
|
||||||
|
options = [x.value for x in parameter_rule.options]
|
||||||
|
for value in parameter_value:
|
||||||
|
if value not in options:
|
||||||
|
raise ValueError(f"tool parameter {parameter_rule.name} value {value} not in options {options}")
|
||||||
|
|
||||||
return parameter_rule.type.cast_value(parameter_value)
|
return parameter_rule.type.cast_value(parameter_value)
|
||||||
|
|
||||||
|
@ -228,7 +233,7 @@ class ToolManager:
|
||||||
cls, tenant_id: str, app_id: str, agent_tool: AgentToolEntity, invoke_from: InvokeFrom = InvokeFrom.DEBUGGER
|
cls, tenant_id: str, app_id: str, agent_tool: AgentToolEntity, invoke_from: InvokeFrom = InvokeFrom.DEBUGGER
|
||||||
) -> Tool:
|
) -> Tool:
|
||||||
"""
|
"""
|
||||||
get the agent tool runtime
|
get the agent tool runtim
|
||||||
"""
|
"""
|
||||||
tool_entity = cls.get_tool_runtime(
|
tool_entity = cls.get_tool_runtime(
|
||||||
provider_type=agent_tool.provider_type,
|
provider_type=agent_tool.provider_type,
|
||||||
|
|
|
@ -5,6 +5,7 @@ def test_get_parameter_type():
|
||||||
assert ToolParameter.ToolParameterType.STRING.as_normal_type() == "string"
|
assert ToolParameter.ToolParameterType.STRING.as_normal_type() == "string"
|
||||||
assert ToolParameter.ToolParameterType.SELECT.as_normal_type() == "string"
|
assert ToolParameter.ToolParameterType.SELECT.as_normal_type() == "string"
|
||||||
assert ToolParameter.ToolParameterType.SECRET_INPUT.as_normal_type() == "string"
|
assert ToolParameter.ToolParameterType.SECRET_INPUT.as_normal_type() == "string"
|
||||||
|
assert ToolParameter.ToolParameterType.MULTI_SELECT.as_normal_type() == "array"
|
||||||
assert ToolParameter.ToolParameterType.BOOLEAN.as_normal_type() == "boolean"
|
assert ToolParameter.ToolParameterType.BOOLEAN.as_normal_type() == "boolean"
|
||||||
assert ToolParameter.ToolParameterType.NUMBER.as_normal_type() == "number"
|
assert ToolParameter.ToolParameterType.NUMBER.as_normal_type() == "number"
|
||||||
assert ToolParameter.ToolParameterType.FILE.as_normal_type() == "file"
|
assert ToolParameter.ToolParameterType.FILE.as_normal_type() == "file"
|
||||||
|
@ -30,6 +31,10 @@ def test_cast_parameter_by_type():
|
||||||
assert ToolParameter.ToolParameterType.SELECT.cast_value(1.0) == "1.0"
|
assert ToolParameter.ToolParameterType.SELECT.cast_value(1.0) == "1.0"
|
||||||
assert ToolParameter.ToolParameterType.SELECT.cast_value(None) == ""
|
assert ToolParameter.ToolParameterType.SELECT.cast_value(None) == ""
|
||||||
|
|
||||||
|
# multi select
|
||||||
|
assert ToolParameter.ToolParameterType.MULTI_SELECT.cast_value(["test", "test2"]) == ["test", "test2"]
|
||||||
|
assert ToolParameter.ToolParameterType.MULTI_SELECT.cast_value([2, 1, 1.0]) == [2, 1, 1.0]
|
||||||
|
|
||||||
# boolean
|
# boolean
|
||||||
true_values = [True, "True", "true", "1", "YES", "Yes", "yes", "y", "something"]
|
true_values = [True, "True", "true", "1", "YES", "Yes", "yes", "y", "something"]
|
||||||
for value in true_values:
|
for value in true_values:
|
||||||
|
|
|
@ -29,7 +29,7 @@ export type Item = {
|
||||||
export type ISelectProps = {
|
export type ISelectProps = {
|
||||||
className?: string
|
className?: string
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
renderTrigger?: (value: Item | null) => JSX.Element | null
|
renderTrigger?: (value: Item | Item[] | null) => JSX.Element | null
|
||||||
items?: Item[]
|
items?: Item[]
|
||||||
defaultValue?: number | string
|
defaultValue?: number | string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
@ -378,5 +378,127 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export { SimpleSelect, PortalSelect }
|
|
||||||
|
export type MultiSelectProps = Omit<ISelectProps, 'onSelect'> & {
|
||||||
|
onSelect: (values: Item[]) => void
|
||||||
|
selectedValues?: (number | string)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const MultiSelect: FC<MultiSelectProps> = ({
|
||||||
|
className,
|
||||||
|
wrapperClassName = '',
|
||||||
|
renderTrigger,
|
||||||
|
items = defaultItems,
|
||||||
|
selectedValues = [],
|
||||||
|
disabled = false,
|
||||||
|
onSelect,
|
||||||
|
placeholder,
|
||||||
|
optionWrapClassName,
|
||||||
|
optionClassName,
|
||||||
|
hideChecked,
|
||||||
|
renderOption,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const localPlaceholder = placeholder || t('common.placeholder.select')
|
||||||
|
|
||||||
|
const [selectedItems, setSelectedItems] = useState<Item[]>([])
|
||||||
|
useEffect(() => {
|
||||||
|
const defaultSelected = items.filter((item: Item) => selectedValues.includes(item.value))
|
||||||
|
setSelectedItems(defaultSelected)
|
||||||
|
}, [selectedValues, items])
|
||||||
|
|
||||||
|
const handleSelect = (newItems: Item[]) => {
|
||||||
|
if (!disabled) {
|
||||||
|
setSelectedItems(newItems)
|
||||||
|
onSelect(newItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = (item: Item) => {
|
||||||
|
const newSelectedItems = selectedItems.filter(i => i.value !== item.value)
|
||||||
|
setSelectedItems(newSelectedItems)
|
||||||
|
onSelect(newSelectedItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Listbox
|
||||||
|
value={selectedItems}
|
||||||
|
onChange={handleSelect}
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<div className={classNames('group/multi-select relative', wrapperClassName)}>
|
||||||
|
{renderTrigger && <Listbox.Button className='w-full'>{renderTrigger(selectedItems)}</Listbox.Button>}
|
||||||
|
{!renderTrigger && (
|
||||||
|
<Listbox.Button className={classNames(`flex flex-wrap items-center w-full min-h-[36px] rounded-lg border-0 bg-gray-100 pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover/multi-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}>
|
||||||
|
{selectedItems.length > 0
|
||||||
|
? (selectedItems.map(item => (
|
||||||
|
<span key={item.value} className="inline-flex items-center m-1 px-2 py-1 rounded bg-gray-200">
|
||||||
|
{item.name}
|
||||||
|
<XMarkIcon
|
||||||
|
className="ml-1 h-4 w-4 text-gray-400 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
removeItem(item)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<span className="text-gray-400">{localPlaceholder}</span>
|
||||||
|
)}
|
||||||
|
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="h-4 w-4 text-text-quaternary group-hover/multi-select:text-text-secondary"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</Listbox.Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!disabled && (
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Listbox.Options className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
|
||||||
|
{items.map((item: Item) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={item.value}
|
||||||
|
className={({ active }) =>
|
||||||
|
classNames(
|
||||||
|
`relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''}`,
|
||||||
|
optionClassName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value={item}
|
||||||
|
>
|
||||||
|
{({ selected }) => (
|
||||||
|
<>
|
||||||
|
{renderOption
|
||||||
|
? renderOption({ item, selected })
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<span className={classNames('block', selected && 'font-normal')}>{item.name}</span>
|
||||||
|
{selected && !hideChecked && (
|
||||||
|
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700">
|
||||||
|
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
</Transition>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export { SimpleSelect, PortalSelect, MultiSelect }
|
||||||
export default React.memo(Select)
|
export default React.memo(Select)
|
||||||
|
|
|
@ -11,6 +11,7 @@ export enum FormTypeEnum {
|
||||||
textNumber = 'number-input',
|
textNumber = 'number-input',
|
||||||
secretInput = 'secret-input',
|
secretInput = 'secret-input',
|
||||||
select = 'select',
|
select = 'select',
|
||||||
|
multiSelect = 'multi-select',
|
||||||
radio = 'radio',
|
radio = 'radio',
|
||||||
boolean = 'boolean',
|
boolean = 'boolean',
|
||||||
files = 'files',
|
files = 'files',
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { FormTypeEnum } from '../declarations'
|
||||||
import { useLanguage } from '../hooks'
|
import { useLanguage } from '../hooks'
|
||||||
import Input from './Input'
|
import Input from './Input'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { SimpleSelect } from '@/app/components/base/select'
|
import { MultiSelect, SimpleSelect } from '@/app/components/base/select'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import Radio from '@/app/components/base/radio'
|
import Radio from '@/app/components/base/radio'
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
|
@ -53,7 +53,7 @@ const Form: FC<FormProps> = ({
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const [changeKey, setChangeKey] = useState('')
|
const [changeKey, setChangeKey] = useState('')
|
||||||
|
|
||||||
const handleFormChange = (key: string, val: string | boolean) => {
|
const handleFormChange = (key: string, val: string | boolean | any[]) => {
|
||||||
if (isEditMode && (key === '__model_type' || key === '__model_name'))
|
if (isEditMode && (key === '__model_type' || key === '__model_name'))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -220,6 +220,44 @@ const Form: FC<FormProps> = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formSchema.type === 'multi-select') {
|
||||||
|
const {
|
||||||
|
options,
|
||||||
|
variable,
|
||||||
|
label,
|
||||||
|
show_on,
|
||||||
|
required,
|
||||||
|
placeholder,
|
||||||
|
} = formSchema as CredentialFormSchemaSelect
|
||||||
|
|
||||||
|
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
|
||||||
|
return null
|
||||||
|
|
||||||
|
const handleMultiSelect = (selectedItems: any[]) => {
|
||||||
|
handleFormChange(variable, selectedItems.map(item => item.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={variable} className={cn(itemClassName, 'py-3')}>
|
||||||
|
<div className={cn(fieldLabelClassName, 'flex items-center py-2 text-sm text-gray-900')}>
|
||||||
|
{label[language] || label.en_US}
|
||||||
|
{required && <span className='ml-1 text-red-500'>*</span>}
|
||||||
|
{tooltipContent}
|
||||||
|
</div>
|
||||||
|
<MultiSelect
|
||||||
|
className={cn(inputClassName)}
|
||||||
|
disabled={readonly}
|
||||||
|
selectedValues={value[variable] || []}
|
||||||
|
items={options.map(item => ({ value: item.value, name: item.label[language] || item.label.en_US }))}
|
||||||
|
onSelect={handleMultiSelect}
|
||||||
|
placeholder={placeholder?.[language] || placeholder?.en_US}
|
||||||
|
/>
|
||||||
|
{fieldMoreInfo?.(formSchema)}
|
||||||
|
{validating && changeKey === variable && <ValidatingTip />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (formSchema.type === 'boolean') {
|
if (formSchema.type === 'boolean') {
|
||||||
const {
|
const {
|
||||||
variable,
|
variable,
|
||||||
|
|
|
@ -50,6 +50,8 @@ const InputVarList: FC<Props> = ({
|
||||||
return 'Files'
|
return 'Files'
|
||||||
else if (type === FormTypeEnum.select)
|
else if (type === FormTypeEnum.select)
|
||||||
return 'Options'
|
return 'Options'
|
||||||
|
else if (type === FormTypeEnum.multiSelect)
|
||||||
|
return 'Array'
|
||||||
else
|
else
|
||||||
return 'String'
|
return 'String'
|
||||||
}
|
}
|
||||||
|
@ -139,9 +141,10 @@ const InputVarList: FC<Props> = ({
|
||||||
const varInput = value[variable]
|
const varInput = value[variable]
|
||||||
const isNumber = type === FormTypeEnum.textNumber
|
const isNumber = type === FormTypeEnum.textNumber
|
||||||
const isSelect = type === FormTypeEnum.select
|
const isSelect = type === FormTypeEnum.select
|
||||||
|
const isMultiSelect = type === FormTypeEnum.multiSelect
|
||||||
const isFile = type === FormTypeEnum.file
|
const isFile = type === FormTypeEnum.file
|
||||||
const isFileArray = type === FormTypeEnum.files
|
const isFileArray = type === FormTypeEnum.files
|
||||||
const isString = !isNumber && !isSelect && !isFile && !isFileArray
|
const isString = !isNumber && !isSelect && !isMultiSelect && !isFile && !isFileArray
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={variable} className='space-y-1'>
|
<div key={variable} className='space-y-1'>
|
||||||
|
@ -177,6 +180,20 @@ const InputVarList: FC<Props> = ({
|
||||||
schema={schema}
|
schema={schema}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
isMultiSelect && (
|
||||||
|
<VarReferencePicker
|
||||||
|
readonly={readOnly}
|
||||||
|
isShowNodeName
|
||||||
|
nodeId={nodeId}
|
||||||
|
value={varInput?.value || []}
|
||||||
|
onChange={handleNotMixedTypeChange(variable)}
|
||||||
|
onOpen={handleOpen(index)}
|
||||||
|
defaultVarKindType={VarKindType.variable}
|
||||||
|
filterVar={(varPayload: Var) => varPayload.type === VarType.arrayString || varPayload.type === VarType.arrayNumber}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{isFile && (
|
{isFile && (
|
||||||
<VarReferencePicker
|
<VarReferencePicker
|
||||||
readonly={readOnly}
|
readonly={readOnly}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user