merge feat/plugins
commit
9f90d70b38
@ -0,0 +1,338 @@
|
|||||||
|
from core.workflow.entities.variable_pool import VariablePool
|
||||||
|
from core.workflow.nodes.http_request import (
|
||||||
|
BodyData,
|
||||||
|
HttpRequestNodeAuthorization,
|
||||||
|
HttpRequestNodeBody,
|
||||||
|
HttpRequestNodeData,
|
||||||
|
)
|
||||||
|
from core.workflow.nodes.http_request.entities import HttpRequestNodeTimeout
|
||||||
|
from core.workflow.nodes.http_request.executor import Executor
|
||||||
|
|
||||||
|
|
||||||
|
def test_executor_with_json_body_and_number_variable():
|
||||||
|
# Prepare the variable pool
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(["pre_node_id", "number"], 42)
|
||||||
|
|
||||||
|
# Prepare the node data
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test JSON Body with Number Variable",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/data",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: application/json",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="json",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="",
|
||||||
|
type="text",
|
||||||
|
value='{"number": {{#pre_node_id.number#}}}',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the Executor
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check the executor's data
|
||||||
|
assert executor.method == "post"
|
||||||
|
assert executor.url == "https://api.example.com/data"
|
||||||
|
assert executor.headers == {"Content-Type": "application/json"}
|
||||||
|
assert executor.params == []
|
||||||
|
assert executor.json == {"number": 42}
|
||||||
|
assert executor.data is None
|
||||||
|
assert executor.files is None
|
||||||
|
assert executor.content is None
|
||||||
|
|
||||||
|
# Check the raw request (to_log method)
|
||||||
|
raw_request = executor.to_log()
|
||||||
|
assert "POST /data HTTP/1.1" in raw_request
|
||||||
|
assert "Host: api.example.com" in raw_request
|
||||||
|
assert "Content-Type: application/json" in raw_request
|
||||||
|
assert '{"number": 42}' in raw_request
|
||||||
|
|
||||||
|
|
||||||
|
def test_executor_with_json_body_and_object_variable():
|
||||||
|
# Prepare the variable pool
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(["pre_node_id", "object"], {
|
||||||
|
"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||||
|
|
||||||
|
# Prepare the node data
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test JSON Body with Object Variable",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/data",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: application/json",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="json",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="",
|
||||||
|
type="text",
|
||||||
|
value="{{#pre_node_id.object#}}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the Executor
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check the executor's data
|
||||||
|
assert executor.method == "post"
|
||||||
|
assert executor.url == "https://api.example.com/data"
|
||||||
|
assert executor.headers == {"Content-Type": "application/json"}
|
||||||
|
assert executor.params == []
|
||||||
|
assert executor.json == {"name": "John Doe", "age": 30, "email": "john@example.com"}
|
||||||
|
assert executor.data is None
|
||||||
|
assert executor.files is None
|
||||||
|
assert executor.content is None
|
||||||
|
|
||||||
|
# Check the raw request (to_log method)
|
||||||
|
raw_request = executor.to_log()
|
||||||
|
assert "POST /data HTTP/1.1" in raw_request
|
||||||
|
assert "Host: api.example.com" in raw_request
|
||||||
|
assert "Content-Type: application/json" in raw_request
|
||||||
|
assert '"name": "John Doe"' in raw_request
|
||||||
|
assert '"age": 30' in raw_request
|
||||||
|
assert '"email": "john@example.com"' in raw_request
|
||||||
|
|
||||||
|
|
||||||
|
def test_executor_with_json_body_and_nested_object_variable():
|
||||||
|
# Prepare the variable pool
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(["pre_node_id", "object"], {
|
||||||
|
"name": "John Doe", "age": 30, "email": "john@example.com"})
|
||||||
|
|
||||||
|
# Prepare the node data
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test JSON Body with Nested Object Variable",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/data",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: application/json",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="json",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="",
|
||||||
|
type="text",
|
||||||
|
value='{"object": {{#pre_node_id.object#}}}',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the Executor
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check the executor's data
|
||||||
|
assert executor.method == "post"
|
||||||
|
assert executor.url == "https://api.example.com/data"
|
||||||
|
assert executor.headers == {"Content-Type": "application/json"}
|
||||||
|
assert executor.params == []
|
||||||
|
assert executor.json == {"object": {"name": "John Doe", "age": 30, "email": "john@example.com"}}
|
||||||
|
assert executor.data is None
|
||||||
|
assert executor.files is None
|
||||||
|
assert executor.content is None
|
||||||
|
|
||||||
|
# Check the raw request (to_log method)
|
||||||
|
raw_request = executor.to_log()
|
||||||
|
assert "POST /data HTTP/1.1" in raw_request
|
||||||
|
assert "Host: api.example.com" in raw_request
|
||||||
|
assert "Content-Type: application/json" in raw_request
|
||||||
|
assert '"object": {' in raw_request
|
||||||
|
assert '"name": "John Doe"' in raw_request
|
||||||
|
assert '"age": 30' in raw_request
|
||||||
|
assert '"email": "john@example.com"' in raw_request
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_selectors_from_template_with_newline():
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(("node_id", "custom_query"), "line1\nline2")
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test JSON Body with Nested Object Variable",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/data",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: application/json",
|
||||||
|
params="test: {{#node_id.custom_query#}}",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="none",
|
||||||
|
data=[],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert executor.params == [("test", "line1\nline2")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_executor_with_form_data():
|
||||||
|
# Prepare the variable pool
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(["pre_node_id", "text_field"], "Hello, World!")
|
||||||
|
variable_pool.add(["pre_node_id", "number_field"], 42)
|
||||||
|
|
||||||
|
# Prepare the node data
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test Form Data",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/upload",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: multipart/form-data",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="form-data",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="text_field",
|
||||||
|
type="text",
|
||||||
|
value="{{#pre_node_id.text_field#}}",
|
||||||
|
),
|
||||||
|
BodyData(
|
||||||
|
key="number_field",
|
||||||
|
type="text",
|
||||||
|
value="{{#pre_node_id.number_field#}}",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the Executor
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check the executor's data
|
||||||
|
assert executor.method == "post"
|
||||||
|
assert executor.url == "https://api.example.com/upload"
|
||||||
|
assert "Content-Type" in executor.headers
|
||||||
|
assert "multipart/form-data" in executor.headers["Content-Type"]
|
||||||
|
assert executor.params == []
|
||||||
|
assert executor.json is None
|
||||||
|
assert executor.files is None
|
||||||
|
assert executor.content is None
|
||||||
|
|
||||||
|
# Check that the form data is correctly loaded in executor.data
|
||||||
|
assert isinstance(executor.data, dict)
|
||||||
|
assert "text_field" in executor.data
|
||||||
|
assert executor.data["text_field"] == "Hello, World!"
|
||||||
|
assert "number_field" in executor.data
|
||||||
|
assert executor.data["number_field"] == "42"
|
||||||
|
|
||||||
|
# Check the raw request (to_log method)
|
||||||
|
raw_request = executor.to_log()
|
||||||
|
assert "POST /upload HTTP/1.1" in raw_request
|
||||||
|
assert "Host: api.example.com" in raw_request
|
||||||
|
assert "Content-Type: multipart/form-data" in raw_request
|
||||||
|
assert "text_field" in raw_request
|
||||||
|
assert "Hello, World!" in raw_request
|
||||||
|
assert "number_field" in raw_request
|
||||||
|
assert "42" in raw_request
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_headers():
|
||||||
|
def create_executor(headers: str) -> Executor:
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="test",
|
||||||
|
method="get",
|
||||||
|
url="http://example.com",
|
||||||
|
headers=headers,
|
||||||
|
params="",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
)
|
||||||
|
timeout = HttpRequestNodeTimeout(connect=10, read=30, write=30)
|
||||||
|
return Executor(node_data=node_data, timeout=timeout, variable_pool=VariablePool())
|
||||||
|
|
||||||
|
executor = create_executor("aa\n cc:")
|
||||||
|
executor._init_headers()
|
||||||
|
assert executor.headers == {"aa": "", "cc": ""}
|
||||||
|
|
||||||
|
executor = create_executor("aa:bb\n cc:dd")
|
||||||
|
executor._init_headers()
|
||||||
|
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||||
|
|
||||||
|
executor = create_executor("aa:bb\n cc:dd\n")
|
||||||
|
executor._init_headers()
|
||||||
|
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||||
|
|
||||||
|
executor = create_executor("aa:bb\n\n cc : dd\n\n")
|
||||||
|
executor._init_headers()
|
||||||
|
assert executor.headers == {"aa": "bb", "cc": "dd"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_params():
|
||||||
|
def create_executor(params: str) -> Executor:
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="test",
|
||||||
|
method="get",
|
||||||
|
url="http://example.com",
|
||||||
|
headers="",
|
||||||
|
params=params,
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
)
|
||||||
|
timeout = HttpRequestNodeTimeout(connect=10, read=30, write=30)
|
||||||
|
return Executor(node_data=node_data, timeout=timeout, variable_pool=VariablePool())
|
||||||
|
|
||||||
|
# Test basic key-value pairs
|
||||||
|
executor = create_executor("key1:value1\nkey2:value2")
|
||||||
|
executor._init_params()
|
||||||
|
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||||
|
|
||||||
|
# Test empty values
|
||||||
|
executor = create_executor("key1:\nkey2:")
|
||||||
|
executor._init_params()
|
||||||
|
assert executor.params == [("key1", ""), ("key2", "")]
|
||||||
|
|
||||||
|
# Test duplicate keys (which is allowed for params)
|
||||||
|
executor = create_executor("key1:value1\nkey1:value2")
|
||||||
|
executor._init_params()
|
||||||
|
assert executor.params == [("key1", "value1"), ("key1", "value2")]
|
||||||
|
|
||||||
|
# Test whitespace handling
|
||||||
|
executor = create_executor(" key1 : value1 \n key2 : value2 ")
|
||||||
|
executor._init_params()
|
||||||
|
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||||
|
|
||||||
|
# Test empty lines and extra whitespace
|
||||||
|
executor = create_executor("key1:value1\n\nkey2:value2\n\n")
|
||||||
|
executor._init_params()
|
||||||
|
assert executor.params == [("key1", "value1"), ("key2", "value2")]
|
||||||
@ -0,0 +1,203 @@
|
|||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||||
|
from core.file import File, FileTransferMethod, FileType
|
||||||
|
from core.variables import FileVariable
|
||||||
|
from core.workflow.entities.variable_pool import VariablePool
|
||||||
|
from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState
|
||||||
|
from core.workflow.nodes.answer import AnswerStreamGenerateRoute
|
||||||
|
from core.workflow.nodes.end import EndStreamParam
|
||||||
|
from core.workflow.nodes.http_request import (
|
||||||
|
BodyData,
|
||||||
|
HttpRequestNode,
|
||||||
|
HttpRequestNodeAuthorization,
|
||||||
|
HttpRequestNodeBody,
|
||||||
|
HttpRequestNodeData,
|
||||||
|
)
|
||||||
|
from models.enums import UserFrom
|
||||||
|
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
|
||||||
|
|
||||||
|
|
||||||
|
def test_plain_text_to_dict():
|
||||||
|
assert _plain_text_to_dict("aa\n cc:") == {"aa": "", "cc": ""}
|
||||||
|
assert _plain_text_to_dict("aa:bb\n cc:dd") == {"aa": "bb", "cc": "dd"}
|
||||||
|
assert _plain_text_to_dict("aa:bb\n cc:dd\n") == {"aa": "bb", "cc": "dd"}
|
||||||
|
assert _plain_text_to_dict("aa:bb\n\n cc : dd\n\n") == {
|
||||||
|
"aa": "bb", "cc": "dd"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_request_node_binary_file(monkeypatch):
|
||||||
|
data = HttpRequestNodeData(
|
||||||
|
title="test",
|
||||||
|
method="post",
|
||||||
|
url="http://example.org/post",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="binary",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="file",
|
||||||
|
type="file",
|
||||||
|
value="",
|
||||||
|
file=["1111", "file"],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(
|
||||||
|
["1111", "file"],
|
||||||
|
FileVariable(
|
||||||
|
name="file",
|
||||||
|
value=File(
|
||||||
|
tenant_id="1",
|
||||||
|
type=FileType.IMAGE,
|
||||||
|
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||||
|
related_id="1111",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
node = HttpRequestNode(
|
||||||
|
id="1",
|
||||||
|
config={
|
||||||
|
"id": "1",
|
||||||
|
"data": data.model_dump(),
|
||||||
|
},
|
||||||
|
graph_init_params=GraphInitParams(
|
||||||
|
tenant_id="1",
|
||||||
|
app_id="1",
|
||||||
|
workflow_type=WorkflowType.WORKFLOW,
|
||||||
|
workflow_id="1",
|
||||||
|
graph_config={},
|
||||||
|
user_id="1",
|
||||||
|
user_from=UserFrom.ACCOUNT,
|
||||||
|
invoke_from=InvokeFrom.SERVICE_API,
|
||||||
|
call_depth=0,
|
||||||
|
),
|
||||||
|
graph=Graph(
|
||||||
|
root_node_id="1",
|
||||||
|
answer_stream_generate_routes=AnswerStreamGenerateRoute(
|
||||||
|
answer_dependencies={},
|
||||||
|
answer_generate_route={},
|
||||||
|
),
|
||||||
|
end_stream_param=EndStreamParam(
|
||||||
|
end_dependencies={},
|
||||||
|
end_stream_variable_selector_mapping={},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
graph_runtime_state=GraphRuntimeState(
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
start_at=0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"core.workflow.nodes.http_request.executor.file_manager.download",
|
||||||
|
lambda *args, **kwargs: b"test",
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"core.helper.ssrf_proxy.post",
|
||||||
|
lambda *args, **kwargs: httpx.Response(200, content=kwargs["content"]),
|
||||||
|
)
|
||||||
|
result = node._run()
|
||||||
|
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||||
|
assert result.outputs is not None
|
||||||
|
assert result.outputs["body"] == "test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_request_node_form_with_file(monkeypatch):
|
||||||
|
data = HttpRequestNodeData(
|
||||||
|
title="test",
|
||||||
|
method="post",
|
||||||
|
url="http://example.org/post",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="form-data",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="file",
|
||||||
|
type="file",
|
||||||
|
file=["1111", "file"],
|
||||||
|
),
|
||||||
|
BodyData(
|
||||||
|
key="name",
|
||||||
|
type="text",
|
||||||
|
value="test",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(
|
||||||
|
["1111", "file"],
|
||||||
|
FileVariable(
|
||||||
|
name="file",
|
||||||
|
value=File(
|
||||||
|
tenant_id="1",
|
||||||
|
type=FileType.IMAGE,
|
||||||
|
transfer_method=FileTransferMethod.LOCAL_FILE,
|
||||||
|
related_id="1111",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
node = HttpRequestNode(
|
||||||
|
id="1",
|
||||||
|
config={
|
||||||
|
"id": "1",
|
||||||
|
"data": data.model_dump(),
|
||||||
|
},
|
||||||
|
graph_init_params=GraphInitParams(
|
||||||
|
tenant_id="1",
|
||||||
|
app_id="1",
|
||||||
|
workflow_type=WorkflowType.WORKFLOW,
|
||||||
|
workflow_id="1",
|
||||||
|
graph_config={},
|
||||||
|
user_id="1",
|
||||||
|
user_from=UserFrom.ACCOUNT,
|
||||||
|
invoke_from=InvokeFrom.SERVICE_API,
|
||||||
|
call_depth=0,
|
||||||
|
),
|
||||||
|
graph=Graph(
|
||||||
|
root_node_id="1",
|
||||||
|
answer_stream_generate_routes=AnswerStreamGenerateRoute(
|
||||||
|
answer_dependencies={},
|
||||||
|
answer_generate_route={},
|
||||||
|
),
|
||||||
|
end_stream_param=EndStreamParam(
|
||||||
|
end_dependencies={},
|
||||||
|
end_stream_variable_selector_mapping={},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
graph_runtime_state=GraphRuntimeState(
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
start_at=0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"core.workflow.nodes.http_request.executor.file_manager.download",
|
||||||
|
lambda *args, **kwargs: b"test",
|
||||||
|
)
|
||||||
|
|
||||||
|
def attr_checker(*args, **kwargs):
|
||||||
|
assert kwargs["data"] == {"name": "test"}
|
||||||
|
assert kwargs["files"] == {
|
||||||
|
"file": (None, b"test", "application/octet-stream")}
|
||||||
|
return httpx.Response(200, content=b"")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"core.helper.ssrf_proxy.post",
|
||||||
|
attr_checker,
|
||||||
|
)
|
||||||
|
result = node._run()
|
||||||
|
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
|
||||||
|
assert result.outputs is not None
|
||||||
|
assert result.outputs["body"] == ""
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
.appIcon {
|
||||||
|
@apply flex items-center justify-center relative w-9 h-9 text-lg rounded-lg grow-0 shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.large {
|
||||||
|
@apply w-10 h-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.small {
|
||||||
|
@apply w-8 h-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.tiny {
|
||||||
|
@apply w-6 h-6 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.xs {
|
||||||
|
@apply w-5 h-5 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appIcon.rounded {
|
||||||
|
@apply rounded-full;
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 362px;
|
|
||||||
max-height: 552px;
|
|
||||||
|
|
||||||
border: 0.5px solid #EAECF0;
|
|
||||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import type { FC } from 'react'
|
|
||||||
|
|
||||||
type TabProps = {
|
|
||||||
active: string
|
|
||||||
onSelect: (active: string) => void
|
|
||||||
}
|
|
||||||
const Tab: FC<TabProps> = ({
|
|
||||||
active,
|
|
||||||
onSelect,
|
|
||||||
}) => {
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
key: 'all',
|
|
||||||
text: 'All',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'added',
|
|
||||||
text: 'Added',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'build-in',
|
|
||||||
text: 'Build-in',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
return (
|
|
||||||
<div className='flex items-center'>
|
|
||||||
{
|
|
||||||
tabs.map(tab => (
|
|
||||||
<div
|
|
||||||
key={tab.key}
|
|
||||||
className={`
|
|
||||||
flex items-center mr-1 px-[5px] h-[18px] rounded-md text-xs cursor-pointer
|
|
||||||
${active === tab.key ? 'bg-gray-200 font-medium text-gray-900' : 'text-gray-500 font-normal'}
|
|
||||||
`}
|
|
||||||
onClick={() => onSelect(tab.key)}
|
|
||||||
>
|
|
||||||
{tab.text}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tab
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.vender {
|
|
||||||
background: linear-gradient(131deg, #2250F2 0%, #0EBCF3 100%);
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
import type { FC } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import {
|
|
||||||
RiAddLine,
|
|
||||||
} from '@remixicon/react'
|
|
||||||
import type {
|
|
||||||
ModelProvider,
|
|
||||||
} from '../declarations'
|
|
||||||
import { ConfigurationMethodEnum } from '../declarations'
|
|
||||||
import {
|
|
||||||
DEFAULT_BACKGROUND_COLOR,
|
|
||||||
modelTypeFormat,
|
|
||||||
} from '../utils'
|
|
||||||
import {
|
|
||||||
useLanguage,
|
|
||||||
} from '../hooks'
|
|
||||||
import ModelBadge from '../model-badge'
|
|
||||||
import ProviderIcon from '../provider-icon'
|
|
||||||
import s from './index.module.css'
|
|
||||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
import Button from '@/app/components/base/button'
|
|
||||||
import { useAppContext } from '@/context/app-context'
|
|
||||||
|
|
||||||
type ProviderCardProps = {
|
|
||||||
provider: ModelProvider
|
|
||||||
onOpenModal: (configurateMethod: ConfigurationMethodEnum) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProviderCard: FC<ProviderCardProps> = ({
|
|
||||||
provider,
|
|
||||||
onOpenModal,
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const language = useLanguage()
|
|
||||||
const { isCurrentWorkspaceManager } = useAppContext()
|
|
||||||
const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='group relative flex flex-col px-4 py-3 h-[148px] border-[0.5px] border-black/5 rounded-xl shadow-xs hover:shadow-lg'
|
|
||||||
style={{ background: provider.background || DEFAULT_BACKGROUND_COLOR }}
|
|
||||||
>
|
|
||||||
<div className='grow h-0'>
|
|
||||||
<div className='py-0.5'>
|
|
||||||
<ProviderIcon provider={provider} />
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
provider.description && (
|
|
||||||
<div
|
|
||||||
className='mt-1 leading-4 text-xs text-black/[48] line-clamp-4'
|
|
||||||
title={provider.description[language] || provider.description.en_US}
|
|
||||||
>
|
|
||||||
{provider.description[language] || provider.description.en_US}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className='shrink-0'>
|
|
||||||
<div className={'flex flex-wrap group-hover:hidden gap-0.5'}>
|
|
||||||
{
|
|
||||||
provider.supported_model_types.map(modelType => (
|
|
||||||
<ModelBadge key={modelType}>
|
|
||||||
{modelTypeFormat(modelType)}
|
|
||||||
</ModelBadge>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className={`hidden group-hover:grid grid-cols-${configurateMethods.length} gap-1`}>
|
|
||||||
{
|
|
||||||
configurateMethods.map((method) => {
|
|
||||||
if (method === ConfigurationMethodEnum.predefinedModel) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={method}
|
|
||||||
className={'h-7 text-xs shrink-0'}
|
|
||||||
onClick={() => onOpenModal(method)}
|
|
||||||
disabled={!isCurrentWorkspaceManager}
|
|
||||||
>
|
|
||||||
<Settings01 className={`mr-[5px] w-3.5 h-3.5 ${s.icon}`} />
|
|
||||||
<span className='text-xs inline-flex items-center justify-center overflow-ellipsis shrink-0'>{t('common.operation.setup')}</span>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
key={method}
|
|
||||||
className='px-0 h-7 text-xs'
|
|
||||||
onClick={() => onOpenModal(method)}
|
|
||||||
disabled={!isCurrentWorkspaceManager}
|
|
||||||
>
|
|
||||||
<RiAddLine className='mr-[5px] w-3.5 h-3.5' />
|
|
||||||
{t('common.modelProvider.addModel')}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProviderCard
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.bg {
|
|
||||||
background: linear-gradient(180deg, rgba(247, 144, 9, 0.05) 0%, rgba(247, 144, 9, 0.00) 24.41%), #F9FAFB;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue