mirror of https://github.com/langgenius/dify.git
merge feat/plugins
This commit is contained in:
commit
9f90d70b38
|
|
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||
|
||||
CURRENT_VERSION: str = Field(
|
||||
description="Dify version",
|
||||
default="0.13.2",
|
||||
default="0.14.0",
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
|
|
|||
|
|
@ -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"] == ""
|
||||
|
|
@ -2,7 +2,7 @@ version: '3'
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.13.2
|
||||
image: langgenius/dify-api:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
# Startup mode, 'api' starts the API server.
|
||||
|
|
@ -227,7 +227,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.13.2
|
||||
image: langgenius/dify-api:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_WEB_URL: ''
|
||||
|
|
@ -397,7 +397,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.13.2
|
||||
image: langgenius/dify-web:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:0.13.2
|
||||
image: langgenius/dify-api:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -312,7 +312,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing the queue.
|
||||
worker:
|
||||
image: langgenius/dify-api:0.13.2
|
||||
image: langgenius/dify-api:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -331,7 +331,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:0.13.2
|
||||
image: langgenius/dify-web:0.14.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
|||
import { CopyIcon } from '@/app/components/base/copy-icon'
|
||||
import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import { correctProvider } from '@/utils'
|
||||
import { TONE_LIST } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
|
|
@ -300,22 +299,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|||
const isChatMode = appDetail?.mode !== 'completion'
|
||||
const isAdvanced = appDetail?.mode === 'advanced-chat'
|
||||
|
||||
const targetTone = TONE_LIST.find((item: any) => {
|
||||
let res = true
|
||||
validatedParams.forEach((param) => {
|
||||
res = item.config?.[param] === detail?.model_config.model?.completion_params?.[param]
|
||||
})
|
||||
return res
|
||||
})?.name ?? 'custom'
|
||||
|
||||
const modelName = (detail.model_config as any).model?.name
|
||||
const provideName = correctProvider((detail.model_config as any).model?.provider as any)
|
||||
const {
|
||||
currentModel,
|
||||
currentProvider,
|
||||
} = useTextGenerationCurrentProviderAndModelAndModelList(
|
||||
{ provider: provideName, model: modelName },
|
||||
)
|
||||
const varList = (detail.model_config as any).user_input_form?.map((item: any) => {
|
||||
const itemContent = item[Object.keys(item)[0]]
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ const CopyBtn = ({
|
|||
>
|
||||
<div
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={'box-border p-0.5 flex items-center justify-center rounded-md bg-white cursor-pointer'}
|
||||
className={'box-border p-0.5 flex items-center justify-center rounded-md bg-components-button-secondary-bg cursor-pointer'}
|
||||
style={!isPlain
|
||||
? {
|
||||
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)',
|
||||
|
|
@ -44,7 +44,7 @@ const CopyBtn = ({
|
|||
: {}}
|
||||
onClick={onClickCopy}
|
||||
>
|
||||
<div className={`w-6 h-6 rounded-md hover:bg-gray-50 ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
||||
<div className={`w-6 h-6 rounded-md hover:bg-components-button-secondary-bg-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,18 +49,18 @@ export default function Drawer({
|
|||
<Dialog.Overlay
|
||||
className={cn('z-40 fixed inset-0', mask && 'bg-black bg-opacity-30')}
|
||||
/>
|
||||
<div className={cn('relative z-50 flex flex-col justify-between bg-background-body w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
|
||||
<div className={cn('relative z-50 flex flex-col justify-between bg-components-panel-bg w-full max-w-sm p-6 overflow-hidden text-left align-middle shadow-xl', panelClassname)}>
|
||||
<>
|
||||
{title && <Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
className="text-lg font-medium leading-6 text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>}
|
||||
{showClose && <Dialog.Title className="flex items-center mb-4" as="div">
|
||||
<XMarkIcon className='w-4 h-4 text-gray-500' onClick={onClose} />
|
||||
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={onClose} />
|
||||
</Dialog.Title>}
|
||||
{description && <Dialog.Description className='text-gray-500 text-xs font-normal mt-2'>{description}</Dialog.Description>}
|
||||
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>{description}</Dialog.Description>}
|
||||
{children}
|
||||
</>
|
||||
{footer || (footer === null
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import { init } from 'emoji-mart'
|
|||
import {
|
||||
MagnifyingGlassIcon,
|
||||
} from '@heroicons/react/24/outline'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { searchEmoji } from '@/utils/emoji'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
|
|
@ -72,12 +73,12 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
<div className='flex flex-col items-center w-full px-3 pb-2'>
|
||||
<div className="relative w-full">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<MagnifyingGlassIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||
<MagnifyingGlassIcon className="w-5 h-5 text-text-quaternary" aria-hidden="true" />
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
className="pl-10"
|
||||
type="search"
|
||||
id="search"
|
||||
className='block w-full h-10 px-3 pl-10 text-sm font-normal bg-gray-100 rounded-lg'
|
||||
placeholder="Search emojis..."
|
||||
onChange={async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.value === '') {
|
||||
|
|
@ -92,12 +93,12 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className='m-0 mb-3' />
|
||||
<Divider className='my-3' />
|
||||
|
||||
<div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
|
||||
{isSearching && <>
|
||||
<div key={'category-search'} className='flex flex-col'>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>Search</p>
|
||||
<p className='system-xs-medium-uppercase text-text-primary mb-1'>Search</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{searchedEmojis.map((emoji: string, index: number) => {
|
||||
return <div
|
||||
|
|
@ -107,7 +108,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
setSelectedEmoji(emoji)
|
||||
}}
|
||||
>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-components-input-border-hover'>
|
||||
<em-emoji id={emoji} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -118,7 +119,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
|
||||
{categories.map((category, index: number) => {
|
||||
return <div key={`category-${index}`} className='flex flex-col'>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-1'>{category.id}</p>
|
||||
<p className='system-xs-medium-uppercase text-text-primary mb-1'>{category.id}</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{category.emojis.map((emoji, index: number) => {
|
||||
return <div
|
||||
|
|
@ -128,7 +129,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
setSelectedEmoji(emoji)
|
||||
}}
|
||||
>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-gray-300'>
|
||||
<div className='cursor-pointer w-8 h-8 p-1 flex items-center justify-center rounded-lg hover:ring-1 ring-offset-1 ring-components-input-border-hover'>
|
||||
<em-emoji id={emoji} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -141,7 +142,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
|
||||
{/* Color Select */}
|
||||
<div className={cn('p-3 pb-0', selectedEmoji === '' ? 'opacity-25' : '')}>
|
||||
<p className='font-medium uppercase text-xs text-[#101828] mb-2'>Choose Style</p>
|
||||
<p className='system-xs-medium-uppercase text-text-primary mb-2'>Choose Style</p>
|
||||
<div className='w-full h-full grid grid-cols-8 gap-1'>
|
||||
{backgroundColors.map((color) => {
|
||||
return <div
|
||||
|
|
@ -151,7 +152,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
|||
'cursor-pointer',
|
||||
'hover:ring-1 ring-offset-1',
|
||||
'inline-flex w-10 h-10 rounded-lg items-center justify-center',
|
||||
color === selectedBackground ? 'ring-1 ring-gray-300' : '',
|
||||
color === selectedBackground ? 'ring-1 ring-components-input-border-hover' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
setSelectedBackground(color)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import s from './style.module.css'
|
||||
import EmojiPickerInner from './Inner'
|
||||
import cn from '@/utils/classnames'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
|
@ -37,12 +36,12 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
|
|||
isShow
|
||||
closable={false}
|
||||
wrapperClassName={className}
|
||||
className={cn(s.container, '!w-[362px] !p-0')}
|
||||
className={cn('flex flex-col max-h-[552px] border-[0.5px] border-divider-subtle rounded-xl shadow-xl p-0')}
|
||||
>
|
||||
<EmojiPickerInner
|
||||
className="pt-3"
|
||||
onSelect={handleSelectEmoji} />
|
||||
<Divider className='m-0' />
|
||||
<Divider className='mb-0 mt-3' />
|
||||
<div className='w-full flex items-center justify-center p-3 gap-2'>
|
||||
<Button className='w-full' onClick={() => {
|
||||
onClose && onClose()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -36,8 +36,8 @@ const GridMask: FC<GridMaskProps> = ({
|
|||
const drawRecord = useCallback(() => {
|
||||
const canvas = canvasRef.current!
|
||||
const ctx = ctxRef.current!
|
||||
const rowNumber = parseInt(`${canvas.width / 24}`)
|
||||
const colNumber = parseInt(`${canvas.height / 24}`)
|
||||
const rowNumber = Number.parseInt(`${canvas.width / 24}`)
|
||||
const colNumber = Number.parseInt(`${canvas.height / 24}`)
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.beginPath()
|
||||
|
|
@ -82,9 +82,9 @@ const GridMask: FC<GridMaskProps> = ({
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`relative bg-white ${wrapperClassName}`}>
|
||||
<div className={`relative bg-components-panel-bg ${wrapperClassName}`}>
|
||||
<canvas ref={canvasRef} className={`absolute inset-0 w-full h-full ${canvasClassName}`} />
|
||||
<div className={`absolute w-full h-full z-[1] bg-gradient-to-b from-white/80 to-white rounded-lg ${gradientClassName}`} />
|
||||
<div className={`absolute w-full h-full z-[1] bg-gradient-to-b from-background-body to-background-gradient-mask-transparent rounded-lg ${gradientClassName}`} />
|
||||
<div className='relative z-[2]'>{children}</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const RadioUI: FC<Props> = ({
|
|||
isChecked,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn(isChecked ? 'border-[5px] border-[#155eef]' : 'border-[2px] border-gray-200', 'w-4 h-4 rounded-full')}>
|
||||
<div className={cn(isChecked ? 'border-[5px] border-components-radio-border-checked' : 'border-[2px] border-components-radio-border', 'w-4 h-4 rounded-full')}>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,14 @@ const Item: FC<ItemProps> = ({
|
|||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn('relative pb-2.5 text-text-tertiary system-sm-semibold-uppercase', !isActive && 'cursor-pointer', className)}
|
||||
className={cn(
|
||||
'relative pb-2.5 system-xl-semibold',
|
||||
!isActive && 'cursor-pointer',
|
||||
className,
|
||||
)}
|
||||
onClick={() => !isActive && onClick(option.value)}
|
||||
>
|
||||
<div className={cn(isActive && 'text-text-primary')}>{option.text}</div>
|
||||
<div className={cn(isActive ? 'text-text-primary' : 'text-text-tertiary')}>{option.text}</div>
|
||||
{isActive && (
|
||||
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-blue-500'></div>
|
||||
)}
|
||||
|
|
@ -52,7 +56,7 @@ const TabSlider: FC<Props> = ({
|
|||
itemClassName,
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('flex space-x-6', !noBorderBottom && 'border-b border-[#EAECF0]', className)}>
|
||||
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
|
||||
{options.map(option => (
|
||||
<Item
|
||||
isActive={option.value === value}
|
||||
|
|
|
|||
|
|
@ -193,11 +193,11 @@ export default function AccountSetting({
|
|||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||
</div>
|
||||
<div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b')}>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b border-divider-regular')}>
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||
<div className='shrink-0 ml-2 text-xs text-text-tertiary'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
{activeItem?.key === 'provider' && (
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ import {
|
|||
} from '@/app/components/plugins/marketplace/hooks'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { PluginType } from '@/app/components/plugins/types'
|
||||
// import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
|
||||
import type { MarketplaceCollection } from '@/app/components/plugins/marketplace/types'
|
||||
import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
|
||||
|
||||
type UseDefaultModelAndModelList = (
|
||||
defaultModel: DefaultModelResponse | undefined,
|
||||
|
|
@ -242,34 +241,11 @@ export const useUpdateModelProviders = () => {
|
|||
return updateModelProviders
|
||||
}
|
||||
|
||||
export const useMarketplace = () => {
|
||||
const [marketplaceCollections] = useState<MarketplaceCollection[]>([])
|
||||
const [marketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>()
|
||||
const [isLoading] = useState(false)
|
||||
|
||||
// const getCollectionPlugins = useCallback(async () => {
|
||||
// setIsLoading(true)
|
||||
// const collectionPlugins = await getMarketplacePluginsByCollectionId('')
|
||||
// setIsLoading(false)
|
||||
|
||||
// setCollectionPlugins(collectionPlugins)
|
||||
// }, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// getCollectionPlugins()
|
||||
// }, [getCollectionPlugins])
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
}
|
||||
}
|
||||
|
||||
export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText: string) => {
|
||||
const exclude = useMemo(() => {
|
||||
return providers.map(provider => provider.provider.replace(/(.+)\/([^/]+)$/, '$1'))
|
||||
}, [providers])
|
||||
const [collectionPlugins, setCollectionPlugins] = useState<Plugin[]>([])
|
||||
|
||||
const {
|
||||
plugins,
|
||||
|
|
@ -278,6 +254,16 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
|
|||
isLoading,
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
const getCollectionPlugins = useCallback(async () => {
|
||||
const collectionPlugins = await getMarketplacePluginsByCollectionId('__model-settings-pinned-models')
|
||||
|
||||
setCollectionPlugins(collectionPlugins)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
getCollectionPlugins()
|
||||
}, [getCollectionPlugins])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
queryPluginsWithDebounced({
|
||||
|
|
@ -298,8 +284,23 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
|
|||
}
|
||||
}, [queryPlugins, queryPluginsWithDebounced, searchText, exclude])
|
||||
|
||||
const allPlugins = useMemo(() => {
|
||||
const allPlugins = [...collectionPlugins.filter(plugin => !exclude.includes(plugin.plugin_id))]
|
||||
|
||||
if (plugins?.length) {
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = plugins[i]
|
||||
|
||||
if (plugin.type !== 'bundle' && !allPlugins.find(p => p.plugin_id === plugin.plugin_id))
|
||||
allPlugins.push(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
return allPlugins
|
||||
}, [plugins, collectionPlugins, exclude])
|
||||
|
||||
return {
|
||||
plugins: plugins?.filter(plugin => plugin.type !== 'bundle'),
|
||||
plugins: allPlugins,
|
||||
isLoading,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {
|
|||
} from './declarations'
|
||||
import {
|
||||
useDefaultModel,
|
||||
useMarketplace,
|
||||
useMarketplaceAllPlugins,
|
||||
useUpdateModelList,
|
||||
useUpdateModelProviders,
|
||||
|
|
@ -121,11 +120,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const locale = getLocaleOnClient()
|
||||
const {
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
isLoading: isPluginsLoading,
|
||||
} = useMarketplace()
|
||||
const {
|
||||
plugins: allPlugins,
|
||||
isLoading: isAllPluginsLoading,
|
||||
|
|
@ -143,7 +137,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||
<div className={cn('flex items-center mb-2')}>
|
||||
<div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
|
||||
<div className={cn(
|
||||
'shrink-0 relative flex items-center justify-end gap-2 p-0.5 rounded-lg border border-transparent',
|
||||
'shrink-0 relative flex items-center justify-end gap-2 p-px rounded-lg border border-transparent',
|
||||
defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
|
||||
)}>
|
||||
{defaultModelNotConfigured && <div className='absolute top-0 bottom-0 right-0 left-0 opacity-40' style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)' }} />}
|
||||
|
|
@ -164,7 +158,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
{!filteredConfiguredProviders?.length && (
|
||||
<div className='mb-2 p-4 rounded-[10px]' style={{ background: 'linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%)' }}>
|
||||
<div className='mb-2 p-4 rounded-[10px] bg-workflow-process-bg'>
|
||||
<div className='w-10 h-10 flex items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur'>
|
||||
<RiBrainLine className='w-5 h-5 text-text-primary' />
|
||||
</div>
|
||||
|
|
@ -213,20 +207,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
|||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!collapse && (isPluginsLoading || isAllPluginsLoading) && <Loading type='area' />}
|
||||
{
|
||||
!isPluginsLoading && !collapse && (
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections || []}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
|
||||
plugins={undefined}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
cardContainerClassName='grid grid-cols-2 gap-2'
|
||||
cardRender={cardRender}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{!collapse && isAllPluginsLoading && <Loading type='area' />}
|
||||
{
|
||||
!isAllPluginsLoading && !collapse && (
|
||||
<List
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type {
|
|||
import { useLanguage } from '../hooks'
|
||||
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
|
||||
import { OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ModelIconProps = {
|
||||
provider?: Model | ModelProvider
|
||||
|
|
@ -20,7 +21,7 @@ const ModelIcon: FC<ModelIconProps> = ({
|
|||
const language = useLanguage()
|
||||
|
||||
if (provider?.provider.includes('openai') && (modelName?.startsWith('gpt-4') || modelName?.includes('4o')))
|
||||
return <OpenaiViolet className={`w-4 h-4 ${className}`}/>
|
||||
return <OpenaiViolet className={cn('w-4 h-4', className)}/>
|
||||
|
||||
if (provider?.icon_small) {
|
||||
return (
|
||||
|
|
@ -28,17 +29,17 @@ const ModelIcon: FC<ModelIconProps> = ({
|
|||
<img
|
||||
alt='model-icon'
|
||||
src={`${provider.icon_small[language] || provider.icon_small.en_US}`}
|
||||
className={`w-4 h-4 ${className}`}
|
||||
className={cn('w-4 h-4', className)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
flex items-center justify-center w-6 h-6 rounded border-[0.5px] border-black/5 bg-gray-50
|
||||
${className}
|
||||
`}>
|
||||
<CubeOutline className='w-4 h-4 text-gray-400' />
|
||||
<div className={cn(
|
||||
'flex items-center justify-center w-6 h-6 rounded border-[0.5px] border-black/5 bg-gray-50',
|
||||
className,
|
||||
)}>
|
||||
<CubeOutline className='w-4 h-4 text-text-quaternary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import Radio from '@/app/components/base/radio'
|
|||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
|
||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||
import RadioE from '@/app/components/base/radio/ui'
|
||||
|
||||
type FormProps = {
|
||||
className?: string
|
||||
|
|
@ -177,17 +178,15 @@ const Form: FC<FormProps> = ({
|
|||
}).map(option => (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
|
||||
${value[variable] === option.value && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
|
||||
flex items-center gap-2 px-3 py-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer
|
||||
${value[variable] === option.value && 'bg-components-option-card-option-selected-bg border-[1.5px] border-components-option-card-option-selected-border shadow-sm'}
|
||||
${disabled && '!cursor-not-allowed opacity-60'}
|
||||
`}
|
||||
onClick={() => handleFormChange(variable, option.value)}
|
||||
key={`${variable}-${option.value}`}
|
||||
>
|
||||
<div className={`
|
||||
flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
|
||||
${value[variable] === option.value && 'border-[5px] border-primary-600'}
|
||||
`} />
|
||||
<RadioE isChecked={value[variable] === option.value} />
|
||||
|
||||
<div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -41,11 +41,11 @@ const Input: FC<InputProps> = ({
|
|||
<input
|
||||
tabIndex={0}
|
||||
className={`
|
||||
block px-3 w-full h-8 bg-components-input-bg-normal text-sm rounded-lg border border-transparent
|
||||
block px-3 w-full h-8 bg-components-input-bg-normal text-sm text-components-input-text-filled rounded-lg border border-transparent
|
||||
appearance-none outline-none caret-primary-600
|
||||
hover:border-[rgba(0,0,0,0.08)] hover:bg-state-hover-alt
|
||||
focus:bg-white focus:border-gray-300 focus:shadow-xs
|
||||
placeholder:text-sm placeholder:text-gray-400
|
||||
hover:border-components-input-border-hover hover:bg-components-input-bg-hover
|
||||
focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs
|
||||
placeholder:text-sm placeholder:text-text-tertiary
|
||||
${validated && 'pr-[30px]'}
|
||||
${className}
|
||||
`}
|
||||
|
|
|
|||
|
|
@ -280,10 +280,10 @@ const ModelModal: FC<ModelModalProps> = ({
|
|||
<PortalToFollowElem open>
|
||||
<PortalToFollowElemContent className='w-full h-full z-[60]'>
|
||||
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-components-panel-bg shadow-xl rounded-2xl overflow-y-auto'>
|
||||
<div className='px-8 pt-8'>
|
||||
<div className='flex justify-between items-center mb-2'>
|
||||
<div className='text-xl font-semibold text-gray-900'>{renderTitlePrefix()}</div>
|
||||
<div className='text-xl font-semibold text-text-primary'>{renderTitlePrefix()}</div>
|
||||
<ProviderIcon provider={provider} />
|
||||
</div>
|
||||
|
||||
|
|
@ -297,7 +297,7 @@ const ModelModal: FC<ModelModalProps> = ({
|
|||
isEditMode={isEditMode}
|
||||
/>
|
||||
|
||||
<div className='mt-1 mb-4 border-t-[0.5px] border-t-gray-100' />
|
||||
<div className='mt-1 mb-4 border-t-[0.5px] border-t-divider-regular' />
|
||||
<ModelLoadBalancingConfigs withSwitch {...{
|
||||
draftConfig,
|
||||
setDraftConfig,
|
||||
|
|
@ -306,7 +306,7 @@ const ModelModal: FC<ModelModalProps> = ({
|
|||
configurationMethod: configurateMethod,
|
||||
}} />
|
||||
|
||||
<div className='sticky bottom-0 flex justify-between items-center mt-2 -mx-2 pt-4 px-2 pb-6 flex-wrap gap-y-2 bg-white'>
|
||||
<div className='sticky bottom-0 flex justify-between items-center mt-2 -mx-2 pt-4 px-2 pb-6 flex-wrap gap-y-2 bg-components-panel-bg'>
|
||||
{
|
||||
(provider.help && (provider.help.title || provider.help.url))
|
||||
? (
|
||||
|
|
@ -326,8 +326,9 @@ const ModelModal: FC<ModelModalProps> = ({
|
|||
{
|
||||
isEditMode && (
|
||||
<Button
|
||||
variant='warning'
|
||||
size='large'
|
||||
className='mr-2 text-[#D92D20]'
|
||||
className='mr-2'
|
||||
onClick={() => setShowConfirm(true)}
|
||||
>
|
||||
{t('common.operation.remove')}
|
||||
|
|
@ -357,21 +358,21 @@ const ModelModal: FC<ModelModalProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-t-[0.5px] border-t-black/5'>
|
||||
<div className='border-t-[0.5px] border-t-divider-regular'>
|
||||
{
|
||||
(validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message)
|
||||
? (
|
||||
<div className='flex px-[10px] py-3 bg-[#FEF3F2] text-xs text-[#D92D20]'>
|
||||
<div className='flex px-[10px] py-3 bg-background-section-burn text-xs text-[#D92D20]'>
|
||||
<RiErrorWarningFill className='mt-[1px] mr-2 w-[14px] h-[14px]' />
|
||||
{validatedStatusState.message}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<div className='flex justify-center items-center py-3 bg-background-section-burn text-xs text-text-tertiary'>
|
||||
<Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
|
||||
{t('common.modelProvider.encrypted.front')}
|
||||
<a
|
||||
className='text-primary-600 mx-1'
|
||||
className='text-text-accent mx-1'
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type AddModelButtonProps = {
|
||||
className?: string
|
||||
|
|
@ -14,10 +15,7 @@ const AddModelButton: FC<AddModelButtonProps> = ({
|
|||
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
shrink-0 flex items-center px-1.5 h-6 text-xs font-medium text-gray-500 cursor-pointer
|
||||
hover:bg-primary-50 hover:text-primary-600 rounded-md ${className}
|
||||
`}
|
||||
className={cn('shrink-0 flex items-center px-1.5 h-6 text-text-tertiary system-xs-medium cursor-pointer hover:bg-components-button-ghost-bg-hover hover:text-components-button-ghost-text rounded-md', className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<PlusCircle className='mr-1 w-3 h-3' />
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
|||
<>
|
||||
{
|
||||
provider.provider_credential_schema && (
|
||||
<div className='shrink-0 relative ml-1 p-1 w-[112px] rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
||||
<div className='flex items-center justify-between mb-1 pt-1 pl-2 pr-[7px] h-5 text-xs font-medium text-gray-500'>
|
||||
<div className='shrink-0 relative ml-1 p-1 w-[112px] rounded-lg bg-white/[0.18] border-[0.5px] border-components-panel-border'>
|
||||
<div className='flex items-center justify-between mb-1 pt-1 pl-2 pr-[7px] h-5 system-xs-medium-uppercase text-text-tertiary'>
|
||||
API-KEY
|
||||
<Indicator color={isCustomConfigured ? 'green' : 'red'} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import type {
|
|||
} from '../declarations'
|
||||
import { ConfigurationMethodEnum } from '../declarations'
|
||||
import {
|
||||
DEFAULT_BACKGROUND_COLOR,
|
||||
MODEL_PROVIDER_QUOTA_GET_PAID,
|
||||
modelTypeFormat,
|
||||
} from '../utils'
|
||||
|
|
@ -27,6 +26,7 @@ import { fetchModelProviderModelList } from '@/service/common'
|
|||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||
type ProviderAddedCardProps = {
|
||||
|
|
@ -82,8 +82,11 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className='mb-2 rounded-xl border-[0.5px] border-black/5 shadow-xs'
|
||||
style={{ background: provider.background || DEFAULT_BACKGROUND_COLOR }}
|
||||
className={cn(
|
||||
'mb-2 rounded-xl border-[0.5px] border-divider-regular shadow-xs bg-third-party-model-bg-default',
|
||||
provider.provider === 'langgenius/openai/openai' && 'bg-third-party-model-bg-openai',
|
||||
provider.provider === 'langgenius/anthropic/anthropic' && 'bg-third-party-model-bg-anthropic',
|
||||
)}
|
||||
>
|
||||
<div className='flex pl-3 py-2 pr-2 rounded-t-xl'>
|
||||
<div className='grow px-1 pt-1 pb-0.5'>
|
||||
|
|
@ -119,7 +122,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
</div>
|
||||
{
|
||||
collapsed && (
|
||||
<div className='group flex items-center justify-between pl-2 py-1.5 pr-[11px] border-t border-t-black/5 bg-white/30 text-xs font-medium text-gray-500'>
|
||||
<div className='group flex items-center justify-between pl-2 py-1.5 pr-[11px] border-t border-t-divider-subtle text-text-tertiary system-xs-medium'>
|
||||
{(showQuota || !notConfigured) && (
|
||||
<>
|
||||
<div className='group-hover:hidden flex items-center pl-1 pr-1.5 h-6 leading-6'>
|
||||
|
|
@ -131,7 +134,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
{!loading && <RiArrowRightSLine className='w-4 h-4' />}
|
||||
</div>
|
||||
<div
|
||||
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer'
|
||||
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-components-button-ghost-bg-hover cursor-pointer'
|
||||
onClick={handleOpenModelList}
|
||||
>
|
||||
{
|
||||
|
|
@ -151,7 +154,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
|||
{!showQuota && notConfigured && (
|
||||
<div className='flex items-center pl-1 pr-1.5 h-6'>
|
||||
<RiInformation2Fill className='mr-1 w-4 h-4 text-text-accent' />
|
||||
<span>{t('common.modelProvider.configureTip')}</span>
|
||||
<span className='text-text-secondary system-xs-medium'>{t('common.modelProvider.configureTip')}</span>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
|
|||
key={model.model}
|
||||
className={classNames(
|
||||
'group flex items-center pl-2 pr-2.5 h-8 rounded-lg',
|
||||
isConfigurable && 'hover:bg-gray-50',
|
||||
isConfigurable && 'hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
model.deprecated && 'opacity-60',
|
||||
)}
|
||||
>
|
||||
|
|
@ -59,14 +59,14 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
|
|||
modelName={model.model}
|
||||
/>
|
||||
<ModelName
|
||||
className='grow text-sm font-normal text-gray-900'
|
||||
className='grow system-md-regular text-text-secondary'
|
||||
modelItem={model}
|
||||
showModelType
|
||||
showMode
|
||||
showContextSize
|
||||
>
|
||||
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && (
|
||||
<ModelBadge className='ml-1 uppercase text-indigo-600 border-indigo-300'>
|
||||
<ModelBadge className='ml-1 uppercase text-text-accent-secondary border-text-accent-secondary'>
|
||||
<Balance className='w-3 h-3 mr-0.5' />
|
||||
{t('common.modelProvider.loadBalancingHeadline')}
|
||||
</ModelBadge>
|
||||
|
|
@ -77,20 +77,22 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
|
|||
model.fetch_from === ConfigurationMethodEnum.customizableModel
|
||||
? (isCurrentWorkspaceManager && (
|
||||
<Button
|
||||
className='hidden group-hover:flex h-7'
|
||||
size='small'
|
||||
className='hidden group-hover:flex'
|
||||
onClick={() => onConfig({ __model_name: model.model, __model_type: model.model_type })}
|
||||
>
|
||||
<Settings01 className='mr-[5px] w-3.5 h-3.5' />
|
||||
<Settings01 className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.modelProvider.config')}
|
||||
</Button>
|
||||
))
|
||||
: (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status))
|
||||
? (
|
||||
<Button
|
||||
className='opacity-0 group-hover:opacity-100 h-[28px] transition-opacity'
|
||||
size='small'
|
||||
className='opacity-0 group-hover:opacity-100 transition-opacity'
|
||||
onClick={() => onModifyLoadBalancing?.(model)}
|
||||
>
|
||||
<Balance className='mr-1 w-[14px] h-[14px]' />
|
||||
<Balance className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.modelProvider.configLoadBalancing')}
|
||||
</Button>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -145,19 +145,19 @@ const ModelLoadBalancingConfigs = ({
|
|||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'min-h-16 bg-gray-50 border rounded-xl transition-colors',
|
||||
(withSwitch || !draftConfig.enabled) ? 'border-gray-200' : 'border-primary-400',
|
||||
'min-h-16 bg-components-panel-bg border rounded-xl transition-colors',
|
||||
(withSwitch || !draftConfig.enabled) ? 'border-components-panel-border' : 'border-util-colors-blue-blue-600',
|
||||
(withSwitch || draftConfig.enabled) ? 'cursor-default' : 'cursor-pointer',
|
||||
className,
|
||||
)}
|
||||
onClick={(!withSwitch && !draftConfig.enabled) ? () => toggleModalBalancing(true) : undefined}
|
||||
>
|
||||
<div className='flex items-center px-[15px] py-3 gap-2 select-none'>
|
||||
<div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 text-primary-600 bg-indigo-50 border border-indigo-100 rounded-lg'>
|
||||
<div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 text-util-colors-blue-blue-600 bg-util-colors-indigo-indigo-50 border border-util-colors-indigo-indigo-100 rounded-lg'>
|
||||
<Balance className='w-4 h-4' />
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='flex items-center gap-1 text-sm'>
|
||||
<div className='flex items-center gap-1 text-sm text-text-primary'>
|
||||
{t('common.modelProvider.loadBalancing')}
|
||||
<Tooltip
|
||||
popupContent={t('common.modelProvider.loadBalancingInfo')}
|
||||
|
|
@ -165,7 +165,7 @@ const ModelLoadBalancingConfigs = ({
|
|||
triggerClassName='w-3 h-3'
|
||||
/>
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>{t('common.modelProvider.loadBalancingDescription')}</div>
|
||||
<div className='text-xs text-text-tertiary'>{t('common.modelProvider.loadBalancingDescription')}</div>
|
||||
</div>
|
||||
{
|
||||
withSwitch && (
|
||||
|
|
@ -184,7 +184,7 @@ const ModelLoadBalancingConfigs = ({
|
|||
{draftConfig.configs.map((config, index) => {
|
||||
const isProviderManaged = config.name === '__inherit__'
|
||||
return (
|
||||
<div key={config.id || index} className='group flex items-center px-3 h-10 bg-white border border-gray-200 rounded-lg shadow-xs'>
|
||||
<div key={config.id || index} className='group flex items-center px-3 h-10 bg-components-panel-on-panel-item-bg border border-components-panel-border rounded-lg shadow-xs'>
|
||||
<div className='grow flex items-center'>
|
||||
<div className='flex items-center justify-center mr-2 w-3 h-3'>
|
||||
{(config.in_cooldown && Boolean(config.ttl))
|
||||
|
|
@ -201,7 +201,7 @@ const ModelLoadBalancingConfigs = ({
|
|||
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
|
||||
</div>
|
||||
{isProviderManaged && (
|
||||
<span className='px-1 text-2xs uppercase text-gray-500 border border-black/8 rounded-[5px]'>{t('common.modelProvider.providerManaged')}</span>
|
||||
<span className='px-1 text-2xs uppercase text-text-tertiary border border-divider-regular rounded-[5px]'>{t('common.modelProvider.providerManaged')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
|
|
@ -209,18 +209,18 @@ const ModelLoadBalancingConfigs = ({
|
|||
<>
|
||||
<div className='flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
|
||||
<span
|
||||
className='flex items-center justify-center w-8 h-8 text-gray-500 bg-white rounded-lg transition-colors cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 text-text-tertiary bg-components-button-secondary-bg rounded-lg transition-colors cursor-pointer hover:bg-components-button-secondary-bg-hover'
|
||||
onClick={() => toggleEntryModal(index, config)}
|
||||
>
|
||||
<Edit02 className='w-4 h-4' />
|
||||
</span>
|
||||
<span
|
||||
className='flex items-center justify-center w-8 h-8 text-gray-500 bg-white rounded-lg transition-colors cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 text-text-tertiary bg-components-button-secondary-bg rounded-lg transition-colors cursor-pointer hover:bg-components-button-secondary-bg-hover'
|
||||
onClick={() => updateConfigEntry(index, () => undefined)}
|
||||
>
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</span>
|
||||
<span className='mr-2 h-3 border-r border-r-gray-100' />
|
||||
<span className='mr-2 h-3 border-r border-r-divider-subtle' />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -247,7 +247,7 @@ const ModelLoadBalancingConfigs = ({
|
|||
)}
|
||||
{
|
||||
draftConfig.enabled && draftConfig.configs.length < 2 && (
|
||||
<div className='flex items-center px-6 h-[34px] text-xs text-gray-700 bg-black/2 border-t border-t-black/5'>
|
||||
<div className='flex items-center px-6 h-[34px] text-xs text-text-secondary bg-components-panel-bg border-t border-t-divider-subtle'>
|
||||
<AlertTriangle className='mr-1 w-3 h-3 text-[#f79009]' />
|
||||
{t('common.modelProvider.loadBalancingLeastKeyWarning')}
|
||||
</div>
|
||||
|
|
@ -257,7 +257,7 @@ const ModelLoadBalancingConfigs = ({
|
|||
|
||||
{!modelLoadBalancingEnabled && !IS_CE_EDITION && (
|
||||
<GridMask canvasClassName='!rounded-xl'>
|
||||
<div className='flex items-center justify-between mt-2 px-4 h-14 border-[0.5px] border-gray-200 rounded-xl shadow-md'>
|
||||
<div className='flex items-center justify-between mt-2 px-4 h-14 border-[0.5px] border-components-panel-border rounded-xl shadow-md'>
|
||||
<div
|
||||
className={classNames('text-sm font-semibold leading-tight text-gradient', s.textGradient)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
modelName={model!.model}
|
||||
/>
|
||||
<ModelName
|
||||
className='grow text-sm font-normal text-gray-900'
|
||||
className='grow system-md-regular text-text-secondary'
|
||||
modelItem={model!}
|
||||
showModelType
|
||||
showMode
|
||||
|
|
@ -137,20 +137,20 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
|
|||
<div className='py-2'>
|
||||
<div
|
||||
className={classNames(
|
||||
'min-h-16 bg-gray-50 border rounded-xl transition-colors',
|
||||
draftConfig.enabled ? 'border-gray-200 cursor-pointer' : 'border-primary-400 cursor-default',
|
||||
'min-h-16 bg-components-panel-bg border rounded-xl transition-colors',
|
||||
draftConfig.enabled ? 'border-components-panel-border cursor-pointer' : 'border-util-colors-blue-blue-600 cursor-default',
|
||||
)}
|
||||
onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}
|
||||
>
|
||||
<div className='flex items-center px-[15px] py-3 gap-2 select-none'>
|
||||
<div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 bg-white border rounded-lg'>
|
||||
<div className='grow-0 shrink-0 flex items-center justify-center w-8 h-8 bg-components-card-bg border border-components-card-border rounded-lg'>
|
||||
{Boolean(model) && (
|
||||
<ModelIcon className='shrink-0' provider={provider} modelName={model!.model} />
|
||||
)}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='text-sm'>{t('common.modelProvider.providerManaged')}</div>
|
||||
<div className='text-xs text-gray-500'>{t('common.modelProvider.providerManagedDescription')}</div>
|
||||
<div className='text-sm text-text-secondary'>{t('common.modelProvider.providerManaged')}</div>
|
||||
<div className='text-xs text-text-tertiary'>{t('common.modelProvider.providerManagedDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from '@remixicon/react'
|
||||
import { PreferredProviderTypeEnum } from '../declarations'
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type SelectorProps = {
|
||||
value?: string
|
||||
|
|
@ -34,11 +35,11 @@ const Selector: FC<SelectorProps> = ({
|
|||
<Popover.Button>
|
||||
{
|
||||
({ open }) => (
|
||||
<Button className={`
|
||||
px-0 w-6 h-6 bg-white rounded-md
|
||||
${open && '!bg-gray-100'}
|
||||
`}>
|
||||
<RiMoreFill className='w-3 h-3 text-gray-700' />
|
||||
<Button className={cn(
|
||||
'px-0 w-6 h-6 rounded-md',
|
||||
open && 'bg-components-button-secondary-bg-hover',
|
||||
)}>
|
||||
<RiMoreFill className='w-3 h-3' />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -49,18 +50,18 @@ const Selector: FC<SelectorProps> = ({
|
|||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<Popover.Panel className='absolute top-7 right-0 w-[144px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg z-10'>
|
||||
<Popover.Panel className='absolute top-7 right-0 w-[144px] bg-components-panel-bg border-[0.5px] border-components-panel-border rounded-lg shadow-lg z-10'>
|
||||
<div className='p-1'>
|
||||
<div className='px-3 pt-2 pb-1 text-sm font-medium text-gray-700'>{t('common.modelProvider.card.priorityUse')}</div>
|
||||
<div className='px-3 pt-2 pb-1 text-sm font-medium text-text-secondary'>{t('common.modelProvider.card.priorityUse')}</div>
|
||||
{
|
||||
options.map(option => (
|
||||
<Popover.Button as={Fragment} key={option.key}>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-9 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-9 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover'
|
||||
onClick={() => onSelect(option.key)}
|
||||
>
|
||||
<div className='grow'>{option.text}</div>
|
||||
{value === option.key && <RiCheckLine className='w-4 h-4 text-primary-600' />}
|
||||
{value === option.key && <RiCheckLine className='w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
</Popover.Button>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ const PriorityUseTip = () => {
|
|||
<Tooltip
|
||||
popupContent={t('common.modelProvider.priorityUsing') || ''}
|
||||
>
|
||||
<div className='absolute -right-[5px] -top-[5px] bg-indigo-50 rounded-[5px] border-[0.5px] border-indigo-100 cursor-pointer'>
|
||||
<ChevronDownDouble className='rotate-180 w-3 h-3 text-indigo-600' />
|
||||
<div className='absolute -right-[5px] -top-[5px] bg-util-colors-indigo-indigo-50 rounded-[5px] border-[0.5px] border-components-panel-border-subtle shadow-xs cursor-pointer'>
|
||||
<ChevronDownDouble className='rotate-180 w-3 h-3 text-util-colors-indigo-indigo-600' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
|||
const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider)
|
||||
|
||||
return (
|
||||
<div className='group relative shrink-0 min-w-[112px] px-3 py-2 rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
||||
<div className='flex items-center mb-2 h-4 text-xs font-medium text-gray-500'>
|
||||
<div className='group relative shrink-0 min-w-[112px] px-3 py-2 rounded-lg bg-white/[0.18] border-[0.5px] border-components-panel-border shadow-xs'>
|
||||
<div className='flex items-center mb-2 h-4 system-xs-medium-uppercase text-text-tertiary'>
|
||||
{t('common.modelProvider.quota')}
|
||||
<Tooltip popupContent={
|
||||
openaiOrAnthropic
|
||||
|
|
@ -40,8 +40,8 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
|
|||
</div>
|
||||
{
|
||||
currentQuota && (
|
||||
<div className='flex items-center h-4 text-xs text-gray-500'>
|
||||
<span className='mr-0.5 text-sm font-semibold text-gray-700'>{formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))}</span>
|
||||
<div className='flex items-center h-4 text-xs text-text-tertiary'>
|
||||
<span className='mr-0.5 system-md-semibold-uppercase text-text-secondary'>{formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))}</span>
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -20,8 +20,6 @@ import {
|
|||
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = ['langgenius/anthropic/anthropic', 'langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
|
||||
|
||||
export const DEFAULT_BACKGROUND_COLOR = '#F3F4F6'
|
||||
|
||||
export const isNullOrUndefined = (value: any) => {
|
||||
return value === undefined || value === null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import type {
|
|||
MarketplaceCollection,
|
||||
PluginsSort,
|
||||
SearchParams,
|
||||
SearchParamsFromCollection,
|
||||
} from './types'
|
||||
import { DEFAULT_SORT } from './constants'
|
||||
import {
|
||||
|
|
@ -49,10 +50,12 @@ export type MarketplaceContextValue = {
|
|||
page: number
|
||||
handlePageChange: (page: number) => void
|
||||
plugins?: Plugin[]
|
||||
pluginsTotal?: number
|
||||
resetPlugins: () => void
|
||||
sort: PluginsSort
|
||||
handleSortChange: (sort: PluginsSort) => void
|
||||
handleQueryPlugins: () => void
|
||||
handleMoreClick: (searchParams: SearchParamsFromCollection) => void
|
||||
marketplaceCollectionsFromClient?: MarketplaceCollection[]
|
||||
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
|
||||
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
|
||||
|
|
@ -73,10 +76,12 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
|||
page: 1,
|
||||
handlePageChange: () => {},
|
||||
plugins: undefined,
|
||||
pluginsTotal: 0,
|
||||
resetPlugins: () => {},
|
||||
sort: DEFAULT_SORT,
|
||||
handleSortChange: () => {},
|
||||
handleQueryPlugins: () => {},
|
||||
handleMoreClick: () => {},
|
||||
marketplaceCollectionsFromClient: [],
|
||||
setMarketplaceCollectionsFromClient: () => {},
|
||||
marketplaceCollectionPluginsMapFromClient: {},
|
||||
|
|
@ -248,7 +253,7 @@ export const MarketplaceContextProvider = ({
|
|||
}, [handleQueryPlugins])
|
||||
|
||||
const handlePageChange = useCallback(() => {
|
||||
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginTextRef.current || !!filterPluginTagsRef.current.length)) {
|
||||
if (pluginsTotal && plugins && pluginsTotal > plugins.length) {
|
||||
setPage(pageRef.current + 1)
|
||||
pageRef.current++
|
||||
|
||||
|
|
@ -256,6 +261,23 @@ export const MarketplaceContextProvider = ({
|
|||
}
|
||||
}, [handleQueryPlugins, plugins, pluginsTotal])
|
||||
|
||||
const handleMoreClick = useCallback((searchParams: SearchParamsFromCollection) => {
|
||||
setSearchPluginText(searchParams?.query || '')
|
||||
searchPluginTextRef.current = searchParams?.query || ''
|
||||
setSort({
|
||||
sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy,
|
||||
sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder,
|
||||
})
|
||||
sortRef.current = {
|
||||
sortBy: searchParams?.sort_by || DEFAULT_SORT.sortBy,
|
||||
sortOrder: searchParams?.sort_order || DEFAULT_SORT.sortOrder,
|
||||
}
|
||||
setPage(1)
|
||||
pageRef.current = 1
|
||||
|
||||
handleQueryPlugins()
|
||||
}, [handleQueryPlugins])
|
||||
|
||||
useMarketplaceContainerScroll(handlePageChange, scrollContainerId)
|
||||
|
||||
return (
|
||||
|
|
@ -272,10 +294,12 @@ export const MarketplaceContextProvider = ({
|
|||
page,
|
||||
handlePageChange,
|
||||
plugins,
|
||||
pluginsTotal,
|
||||
resetPlugins,
|
||||
sort,
|
||||
handleSortChange,
|
||||
handleQueryPlugins,
|
||||
handleMoreClick,
|
||||
marketplaceCollectionsFromClient,
|
||||
setMarketplaceCollectionsFromClient,
|
||||
marketplaceCollectionPluginsMapFromClient,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const CardWrapper = ({
|
|||
if (showInstallButton) {
|
||||
return (
|
||||
<div
|
||||
className='group relative rounded-xl cursor-pointer'
|
||||
className='group relative rounded-xl cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover'
|
||||
>
|
||||
<Card
|
||||
key={plugin.name}
|
||||
|
|
@ -47,7 +47,7 @@ const CardWrapper = ({
|
|||
/>
|
||||
{
|
||||
showInstallButton && (
|
||||
<div className='hidden absolute bottom-0 group-hover:flex items-center space-x-2 px-4 pt-8 pb-4 w-full bg-gradient-to-tr from-[#f9fafb] to-[rgba(249,250,251,0)] rounded-b-xl'>
|
||||
<div className='hidden absolute bottom-0 group-hover:flex items-center space-x-2 px-4 pt-8 pb-4 w-full bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent rounded-b-xl'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-[calc(50%-4px)]'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
'use client'
|
||||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightSLine } from '@remixicon/react'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import CardWrapper from './card-wrapper'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types'
|
||||
|
||||
type ListWithCollectionProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
|
|
@ -12,7 +16,7 @@ type ListWithCollectionProps = {
|
|||
locale: string
|
||||
cardContainerClassName?: string
|
||||
cardRender?: (plugin: Plugin) => JSX.Element | null
|
||||
onMoreClick?: () => void
|
||||
onMoreClick?: (searchParams?: SearchParamsFromCollection) => void
|
||||
}
|
||||
const ListWithCollection = ({
|
||||
marketplaceCollections,
|
||||
|
|
@ -21,8 +25,10 @@ const ListWithCollection = ({
|
|||
locale,
|
||||
cardContainerClassName,
|
||||
cardRender,
|
||||
// onMoreClick,
|
||||
onMoreClick,
|
||||
}: ListWithCollectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
|
|
@ -31,15 +37,22 @@ const ListWithCollection = ({
|
|||
key={collection.name}
|
||||
className='py-3'
|
||||
>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex justify-between items-end'>
|
||||
<div>
|
||||
<div className='title-xl-semi-bold text-text-primary'>{collection.label[getLanguage(locale)]}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{collection.description[getLanguage(locale)]}</div>
|
||||
</div>
|
||||
{/* <div
|
||||
className='system-xs-regular text-text-tertiary cursor-pointer hover:underline'
|
||||
onClick={() => onMoreClick?.()}
|
||||
>more</div> */}
|
||||
{
|
||||
collection.searchable && onMoreClick && (
|
||||
<div
|
||||
className='flex items-center system-xs-medium text-text-accent cursor-pointer '
|
||||
onClick={() => onMoreClick?.(collection.search_params)}
|
||||
>
|
||||
{t('plugin.marketplace.viewMore')}
|
||||
<RiArrowRightSLine className='w-4 h-4' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'grid grid-cols-4 gap-3 mt-2',
|
||||
|
|
|
|||
|
|
@ -22,12 +22,14 @@ const ListWrapper = ({
|
|||
}: ListWrapperProps) => {
|
||||
const { t } = useTranslation()
|
||||
const plugins = useMarketplaceContext(v => v.plugins)
|
||||
const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal)
|
||||
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
|
||||
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
|
||||
const isLoading = useMarketplaceContext(v => v.isLoading)
|
||||
const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections)
|
||||
const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins)
|
||||
const page = useMarketplaceContext(v => v.page)
|
||||
const handleMoreClick = useMarketplaceContext(v => v.handleMoreClick)
|
||||
|
||||
useEffect(() => {
|
||||
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections)
|
||||
|
|
@ -39,7 +41,7 @@ const ListWrapper = ({
|
|||
{
|
||||
plugins && (
|
||||
<div className='top-5 flex items-center mb-4 pt-3'>
|
||||
<div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: plugins.length })}</div>
|
||||
<div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: pluginsTotal })}</div>
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
<SortDropdown />
|
||||
</div>
|
||||
|
|
@ -60,6 +62,7 @@ const ListWrapper = ({
|
|||
plugins={plugins}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
onMoreClick={handleMoreClick}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,22 +53,22 @@ const SortDropdown = () => {
|
|||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='flex items-center px-2 pr-3 h-8 rounded-lg bg-state-base-hover-alt cursor-pointer'>
|
||||
<span className='mr-1 system-sm-regular'>
|
||||
<span className='mr-1 system-sm-regular text-text-secondary'>
|
||||
{t('plugin.marketplace.sortBy')}
|
||||
</span>
|
||||
<span className='mr-1 system-sm-medium'>
|
||||
<span className='mr-1 system-sm-medium text-text-primary'>
|
||||
{selectedOption.text}
|
||||
</span>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent>
|
||||
<div className='p-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
<div className='p-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur backdrop-blur-sm shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={`${option.value}-${option.order}`}
|
||||
className='flex items-center justify-between px-3 pr-2 h-8 cursor-pointer system-md-regular'
|
||||
className='flex items-center justify-between px-3 pr-2 h-8 cursor-pointer system-md-regular text-text-primary rounded-lg hover:bg-components-panel-on-panel-item-bg-hover'
|
||||
onClick={() => handleSortChange({ sortBy: option.value, sortOrder: option.order })}
|
||||
>
|
||||
{option.text}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import type { Plugin } from '../types'
|
||||
|
||||
export type SearchParamsFromCollection = {
|
||||
query?: string
|
||||
sort_by?: string
|
||||
sort_order?: string
|
||||
}
|
||||
|
||||
export type MarketplaceCollection = {
|
||||
name: string
|
||||
label: Record<string, string>
|
||||
|
|
@ -7,6 +13,8 @@ export type MarketplaceCollection = {
|
|||
rule: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
searchable?: boolean
|
||||
search_params?: SearchParamsFromCollection
|
||||
}
|
||||
|
||||
export type MarketplaceCollectionsResponse = {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { RiClipboardLine, RiDeleteBinLine, RiEditLine, RiLoginCircleLine } from '@remixicon/react'
|
||||
import type { EndpointListItem } from '../types'
|
||||
import EndpointModal from './endpoint-modal'
|
||||
import { NAME_FIELD } from './utils'
|
||||
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import { ClipboardCheck } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyBtn from '@/app/components/base/copy-btn'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import {
|
||||
useDeleteEndpoint,
|
||||
useDisableEndpoint,
|
||||
|
|
@ -111,6 +113,25 @@ const EndpointCard = ({
|
|||
state,
|
||||
})
|
||||
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const handleCopy = (value: string) => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isCopied) {
|
||||
const timer = setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const CopyIcon = isCopied ? ClipboardCheck : RiClipboardLine
|
||||
|
||||
return (
|
||||
<div className='p-0.5 bg-background-section-burn rounded-xl'>
|
||||
<div className='group p-2.5 pl-3 bg-components-panel-on-panel-item-bg rounded-[10px] border-[0.5px] border-components-panel-border'>
|
||||
|
|
@ -133,11 +154,11 @@ const EndpointCard = ({
|
|||
<div className='shrink-0 w-12 text-text-tertiary system-xs-regular'>{endpoint.method}</div>
|
||||
<div className='group/item grow flex items-center text-text-secondary system-xs-regular truncate'>
|
||||
<div title={`${data.url}${endpoint.path}`} className='truncate'>{`${data.url}${endpoint.path}`}</div>
|
||||
<CopyBtn
|
||||
className='hidden shrink-0 ml-2 group-hover/item:block'
|
||||
value={`${data.url}${endpoint.path}`}
|
||||
isPlain
|
||||
/>
|
||||
<Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
|
||||
<ActionButton className='hidden shrink-0 ml-2 group-hover/item:flex' onClick={() => handleCopy(`${data.url}${endpoint.path}`)}>
|
||||
<CopyIcon className='w-3.5 h-3.5 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
RiAddLine,
|
||||
|
|
@ -19,6 +20,8 @@ import {
|
|||
useInvalidateEndpointList,
|
||||
} from '@/service/use-endpoints'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import I18n from '@/context/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -26,6 +29,7 @@ type Props = {
|
|||
}
|
||||
const EndpointList = ({ detail }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const pluginUniqueID = detail.plugin_unique_identifier
|
||||
const declaration = detail.declaration.endpoint
|
||||
const showTopBorder = detail.declaration.tool
|
||||
|
|
@ -74,9 +78,8 @@ const EndpointList = ({ detail }: Props) => {
|
|||
<RiApps2AddLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsTip')}</div>
|
||||
{/* TODO endpoints doc link */}
|
||||
<a
|
||||
href=''
|
||||
href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}guides/api-documentation/endpoint`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ type IPluginListProps = {
|
|||
|
||||
const PluginList: FC<IPluginListProps> = ({ pluginList }) => {
|
||||
return (
|
||||
<div className='pb-3 bg-white'>
|
||||
<div className='pb-3'>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
{pluginList.map(plugin => (
|
||||
<PluginItem
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const ProviderCard: FC<Props> = ({
|
|||
const { locale } = useI18N()
|
||||
|
||||
return (
|
||||
<div className={cn('group relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)}>
|
||||
<div className={cn('group relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover:bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)}>
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<Icon src={payload.icon} />
|
||||
|
|
@ -61,7 +61,7 @@ const ProviderCard: FC<Props> = ({
|
|||
))}
|
||||
</div>
|
||||
<div
|
||||
className='hidden group-hover:flex items-center gap-2 absolute bottom-0 left-0 right-0 p-4 pt-8 rounded-xl bg-gradient-to-tr from-[#f9fafb] to-[rgba(249,250,251,0)]'
|
||||
className='hidden group-hover:flex items-center gap-2 absolute bottom-0 left-0 right-0 p-4 pt-8 rounded-xl bg-gradient-to-tr from-components-panel-on-panel-item-bg to-background-gradient-mask-transparent'
|
||||
>
|
||||
<Button
|
||||
className='grow'
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ const Empty = () => {
|
|||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
<div className="shrink-0 w-[163px] h-[149px] bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/empty.png')]"></div>
|
||||
<div className='mb-1 text-[13px] font-medium text-gray-700 leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div>
|
||||
<div className='text-[13px] text-gray-500 leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div>
|
||||
<div className='mb-1 text-[13px] font-medium text-text-secondary leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div>
|
||||
<div className='text-[13px] text-text-tertiary leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Credential } from '@/app/components/tools/types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Radio from '@/app/components/base/radio/ui'
|
||||
|
|
@ -16,7 +17,6 @@ type Props = {
|
|||
onChange: (credential: Credential) => void
|
||||
onHide: () => void
|
||||
}
|
||||
const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
|
||||
|
||||
type ItemProps = {
|
||||
text: string
|
||||
|
|
@ -28,11 +28,11 @@ type ItemProps = {
|
|||
const SelectItem: FC<ItemProps> = ({ text, value, isChecked, onClick }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(isChecked ? 'border-[2px] border-indigo-600 shadow-sm bg-white' : 'border border-gray-100', 'mb-2 flex items-center h-9 pl-3 w-[150px] rounded-xl bg-gray-25 hover:bg-gray-50 cursor-pointer space-x-2')}
|
||||
className={cn(isChecked ? 'border-[2px] border-util-colors-indigo-indigo-600 shadow-sm bg-components-panel-on-panel-item-bg' : 'border border-components-card-border', 'mb-2 flex items-center h-9 pl-3 w-[150px] rounded-xl bg-components-panel-on-panel-item-bg hover:bg-components-panel-on-panel-item-bg-hover cursor-pointer space-x-2')}
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
<Radio isChecked={isChecked} />
|
||||
<div className='text-sm font-normal text-gray-900'>{text}</div>
|
||||
<div className='system-sm-regular text-text-primary'>{text}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -55,12 +55,12 @@ const ConfigCredential: FC<Props> = ({
|
|||
panelClassName='mt-2 !w-[520px] h-fit'
|
||||
maxWidthClassName='!max-w-[520px]'
|
||||
height={'fit-content'}
|
||||
headerClassName='!border-b-black/5'
|
||||
headerClassName='!border-b-divider-regular'
|
||||
body={
|
||||
<div className='pt-2 px-6'>
|
||||
<div className='space-y-4'>
|
||||
<div>
|
||||
<div className={keyClassNames}>{t('tools.createTool.authMethod.type')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.type')}</div>
|
||||
<div className='flex space-x-3'>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authMethod.types.none')}
|
||||
|
|
@ -84,52 +84,52 @@ const ConfigCredential: FC<Props> = ({
|
|||
</div>
|
||||
{tempCredential.auth_type === AuthType.apiKey && (
|
||||
<>
|
||||
<div className={keyClassNames}>{t('tools.createTool.authHeaderPrefix.title')}</div>
|
||||
<div className='flex space-x-3'>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.basic')}
|
||||
value={AuthHeaderPrefix.basic}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.basic}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.bearer')}
|
||||
value={AuthHeaderPrefix.bearer}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.bearer}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.custom')}
|
||||
value={AuthHeaderPrefix.custom}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
<div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authHeaderPrefix.title')}</div>
|
||||
<div className='flex space-x-3'>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.basic')}
|
||||
value={AuthHeaderPrefix.basic}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.basic}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.bearer')}
|
||||
value={AuthHeaderPrefix.bearer}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.bearer}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
<SelectItem
|
||||
text={t('tools.createTool.authHeaderPrefix.types.custom')}
|
||||
value={AuthHeaderPrefix.custom}
|
||||
isChecked={tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom}
|
||||
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='flex items-center h-8 text-[13px] font-medium text-gray-900'>
|
||||
<div className='flex items-center py-2 system-sm-medium text-text-primary'>
|
||||
{t('tools.createTool.authMethod.key')}
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className='w-[261px] text-gray-500'>
|
||||
<div className='w-[261px] text-text-tertiary'>
|
||||
{t('tools.createTool.authMethod.keyTooltip')}
|
||||
</div>
|
||||
}
|
||||
triggerClassName='ml-0.5 w-4 h-4'
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
value={tempCredential.api_key_header}
|
||||
onChange={e => setTempCredential({ ...tempCredential, api_key_header: e.target.value })}
|
||||
className='w-full h-10 px-3 text-sm font-normal border border-transparent bg-gray-100 rounded-lg grow outline-none focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs'
|
||||
placeholder={t('tools.createTool.authMethod.types.apiKeyPlaceholder')!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={keyClassNames}>{t('tools.createTool.authMethod.value')}</div>
|
||||
<input
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.value')}</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={e => setTempCredential({ ...tempCredential, api_key_value: e.target.value })}
|
||||
className='w-full h-10 px-3 text-sm font-normal border border-transparent bg-gray-100 rounded-lg grow outline-none focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs'
|
||||
placeholder={t('tools.createTool.authMethod.types.apiValuePlaceholder')!}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import Toast from '../../base/toast'
|
||||
import examples from './examples'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { importSchemaFromURL } from '@/service/tools'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -63,14 +64,14 @@ const GetSchema: FC<Props> = ({
|
|||
onClick={() => { setShowImportFromUrl(!showImportFromUrl) }}
|
||||
>
|
||||
<RiAddLine className='w-3 h-3' />
|
||||
<div className='text-xs font-medium text-gray-700'>{t('tools.createTool.importFromUrl')}</div>
|
||||
<div className='system-xs-medium text-text-secondary'>{t('tools.createTool.importFromUrl')}</div>
|
||||
</Button>
|
||||
{showImportFromUrl && (
|
||||
<div className=' absolute left-[-35px] top-[26px] p-2 rounded-lg border border-gray-200 bg-white shadow-lg'>
|
||||
<div className=' absolute left-[-35px] top-[26px] p-2 rounded-lg border border-components-panel-border bg-components-panel-bg shadow-lg'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
<Input
|
||||
type='text'
|
||||
className='w-[244px] h-8 pl-1.5 pr-[44px] overflow-x-auto border border-gray-200 rounded-lg text-[13px] focus:outline-none focus:border-components-input-border-active'
|
||||
className='w-[244px]'
|
||||
placeholder={t('tools.createTool.importFromUrlPlaceHolder')!}
|
||||
value={importUrl}
|
||||
onChange={e => setImportUrl(e.target.value)}
|
||||
|
|
@ -95,11 +96,11 @@ const GetSchema: FC<Props> = ({
|
|||
className='space-x-1'
|
||||
onClick={() => { setShowExamples(!showExamples) }}
|
||||
>
|
||||
<div className='text-xs font-medium text-gray-700'>{t('tools.createTool.examples')}</div>
|
||||
<div className='system-xs-medium text-text-secondary'>{t('tools.createTool.examples')}</div>
|
||||
<RiArrowDownSLine className='w-3 h-3' />
|
||||
</Button>
|
||||
{showExamples && (
|
||||
<div className='absolute top-7 right-0 p-1 rounded-lg bg-white shadow-sm'>
|
||||
<div className='absolute top-7 right-0 p-1 rounded-lg bg-components-panel-bg shadow-sm'>
|
||||
{examples.map(item => (
|
||||
<div
|
||||
key={item.key}
|
||||
|
|
@ -107,7 +108,7 @@ const GetSchema: FC<Props> = ({
|
|||
onChange(item.content)
|
||||
setShowExamples(false)
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg hover:bg-gray-50 leading-5 text-sm font-normal text-gray-700 cursor-pointer whitespace-nowrap'
|
||||
className='px-3 py-1.5 rounded-lg hover:bg-components-panel-on-panel-item-bg-hover leading-5 system-sm-regular text-text-secondary cursor-pointer whitespace-nowrap'
|
||||
>
|
||||
{t(`tools.createTool.exampleOptions.${item.key}`)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import type { FC } from 'react'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounce, useGetState } from 'ahooks'
|
||||
import { RiSettings2Line } from '@remixicon/react'
|
||||
import produce from 'immer'
|
||||
import { LinkExternal02, Settings01 } from '../../base/icons/src/vender/line/general'
|
||||
import { LinkExternal02 } from '../../base/icons/src/vender/line/general'
|
||||
import type { Credential, CustomCollectionBackend, CustomParamSchema, Emoji } from '../types'
|
||||
import { AuthHeaderPrefix, AuthType } from '../types'
|
||||
import GetSchema from './get-schema'
|
||||
|
|
@ -21,7 +22,6 @@ import { parseParamsSchema } from '@/service/tools'
|
|||
import LabelSelector from '@/app/components/tools/labels/selector'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const fieldNameClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
|
||||
type Props = {
|
||||
positionLeft?: boolean
|
||||
payload: any
|
||||
|
|
@ -189,12 +189,12 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
panelClassName='mt-2 !w-[640px]'
|
||||
maxWidthClassName='!max-w-[640px]'
|
||||
height='calc(100vh - 16px)'
|
||||
headerClassName='!border-b-black/5'
|
||||
headerClassName='!border-b-divider-regular'
|
||||
body={
|
||||
<div className='flex flex-col h-full'>
|
||||
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} />
|
||||
<Input
|
||||
|
|
@ -214,12 +214,12 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
<div className='select-none'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center'>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.schema')}<span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='mx-2 w-px h-3 bg-black/5'></div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.schema')}<span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='mx-2 w-px h-3 bg-divider-regular'></div>
|
||||
<a
|
||||
href="https://swagger.io/specification/"
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='flex items-center h-[18px] space-x-1 text-[#155EEF]'
|
||||
className='flex items-center h-[18px] space-x-1 text-text-accent'
|
||||
>
|
||||
<div className='text-xs font-normal'>{t('tools.createTool.viewSchemaSpec')}</div>
|
||||
<LinkExternal02 className='w-3 h-3' />
|
||||
|
|
@ -238,11 +238,11 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
|
||||
{/* Available Tools */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.availableTools.title')}</div>
|
||||
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'>
|
||||
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'>
|
||||
<thead className='text-gray-500 uppercase'>
|
||||
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-gray-200')}>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.availableTools.title')}</div>
|
||||
<div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
|
||||
<table className='w-full system-xs-regular text-text-secondary'>
|
||||
<thead className='text-text-tertiary uppercase'>
|
||||
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-divider-regular')}>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.name')}</th>
|
||||
<th className="p-2 pl-3 font-medium w-[236px]">{t('tools.createTool.availableTools.description')}</th>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th>
|
||||
|
|
@ -252,9 +252,9 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</thead>
|
||||
<tbody>
|
||||
{paramsSchemas.map((item, index) => (
|
||||
<tr key={index} className='border-b last:border-0 border-gray-200'>
|
||||
<tr key={index} className='border-b last:border-0 border-divider-regular'>
|
||||
<td className="p-2 pl-3">{item.operation_id}</td>
|
||||
<td className="p-2 pl-3 text-gray-500 w-[236px]">{item.summary}</td>
|
||||
<td className="p-2 pl-3 w-[236px]">{item.summary}</td>
|
||||
<td className="p-2 pl-3">{item.method}</td>
|
||||
<td className="p-2 pl-3">{getPath(item.server_url)}</td>
|
||||
<td className="p-2 pl-3 w-[62px]">
|
||||
|
|
@ -277,22 +277,22 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
|
||||
{/* Authorization method */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
|
||||
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' />
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-components-input-bg-normal rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
|
||||
<RiSettings2Line className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Labels */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<LabelSelector value={labels} onChange={handleLabelSelect} />
|
||||
</div>
|
||||
|
||||
{/* Privacy Policy */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<Input
|
||||
value={customCollection.privacy_policy}
|
||||
onChange={(e) => {
|
||||
|
|
@ -305,7 +305,7 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.customDisclaimer')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.customDisclaimer')}</div>
|
||||
<Input
|
||||
value={customCollection.custom_disclaimer}
|
||||
onChange={(e) => {
|
||||
|
|
@ -318,10 +318,10 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div className={cn(isEdit ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-gray-50 border-t border-black/5')} >
|
||||
<div className={cn(isEdit ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-background-section-burn border-t border-divider-regular')} >
|
||||
{
|
||||
isEdit && (
|
||||
<Button onClick={onRemove} className='text-red-500 border-red-50 hover:border-red-500'>{t('common.operation.delete')}</Button>
|
||||
<Button variant='warning' onClick={onRemove}>{t('common.operation.delete')}</Button>
|
||||
)
|
||||
}
|
||||
<div className='flex space-x-2 '>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import Toast from '@/app/components/base/toast'
|
|||
import Modal from '../../base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
const fieldNameClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
|
||||
type Props = {
|
||||
positionLeft?: boolean
|
||||
payload: any
|
||||
|
|
@ -187,12 +186,12 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
className='!p-0 !max-w-[630px] !h-[calc(100vh-16px)]'
|
||||
>
|
||||
<div className='flex flex-col h-full'>
|
||||
<div className='ml-6 mt-6 text-base font-semibold text-gray-900'>
|
||||
<div className='ml-6 mt-6 text-base font-semibold text-text-primary'>
|
||||
{t('tools.createTool.title')}
|
||||
</div>
|
||||
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} />
|
||||
<Input
|
||||
|
|
@ -212,12 +211,12 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
<div className='select-none'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center'>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.schema')}<span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='mx-2 w-px h-3 bg-black/5'></div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.schema')}<span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='mx-2 w-px h-3 bg-divider-regular'></div>
|
||||
<a
|
||||
href="https://swagger.io/specification/"
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='flex items-center h-[18px] space-x-1 text-[#155EEF]'
|
||||
className='flex items-center h-[18px] space-x-1 text-text-accent'
|
||||
>
|
||||
<div className='text-xs font-normal'>{t('tools.createTool.viewSchemaSpec')}</div>
|
||||
<LinkExternal02 className='w-3 h-3' />
|
||||
|
|
@ -236,11 +235,11 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
|
||||
{/* Available Tools */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.availableTools.title')}</div>
|
||||
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'>
|
||||
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'>
|
||||
<thead className='text-gray-500 uppercase'>
|
||||
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-gray-200')}>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.availableTools.title')}</div>
|
||||
<div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
|
||||
<table className='w-full system-xs-regular text-text-secondary'>
|
||||
<thead className='text-text-tertiary uppercase'>
|
||||
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-divider-regular')}>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.name')}</th>
|
||||
<th className="p-2 pl-3 font-medium w-[236px]">{t('tools.createTool.availableTools.description')}</th>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th>
|
||||
|
|
@ -250,9 +249,9 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</thead>
|
||||
<tbody>
|
||||
{paramsSchemas.map((item, index) => (
|
||||
<tr key={index} className='border-b last:border-0 border-gray-200'>
|
||||
<tr key={index} className='border-b last:border-0 border-divider-regular'>
|
||||
<td className="p-2 pl-3">{item.operation_id}</td>
|
||||
<td className="p-2 pl-3 text-gray-500 w-[236px]">{item.summary}</td>
|
||||
<td className="p-2 pl-3 w-[236px]">{item.summary}</td>
|
||||
<td className="p-2 pl-3">{item.method}</td>
|
||||
<td className="p-2 pl-3">{getPath(item.server_url)}</td>
|
||||
<td className="p-2 pl-3 w-[62px]">
|
||||
|
|
@ -275,22 +274,22 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
|
||||
{/* Authorization method */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
|
||||
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' />
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-components-input-bg-normal rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
|
||||
<Settings01 className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Labels */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<LabelSelector value={labels} onChange={handleLabelSelect} />
|
||||
</div>
|
||||
|
||||
{/* Privacy Policy */}
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<Input
|
||||
value={customCollection.privacy_policy}
|
||||
onChange={(e) => {
|
||||
|
|
@ -303,7 +302,7 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<div className={fieldNameClassNames}>{t('tools.createTool.customDisclaimer')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.customDisclaimer')}</div>
|
||||
<Input
|
||||
value={customCollection.custom_disclaimer}
|
||||
onChange={(e) => {
|
||||
|
|
@ -316,10 +315,10 @@ const EditCustomCollectionModal: FC<Props> = ({
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div className={cn(isEdit ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-gray-50 border-t border-black/5')} >
|
||||
<div className={cn(isEdit ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-background-section-burn border-t border-divider-regular')} >
|
||||
{
|
||||
isEdit && (
|
||||
<Button onClick={onRemove} className='text-red-500 border-red-50 hover:border-red-500'>{t('common.operation.delete')}</Button>
|
||||
<Button variant='warning' onClick={onRemove}>{t('common.operation.delete')}</Button>
|
||||
)
|
||||
}
|
||||
<div className='flex space-x-2 '>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import type { FC } from 'react'
|
|||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { Settings01 } from '../../base/icons/src/vender/line/general'
|
||||
import { RiSettings2Line } from '@remixicon/react'
|
||||
import ConfigCredentials from './config-credentials'
|
||||
import { AuthType, type Credential, type CustomCollectionBackend, type CustomParamSchema } from '@/app/components/tools/types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Drawer from '@/app/components/base/drawer-plus'
|
||||
import I18n from '@/context/i18n'
|
||||
import { testAPIAvailable } from '@/service/tools'
|
||||
|
|
@ -19,8 +20,6 @@ type Props = {
|
|||
onHide: () => void
|
||||
}
|
||||
|
||||
const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
|
||||
|
||||
const TestApi: FC<Props> = ({
|
||||
positionCenter,
|
||||
customCollection,
|
||||
|
|
@ -65,39 +64,40 @@ const TestApi: FC<Props> = ({
|
|||
panelClassName='mt-2 !w-[600px]'
|
||||
maxWidthClassName='!max-w-[600px]'
|
||||
height='calc(100vh - 16px)'
|
||||
headerClassName='!border-b-black/5'
|
||||
headerClassName='!border-b-divider-regular'
|
||||
body={
|
||||
<div className='pt-2 px-6 overflow-y-auto'>
|
||||
<div className='space-y-4'>
|
||||
<div>
|
||||
<div className={keyClassNames}>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}</div>
|
||||
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' />
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.title')}</div>
|
||||
<div className='flex items-center h-9 justify-between px-2.5 bg-components-input-bg-normal rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}>
|
||||
<div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}</div>
|
||||
<RiSettings2Line className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={keyClassNames}>{t('tools.test.parametersValue')}</div>
|
||||
<div className='rounded-lg border border-gray-200'>
|
||||
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'>
|
||||
<thead className='text-gray-500 uppercase'>
|
||||
<tr className='border-b border-gray-200'>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.test.parametersValue')}</div>
|
||||
<div className='rounded-lg border border-divider-regular'>
|
||||
<table className='w-full system-xs-regular text-text-secondary font-normal'>
|
||||
<thead className='text-text-tertiary uppercase'>
|
||||
<tr className='border-b border-divider-regular'>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.test.parameters')}</th>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.test.value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{parameters.map((item, index) => (
|
||||
<tr key={index} className='border-b last:border-0 border-gray-200'>
|
||||
<tr key={index} className='border-b last:border-0 border-divider-regular'>
|
||||
<td className="py-2 pl-3 pr-2.5">
|
||||
{item.label[language]}
|
||||
</td>
|
||||
<td className="">
|
||||
<input
|
||||
<Input
|
||||
value={parametersValue[item.name] || ''}
|
||||
onChange={e => setParametersValue({ ...parametersValue, [item.name]: e.target.value })}
|
||||
type='text' className='px-3 h-[34px] w-full outline-none focus:bg-gray-100' ></input>
|
||||
type='text'
|
||||
className='!bg-transparent !border-transparent !hover:border-transparent !hover:bg-transparent !focus:border-transparent !focus:bg-transparent' />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -110,11 +110,11 @@ const TestApi: FC<Props> = ({
|
|||
<Button variant='primary' className=' mt-4 w-full h-10' onClick={handleTest}>{t('tools.test.title')}</Button>
|
||||
<div className='mt-6'>
|
||||
<div className='flex items-center space-x-3'>
|
||||
<div className='leading-[18px] text-xs font-semibold text-gray-500'>{t('tools.test.testResult')}</div>
|
||||
<div className='system-xs-semibold text-text-tertiary'>{t('tools.test.testResult')}</div>
|
||||
<div className='grow w-0 h-px bg-[rgb(243, 244, 246)]'></div>
|
||||
</div>
|
||||
<div className='mt-2 px-3 py-2 h-[200px] overflow-y-auto overflow-x-hidden rounded-lg bg-gray-100 leading-4 text-xs font-normal text-gray-700'>
|
||||
{result || <span className='text-gray-400'>{t('tools.test.testResultPlaceholder')}</span>}
|
||||
<div className='mt-2 px-3 py-2 h-[200px] overflow-y-auto overflow-x-hidden rounded-lg bg-components-input-bg-normal system-xs-regular text-text-secondary'>
|
||||
{result || <span className='text-text-quaternary'>{t('tools.test.testResultPlaceholder')}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -67,24 +67,23 @@ const LabelFilter: FC<LabelFilterProps> = ({
|
|||
className='block'
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 px-2 h-8 rounded-lg border-[0.5px] border-transparent bg-gray-200 cursor-pointer hover:bg-gray-300',
|
||||
open && !value.length && '!bg-gray-300 hover:bg-gray-300',
|
||||
!open && !!value.length && '!bg-white/80 shadow-xs !border-black/5 hover:!bg-gray-200',
|
||||
open && !!value.length && '!bg-gray-200 !border-black/5 shadow-xs hover:!bg-gray-200',
|
||||
'flex items-center gap-1 px-2 h-8 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal cursor-pointer hover:bg-components-input-bg-hover',
|
||||
!open && !!value.length && 'shadow-xs',
|
||||
open && !!value.length && 'shadow-xs',
|
||||
)}>
|
||||
<div className='p-[1px]'>
|
||||
<Tag01 className='h-3.5 w-3.5 text-gray-700' />
|
||||
<Tag01 className='h-3.5 w-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='text-[13px] leading-[18px] text-gray-700'>
|
||||
<div className='text-[13px] leading-[18px] text-text-tertiary'>
|
||||
{!value.length && t('common.tag.placeholder')}
|
||||
{!!value.length && currentLabel?.label}
|
||||
</div>
|
||||
{value.length > 1 && (
|
||||
<div className='text-xs font-medium leading-[18px] text-gray-500'>{`+${value.length - 1}`}</div>
|
||||
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{`+${value.length - 1}`}</div>
|
||||
)}
|
||||
{!value.length && (
|
||||
<div className='p-[1px]'>
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5 text-gray-700' />
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
)}
|
||||
{!!value.length && (
|
||||
|
|
@ -92,14 +91,14 @@ const LabelFilter: FC<LabelFilterProps> = ({
|
|||
e.stopPropagation()
|
||||
onChange([])
|
||||
}}>
|
||||
<XCircle className='h-3.5 w-3.5 text-gray-400 group-hover/clear:text-gray-600' />
|
||||
<XCircle className='h-3.5 w-3.5 text-text-tertiary group-hover/clear:text-text-secondary' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1002]'>
|
||||
<div className='relative w-[240px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'>
|
||||
<div className='p-2 border-b-[0.5px] border-black/5'>
|
||||
<div className='relative w-[240px] bg-components-panel-bg-blur rounded-lg border-[0.5px] backdrop-blur-[5px] border-components-panel-border shadow-lg'>
|
||||
<div className='p-2'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
|
|
@ -112,17 +111,17 @@ const LabelFilter: FC<LabelFilterProps> = ({
|
|||
{filteredLabelList.map(label => (
|
||||
<div
|
||||
key={label.name}
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-100'
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => selectLabel(label)}
|
||||
>
|
||||
<div title={label.label} className='grow text-sm text-gray-700 leading-5 truncate'>{label.label}</div>
|
||||
{value.includes(label.name) && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
<div title={label.label} className='grow text-sm text-text-secondary leading-5 truncate'>{label.label}</div>
|
||||
{value.includes(label.name) && <Check className='shrink-0 w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
))}
|
||||
{!filteredLabelList.length && (
|
||||
<div className='p-3 flex flex-col items-center gap-1'>
|
||||
<Tag03 className='h-6 w-6 text-gray-300' />
|
||||
<div className='text-gray-500 text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
<Tag03 className='h-6 w-6 text-text-quaternary' />
|
||||
<div className='text-text-tertiary text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -66,21 +66,21 @@ const LabelSelector: FC<LabelSelectorProps> = ({
|
|||
className='block'
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 px-3 h-10 rounded-lg border-[0.5px] border-transparent bg-gray-100 cursor-pointer hover:bg-gray-200',
|
||||
open && '!bg-gray-200 hover:bg-gray-200',
|
||||
'flex items-center gap-1 px-3 h-10 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal cursor-pointer hover:bg-components-input-bg-hover',
|
||||
open && '!hover:bg-components-input-bg-hover hover:bg-components-input-bg-hover',
|
||||
)}>
|
||||
<div title={value.length > 0 ? selectedLabels : ''} className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate', !value.length && '!text-gray-400')}>
|
||||
<div title={value.length > 0 ? selectedLabels : ''} className={cn('grow text-[13px] leading-[18px] text-text-secondary truncate', !value.length && '!text-text-quaternary')}>
|
||||
{!value.length && t('tools.createTool.toolInput.labelPlaceholder')}
|
||||
{!!value.length && selectedLabels}
|
||||
</div>
|
||||
<div className='shrink-0 ml-1 text-gray-700 opacity-60'>
|
||||
<div className='shrink-0 ml-1 text-text-secondary opacity-60'>
|
||||
<RiArrowDownSLine className='h-4 w-4' />
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1040]'>
|
||||
<div className='relative w-[591px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'>
|
||||
<div className='p-2 border-b-[0.5px] border-black/5'>
|
||||
<div className='relative w-[591px] bg-components-panel-bg-blur backdrop-blur-[5px] rounded-lg border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div className='p-2 border-b-[0.5px] border-divider-regular'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
|
|
@ -93,7 +93,7 @@ const LabelSelector: FC<LabelSelectorProps> = ({
|
|||
{filteredLabelList.map(label => (
|
||||
<div
|
||||
key={label.name}
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-gray-100'
|
||||
className='flex items-center gap-2 pl-3 py-[6px] pr-2 rounded-lg cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover'
|
||||
onClick={() => selectLabel(label)}
|
||||
>
|
||||
<Checkbox
|
||||
|
|
@ -101,13 +101,13 @@ const LabelSelector: FC<LabelSelectorProps> = ({
|
|||
checked={value.includes(label.name)}
|
||||
onCheck={() => { }}
|
||||
/>
|
||||
<div title={label.label} className='grow text-sm text-gray-700 leading-5 truncate'>{label.label}</div>
|
||||
<div title={label.label} className='grow text-sm text-text-secondary leading-5 truncate'>{label.label}</div>
|
||||
</div>
|
||||
))}
|
||||
{!filteredLabelList.length && (
|
||||
<div className='p-3 flex flex-col items-center gap-1'>
|
||||
<Tag03 className='h-6 w-6 text-gray-300' />
|
||||
<div className='text-gray-500 text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
<Tag03 className='h-6 w-6 text-text-quaternary' />
|
||||
<div className='text-text-tertiary text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ const ProviderList = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className='relative flex overflow-hidden bg-gray-100 shrink-0 h-0 grow'>
|
||||
<div className='relative flex overflow-hidden shrink-0 h-0 grow'>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className='relative flex flex-col overflow-y-auto bg-gray-100 grow'
|
||||
className='relative flex flex-col overflow-y-auto bg-background-body grow'
|
||||
>
|
||||
<div className={cn(
|
||||
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-20 flex-wrap gap-y-2',
|
||||
'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] z-20 flex-wrap gap-y-2',
|
||||
currentProvider && 'pr-6',
|
||||
)}>
|
||||
<TabSliderNew
|
||||
|
|
@ -96,6 +96,7 @@ const ProviderList = () => {
|
|||
{(filteredCollectionList.length > 0 || activeTab !== 'builtin') && (
|
||||
<div className={cn(
|
||||
'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 shrink-0',
|
||||
!filteredCollectionList.length && activeTab === 'workflow' && 'grow',
|
||||
)}>
|
||||
{activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
|
||||
{filteredCollectionList.map(collection => (
|
||||
|
|
|
|||
|
|
@ -45,16 +45,16 @@ const Contribute = ({ onRefreshData }: Props) => {
|
|||
return (
|
||||
<>
|
||||
{isCurrentWorkspaceManager && (
|
||||
<div className='flex flex-col col-span-1 bg-gray-200 border-[0.5px] border-black/5 rounded-xl min-h-[135px] transition-all duration-200 ease-in-out cursor-pointer hover:bg-gray-50 hover:shadow-lg'>
|
||||
<div className='group grow rounded-t-xl hover:bg-white' onClick={() => setIsShowEditCustomCollectionModal(true)}>
|
||||
<div className='flex flex-col col-span-1 bg-components-panel-on-panel-item-bg border-[0.5px] border-divider-subtle rounded-xl min-h-[135px] transition-all duration-200 ease-in-out cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-lg'>
|
||||
<div className='group grow rounded-t-xl hover:bg-background-body' onClick={() => setIsShowEditCustomCollectionModal(true)}>
|
||||
<div className='shrink-0 flex items-center p-4 pb-3'>
|
||||
<div className='w-10 h-10 flex items-center justify-center border border-gray-200 bg-gray-100 rounded-lg group-hover:border-primary-100 group-hover:bg-primary-50'>
|
||||
<RiAddLine className='w-4 h-4 text-gray-500 group-hover:text-primary-600'/>
|
||||
<div className='w-10 h-10 flex items-center justify-center border border-components-option-card-option-border bg-components-option-card-option-bg rounded-lg group-hover:border-components-option-card-option-border-hover group-hover:bg-components-option-card-option-bg-hover'>
|
||||
<RiAddLine className='w-4 h-4 text-text-tertiary group-hover:text-text-accent'/>
|
||||
</div>
|
||||
<div className='ml-3 text-sm font-semibold leading-5 text-gray-800 group-hover:text-primary-600'>{t('tools.createCustomTool')}</div>
|
||||
<div className='ml-3 text-sm font-semibold leading-5 text-text-primary group-hover:text-text-accent'>{t('tools.createCustomTool')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 py-3 rounded-b-xl border-t-[0.5px] border-black/5 text-gray-500 hover:text-[#155EEF] hover:bg-white'>
|
||||
<div className='px-4 py-3 rounded-b-xl border-t-[0.5px] border-divider-regular text-text-tertiary hover:text-text-accent hover:bg-background-body'>
|
||||
<a href={linkUrl} target='_blank' rel='noopener noreferrer' className='flex items-center space-x-1'>
|
||||
<BookOpen01 className='shrink-0 w-3 h-3' />
|
||||
<div className='grow leading-[18px] text-xs font-normal truncate' title={t('tools.customToolTip') || ''}>{t('tools.customToolTip')}</div>
|
||||
|
|
|
|||
|
|
@ -260,14 +260,14 @@ const ProviderDetail = ({
|
|||
{!!collection.description[language] && (
|
||||
<Description text={collection.description[language]} descriptionLineRows={2}></Description>
|
||||
)}
|
||||
<div className='flex gap-1 border-b-[0.5px] border-black/5'>
|
||||
<div className='flex gap-1 border-b-[0.5px] border-divider-subtle'>
|
||||
{collection.type === CollectionType.custom && !isDetailLoading && (
|
||||
<Button
|
||||
className={cn('shrink-0 my-3 w-full')}
|
||||
onClick={() => setIsShowEditCustomCollectionModal(true)}
|
||||
>
|
||||
<Settings01 className='mr-1 w-4 h-4 text-gray-500' />
|
||||
<div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div>
|
||||
<Settings01 className='mr-1 w-4 h-4 text-text-tertiary' />
|
||||
<div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div>
|
||||
</Button>
|
||||
)}
|
||||
{collection.type === CollectionType.workflow && !isDetailLoading && customCollection && (
|
||||
|
|
@ -276,8 +276,8 @@ const ProviderDetail = ({
|
|||
variant='primary'
|
||||
className={cn('shrink-0 my-3 w-[183px]')}
|
||||
>
|
||||
<a className='flex items-center text-white' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
|
||||
<div className='leading-5 text-sm font-medium'>{t('tools.openInStudio')}</div>
|
||||
<a className='flex items-center text-text-primary' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
|
||||
<div className='system-sm-medium'>{t('tools.openInStudio')}</div>
|
||||
<LinkExternal02 className='ml-1 w-4 h-4' />
|
||||
</a>
|
||||
</Button>
|
||||
|
|
@ -286,7 +286,7 @@ const ProviderDetail = ({
|
|||
onClick={() => setIsShowEditWorkflowToolModal(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
<div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div>
|
||||
<div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -319,7 +319,7 @@ const ProviderDetail = ({
|
|||
<div className='text-text-secondary system-sm-semibold-uppercase'>
|
||||
<span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span>
|
||||
<span className='px-1'>·</span>
|
||||
<span className='text-[#DC6803]'>{t('tools.auth.setup').toLocaleUpperCase()}</span>
|
||||
<span className='text-util-colors-orange-orange-600'>{t('tools.auth.setup').toLocaleUpperCase()}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant='primary'
|
||||
|
|
|
|||
|
|
@ -71,11 +71,11 @@ const ConfigCredential: FC<Props> = ({
|
|||
onHide={onCancel}
|
||||
title={t('tools.auth.setupModalTitle') as string}
|
||||
titleDescription={t('tools.auth.setupModalTitleDescription') as string}
|
||||
panelClassName='mt-[64px] mb-2 !w-[420px]'
|
||||
panelClassName='mt-[64px] mb-2 !w-[420px] border-components-panel-border'
|
||||
maxWidthClassName='!max-w-[420px]'
|
||||
height='calc(100vh - 64px)'
|
||||
contentClassName='!bg-gray-100'
|
||||
headerClassName='!border-b-black/5'
|
||||
contentClassName='!bg-components-panel-bg'
|
||||
headerClassName='!border-b-divider-subtle'
|
||||
body={
|
||||
|
||||
<div className='px-6 py-3 h-full'>
|
||||
|
|
@ -92,12 +92,12 @@ const ConfigCredential: FC<Props> = ({
|
|||
isEditMode={true}
|
||||
showOnVariableMap={{}}
|
||||
validating={false}
|
||||
inputClassName='!bg-gray-50'
|
||||
inputClassName='!bg-components-input-bg-normal'
|
||||
fieldMoreInfo={item => item.url
|
||||
? (<a
|
||||
href={item.url}
|
||||
target='_blank' rel='noopener noreferrer'
|
||||
className='inline-flex items-center text-xs text-primary-600'
|
||||
className='inline-flex items-center text-xs text-text-accent'
|
||||
>
|
||||
{t('tools.howToGet')}
|
||||
<LinkExternal02 className='ml-1 w-3 h-3' />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
|
|
@ -19,24 +18,24 @@ const ConfirmModal = ({ show, onConfirm, onClose }: ConfirmModalProps) => {
|
|||
|
||||
return (
|
||||
<Modal
|
||||
className={cn('p-8 max-w-[600px] w-[600px]', s.bg)}
|
||||
className={cn('p-8 max-w-[600px] w-[600px]')}
|
||||
isShow={show}
|
||||
onClose={() => { }}
|
||||
>
|
||||
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='w-12 h-12 p-3 bg-white rounded-xl border-[0.5px] border-gray-100 shadow-xl'>
|
||||
<div className='w-12 h-12 p-3 bg-background-section rounded-xl border-[0.5px] border-divider-regular shadow-xl'>
|
||||
<AlertTriangle className='w-6 h-6 text-[rgb(247,144,9)]' />
|
||||
</div>
|
||||
<div className='relative mt-3 text-xl font-semibold leading-[30px] text-gray-900'>{t('tools.createTool.confirmTitle')}</div>
|
||||
<div className='my-1 text-gray-500 text-sm leading-5'>
|
||||
<div className='relative mt-3 text-xl font-semibold leading-[30px] text-text-primary'>{t('tools.createTool.confirmTitle')}</div>
|
||||
<div className='my-1 text-text-tertiary text-sm leading-5'>
|
||||
{t('tools.createTool.confirmTip')}
|
||||
</div>
|
||||
<div className='pt-6 flex justify-end items-center'>
|
||||
<div className='flex items-center'>
|
||||
<Button className='mr-2' onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button className='border-red-700' variant="warning" onClick={onConfirm}>{t('common.operation.confirm')}</Button>
|
||||
<Button variant="warning" onClick={onConfirm}>{t('common.operation.confirm')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -124,13 +124,13 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
panelClassName='mt-2 !w-[640px]'
|
||||
maxWidthClassName='!max-w-[640px]'
|
||||
height='calc(100vh - 16px)'
|
||||
headerClassName='!border-b-black/5'
|
||||
headerClassName='!border-b-divider'
|
||||
body={
|
||||
<div className='flex flex-col h-full'>
|
||||
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
|
||||
{/* name & icon */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div>
|
||||
<div className='flex items-center justify-between gap-3'>
|
||||
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' iconType='emoji' icon={emoji.content} background={emoji.background} />
|
||||
<Input
|
||||
|
|
@ -143,7 +143,7 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
</div>
|
||||
{/* name for tool call */}
|
||||
<div>
|
||||
<div className='flex items-center py-2 leading-5 text-sm font-medium text-gray-900'>
|
||||
<div className='flex items-center py-2 system-sm-medium text-text-primary'>
|
||||
{t('tools.createTool.nameForToolCall')} <span className='ml-1 text-red-500'>*</span>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
|
|
@ -165,7 +165,7 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
</div>
|
||||
{/* description */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.description')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.description')}</div>
|
||||
<Textarea
|
||||
placeholder={t('tools.createTool.descriptionPlaceholder') || ''}
|
||||
value={description}
|
||||
|
|
@ -174,11 +174,11 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
</div>
|
||||
{/* Tool Input */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.title')}</div>
|
||||
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'>
|
||||
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'>
|
||||
<thead className='text-gray-500 uppercase'>
|
||||
<tr className='border-b border-gray-200'>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.toolInput.title')}</div>
|
||||
<div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
|
||||
<table className='w-full leading-[18px] text-xs text-text-secondary font-normal'>
|
||||
<thead className='text-text-tertiary uppercase'>
|
||||
<tr className='border-b border-divider-regular'>
|
||||
<th className="p-2 pl-3 font-medium w-[156px]">{t('tools.createTool.toolInput.name')}</th>
|
||||
<th className="p-2 pl-3 font-medium w-[102px]">{t('tools.createTool.toolInput.method')}</th>
|
||||
<th className="p-2 pl-3 font-medium">{t('tools.createTool.toolInput.description')}</th>
|
||||
|
|
@ -186,22 +186,22 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
</thead>
|
||||
<tbody>
|
||||
{parameters.map((item, index) => (
|
||||
<tr key={index} className='border-b last:border-0 border-gray-200'>
|
||||
<tr key={index} className='border-b last:border-0 border-divider-regular'>
|
||||
<td className="p-2 pl-3 max-w-[156px]">
|
||||
<div className='text-[13px] leading-[18px]'>
|
||||
<div title={item.name} className='flex'>
|
||||
<span className='font-medium text-gray-900 truncate'>{item.name}</span>
|
||||
<span className='font-medium text-text-primary truncate'>{item.name}</span>
|
||||
<span className='shrink-0 pl-1 text-[#ec4a0a] text-xs leading-[18px]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span>
|
||||
</div>
|
||||
<div className='text-gray-500'>{item.type}</div>
|
||||
<div className='text-text-tertiary'>{item.type}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{item.name === '__image' && (
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 min-h-[56px] px-3 py-2 h-9 bg-white cursor-default',
|
||||
'flex items-center gap-1 min-h-[56px] px-3 py-2 h-9 bg-transparent cursor-default',
|
||||
)}>
|
||||
<div className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate')}>
|
||||
<div className={cn('grow text-[13px] leading-[18px] text-text-secondary truncate')}>
|
||||
{t('tools.createTool.toolInput.methodParameter')}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -210,10 +210,10 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
<MethodSelector value={item.form} onChange={value => handleParameterChange('form', value, index)} />
|
||||
)}
|
||||
</td>
|
||||
<td className="p-2 pl-3 text-gray-500 w-[236px]">
|
||||
<td className="p-2 pl-3 text-text-tertiary w-[236px]">
|
||||
<input
|
||||
type='text'
|
||||
className='grow text-gray-700 text-[13px] leading-[18px] font-normal bg-white outline-none appearance-none caret-primary-600 placeholder:text-gray-300'
|
||||
className='w-full text-text-secondary text-[13px] leading-[18px] font-normal bg-transparent outline-none appearance-none caret-primary-600 placeholder:text-text-quaternary'
|
||||
placeholder={t('tools.createTool.toolInput.descriptionPlaceholder')!}
|
||||
value={item.description}
|
||||
onChange={e => handleParameterChange('description', e.target.value, index)}
|
||||
|
|
@ -227,12 +227,12 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
</div>
|
||||
{/* Tags */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.toolInput.label')}</div>
|
||||
<LabelSelector value={labels} onChange={handleLabelSelect} />
|
||||
</div>
|
||||
{/* Privacy Policy */}
|
||||
<div>
|
||||
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.privacyPolicy')}</div>
|
||||
<Input
|
||||
className='h-10'
|
||||
value={privacyPolicy}
|
||||
|
|
@ -240,9 +240,9 @@ const WorkflowToolAsModal: FC<Props> = ({
|
|||
placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn((!isAdd && onRemove) ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-gray-50 border-t border-black/5')} >
|
||||
<div className={cn((!isAdd && onRemove) ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-background-section-burn border-t border-divider-regular')} >
|
||||
{!isAdd && onRemove && (
|
||||
<Button onClick={onRemove} className='text-red-500 border-red-50 hover:border-red-500'>{t('common.operation.delete')}</Button>
|
||||
<Button variant='warning' onClick={onRemove}>{t('common.operation.delete')}</Button>
|
||||
)}
|
||||
<div className='flex space-x-2 '>
|
||||
<Button onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
|
|
|
|||
|
|
@ -34,37 +34,37 @@ const MethodSelector: FC<MethodSelectorProps> = ({
|
|||
className='block'
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center gap-1 min-h-[56px] px-3 py-2 h-9 bg-white cursor-pointer hover:bg-gray-100',
|
||||
open && '!bg-gray-100 hover:bg-gray-100',
|
||||
'flex items-center gap-1 min-h-[56px] px-3 py-2 h-9 bg-transparent cursor-pointer hover:bg-background-section-burn',
|
||||
open && '!bg-background-section-burn hover:bg-background-section-burn',
|
||||
)}>
|
||||
<div className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate')}>
|
||||
<div className={cn('grow text-[13px] leading-[18px] text-text-secondary truncate')}>
|
||||
{value === 'llm' ? t('tools.createTool.toolInput.methodParameter') : t('tools.createTool.toolInput.methodSetting')}
|
||||
</div>
|
||||
<div className='shrink-0 ml-1 text-gray-700 opacity-60'>
|
||||
<div className='shrink-0 ml-1 text-text-secondary opacity-60'>
|
||||
<RiArrowDownSLine className='h-4 w-4' />
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1040]'>
|
||||
<div className='relative w-[320px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'>
|
||||
<div className='relative w-[320px] bg-components-panel-bg-blur backdrop-blur-sm rounded-lg border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div className='p-1'>
|
||||
<div className='pl-3 pr-2 py-2.5 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => onChange('llm')}>
|
||||
<div className='pl-3 pr-2 py-2.5 rounded-lg hover:bg-components-panel-on-panel-item-bg-hover cursor-pointer' onClick={() => onChange('llm')}>
|
||||
<div className='flex item-center gap-1'>
|
||||
<div className='shrink-0 w-4 h-4'>
|
||||
{value === 'llm' && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
{value === 'llm' && <Check className='shrink-0 w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
<div className='text-[13px] text-gray-700 font-medium leading-[18px]'>{t('tools.createTool.toolInput.methodParameter')}</div>
|
||||
<div className='text-[13px] text-text-secondary font-medium leading-[18px]'>{t('tools.createTool.toolInput.methodParameter')}</div>
|
||||
</div>
|
||||
<div className='pl-5 text-gray-500 text-[13px] leading-[18px]'>{t('tools.createTool.toolInput.methodParameterTip')}</div>
|
||||
<div className='pl-5 text-text-tertiary text-[13px] leading-[18px]'>{t('tools.createTool.toolInput.methodParameterTip')}</div>
|
||||
</div>
|
||||
<div className='pl-3 pr-2 py-2.5 rounded-lg hover:bg-gray-50 cursor-pointer' onClick={() => onChange('form')}>
|
||||
<div className='pl-3 pr-2 py-2.5 rounded-lg hover:bg-components-panel-on-panel-item-bg-hover cursor-pointer' onClick={() => onChange('form')}>
|
||||
<div className='flex item-center gap-1'>
|
||||
<div className='shrink-0 w-4 h-4'>
|
||||
{value === 'form' && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
|
||||
{value === 'form' && <Check className='shrink-0 w-4 h-4 text-text-accent' />}
|
||||
</div>
|
||||
<div className='text-[13px] text-gray-700 font-medium leading-[18px]'>{t('tools.createTool.toolInput.methodSetting')}</div>
|
||||
<div className='text-[13px] text-text-secondary font-medium leading-[18px]'>{t('tools.createTool.toolInput.methodSetting')}</div>
|
||||
</div>
|
||||
<div className='pl-5 text-gray-500 text-[13px] leading-[18px]'>{t('tools.createTool.toolInput.methodSettingTip')}</div>
|
||||
<div className='pl-5 text-text-tertiary text-[13px] leading-[18px]'>{t('tools.createTool.toolInput.methodSettingTip')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ export const useWorkflowRun = () => {
|
|||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.data._waitingRun = true
|
||||
node.data._runningBranchId = undefined
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { OutputVar } from '../../code/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import type {
|
||||
CodeNodeType,
|
||||
OutputVar,
|
||||
} from '../../code/types'
|
||||
import type {
|
||||
ValueSelector,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
BlockEnum,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
|||
[VarType.number]: VarType.arrayNumber,
|
||||
[VarType.object]: VarType.arrayObject,
|
||||
[VarType.file]: VarType.arrayFile,
|
||||
// list operator node can output array
|
||||
[VarType.array]: VarType.array,
|
||||
[VarType.arrayFile]: VarType.arrayFile,
|
||||
[VarType.arrayString]: VarType.arrayString,
|
||||
[VarType.arrayNumber]: VarType.arrayNumber,
|
||||
[VarType.arrayObject]: VarType.arrayObject,
|
||||
} as Record<VarType, VarType>)[outputItemType] || VarType.arrayString
|
||||
})
|
||||
setInputs(newInputs)
|
||||
|
|
|
|||
|
|
@ -59,20 +59,12 @@ const InputVarList: FC<Props> = ({
|
|||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
const target = draft[variable]
|
||||
if (target) {
|
||||
if (!isSupportConstantValue || varKindType === VarKindType.variable) {
|
||||
if (isSupportConstantValue)
|
||||
target.type = VarKindType.variable
|
||||
|
||||
target.value = varValue as ValueSelector
|
||||
}
|
||||
else {
|
||||
target.type = VarKindType.constant
|
||||
target.value = varValue as string
|
||||
}
|
||||
target.type = varKindType
|
||||
target.value = varValue
|
||||
}
|
||||
else {
|
||||
draft[variable] = {
|
||||
type: VarKindType.variable,
|
||||
type: varKindType,
|
||||
value: varValue,
|
||||
}
|
||||
}
|
||||
|
|
@ -170,7 +162,7 @@ const InputVarList: FC<Props> = ({
|
|||
value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])}
|
||||
onChange={handleNotMixedTypeChange(variable)}
|
||||
onOpen={handleOpen(index)}
|
||||
defaultVarKindType={isNumber ? VarKindType.constant : VarKindType.variable}
|
||||
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
|
||||
isSupportConstantValue={isSupportConstantValue}
|
||||
filterVar={isNumber ? filterVar : undefined}
|
||||
availableVars={isSelect ? availableVars : undefined}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
|||
draft.tool_parameters = {}
|
||||
})
|
||||
setInputs(inputsWithDefaultValue)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currTool])
|
||||
|
||||
// setting when call
|
||||
|
|
@ -214,8 +214,13 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
|||
.map(k => inputs.tool_parameters[k])
|
||||
|
||||
const varInputs = getInputVars(hadVarParams.map((p) => {
|
||||
if (p.type === VarType.variable)
|
||||
if (p.type === VarType.variable) {
|
||||
// handle the old wrong value not crash the page
|
||||
if (!(p.value as any).join)
|
||||
return `{{#${p.value}#}}`
|
||||
|
||||
return `{{#${(p.value as ValueSelector).join('.')}#}}`
|
||||
}
|
||||
|
||||
return p.value as string
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ const translation = {
|
|||
newlyReleased: 'Newly Released',
|
||||
firstReleased: 'First Released',
|
||||
},
|
||||
viewMore: 'View more',
|
||||
},
|
||||
task: {
|
||||
installing: 'Installing {{installingLength}} plugins, 0 done.',
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ const translation = {
|
|||
newlyReleased: '最新发布',
|
||||
firstReleased: '首次发布',
|
||||
},
|
||||
viewMore: '查看更多',
|
||||
},
|
||||
task: {
|
||||
installing: '{{installingLength}} 个插件安装中,0 已完成',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "dify-web",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
"@next/mdx": "^14.0.4",
|
||||
"@octokit/core": "^6.1.2",
|
||||
"@octokit/request-error": "^6.1.5",
|
||||
"@remixicon/react": "^4.3.0",
|
||||
"@remixicon/react": "^4.5.0",
|
||||
"@sentry/react": "^7.54.0",
|
||||
"@sentry/utils": "^7.54.0",
|
||||
"@svgdotjs/svg.js": "^3.2.4",
|
||||
|
|
@ -91,6 +91,8 @@
|
|||
"react-multi-email": "^1.0.25",
|
||||
"react-papaparse": "^4.4.0",
|
||||
"react-slider": "^2.0.6",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-pdf-highlighter": "^8.0.0-rc.0",
|
||||
"react-sortablejs": "^6.1.4",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-tooltip": "5.8.3",
|
||||
|
|
|
|||
30574
web/pnpm-lock.yaml
30574
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -66,6 +66,7 @@ const config = {
|
|||
// => @media (min-width: 600px) { ... }
|
||||
pc: '769px',
|
||||
// => @media (min-width: 769px) { ... }
|
||||
'2k': '2560px',
|
||||
},
|
||||
boxShadow: {
|
||||
'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)',
|
||||
|
|
|
|||
|
|
@ -2242,10 +2242,10 @@
|
|||
classcat "^5.0.3"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@remixicon/react@^4.3.0":
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmjs.org/@remixicon/react/-/react-4.3.0.tgz#8ab34d03fccca53bf66f87c6e3f943ef5c65684f"
|
||||
integrity sha512-mAVDn8pAa9dURltGwiYrf7bPIqjG4ZAnCUHfjpgz3g+HLSDNXOaJ67Z5wmjVB5KMGpp9JbbTN5vsp2z+ajVLWg==
|
||||
"@remixicon/react@^4.5.0":
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@remixicon/react/-/react-4.5.0.tgz#5600d122ee4995bff2c4442cb056eeb4f11ecb5a"
|
||||
integrity sha512-Xr20SxMpRNlgXZnoF5BCMyZuQEhXY3yJCyms8kxB/vJCCiV1nWdiO48XqRG5LBd1192iSHC4m658AIWi6rmBFg==
|
||||
|
||||
"@rgrove/parse-xml@^4.1.0":
|
||||
version "4.1.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue