mirror of
https://gitee.com/dify_ai/dify.git
synced 2025-12-07 03:45:27 +08:00
Compare commits
12 Commits
feat/add-c
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b04e603f2 | ||
|
|
2ed6bb86c1 | ||
|
|
61da0f08dd | ||
|
|
1432c268a8 | ||
|
|
ec6a03afdd | ||
|
|
bf371a6e5d | ||
|
|
b28cf68097 | ||
|
|
a0af7a51ed | ||
|
|
dfa3ef0564 | ||
|
|
0066531266 | ||
|
|
53a7cb0e9d | ||
|
|
86739bea8b |
@@ -1,6 +1,5 @@
|
||||
import urllib.parse
|
||||
|
||||
from flask_login import current_user
|
||||
from flask_restful import marshal_with, reqparse
|
||||
|
||||
from controllers.common import helpers
|
||||
@@ -27,7 +26,7 @@ class RemoteFileInfoApi(WebApiResource):
|
||||
|
||||
class RemoteFileUploadApi(WebApiResource):
|
||||
@marshal_with(file_fields_with_signed_url)
|
||||
def post(self):
|
||||
def post(self, app_model, end_user): # Add app_model and end_user parameters
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument("url", type=str, required=True, help="URL is required")
|
||||
args = parser.parse_args()
|
||||
@@ -51,7 +50,7 @@ class RemoteFileUploadApi(WebApiResource):
|
||||
filename=file_info.filename,
|
||||
content=content,
|
||||
mimetype=file_info.mimetype,
|
||||
user=current_user,
|
||||
user=end_user, # Use end_user instead of current_user
|
||||
source_url=url,
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
@@ -76,6 +76,7 @@ class BaseAppGenerator:
|
||||
|
||||
def _validate_input(self, *, inputs: Mapping[str, Any], var: "VariableEntity"):
|
||||
user_input_value = inputs.get(var.variable)
|
||||
|
||||
if not user_input_value:
|
||||
if var.required:
|
||||
raise ValueError(f"{var.variable} is required in input form")
|
||||
@@ -88,6 +89,7 @@ class BaseAppGenerator:
|
||||
VariableEntityType.PARAGRAPH,
|
||||
} and not isinstance(user_input_value, str):
|
||||
raise ValueError(f"(type '{var.type}') {var.variable} in input form must be a string")
|
||||
|
||||
if var.type == VariableEntityType.NUMBER and isinstance(user_input_value, str):
|
||||
# may raise ValueError if user_input_value is not a valid number
|
||||
try:
|
||||
@@ -97,25 +99,30 @@ class BaseAppGenerator:
|
||||
return int(user_input_value)
|
||||
except ValueError:
|
||||
raise ValueError(f"{var.variable} in input form must be a valid number")
|
||||
if var.type == VariableEntityType.SELECT:
|
||||
options = var.options
|
||||
if user_input_value not in options:
|
||||
raise ValueError(f"{var.variable} in input form must be one of the following: {options}")
|
||||
elif var.type in {VariableEntityType.TEXT_INPUT, VariableEntityType.PARAGRAPH}:
|
||||
if var.max_length and len(user_input_value) > var.max_length:
|
||||
raise ValueError(f"{var.variable} in input form must be less than {var.max_length} characters")
|
||||
elif var.type == VariableEntityType.FILE:
|
||||
if not isinstance(user_input_value, dict) and not isinstance(user_input_value, File):
|
||||
raise ValueError(f"{var.variable} in input form must be a file")
|
||||
elif var.type == VariableEntityType.FILE_LIST:
|
||||
if not (
|
||||
isinstance(user_input_value, list)
|
||||
and (
|
||||
all(isinstance(item, dict) for item in user_input_value)
|
||||
or all(isinstance(item, File) for item in user_input_value)
|
||||
)
|
||||
):
|
||||
raise ValueError(f"{var.variable} in input form must be a list of files")
|
||||
|
||||
match var.type:
|
||||
case VariableEntityType.SELECT:
|
||||
if user_input_value not in var.options:
|
||||
raise ValueError(f"{var.variable} in input form must be one of the following: {var.options}")
|
||||
case VariableEntityType.TEXT_INPUT | VariableEntityType.PARAGRAPH:
|
||||
if var.max_length and len(user_input_value) > var.max_length:
|
||||
raise ValueError(f"{var.variable} in input form must be less than {var.max_length} characters")
|
||||
case VariableEntityType.FILE:
|
||||
if not isinstance(user_input_value, dict) and not isinstance(user_input_value, File):
|
||||
raise ValueError(f"{var.variable} in input form must be a file")
|
||||
case VariableEntityType.FILE_LIST:
|
||||
# if number of files exceeds the limit, raise ValueError
|
||||
if not (
|
||||
isinstance(user_input_value, list)
|
||||
and (
|
||||
all(isinstance(item, dict) for item in user_input_value)
|
||||
or all(isinstance(item, File) for item in user_input_value)
|
||||
)
|
||||
):
|
||||
raise ValueError(f"{var.variable} in input form must be a list of files")
|
||||
|
||||
if var.max_length and len(user_input_value) > var.max_length:
|
||||
raise ValueError(f"{var.variable} in input form must be less than {var.max_length} files")
|
||||
|
||||
return user_input_value
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ label:
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 131072
|
||||
|
||||
@@ -5,6 +5,7 @@ label:
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 131072
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import concurrent.futures
|
||||
import io
|
||||
import random
|
||||
import warnings
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from warnings import catch_warnings
|
||||
|
||||
import openai
|
||||
|
||||
@@ -10,7 +10,8 @@ from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.errors import ToolParameterValidationError, ToolProviderCredentialValidationError
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
with catch_warnings(action="ignore", category=RuntimeWarning):
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
from pydub import AudioSegment
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class DocumentExtractorError(Exception):
|
||||
class DocumentExtractorError(ValueError):
|
||||
"""Base exception for errors related to the DocumentExtractorNode."""
|
||||
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ import docx
|
||||
import pandas as pd
|
||||
import pypdfium2
|
||||
import yaml
|
||||
from unstructured.partition.api import partition_via_api
|
||||
from unstructured.partition.email import partition_email
|
||||
from unstructured.partition.epub import partition_epub
|
||||
from unstructured.partition.msg import partition_msg
|
||||
from unstructured.partition.ppt import partition_ppt
|
||||
from unstructured.partition.pptx import partition_pptx
|
||||
|
||||
from configs import dify_config
|
||||
from core.file import File, FileTransferMethod, file_manager
|
||||
from core.helper import ssrf_proxy
|
||||
from core.variables import ArrayFileSegment
|
||||
@@ -263,7 +265,14 @@ def _extract_text_from_ppt(file_content: bytes) -> str:
|
||||
def _extract_text_from_pptx(file_content: bytes) -> str:
|
||||
try:
|
||||
with io.BytesIO(file_content) as file:
|
||||
elements = partition_pptx(file=file)
|
||||
if dify_config.UNSTRUCTURED_API_URL and dify_config.UNSTRUCTURED_API_KEY:
|
||||
elements = partition_via_api(
|
||||
file=file,
|
||||
api_url=dify_config.UNSTRUCTURED_API_URL,
|
||||
api_key=dify_config.UNSTRUCTURED_API_KEY,
|
||||
)
|
||||
else:
|
||||
elements = partition_pptx(file=file)
|
||||
return "\n".join([getattr(element, "text", "") for element in elements])
|
||||
except Exception as e:
|
||||
raise TextExtractionError(f"Failed to extract text from PPTX: {str(e)}") from e
|
||||
|
||||
18
api/core/workflow/nodes/http_request/exc.py
Normal file
18
api/core/workflow/nodes/http_request/exc.py
Normal file
@@ -0,0 +1,18 @@
|
||||
class HttpRequestNodeError(ValueError):
|
||||
"""Custom error for HTTP request node."""
|
||||
|
||||
|
||||
class AuthorizationConfigError(HttpRequestNodeError):
|
||||
"""Raised when authorization config is missing or invalid."""
|
||||
|
||||
|
||||
class FileFetchError(HttpRequestNodeError):
|
||||
"""Raised when a file cannot be fetched."""
|
||||
|
||||
|
||||
class InvalidHttpMethodError(HttpRequestNodeError):
|
||||
"""Raised when an invalid HTTP method is used."""
|
||||
|
||||
|
||||
class ResponseSizeError(HttpRequestNodeError):
|
||||
"""Raised when the response size exceeds the allowed threshold."""
|
||||
@@ -18,6 +18,12 @@ from .entities import (
|
||||
HttpRequestNodeTimeout,
|
||||
Response,
|
||||
)
|
||||
from .exc import (
|
||||
AuthorizationConfigError,
|
||||
FileFetchError,
|
||||
InvalidHttpMethodError,
|
||||
ResponseSizeError,
|
||||
)
|
||||
|
||||
BODY_TYPE_TO_CONTENT_TYPE = {
|
||||
"json": "application/json",
|
||||
@@ -51,7 +57,7 @@ class Executor:
|
||||
# If authorization API key is present, convert the API key using the variable pool
|
||||
if node_data.authorization.type == "api-key":
|
||||
if node_data.authorization.config is None:
|
||||
raise ValueError("authorization config is required")
|
||||
raise AuthorizationConfigError("authorization config is required")
|
||||
node_data.authorization.config.api_key = variable_pool.convert_template(
|
||||
node_data.authorization.config.api_key
|
||||
).text
|
||||
@@ -116,7 +122,7 @@ class Executor:
|
||||
file_selector = data[0].file
|
||||
file_variable = self.variable_pool.get_file(file_selector)
|
||||
if file_variable is None:
|
||||
raise ValueError(f"cannot fetch file with selector {file_selector}")
|
||||
raise FileFetchError(f"cannot fetch file with selector {file_selector}")
|
||||
file = file_variable.value
|
||||
self.content = file_manager.download(file)
|
||||
case "x-www-form-urlencoded":
|
||||
@@ -155,12 +161,12 @@ class Executor:
|
||||
headers = deepcopy(self.headers) or {}
|
||||
if self.auth.type == "api-key":
|
||||
if self.auth.config is None:
|
||||
raise ValueError("self.authorization config is required")
|
||||
raise AuthorizationConfigError("self.authorization config is required")
|
||||
if authorization.config is None:
|
||||
raise ValueError("authorization config is required")
|
||||
raise AuthorizationConfigError("authorization config is required")
|
||||
|
||||
if self.auth.config.api_key is None:
|
||||
raise ValueError("api_key is required")
|
||||
raise AuthorizationConfigError("api_key is required")
|
||||
|
||||
if not authorization.config.header:
|
||||
authorization.config.header = "Authorization"
|
||||
@@ -183,7 +189,7 @@ class Executor:
|
||||
else dify_config.HTTP_REQUEST_NODE_MAX_TEXT_SIZE
|
||||
)
|
||||
if executor_response.size > threshold_size:
|
||||
raise ValueError(
|
||||
raise ResponseSizeError(
|
||||
f'{"File" if executor_response.is_file else "Text"} size is too large,'
|
||||
f' max size is {threshold_size / 1024 / 1024:.2f} MB,'
|
||||
f' but current size is {executor_response.readable_size}.'
|
||||
@@ -196,7 +202,7 @@ class Executor:
|
||||
do http request depending on api bundle
|
||||
"""
|
||||
if self.method not in {"get", "head", "post", "put", "delete", "patch"}:
|
||||
raise ValueError(f"Invalid http method {self.method}")
|
||||
raise InvalidHttpMethodError(f"Invalid http method {self.method}")
|
||||
|
||||
request_args = {
|
||||
"url": self.url,
|
||||
|
||||
@@ -20,6 +20,7 @@ from .entities import (
|
||||
HttpRequestNodeTimeout,
|
||||
Response,
|
||||
)
|
||||
from .exc import HttpRequestNodeError
|
||||
|
||||
HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeTimeout(
|
||||
connect=dify_config.HTTP_REQUEST_MAX_CONNECT_TIMEOUT,
|
||||
@@ -77,7 +78,7 @@ class HttpRequestNode(BaseNode[HttpRequestNodeData]):
|
||||
"request": http_executor.to_log(),
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
except HttpRequestNodeError as e:
|
||||
logger.warning(f"http request node {self.node_id} failed to run: {e}")
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.FAILED,
|
||||
|
||||
16
api/core/workflow/nodes/list_operator/exc.py
Normal file
16
api/core/workflow/nodes/list_operator/exc.py
Normal file
@@ -0,0 +1,16 @@
|
||||
class ListOperatorError(ValueError):
|
||||
"""Base class for all ListOperator errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InvalidFilterValueError(ListOperatorError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidKeyError(ListOperatorError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidConditionError(ListOperatorError):
|
||||
pass
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Literal
|
||||
from typing import Literal, Union
|
||||
|
||||
from core.file import File
|
||||
from core.variables import ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment
|
||||
@@ -9,6 +9,7 @@ from core.workflow.nodes.enums import NodeType
|
||||
from models.workflow import WorkflowNodeExecutionStatus
|
||||
|
||||
from .entities import ListOperatorNodeData
|
||||
from .exc import InvalidConditionError, InvalidFilterValueError, InvalidKeyError, ListOperatorError
|
||||
|
||||
|
||||
class ListOperatorNode(BaseNode[ListOperatorNodeData]):
|
||||
@@ -26,7 +27,17 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]):
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.FAILED, error=error_message, inputs=inputs, outputs=outputs
|
||||
)
|
||||
if variable.value and not isinstance(variable, ArrayFileSegment | ArrayNumberSegment | ArrayStringSegment):
|
||||
if not variable.value:
|
||||
inputs = {"variable": []}
|
||||
process_data = {"variable": []}
|
||||
outputs = {"result": [], "first_record": None, "last_record": None}
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
inputs=inputs,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
)
|
||||
if not isinstance(variable, ArrayFileSegment | ArrayNumberSegment | ArrayStringSegment):
|
||||
error_message = (
|
||||
f"Variable {self.node_data.variable} is not an ArrayFileSegment, ArrayNumberSegment "
|
||||
"or ArrayStringSegment"
|
||||
@@ -36,70 +47,98 @@ class ListOperatorNode(BaseNode[ListOperatorNodeData]):
|
||||
)
|
||||
|
||||
if isinstance(variable, ArrayFileSegment):
|
||||
inputs = {"variable": [item.to_dict() for item in variable.value]}
|
||||
process_data["variable"] = [item.to_dict() for item in variable.value]
|
||||
else:
|
||||
inputs = {"variable": variable.value}
|
||||
process_data["variable"] = variable.value
|
||||
|
||||
# Filter
|
||||
if self.node_data.filter_by.enabled:
|
||||
for condition in self.node_data.filter_by.conditions:
|
||||
if isinstance(variable, ArrayStringSegment):
|
||||
if not isinstance(condition.value, str):
|
||||
raise ValueError(f"Invalid filter value: {condition.value}")
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
filter_func = _get_string_filter_func(condition=condition.comparison_operator, value=value)
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayNumberSegment):
|
||||
if not isinstance(condition.value, str):
|
||||
raise ValueError(f"Invalid filter value: {condition.value}")
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
filter_func = _get_number_filter_func(condition=condition.comparison_operator, value=float(value))
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayFileSegment):
|
||||
if isinstance(condition.value, str):
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
else:
|
||||
value = condition.value
|
||||
filter_func = _get_file_filter_func(
|
||||
key=condition.key,
|
||||
condition=condition.comparison_operator,
|
||||
value=value,
|
||||
)
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
try:
|
||||
# Filter
|
||||
if self.node_data.filter_by.enabled:
|
||||
variable = self._apply_filter(variable)
|
||||
|
||||
# Order
|
||||
if self.node_data.order_by.enabled:
|
||||
# Order
|
||||
if self.node_data.order_by.enabled:
|
||||
variable = self._apply_order(variable)
|
||||
|
||||
# Slice
|
||||
if self.node_data.limit.enabled:
|
||||
variable = self._apply_slice(variable)
|
||||
|
||||
outputs = {
|
||||
"result": variable.value,
|
||||
"first_record": variable.value[0] if variable.value else None,
|
||||
"last_record": variable.value[-1] if variable.value else None,
|
||||
}
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
inputs=inputs,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
)
|
||||
except ListOperatorError as e:
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.FAILED,
|
||||
error=str(e),
|
||||
inputs=inputs,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
)
|
||||
|
||||
def _apply_filter(
|
||||
self, variable: Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]
|
||||
) -> Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]:
|
||||
for condition in self.node_data.filter_by.conditions:
|
||||
if isinstance(variable, ArrayStringSegment):
|
||||
result = _order_string(order=self.node_data.order_by.value, array=variable.value)
|
||||
if not isinstance(condition.value, str):
|
||||
raise InvalidFilterValueError(f"Invalid filter value: {condition.value}")
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
filter_func = _get_string_filter_func(condition=condition.comparison_operator, value=value)
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayNumberSegment):
|
||||
result = _order_number(order=self.node_data.order_by.value, array=variable.value)
|
||||
if not isinstance(condition.value, str):
|
||||
raise InvalidFilterValueError(f"Invalid filter value: {condition.value}")
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
filter_func = _get_number_filter_func(condition=condition.comparison_operator, value=float(value))
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayFileSegment):
|
||||
result = _order_file(
|
||||
order=self.node_data.order_by.value, order_by=self.node_data.order_by.key, array=variable.value
|
||||
if isinstance(condition.value, str):
|
||||
value = self.graph_runtime_state.variable_pool.convert_template(condition.value).text
|
||||
else:
|
||||
value = condition.value
|
||||
filter_func = _get_file_filter_func(
|
||||
key=condition.key,
|
||||
condition=condition.comparison_operator,
|
||||
value=value,
|
||||
)
|
||||
result = list(filter(filter_func, variable.value))
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
return variable
|
||||
|
||||
# Slice
|
||||
if self.node_data.limit.enabled:
|
||||
result = variable.value[: self.node_data.limit.size]
|
||||
def _apply_order(
|
||||
self, variable: Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]
|
||||
) -> Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]:
|
||||
if isinstance(variable, ArrayStringSegment):
|
||||
result = _order_string(order=self.node_data.order_by.value, array=variable.value)
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayNumberSegment):
|
||||
result = _order_number(order=self.node_data.order_by.value, array=variable.value)
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
elif isinstance(variable, ArrayFileSegment):
|
||||
result = _order_file(
|
||||
order=self.node_data.order_by.value, order_by=self.node_data.order_by.key, array=variable.value
|
||||
)
|
||||
variable = variable.model_copy(update={"value": result})
|
||||
return variable
|
||||
|
||||
outputs = {
|
||||
"result": variable.value,
|
||||
"first_record": variable.value[0] if variable.value else None,
|
||||
"last_record": variable.value[-1] if variable.value else None,
|
||||
}
|
||||
return NodeRunResult(
|
||||
status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
||||
inputs=inputs,
|
||||
process_data=process_data,
|
||||
outputs=outputs,
|
||||
)
|
||||
def _apply_slice(
|
||||
self, variable: Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]
|
||||
) -> Union[ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment]:
|
||||
result = variable.value[: self.node_data.limit.size]
|
||||
return variable.model_copy(update={"value": result})
|
||||
|
||||
|
||||
def _get_file_extract_number_func(*, key: str) -> Callable[[File], int]:
|
||||
@@ -107,7 +146,7 @@ def _get_file_extract_number_func(*, key: str) -> Callable[[File], int]:
|
||||
case "size":
|
||||
return lambda x: x.size
|
||||
case _:
|
||||
raise ValueError(f"Invalid key: {key}")
|
||||
raise InvalidKeyError(f"Invalid key: {key}")
|
||||
|
||||
|
||||
def _get_file_extract_string_func(*, key: str) -> Callable[[File], str]:
|
||||
@@ -125,7 +164,7 @@ def _get_file_extract_string_func(*, key: str) -> Callable[[File], str]:
|
||||
case "url":
|
||||
return lambda x: x.remote_url or ""
|
||||
case _:
|
||||
raise ValueError(f"Invalid key: {key}")
|
||||
raise InvalidKeyError(f"Invalid key: {key}")
|
||||
|
||||
|
||||
def _get_string_filter_func(*, condition: str, value: str) -> Callable[[str], bool]:
|
||||
@@ -151,7 +190,7 @@ def _get_string_filter_func(*, condition: str, value: str) -> Callable[[str], bo
|
||||
case "not empty":
|
||||
return lambda x: x != ""
|
||||
case _:
|
||||
raise ValueError(f"Invalid condition: {condition}")
|
||||
raise InvalidConditionError(f"Invalid condition: {condition}")
|
||||
|
||||
|
||||
def _get_sequence_filter_func(*, condition: str, value: Sequence[str]) -> Callable[[str], bool]:
|
||||
@@ -161,7 +200,7 @@ def _get_sequence_filter_func(*, condition: str, value: Sequence[str]) -> Callab
|
||||
case "not in":
|
||||
return lambda x: not _in(value)(x)
|
||||
case _:
|
||||
raise ValueError(f"Invalid condition: {condition}")
|
||||
raise InvalidConditionError(f"Invalid condition: {condition}")
|
||||
|
||||
|
||||
def _get_number_filter_func(*, condition: str, value: int | float) -> Callable[[int | float], bool]:
|
||||
@@ -179,7 +218,7 @@ def _get_number_filter_func(*, condition: str, value: int | float) -> Callable[[
|
||||
case "≥":
|
||||
return _ge(value)
|
||||
case _:
|
||||
raise ValueError(f"Invalid condition: {condition}")
|
||||
raise InvalidConditionError(f"Invalid condition: {condition}")
|
||||
|
||||
|
||||
def _get_file_filter_func(*, key: str, condition: str, value: str | Sequence[str]) -> Callable[[File], bool]:
|
||||
@@ -193,7 +232,7 @@ def _get_file_filter_func(*, key: str, condition: str, value: str | Sequence[str
|
||||
extract_func = _get_file_extract_number_func(key=key)
|
||||
return lambda x: _get_number_filter_func(condition=condition, value=float(value))(extract_func(x))
|
||||
else:
|
||||
raise ValueError(f"Invalid key: {key}")
|
||||
raise InvalidKeyError(f"Invalid key: {key}")
|
||||
|
||||
|
||||
def _contains(value: str):
|
||||
|
||||
@@ -33,6 +33,10 @@ import { LoveMessage } from '@/app/components/base/icons/src/vender/features'
|
||||
// type
|
||||
import type { AutomaticRes } from '@/service/debug'
|
||||
import { Generator } from '@/app/components/base/icons/src/vender/other'
|
||||
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
|
||||
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
|
||||
export type IGetAutomaticResProps = {
|
||||
mode: AppType
|
||||
@@ -68,7 +72,10 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
onFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
currentProvider,
|
||||
currentModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||
const tryList = [
|
||||
{
|
||||
icon: RiTerminalBoxLine,
|
||||
@@ -191,6 +198,19 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
<div className={`leading-[28px] text-lg font-bold ${s.textGradient}`}>{t('appDebug.generate.title')}</div>
|
||||
<div className='mt-1 text-[13px] font-normal text-gray-500'>{t('appDebug.generate.description')}</div>
|
||||
</div>
|
||||
<div className='flex items-center mb-8'>
|
||||
<ModelIcon
|
||||
className='shrink-0 mr-1.5 '
|
||||
provider={currentProvider}
|
||||
modelName={currentModel?.model}
|
||||
/>
|
||||
<ModelName
|
||||
className='grow'
|
||||
modelItem={currentModel!}
|
||||
showMode
|
||||
showFeatures
|
||||
/>
|
||||
</div>
|
||||
<div >
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 leading-[18px] text-xs font-semibold text-gray-500 uppercase'>{t('appDebug.generate.tryIt')}</div>
|
||||
|
||||
@@ -105,6 +105,15 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
<div className='text-[13px] text-gray-400'>{t('appDebug.codegen.loading')}</div>
|
||||
</div>
|
||||
)
|
||||
const renderNoData = (
|
||||
<div className='w-0 grow flex flex-col items-center px-8 justify-center h-full space-y-3'>
|
||||
<Generator className='w-14 h-14 text-gray-300' />
|
||||
<div className='leading-5 text-center text-[13px] font-normal text-gray-400'>
|
||||
<div>{t('appDebug.codegen.noDataLine1')}</div>
|
||||
<div>{t('appDebug.codegen.noDataLine2')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -157,6 +166,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
|
||||
</div>
|
||||
</div>
|
||||
{isLoading && renderLoading}
|
||||
{!isLoading && !res && renderNoData}
|
||||
{(!isLoading && res) && (
|
||||
<div className='w-0 grow p-6 pb-0 h-full'>
|
||||
<div className='shrink-0 mb-3 leading-[160%] text-base font-semibold text-gray-800'>{t('appDebug.codegen.resTitle')}</div>
|
||||
|
||||
@@ -216,7 +216,7 @@ export const useFile = (fileConfig: FileUpload) => {
|
||||
handleAddFile(uploadingFile)
|
||||
startProgressTimer(uploadingFile.id)
|
||||
|
||||
uploadRemoteFileInfo(url).then((res) => {
|
||||
uploadRemoteFileInfo(url, !!params.token).then((res) => {
|
||||
const newFile = {
|
||||
...uploadingFile,
|
||||
type: res.mime_type,
|
||||
|
||||
@@ -105,32 +105,29 @@ const useOneStepRun = <T>({
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
const targetVar = isSystem ? allOutputVars.find(item => !!item.isStartNode) : allOutputVars.find(v => v.nodeId === valueSelector[0])
|
||||
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
|
||||
if (isSystem)
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
if (!curr)
|
||||
return
|
||||
for (let i = 1; i < valueSelector.length; i++) {
|
||||
const key = valueSelector[i]
|
||||
const isLast = i === valueSelector.length - 1
|
||||
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
// conversation variable is start with 'conversation.'
|
||||
curr = curr?.find((v: any) => v.variable.replace('conversation.', '') === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
else {
|
||||
if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
if (Array.isArray(curr))
|
||||
curr = curr.find((v: any) => v.variable.replace('conversation.', '') === key)
|
||||
|
||||
return res
|
||||
if (isLast)
|
||||
return curr
|
||||
else if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
const checkValid = checkValidFns[data.type]
|
||||
|
||||
@@ -224,6 +224,8 @@ const translation = {
|
||||
description: 'The Code Generator uses configured models to generate high-quality code based on your instructions. Please provide clear and detailed instructions.',
|
||||
instruction: 'Instructions',
|
||||
instructionPlaceholder: 'Enter detailed description of the code you want to generate.',
|
||||
noDataLine1: 'Describe your use case on the left,',
|
||||
noDataLine2: 'the code preview will show here.',
|
||||
generate: 'Generate',
|
||||
generatedCodeTitle: 'Generated Code',
|
||||
loading: 'Generating code...',
|
||||
|
||||
@@ -224,6 +224,8 @@ const translation = {
|
||||
description: 'コードジェネレーターは、設定されたモデルを使用して指示に基づいて高品質なコードを生成します。明確で詳細な指示を提供してください。',
|
||||
instruction: '指示',
|
||||
instructionPlaceholder: '生成したいコードの詳細な説明を入力してください。',
|
||||
noDataLine1: '左側に使用例を記入してください,',
|
||||
noDataLine2: 'コードのプレビューがこちらに表示されます。',
|
||||
generate: '生成',
|
||||
generatedCodeTitle: '生成されたコード',
|
||||
loading: 'コードを生成中...',
|
||||
|
||||
@@ -224,6 +224,8 @@ const translation = {
|
||||
description: '代码生成器使用配置的模型根据您的指令生成高质量的代码。请提供清晰详细的说明。',
|
||||
instruction: '指令',
|
||||
instructionPlaceholder: '请输入您想要生成的代码的详细描述。',
|
||||
noDataLine1: '在左侧描述您的用例,',
|
||||
noDataLine2: '代码预览将在此处显示。',
|
||||
generate: '生成',
|
||||
generatedCodeTitle: '生成的代码',
|
||||
loading: '正在生成代码...',
|
||||
|
||||
@@ -324,8 +324,8 @@ export const verifyForgotPasswordToken: Fetcher<CommonResponse & { is_valid: boo
|
||||
export const changePasswordWithToken: Fetcher<CommonResponse, { url: string; body: { token: string; new_password: string; password_confirm: string } }> = ({ url, body }) =>
|
||||
post<CommonResponse>(url, { body })
|
||||
|
||||
export const uploadRemoteFileInfo = (url: string) => {
|
||||
return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } })
|
||||
export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => {
|
||||
return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic })
|
||||
}
|
||||
|
||||
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
||||
|
||||
Reference in New Issue
Block a user