diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index 0c2ccd826e..51db50ec3d 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -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( diff --git a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py new file mode 100644 index 0000000000..285384ee6e --- /dev/null +++ b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py @@ -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")] diff --git a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py new file mode 100644 index 0000000000..304c7d6598 --- /dev/null +++ b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py @@ -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"] == "" diff --git a/docker-legacy/docker-compose.yaml b/docker-legacy/docker-compose.yaml index 4392407a73..6c38b5c4f9 100644 --- a/docker-legacy/docker-compose.yaml +++ b/docker-legacy/docker-compose.yaml @@ -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 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 56effa6293..669f6eb4dd 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -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:-} diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 4833563147..8f064c209e 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -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 { diff --git a/web/app/components/base/app-icon/style.module.css b/web/app/components/base/app-icon/style.module.css new file mode 100644 index 0000000000..151bc6d3fc --- /dev/null +++ b/web/app/components/base/app-icon/style.module.css @@ -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; +} \ No newline at end of file diff --git a/web/app/components/base/copy-btn/index.tsx b/web/app/components/base/copy-btn/index.tsx index c6428746f2..5159a96040 100644 --- a/web/app/components/base/copy-btn/index.tsx +++ b/web/app/components/base/copy-btn/index.tsx @@ -36,7 +36,7 @@ const CopyBtn = ({ >
-
+
diff --git a/web/app/components/base/drawer/index.tsx b/web/app/components/base/drawer/index.tsx index d9df7cc053..c1d7565882 100644 --- a/web/app/components/base/drawer/index.tsx +++ b/web/app/components/base/drawer/index.tsx @@ -49,18 +49,18 @@ export default function Drawer({ -
+
<> {title && {title} } {showClose && - + } - {description && {description}} + {description && {description}} {children} {footer || (footer === null diff --git a/web/app/components/base/emoji-picker/Inner.tsx b/web/app/components/base/emoji-picker/Inner.tsx index 80451ac6c6..e3347d4c67 100644 --- a/web/app/components/base/emoji-picker/Inner.tsx +++ b/web/app/components/base/emoji-picker/Inner.tsx @@ -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 = ({
-
- ) => { if (e.target.value === '') { @@ -92,12 +93,12 @@ const EmojiPickerInner: FC = ({ />
- +
{isSearching && <>
-

Search

+

Search

{searchedEmojis.map((emoji: string, index: number) => { return
= ({ setSelectedEmoji(emoji) }} > -
+
@@ -118,7 +119,7 @@ const EmojiPickerInner: FC = ({ {categories.map((category, index: number) => { return
-

{category.id}

+

{category.id}

{category.emojis.map((emoji, index: number) => { return
= ({ setSelectedEmoji(emoji) }} > -
+
@@ -141,7 +142,7 @@ const EmojiPickerInner: FC = ({ {/* Color Select */}
-

Choose Style

+

Choose Style

{backgroundColors.map((color) => { return
= ({ '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) diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index 3add14879a..16b07eddaf 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -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 = ({ 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')} > - +
-
+
{ (validatedStatusState.status === ValidatedStatus.Error && validatedStatusState.message) ? ( -
+
{validatedStatusState.message}
) : ( -
- +
+ {t('common.modelProvider.encrypted.front')} diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/add-model-button.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/add-model-button.tsx index cc8fa67efc..410ab1c090 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/add-model-button.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/add-model-button.tsx @@ -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 = ({ return ( diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx index e7f865f198..f43b5359ab 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx @@ -66,8 +66,8 @@ const CredentialPanel: FC = ({ <> { provider.provider_credential_schema && ( -
-
+
+
API-KEY
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx index 46ef6add24..a3dddf9435 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/index.tsx @@ -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 = ({ return (
@@ -119,7 +122,7 @@ const ProviderAddedCard: FC = ({
{ collapsed && ( -
+
{(showQuota || !notConfigured) && ( <>
@@ -131,7 +134,7 @@ const ProviderAddedCard: FC = ({ {!loading && }
{ @@ -151,7 +154,7 @@ const ProviderAddedCard: FC = ({ {!showQuota && notConfigured && (
- {t('common.modelProvider.configureTip')} + {t('common.modelProvider.configureTip')}
)} { diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx index 3fc73a62b2..6c4a3f575e 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list-item.tsx @@ -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} /> {modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && ( - + {t('common.modelProvider.loadBalancingHeadline')} @@ -77,20 +77,22 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad model.fetch_from === ConfigurationMethodEnum.customizableModel ? (isCurrentWorkspaceManager && ( )) : (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) ? ( ) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx index 94184076fd..2d5cf26257 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx @@ -145,19 +145,19 @@ const ModelLoadBalancingConfigs = ({ <>
toggleModalBalancing(true) : undefined} >
-
+
-
+
{t('common.modelProvider.loadBalancing')}
-
{t('common.modelProvider.loadBalancingDescription')}
+
{t('common.modelProvider.loadBalancingDescription')}
{ withSwitch && ( @@ -184,7 +184,7 @@ const ModelLoadBalancingConfigs = ({ {draftConfig.configs.map((config, index) => { const isProviderManaged = config.name === '__inherit__' return ( -
+
{(config.in_cooldown && Boolean(config.ttl)) @@ -201,7 +201,7 @@ const ModelLoadBalancingConfigs = ({ {isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
{isProviderManaged && ( - {t('common.modelProvider.providerManaged')} + {t('common.modelProvider.providerManaged')} )}
@@ -209,18 +209,18 @@ const ModelLoadBalancingConfigs = ({ <>
toggleEntryModal(index, config)} > updateConfigEntry(index, () => undefined)} > - +
)} @@ -247,7 +247,7 @@ const ModelLoadBalancingConfigs = ({ )} { draftConfig.enabled && draftConfig.configs.length < 2 && ( -
+
{t('common.modelProvider.loadBalancingLeastKeyWarning')}
@@ -257,7 +257,7 @@ const ModelLoadBalancingConfigs = ({ {!modelLoadBalancingEnabled && !IS_CE_EDITION && ( -
+
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx index edbb4665e9..bdc6b13c2c 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal.tsx @@ -119,7 +119,7 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav modelName={model!.model} />
toggleModalBalancing(false) : undefined} >
-
+
{Boolean(model) && ( )}
-
{t('common.modelProvider.providerManaged')}
-
{t('common.modelProvider.providerManagedDescription')}
+
{t('common.modelProvider.providerManaged')}
+
{t('common.modelProvider.providerManagedDescription')}
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-selector.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-selector.tsx index 7e44011ead..e93c6636b5 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-selector.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-selector.tsx @@ -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 = ({ { ({ open }) => ( - ) } @@ -49,18 +50,18 @@ const Selector: FC = ({ leaveFrom='opacity-100' leaveTo='opacity-0' > - +
-
{t('common.modelProvider.card.priorityUse')}
+
{t('common.modelProvider.card.priorityUse')}
{ options.map(option => (
onSelect(option.key)} >
{option.text}
- {value === option.key && } + {value === option.key && }
)) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx index 24e91d2214..91e45ad1a3 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx @@ -9,8 +9,8 @@ const PriorityUseTip = () => { -
- +
+
) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx index 0f5c265d52..8d0ea83d65 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/quota-panel.tsx @@ -28,8 +28,8 @@ const QuotaPanel: FC = ({ const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider) return ( -
-
+
+
{t('common.modelProvider.quota')} = ({
{ currentQuota && ( -
- {formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))} +
+ {formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))} { currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens' } diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/tab.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/tab.tsx deleted file mode 100644 index 5a533947d2..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/tab.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { FC } from 'react' - -type TabProps = { - active: string - onSelect: (active: string) => void -} -const Tab: FC = ({ - active, - onSelect, -}) => { - const tabs = [ - { - key: 'all', - text: 'All', - }, - { - key: 'added', - text: 'Added', - }, - { - key: 'build-in', - text: 'Build-in', - }, - ] - return ( -
- { - tabs.map(tab => ( -
onSelect(tab.key)} - > - {tab.text} -
- )) - } -
- ) -} - -export default Tab diff --git a/web/app/components/header/account-setting/model-provider-page/provider-card/index.module.css b/web/app/components/header/account-setting/model-provider-page/provider-card/index.module.css deleted file mode 100644 index 88c9fd015e..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/provider-card/index.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.vender { - background: linear-gradient(131deg, #2250F2 0%, #0EBCF3 100%); - background-clip: text; -} \ No newline at end of file diff --git a/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx b/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx deleted file mode 100644 index ec66a9928b..0000000000 --- a/web/app/components/header/account-setting/model-provider-page/provider-card/index.tsx +++ /dev/null @@ -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 = ({ - provider, - onOpenModal, -}) => { - const { t } = useTranslation() - const language = useLanguage() - const { isCurrentWorkspaceManager } = useAppContext() - const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurationMethodEnum.fetchFromRemote) - - return ( -
-
-
- -
- { - provider.description && ( -
- {provider.description[language] || provider.description.en_US} -
- ) - } -
-
-
- { - provider.supported_model_types.map(modelType => ( - - {modelTypeFormat(modelType)} - - )) - } -
-
- { - configurateMethods.map((method) => { - if (method === ConfigurationMethodEnum.predefinedModel) { - return ( - - ) - } - return ( - - ) - }) - } -
-
-
- ) -} - -export default ProviderCard diff --git a/web/app/components/header/account-setting/model-provider-page/utils.ts b/web/app/components/header/account-setting/model-provider-page/utils.ts index b8ab0988ae..9056afe69b 100644 --- a/web/app/components/header/account-setting/model-provider-page/utils.ts +++ b/web/app/components/header/account-setting/model-provider-page/utils.ts @@ -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 } diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index 79618c6c9e..e484f1e275 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -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 @@ -73,10 +76,12 @@ export const MarketplaceContext = createContext({ 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, diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index 502a920212..a379102efd 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -32,7 +32,7 @@ const CardWrapper = ({ if (showInstallButton) { return (
{ showInstallButton && ( -
+
{showImportFromUrl && ( -
+
- setImportUrl(e.target.value)} @@ -95,11 +96,11 @@ const GetSchema: FC = ({ className='space-x-1' onClick={() => { setShowExamples(!showExamples) }} > -
{t('tools.createTool.examples')}
+
{t('tools.createTool.examples')}
{showExamples && ( -
+
{examples.map(item => (
= ({ 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}`)}
diff --git a/web/app/components/tools/edit-custom-collection-modal/index.tsx b/web/app/components/tools/edit-custom-collection-modal/index.tsx index 555fd0dd08..786d3a4625 100644 --- a/web/app/components/tools/edit-custom-collection-modal/index.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/index.tsx @@ -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 = ({ panelClassName='mt-2 !w-[640px]' maxWidthClassName='!max-w-[640px]' height='calc(100vh - 16px)' - headerClassName='!border-b-black/5' + headerClassName='!border-b-divider-regular' body={
-
{t('tools.createTool.name')} *
+
{t('tools.createTool.name')} *
{ setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> = ({
-
{t('tools.createTool.schema')}*
-
+
{t('tools.createTool.schema')}*
+
{t('tools.createTool.viewSchemaSpec')}
@@ -238,11 +238,11 @@ const EditCustomCollectionModal: FC = ({ {/* Available Tools */}
-
{t('tools.createTool.availableTools.title')}
-
- - - 0 && 'border-b', 'border-gray-200')}> +
{t('tools.createTool.availableTools.title')}
+
+
+ + 0 && 'border-b', 'border-divider-regular')}> @@ -252,9 +252,9 @@ const EditCustomCollectionModal: FC = ({ {paramsSchemas.map((item, index) => ( - + - +
{t('tools.createTool.availableTools.name')} {t('tools.createTool.availableTools.description')} {t('tools.createTool.availableTools.method')}
{item.operation_id}{item.summary}{item.summary} {item.method} {getPath(item.server_url)} @@ -277,22 +277,22 @@ const EditCustomCollectionModal: FC = ({ {/* Authorization method */}
-
{t('tools.createTool.authMethod.title')}
-
setCredentialsModalShow(true)}> -
{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}
- +
{t('tools.createTool.authMethod.title')}
+
setCredentialsModalShow(true)}> +
{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}
+
{/* Labels */}
-
{t('tools.createTool.toolInput.label')}
+
{t('tools.createTool.toolInput.label')}
{/* Privacy Policy */}
-
{t('tools.createTool.privacyPolicy')}
+
{t('tools.createTool.privacyPolicy')}
{ @@ -305,7 +305,7 @@ const EditCustomCollectionModal: FC = ({
-
{t('tools.createTool.customDisclaimer')}
+
{t('tools.createTool.customDisclaimer')}
{ @@ -318,10 +318,10 @@ const EditCustomCollectionModal: FC = ({
-
+
{ isEdit && ( - + ) }
diff --git a/web/app/components/tools/edit-custom-collection-modal/modal.tsx b/web/app/components/tools/edit-custom-collection-modal/modal.tsx index 099012b277..0b4447850c 100644 --- a/web/app/components/tools/edit-custom-collection-modal/modal.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/modal.tsx @@ -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 = ({ className='!p-0 !max-w-[630px] !h-[calc(100vh-16px)]' >
-
+
{t('tools.createTool.title')}
-
{t('tools.createTool.name')} *
+
{t('tools.createTool.name')} *
{ setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> = ({
-
{t('tools.createTool.schema')}*
-
+
{t('tools.createTool.schema')}*
+
{t('tools.createTool.viewSchemaSpec')}
@@ -236,11 +235,11 @@ const EditCustomCollectionModal: FC = ({ {/* Available Tools */}
-
{t('tools.createTool.availableTools.title')}
-
- - - 0 && 'border-b', 'border-gray-200')}> +
{t('tools.createTool.availableTools.title')}
+
+
+ + 0 && 'border-b', 'border-divider-regular')}> @@ -250,9 +249,9 @@ const EditCustomCollectionModal: FC = ({ {paramsSchemas.map((item, index) => ( - + - +
{t('tools.createTool.availableTools.name')} {t('tools.createTool.availableTools.description')} {t('tools.createTool.availableTools.method')}
{item.operation_id}{item.summary}{item.summary} {item.method} {getPath(item.server_url)} @@ -275,22 +274,22 @@ const EditCustomCollectionModal: FC = ({ {/* Authorization method */}
-
{t('tools.createTool.authMethod.title')}
-
setCredentialsModalShow(true)}> -
{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}
- +
{t('tools.createTool.authMethod.title')}
+
setCredentialsModalShow(true)}> +
{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}
+
{/* Labels */}
-
{t('tools.createTool.toolInput.label')}
+
{t('tools.createTool.toolInput.label')}
{/* Privacy Policy */}
-
{t('tools.createTool.privacyPolicy')}
+
{t('tools.createTool.privacyPolicy')}
{ @@ -303,7 +302,7 @@ const EditCustomCollectionModal: FC = ({
-
{t('tools.createTool.customDisclaimer')}
+
{t('tools.createTool.customDisclaimer')}
{ @@ -316,10 +315,10 @@ const EditCustomCollectionModal: FC = ({
-
+
{ isEdit && ( - + ) }
diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 80a08d07a9..3172110bf6 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -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 = ({ positionCenter, customCollection, @@ -65,39 +64,40 @@ const TestApi: FC = ({ panelClassName='mt-2 !w-[600px]' maxWidthClassName='!max-w-[600px]' height='calc(100vh - 16px)' - headerClassName='!border-b-black/5' + headerClassName='!border-b-divider-regular' body={
-
{t('tools.createTool.authMethod.title')}
-
setCredentialsModalShow(true)}> -
{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}
- +
{t('tools.createTool.authMethod.title')}
+
setCredentialsModalShow(true)}> +
{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}
+
-
{t('tools.test.parametersValue')}
-
- - - +
{t('tools.test.parametersValue')}
+
+
+ + {parameters.map((item, index) => ( - + ))} @@ -110,11 +110,11 @@ const TestApi: FC = ({
-
{t('tools.test.testResult')}
+
{t('tools.test.testResult')}
-
- {result || {t('tools.test.testResultPlaceholder')}} +
+ {result || {t('tools.test.testResultPlaceholder')}}
diff --git a/web/app/components/tools/labels/filter.tsx b/web/app/components/tools/labels/filter.tsx index 8f6e954b92..87e65b5b4e 100644 --- a/web/app/components/tools/labels/filter.tsx +++ b/web/app/components/tools/labels/filter.tsx @@ -67,24 +67,23 @@ const LabelFilter: FC = ({ className='block' >
- +
-
+
{!value.length && t('common.tag.placeholder')} {!!value.length && currentLabel?.label}
{value.length > 1 && ( -
{`+${value.length - 1}`}
+
{`+${value.length - 1}`}
)} {!value.length && (
- +
)} {!!value.length && ( @@ -92,14 +91,14 @@ const LabelFilter: FC = ({ e.stopPropagation() onChange([]) }}> - +
)}
-
-
+
+
= ({ {filteredLabelList.map(label => (
selectLabel(label)} > -
{label.label}
- {value.includes(label.name) && } +
{label.label}
+ {value.includes(label.name) && }
))} {!filteredLabelList.length && (
- -
{t('common.tag.noTag')}
+ +
{t('common.tag.noTag')}
)}
diff --git a/web/app/components/tools/labels/selector.tsx b/web/app/components/tools/labels/selector.tsx index 88b910e87c..26cfc3ad9b 100644 --- a/web/app/components/tools/labels/selector.tsx +++ b/web/app/components/tools/labels/selector.tsx @@ -66,21 +66,21 @@ const LabelSelector: FC = ({ className='block' >
-
0 ? selectedLabels : ''} className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate', !value.length && '!text-gray-400')}> +
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}
-
+
-
-
+
+
= ({ {filteredLabelList.map(label => (
selectLabel(label)} > = ({ checked={value.includes(label.name)} onCheck={() => { }} /> -
{label.label}
+
{label.label}
))} {!filteredLabelList.length && (
- -
{t('common.tag.noTag')}
+ +
{t('common.tag.noTag')}
)}
diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index a4dc602351..148612bedc 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -63,13 +63,13 @@ const ProviderList = () => { return ( <> -
+
{ {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && (
{activeTab === 'api' && } {filteredCollectionList.map(collection => ( diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx index 424a077527..9ae1714a27 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -45,16 +45,16 @@ const Contribute = ({ onRefreshData }: Props) => { return ( <> {isCurrentWorkspaceManager && ( -
-
setIsShowEditCustomCollectionModal(true)}> +
+
setIsShowEditCustomCollectionModal(true)}>
-
- +
+
-
{t('tools.createCustomTool')}
+
{t('tools.createCustomTool')}
-
+
{t('tools.customToolTip')}
diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx index 93b728e4c5..66f2df935d 100644 --- a/web/app/components/tools/provider/detail.tsx +++ b/web/app/components/tools/provider/detail.tsx @@ -260,14 +260,14 @@ const ProviderDetail = ({ {!!collection.description[language] && ( )} -
+
{collection.type === CollectionType.custom && !isDetailLoading && ( )} {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( @@ -276,8 +276,8 @@ const ProviderDetail = ({ variant='primary' className={cn('shrink-0 my-3 w-[183px]')} > - -
{t('tools.openInStudio')}
+
+
{t('tools.openInStudio')}
@@ -286,7 +286,7 @@ const ProviderDetail = ({ onClick={() => setIsShowEditWorkflowToolModal(true)} disabled={!isCurrentWorkspaceManager} > -
{t('tools.createTool.editAction')}
+
{t('tools.createTool.editAction')}
)} @@ -319,7 +319,7 @@ const ProviderDetail = ({
{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()} · - {t('tools.auth.setup').toLocaleUpperCase()} + {t('tools.auth.setup').toLocaleUpperCase()}
- +
diff --git a/web/app/components/tools/workflow-tool/confirm-modal/style.module.css b/web/app/components/tools/workflow-tool/confirm-modal/style.module.css deleted file mode 100644 index 14367ec575..0000000000 --- a/web/app/components/tools/workflow-tool/confirm-modal/style.module.css +++ /dev/null @@ -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; -} diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx index 15198afa70..c4eb07dfe6 100644 --- a/web/app/components/tools/workflow-tool/index.tsx +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -124,13 +124,13 @@ const WorkflowToolAsModal: FC = ({ panelClassName='mt-2 !w-[640px]' maxWidthClassName='!max-w-[640px]' height='calc(100vh - 16px)' - headerClassName='!border-b-black/5' + headerClassName='!border-b-divider' body={
{/* name & icon */}
-
{t('tools.createTool.name')} *
+
{t('tools.createTool.name')} *
{ setShowEmojiPicker(true) }} className='cursor-pointer' iconType='emoji' icon={emoji.content} background={emoji.background} /> = ({
{/* name for tool call */}
-
+
{t('tools.createTool.nameForToolCall')} * = ({
{/* description */}
-
{t('tools.createTool.description')}
+
{t('tools.createTool.description')}
{t('tools.test.parameters')} {t('tools.test.value')}
{item.label[language]} - setParametersValue({ ...parametersValue, [item.name]: e.target.value })} - type='text' className='px-3 h-[34px] w-full outline-none focus:bg-gray-100' > + type='text' + className='!bg-transparent !border-transparent !hover:border-transparent !hover:bg-transparent !focus:border-transparent !focus:bg-transparent' />