merge feat/plugins

pull/12372/head
zxhlyh 1 year ago
commit 9f90d70b38

@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
CURRENT_VERSION: str = Field( CURRENT_VERSION: str = Field(
description="Dify version", description="Dify version",
default="0.13.2", default="0.14.0",
) )
COMMIT_SHA: str = Field( 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: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.13.2 image: langgenius/dify-api:0.14.0
restart: always restart: always
environment: environment:
# Startup mode, 'api' starts the API server. # Startup mode, 'api' starts the API server.
@ -227,7 +227,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.13.2 image: langgenius/dify-api:0.14.0
restart: always restart: always
environment: environment:
CONSOLE_WEB_URL: '' CONSOLE_WEB_URL: ''
@ -397,7 +397,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.13.2 image: langgenius/dify-web:0.14.0
restart: always restart: always
environment: environment:
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is # 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: services:
# API service # API service
api: api:
image: langgenius/dify-api:0.13.2 image: langgenius/dify-api:0.14.0
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -312,7 +312,7 @@ services:
# worker service # worker service
# The Celery worker for processing the queue. # The Celery worker for processing the queue.
worker: worker:
image: langgenius/dify-api:0.13.2 image: langgenius/dify-api:0.14.0
restart: always restart: always
environment: environment:
# Use the shared environment variables. # Use the shared environment variables.
@ -331,7 +331,7 @@ services:
# Frontend web application. # Frontend web application.
web: web:
image: langgenius/dify-web:0.13.2 image: langgenius/dify-web:0.14.0
restart: always restart: always
environment: environment:
CONSOLE_API_URL: ${CONSOLE_API_URL:-} 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 { CopyIcon } from '@/app/components/base/copy-icon'
import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils'
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
import { correctProvider } from '@/utils' import cn from '@/utils/classnames'
import { TONE_LIST } from '@/config'
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
@ -300,22 +299,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
const isChatMode = appDetail?.mode !== 'completion' const isChatMode = appDetail?.mode !== 'completion'
const isAdvanced = appDetail?.mode === 'advanced-chat' 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 varList = (detail.model_config as any).user_input_form?.map((item: any) => {
const itemContent = item[Object.keys(item)[0]] const itemContent = item[Object.keys(item)[0]]
return { 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 <div
onMouseLeave={onMouseLeave} 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 style={!isPlain
? { ? {
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)', 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} 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> </div>
</Tooltip> </Tooltip>
</div> </div>

@ -49,18 +49,18 @@ export default function Drawer({
<Dialog.Overlay <Dialog.Overlay
className={cn('z-40 fixed inset-0', mask && 'bg-black bg-opacity-30')} 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 {title && <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium leading-6 text-gray-900" className="text-lg font-medium leading-6 text-text-primary"
> >
{title} {title}
</Dialog.Title>} </Dialog.Title>}
{showClose && <Dialog.Title className="flex items-center mb-4" as="div"> {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>} </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} {children}
</> </>
{footer || (footer === null {footer || (footer === null

@ -7,9 +7,10 @@ import { init } from 'emoji-mart'
import { import {
MagnifyingGlassIcon, MagnifyingGlassIcon,
} from '@heroicons/react/24/outline' } 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 Divider from '@/app/components/base/divider'
import { searchEmoji } from '@/utils/emoji' import { searchEmoji } from '@/utils/emoji'
import cn from '@/utils/classnames'
declare global { declare global {
// eslint-disable-next-line ts/no-namespace // 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='flex flex-col items-center w-full px-3 pb-2'>
<div className="relative w-full"> <div className="relative w-full">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <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> </div>
<input <Input
className="pl-10"
type="search" type="search"
id="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..." placeholder="Search emojis..."
onChange={async (e: ChangeEvent<HTMLInputElement>) => { onChange={async (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.value === '') { if (e.target.value === '') {
@ -92,12 +93,12 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
/> />
</div> </div>
</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"> <div className="w-full max-h-[200px] overflow-x-hidden overflow-y-auto px-3">
{isSearching && <> {isSearching && <>
<div key={'category-search'} className='flex flex-col'> <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'> <div className='w-full h-full grid grid-cols-8 gap-1'>
{searchedEmojis.map((emoji: string, index: number) => { {searchedEmojis.map((emoji: string, index: number) => {
return <div return <div
@ -107,7 +108,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
setSelectedEmoji(emoji) 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} /> <em-emoji id={emoji} />
</div> </div>
</div> </div>
@ -118,7 +119,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
{categories.map((category, index: number) => { {categories.map((category, index: number) => {
return <div key={`category-${index}`} className='flex flex-col'> 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'> <div className='w-full h-full grid grid-cols-8 gap-1'>
{category.emojis.map((emoji, index: number) => { {category.emojis.map((emoji, index: number) => {
return <div return <div
@ -128,7 +129,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
setSelectedEmoji(emoji) 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} /> <em-emoji id={emoji} />
</div> </div>
</div> </div>
@ -141,7 +142,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
{/* Color Select */} {/* Color Select */}
<div className={cn('p-3 pb-0', selectedEmoji === '' ? 'opacity-25' : '')}> <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'> <div className='w-full h-full grid grid-cols-8 gap-1'>
{backgroundColors.map((color) => { {backgroundColors.map((color) => {
return <div return <div
@ -151,7 +152,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
'cursor-pointer', 'cursor-pointer',
'hover:ring-1 ring-offset-1', 'hover:ring-1 ring-offset-1',
'inline-flex w-10 h-10 rounded-lg items-center justify-center', '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={() => { onClick={() => {
setSelectedBackground(color) setSelectedBackground(color)

@ -2,7 +2,6 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import s from './style.module.css'
import EmojiPickerInner from './Inner' import EmojiPickerInner from './Inner'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
@ -37,12 +36,12 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
isShow isShow
closable={false} closable={false}
wrapperClassName={className} 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 <EmojiPickerInner
className="pt-3" className="pt-3"
onSelect={handleSelectEmoji} /> 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'> <div className='w-full flex items-center justify-center p-3 gap-2'>
<Button className='w-full' onClick={() => { <Button className='w-full' onClick={() => {
onClose && onClose() 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 drawRecord = useCallback(() => {
const canvas = canvasRef.current! const canvas = canvasRef.current!
const ctx = ctxRef.current! const ctx = ctxRef.current!
const rowNumber = parseInt(`${canvas.width / 24}`) const rowNumber = Number.parseInt(`${canvas.width / 24}`)
const colNumber = parseInt(`${canvas.height / 24}`) const colNumber = Number.parseInt(`${canvas.height / 24}`)
ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.beginPath() ctx.beginPath()
@ -82,9 +82,9 @@ const GridMask: FC<GridMaskProps> = ({
}, []) }, [])
return ( 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}`} /> <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 className='relative z-[2]'>{children}</div>
</div> </div>
) )

@ -11,7 +11,7 @@ const RadioUI: FC<Props> = ({
isChecked, isChecked,
}) => { }) => {
return ( 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> </div>
) )
} }

@ -23,10 +23,14 @@ const Item: FC<ItemProps> = ({
return ( return (
<div <div
key={option.value} 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)} 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 && ( {isActive && (
<div className='absolute bottom-0 left-0 right-0 h-0.5 bg-util-colors-blue-blue-500'></div> <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, itemClassName,
}) => { }) => {
return ( 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 => ( {options.map(option => (
<Item <Item
isActive={option.value === value} 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 className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
</div> </div>
<div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'> <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> <div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
{ {
activeItem?.description && ( 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' && ( {activeItem?.key === 'provider' && (

@ -32,8 +32,7 @@ import {
} from '@/app/components/plugins/marketplace/hooks' } from '@/app/components/plugins/marketplace/hooks'
import type { Plugin } from '@/app/components/plugins/types' import type { Plugin } from '@/app/components/plugins/types'
import { PluginType } from '@/app/components/plugins/types' import { PluginType } from '@/app/components/plugins/types'
// import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils' import { getMarketplacePluginsByCollectionId } from '@/app/components/plugins/marketplace/utils'
import type { MarketplaceCollection } from '@/app/components/plugins/marketplace/types'
type UseDefaultModelAndModelList = ( type UseDefaultModelAndModelList = (
defaultModel: DefaultModelResponse | undefined, defaultModel: DefaultModelResponse | undefined,
@ -242,34 +241,11 @@ export const useUpdateModelProviders = () => {
return updateModelProviders 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) => { export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText: string) => {
const exclude = useMemo(() => { const exclude = useMemo(() => {
return providers.map(provider => provider.provider.replace(/(.+)\/([^/]+)$/, '$1')) return providers.map(provider => provider.provider.replace(/(.+)\/([^/]+)$/, '$1'))
}, [providers]) }, [providers])
const [collectionPlugins, setCollectionPlugins] = useState<Plugin[]>([])
const { const {
plugins, plugins,
@ -278,6 +254,16 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
isLoading, isLoading,
} = useMarketplacePlugins() } = useMarketplacePlugins()
const getCollectionPlugins = useCallback(async () => {
const collectionPlugins = await getMarketplacePluginsByCollectionId('__model-settings-pinned-models')
setCollectionPlugins(collectionPlugins)
}, [])
useEffect(() => {
getCollectionPlugins()
}, [getCollectionPlugins])
useEffect(() => { useEffect(() => {
if (searchText) { if (searchText) {
queryPluginsWithDebounced({ queryPluginsWithDebounced({
@ -298,8 +284,23 @@ export const useMarketplaceAllPlugins = (providers: ModelProvider[], searchText:
} }
}, [queryPlugins, queryPluginsWithDebounced, searchText, exclude]) }, [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 { return {
plugins: plugins?.filter(plugin => plugin.type !== 'bundle'), plugins: allPlugins,
isLoading, isLoading,
} }
} }

@ -21,7 +21,6 @@ import {
} from './declarations' } from './declarations'
import { import {
useDefaultModel, useDefaultModel,
useMarketplace,
useMarketplaceAllPlugins, useMarketplaceAllPlugins,
useUpdateModelList, useUpdateModelList,
useUpdateModelProviders, useUpdateModelProviders,
@ -121,11 +120,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
const [collapse, setCollapse] = useState(false) const [collapse, setCollapse] = useState(false)
const locale = getLocaleOnClient() const locale = getLocaleOnClient()
const {
marketplaceCollections,
marketplaceCollectionPluginsMap,
isLoading: isPluginsLoading,
} = useMarketplace()
const { const {
plugins: allPlugins, plugins: allPlugins,
isLoading: isAllPluginsLoading, isLoading: isAllPluginsLoading,
@ -143,7 +137,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
<div className={cn('flex items-center mb-2')}> <div className={cn('flex items-center mb-2')}>
<div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div> <div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
<div className={cn( <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 && '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%)' }} />} {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>
</div> </div>
{!filteredConfiguredProviders?.length && ( {!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'> <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' /> <RiBrainLine className='w-5 h-5 text-text-primary' />
</div> </div>
@ -213,20 +207,7 @@ const ModelProviderPage = ({ searchText }: Props) => {
</Link> </Link>
</div> </div>
</div> </div>
{!collapse && (isPluginsLoading || isAllPluginsLoading) && <Loading type='area' />} {!collapse && 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}
/>
)
}
{ {
!isAllPluginsLoading && !collapse && ( !isAllPluginsLoading && !collapse && (
<List <List

@ -6,6 +6,7 @@ import type {
import { useLanguage } from '../hooks' import { useLanguage } from '../hooks'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes' import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { OpenaiViolet } from '@/app/components/base/icons/src/public/llm' import { OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
import cn from '@/utils/classnames'
type ModelIconProps = { type ModelIconProps = {
provider?: Model | ModelProvider provider?: Model | ModelProvider
@ -20,7 +21,7 @@ const ModelIcon: FC<ModelIconProps> = ({
const language = useLanguage() const language = useLanguage()
if (provider?.provider.includes('openai') && (modelName?.startsWith('gpt-4') || modelName?.includes('4o'))) 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) { if (provider?.icon_small) {
return ( return (
@ -28,17 +29,17 @@ const ModelIcon: FC<ModelIconProps> = ({
<img <img
alt='model-icon' alt='model-icon'
src={`${provider.icon_small[language] || provider.icon_small.en_US}`} src={`${provider.icon_small[language] || provider.icon_small.en_US}`}
className={`w-4 h-4 ${className}`} className={cn('w-4 h-4', className)}
/> />
) )
} }
return ( return (
<div className={` <div className={cn(
flex items-center justify-center w-6 h-6 rounded border-[0.5px] border-black/5 bg-gray-50 'flex items-center justify-center w-6 h-6 rounded border-[0.5px] border-black/5 bg-gray-50',
${className} className,
`}> )}>
<CubeOutline className='w-4 h-4 text-gray-400' /> <CubeOutline className='w-4 h-4 text-text-quaternary' />
</div> </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 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 ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import RadioE from '@/app/components/base/radio/ui'
type FormProps = { type FormProps = {
className?: string className?: string
@ -177,17 +178,15 @@ const Form: FC<FormProps> = ({
}).map(option => ( }).map(option => (
<div <div
className={` className={`
flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer 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-white border-[1.5px] border-primary-400 shadow-sm'} ${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'} ${disabled && '!cursor-not-allowed opacity-60'}
`} `}
onClick={() => handleFormChange(variable, option.value)} onClick={() => handleFormChange(variable, option.value)}
key={`${variable}-${option.value}`} key={`${variable}-${option.value}`}
> >
<div className={` <RadioE isChecked={value[variable] === option.value} />
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'}
`} />
<div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div> <div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div>
</div> </div>
)) ))

@ -41,11 +41,11 @@ const Input: FC<InputProps> = ({
<input <input
tabIndex={0} tabIndex={0}
className={` 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 appearance-none outline-none caret-primary-600
hover:border-[rgba(0,0,0,0.08)] hover:bg-state-hover-alt hover:border-components-input-border-hover hover:bg-components-input-bg-hover
focus:bg-white focus:border-gray-300 focus:shadow-xs focus:bg-components-input-bg-active focus:border-components-input-border-active focus:shadow-xs
placeholder:text-sm placeholder:text-gray-400 placeholder:text-sm placeholder:text-text-tertiary
${validated && 'pr-[30px]'} ${validated && 'pr-[30px]'}
${className} ${className}
`} `}

@ -280,10 +280,10 @@ const ModelModal: FC<ModelModalProps> = ({
<PortalToFollowElem open> <PortalToFollowElem open>
<PortalToFollowElemContent className='w-full h-full z-[60]'> <PortalToFollowElemContent className='w-full h-full z-[60]'>
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'> <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='px-8 pt-8'>
<div className='flex justify-between items-center mb-2'> <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} /> <ProviderIcon provider={provider} />
</div> </div>
@ -297,7 +297,7 @@ const ModelModal: FC<ModelModalProps> = ({
isEditMode={isEditMode} 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 {...{ <ModelLoadBalancingConfigs withSwitch {...{
draftConfig, draftConfig,
setDraftConfig, setDraftConfig,
@ -306,7 +306,7 @@ const ModelModal: FC<ModelModalProps> = ({
configurationMethod: configurateMethod, 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)) (provider.help && (provider.help.title || provider.help.url))
? ( ? (
@ -326,8 +326,9 @@ const ModelModal: FC<ModelModalProps> = ({
{ {
isEditMode && ( isEditMode && (
<Button <Button
variant='warning'
size='large' size='large'
className='mr-2 text-[#D92D20]' className='mr-2'
onClick={() => setShowConfirm(true)} onClick={() => setShowConfirm(true)}
> >
{t('common.operation.remove')} {t('common.operation.remove')}
@ -357,21 +358,21 @@ const ModelModal: FC<ModelModalProps> = ({
</div> </div>
</div> </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) (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]' /> <RiErrorWarningFill className='mt-[1px] mr-2 w-[14px] h-[14px]' />
{validatedStatusState.message} {validatedStatusState.message}
</div> </div>
) )
: ( : (
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs 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-gray-500' /> <Lock01 className='mr-1 w-3 h-3 text-text-tertiary' />
{t('common.modelProvider.encrypted.front')} {t('common.modelProvider.encrypted.front')}
<a <a
className='text-primary-600 mx-1' className='text-text-accent mx-1'
target='_blank' rel='noopener noreferrer' target='_blank' rel='noopener noreferrer'
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html' href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
> >

@ -1,6 +1,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { PlusCircle } from '@/app/components/base/icons/src/vender/solid/general' import { PlusCircle } from '@/app/components/base/icons/src/vender/solid/general'
import cn from '@/utils/classnames'
type AddModelButtonProps = { type AddModelButtonProps = {
className?: string className?: string
@ -14,10 +15,7 @@ const AddModelButton: FC<AddModelButtonProps> = ({
return ( return (
<span <span
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)}
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}
`}
onClick={onClick} onClick={onClick}
> >
<PlusCircle className='mr-1 w-3 h-3' /> <PlusCircle className='mr-1 w-3 h-3' />

@ -66,8 +66,8 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
<> <>
{ {
provider.provider_credential_schema && ( 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='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 text-xs font-medium text-gray-500'> <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 API-KEY
<Indicator color={isCustomConfigured ? 'green' : 'red'} /> <Indicator color={isCustomConfigured ? 'green' : 'red'} />
</div> </div>

@ -13,7 +13,6 @@ import type {
} from '../declarations' } from '../declarations'
import { ConfigurationMethodEnum } from '../declarations' import { ConfigurationMethodEnum } from '../declarations'
import { import {
DEFAULT_BACKGROUND_COLOR,
MODEL_PROVIDER_QUOTA_GET_PAID, MODEL_PROVIDER_QUOTA_GET_PAID,
modelTypeFormat, modelTypeFormat,
} from '../utils' } from '../utils'
@ -27,6 +26,7 @@ import { fetchModelProviderModelList } from '@/service/common'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { IS_CE_EDITION } from '@/config' import { IS_CE_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context' 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' export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
type ProviderAddedCardProps = { type ProviderAddedCardProps = {
@ -82,8 +82,11 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
return ( return (
<div <div
className='mb-2 rounded-xl border-[0.5px] border-black/5 shadow-xs' className={cn(
style={{ background: provider.background || DEFAULT_BACKGROUND_COLOR }} '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='flex pl-3 py-2 pr-2 rounded-t-xl'>
<div className='grow px-1 pt-1 pb-0.5'> <div className='grow px-1 pt-1 pb-0.5'>
@ -119,7 +122,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
</div> </div>
{ {
collapsed && ( 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) && ( {(showQuota || !notConfigured) && (
<> <>
<div className='group-hover:hidden flex items-center pl-1 pr-1.5 h-6 leading-6'> <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' />} {!loading && <RiArrowRightSLine className='w-4 h-4' />}
</div> </div>
<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} onClick={handleOpenModelList}
> >
{ {
@ -151,7 +154,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
{!showQuota && notConfigured && ( {!showQuota && notConfigured && (
<div className='flex items-center pl-1 pr-1.5 h-6'> <div className='flex items-center pl-1 pr-1.5 h-6'>
<RiInformation2Fill className='mr-1 w-4 h-4 text-text-accent' /> <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> </div>
)} )}
{ {

@ -49,7 +49,7 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
key={model.model} key={model.model}
className={classNames( className={classNames(
'group flex items-center pl-2 pr-2.5 h-8 rounded-lg', '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', model.deprecated && 'opacity-60',
)} )}
> >
@ -59,14 +59,14 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
modelName={model.model} modelName={model.model}
/> />
<ModelName <ModelName
className='grow text-sm font-normal text-gray-900' className='grow system-md-regular text-text-secondary'
modelItem={model} modelItem={model}
showModelType showModelType
showMode showMode
showContextSize showContextSize
> >
{modelLoadBalancingEnabled && !model.deprecated && model.load_balancing_enabled && ( {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' /> <Balance className='w-3 h-3 mr-0.5' />
{t('common.modelProvider.loadBalancingHeadline')} {t('common.modelProvider.loadBalancingHeadline')}
</ModelBadge> </ModelBadge>
@ -77,20 +77,22 @@ const ModelListItem = ({ model, provider, isConfigurable, onConfig, onModifyLoad
model.fetch_from === ConfigurationMethodEnum.customizableModel model.fetch_from === ConfigurationMethodEnum.customizableModel
? (isCurrentWorkspaceManager && ( ? (isCurrentWorkspaceManager && (
<Button <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 })} 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')} {t('common.modelProvider.config')}
</Button> </Button>
)) ))
: (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status)) : (isCurrentWorkspaceManager && (modelLoadBalancingEnabled || plan.type === Plan.sandbox) && !model.deprecated && [ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status))
? ( ? (
<Button <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)} 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')} {t('common.modelProvider.configLoadBalancing')}
</Button> </Button>
) )

@ -145,19 +145,19 @@ const ModelLoadBalancingConfigs = ({
<> <>
<div <div
className={classNames( className={classNames(
'min-h-16 bg-gray-50 border rounded-xl transition-colors', 'min-h-16 bg-components-panel-bg border rounded-xl transition-colors',
(withSwitch || !draftConfig.enabled) ? 'border-gray-200' : 'border-primary-400', (withSwitch || !draftConfig.enabled) ? 'border-components-panel-border' : 'border-util-colors-blue-blue-600',
(withSwitch || draftConfig.enabled) ? 'cursor-default' : 'cursor-pointer', (withSwitch || draftConfig.enabled) ? 'cursor-default' : 'cursor-pointer',
className, className,
)} )}
onClick={(!withSwitch && !draftConfig.enabled) ? () => toggleModalBalancing(true) : undefined} onClick={(!withSwitch && !draftConfig.enabled) ? () => toggleModalBalancing(true) : undefined}
> >
<div className='flex items-center px-[15px] py-3 gap-2 select-none'> <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' /> <Balance className='w-4 h-4' />
</div> </div>
<div className='grow'> <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')} {t('common.modelProvider.loadBalancing')}
<Tooltip <Tooltip
popupContent={t('common.modelProvider.loadBalancingInfo')} popupContent={t('common.modelProvider.loadBalancingInfo')}
@ -165,7 +165,7 @@ const ModelLoadBalancingConfigs = ({
triggerClassName='w-3 h-3' triggerClassName='w-3 h-3'
/> />
</div> </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> </div>
{ {
withSwitch && ( withSwitch && (
@ -184,7 +184,7 @@ const ModelLoadBalancingConfigs = ({
{draftConfig.configs.map((config, index) => { {draftConfig.configs.map((config, index) => {
const isProviderManaged = config.name === '__inherit__' const isProviderManaged = config.name === '__inherit__'
return ( 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='grow flex items-center'>
<div className='flex items-center justify-center mr-2 w-3 h-3'> <div className='flex items-center justify-center mr-2 w-3 h-3'>
{(config.in_cooldown && Boolean(config.ttl)) {(config.in_cooldown && Boolean(config.ttl))
@ -201,7 +201,7 @@ const ModelLoadBalancingConfigs = ({
{isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name} {isProviderManaged ? t('common.modelProvider.defaultConfig') : config.name}
</div> </div>
{isProviderManaged && ( {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>
<div className='flex items-center gap-1'> <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'> <div className='flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100'>
<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={() => toggleEntryModal(index, config)} onClick={() => toggleEntryModal(index, config)}
> >
<Edit02 className='w-4 h-4' /> <Edit02 className='w-4 h-4' />
</span> </span>
<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)} onClick={() => updateConfigEntry(index, () => undefined)}
> >
<RiDeleteBinLine className='w-4 h-4' /> <RiDeleteBinLine className='w-4 h-4' />
</span> </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> </div>
</> </>
)} )}
@ -247,7 +247,7 @@ const ModelLoadBalancingConfigs = ({
)} )}
{ {
draftConfig.enabled && draftConfig.configs.length < 2 && ( 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]' /> <AlertTriangle className='mr-1 w-3 h-3 text-[#f79009]' />
{t('common.modelProvider.loadBalancingLeastKeyWarning')} {t('common.modelProvider.loadBalancingLeastKeyWarning')}
</div> </div>
@ -257,7 +257,7 @@ const ModelLoadBalancingConfigs = ({
{!modelLoadBalancingEnabled && !IS_CE_EDITION && ( {!modelLoadBalancingEnabled && !IS_CE_EDITION && (
<GridMask canvasClassName='!rounded-xl'> <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 <div
className={classNames('text-sm font-semibold leading-tight text-gradient', s.textGradient)} 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={model!.model}
/> />
<ModelName <ModelName
className='grow text-sm font-normal text-gray-900' className='grow system-md-regular text-text-secondary'
modelItem={model!} modelItem={model!}
showModelType showModelType
showMode showMode
@ -137,20 +137,20 @@ const ModelLoadBalancingModal = ({ provider, model, open = false, onClose, onSav
<div className='py-2'> <div className='py-2'>
<div <div
className={classNames( className={classNames(
'min-h-16 bg-gray-50 border rounded-xl transition-colors', 'min-h-16 bg-components-panel-bg border rounded-xl transition-colors',
draftConfig.enabled ? 'border-gray-200 cursor-pointer' : 'border-primary-400 cursor-default', draftConfig.enabled ? 'border-components-panel-border cursor-pointer' : 'border-util-colors-blue-blue-600 cursor-default',
)} )}
onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined} onClick={draftConfig.enabled ? () => toggleModalBalancing(false) : undefined}
> >
<div className='flex items-center px-[15px] py-3 gap-2 select-none'> <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) && ( {Boolean(model) && (
<ModelIcon className='shrink-0' provider={provider} modelName={model!.model} /> <ModelIcon className='shrink-0' provider={provider} modelName={model!.model} />
)} )}
</div> </div>
<div className='grow'> <div className='grow'>
<div className='text-sm'>{t('common.modelProvider.providerManaged')}</div> <div className='text-sm text-text-secondary'>{t('common.modelProvider.providerManaged')}</div>
<div className='text-xs text-gray-500'>{t('common.modelProvider.providerManagedDescription')}</div> <div className='text-xs text-text-tertiary'>{t('common.modelProvider.providerManagedDescription')}</div>
</div> </div>
</div> </div>
</div> </div>

@ -8,6 +8,7 @@ import {
} from '@remixicon/react' } from '@remixicon/react'
import { PreferredProviderTypeEnum } from '../declarations' import { PreferredProviderTypeEnum } from '../declarations'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import cn from '@/utils/classnames'
type SelectorProps = { type SelectorProps = {
value?: string value?: string
@ -34,11 +35,11 @@ const Selector: FC<SelectorProps> = ({
<Popover.Button> <Popover.Button>
{ {
({ open }) => ( ({ open }) => (
<Button className={` <Button className={cn(
px-0 w-6 h-6 bg-white rounded-md 'px-0 w-6 h-6 rounded-md',
${open && '!bg-gray-100'} open && 'bg-components-button-secondary-bg-hover',
`}> )}>
<RiMoreFill className='w-3 h-3 text-gray-700' /> <RiMoreFill className='w-3 h-3' />
</Button> </Button>
) )
} }
@ -49,18 +50,18 @@ const Selector: FC<SelectorProps> = ({
leaveFrom='opacity-100' leaveFrom='opacity-100'
leaveTo='opacity-0' 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='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 => ( options.map(option => (
<Popover.Button as={Fragment} key={option.key}> <Popover.Button as={Fragment} key={option.key}>
<div <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)} onClick={() => onSelect(option.key)}
> >
<div className='grow'>{option.text}</div> <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> </div>
</Popover.Button> </Popover.Button>
)) ))

@ -9,8 +9,8 @@ const PriorityUseTip = () => {
<Tooltip <Tooltip
popupContent={t('common.modelProvider.priorityUsing') || ''} 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'> <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-indigo-600' /> <ChevronDownDouble className='rotate-180 w-3 h-3 text-util-colors-indigo-indigo-600' />
</div> </div>
</Tooltip> </Tooltip>
) )

@ -28,8 +28,8 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider) const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider)
return ( 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='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 text-xs font-medium text-gray-500'> <div className='flex items-center mb-2 h-4 system-xs-medium-uppercase text-text-tertiary'>
{t('common.modelProvider.quota')} {t('common.modelProvider.quota')}
<Tooltip popupContent={ <Tooltip popupContent={
openaiOrAnthropic openaiOrAnthropic
@ -40,8 +40,8 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
</div> </div>
{ {
currentQuota && ( currentQuota && (
<div className='flex items-center h-4 text-xs text-gray-500'> <div className='flex items-center h-4 text-xs text-text-tertiary'>
<span className='mr-0.5 text-sm font-semibold text-gray-700'>{formatNumber((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0))}</span> <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' 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 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) => { export const isNullOrUndefined = (value: any) => {
return value === undefined || value === null return value === undefined || value === null
} }

@ -24,6 +24,7 @@ import type {
MarketplaceCollection, MarketplaceCollection,
PluginsSort, PluginsSort,
SearchParams, SearchParams,
SearchParamsFromCollection,
} from './types' } from './types'
import { DEFAULT_SORT } from './constants' import { DEFAULT_SORT } from './constants'
import { import {
@ -49,10 +50,12 @@ export type MarketplaceContextValue = {
page: number page: number
handlePageChange: (page: number) => void handlePageChange: (page: number) => void
plugins?: Plugin[] plugins?: Plugin[]
pluginsTotal?: number
resetPlugins: () => void resetPlugins: () => void
sort: PluginsSort sort: PluginsSort
handleSortChange: (sort: PluginsSort) => void handleSortChange: (sort: PluginsSort) => void
handleQueryPlugins: () => void handleQueryPlugins: () => void
handleMoreClick: (searchParams: SearchParamsFromCollection) => void
marketplaceCollectionsFromClient?: MarketplaceCollection[] marketplaceCollectionsFromClient?: MarketplaceCollection[]
setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void setMarketplaceCollectionsFromClient: (collections: MarketplaceCollection[]) => void
marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]> marketplaceCollectionPluginsMapFromClient?: Record<string, Plugin[]>
@ -73,10 +76,12 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
page: 1, page: 1,
handlePageChange: () => {}, handlePageChange: () => {},
plugins: undefined, plugins: undefined,
pluginsTotal: 0,
resetPlugins: () => {}, resetPlugins: () => {},
sort: DEFAULT_SORT, sort: DEFAULT_SORT,
handleSortChange: () => {}, handleSortChange: () => {},
handleQueryPlugins: () => {}, handleQueryPlugins: () => {},
handleMoreClick: () => {},
marketplaceCollectionsFromClient: [], marketplaceCollectionsFromClient: [],
setMarketplaceCollectionsFromClient: () => {}, setMarketplaceCollectionsFromClient: () => {},
marketplaceCollectionPluginsMapFromClient: {}, marketplaceCollectionPluginsMapFromClient: {},
@ -248,7 +253,7 @@ export const MarketplaceContextProvider = ({
}, [handleQueryPlugins]) }, [handleQueryPlugins])
const handlePageChange = useCallback(() => { const handlePageChange = useCallback(() => {
if (pluginsTotal && plugins && pluginsTotal > plugins.length && (!!searchPluginTextRef.current || !!filterPluginTagsRef.current.length)) { if (pluginsTotal && plugins && pluginsTotal > plugins.length) {
setPage(pageRef.current + 1) setPage(pageRef.current + 1)
pageRef.current++ pageRef.current++
@ -256,6 +261,23 @@ export const MarketplaceContextProvider = ({
} }
}, [handleQueryPlugins, plugins, pluginsTotal]) }, [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) useMarketplaceContainerScroll(handlePageChange, scrollContainerId)
return ( return (
@ -272,10 +294,12 @@ export const MarketplaceContextProvider = ({
page, page,
handlePageChange, handlePageChange,
plugins, plugins,
pluginsTotal,
resetPlugins, resetPlugins,
sort, sort,
handleSortChange, handleSortChange,
handleQueryPlugins, handleQueryPlugins,
handleMoreClick,
marketplaceCollectionsFromClient, marketplaceCollectionsFromClient,
setMarketplaceCollectionsFromClient, setMarketplaceCollectionsFromClient,
marketplaceCollectionPluginsMapFromClient, marketplaceCollectionPluginsMapFromClient,

@ -32,7 +32,7 @@ const CardWrapper = ({
if (showInstallButton) { if (showInstallButton) {
return ( return (
<div <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 <Card
key={plugin.name} key={plugin.name}
@ -47,7 +47,7 @@ const CardWrapper = ({
/> />
{ {
showInstallButton && ( 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 <Button
variant='primary' variant='primary'
className='w-[calc(50%-4px)]' className='w-[calc(50%-4px)]'

@ -1,9 +1,13 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next'
import { RiArrowRightSLine } from '@remixicon/react'
import type { MarketplaceCollection } from '../types' import type { MarketplaceCollection } from '../types'
import CardWrapper from './card-wrapper' import CardWrapper from './card-wrapper'
import type { Plugin } from '@/app/components/plugins/types' import type { Plugin } from '@/app/components/plugins/types'
import { getLanguage } from '@/i18n/language' import { getLanguage } from '@/i18n/language'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types'
type ListWithCollectionProps = { type ListWithCollectionProps = {
marketplaceCollections: MarketplaceCollection[] marketplaceCollections: MarketplaceCollection[]
@ -12,7 +16,7 @@ type ListWithCollectionProps = {
locale: string locale: string
cardContainerClassName?: string cardContainerClassName?: string
cardRender?: (plugin: Plugin) => JSX.Element | null cardRender?: (plugin: Plugin) => JSX.Element | null
onMoreClick?: () => void onMoreClick?: (searchParams?: SearchParamsFromCollection) => void
} }
const ListWithCollection = ({ const ListWithCollection = ({
marketplaceCollections, marketplaceCollections,
@ -21,8 +25,10 @@ const ListWithCollection = ({
locale, locale,
cardContainerClassName, cardContainerClassName,
cardRender, cardRender,
// onMoreClick, onMoreClick,
}: ListWithCollectionProps) => { }: ListWithCollectionProps) => {
const { t } = useTranslation()
return ( return (
<> <>
{ {
@ -31,15 +37,22 @@ const ListWithCollection = ({
key={collection.name} key={collection.name}
className='py-3' className='py-3'
> >
<div className='flex justify-between'> <div className='flex justify-between items-end'>
<div> <div>
<div className='title-xl-semi-bold text-text-primary'>{collection.label[getLanguage(locale)]}</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 className='system-xs-regular text-text-tertiary'>{collection.description[getLanguage(locale)]}</div>
</div> </div>
{/* <div {
className='system-xs-regular text-text-tertiary cursor-pointer hover:underline' collection.searchable && onMoreClick && (
onClick={() => onMoreClick?.()} <div
>more</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>
<div className={cn( <div className={cn(
'grid grid-cols-4 gap-3 mt-2', 'grid grid-cols-4 gap-3 mt-2',

@ -22,12 +22,14 @@ const ListWrapper = ({
}: ListWrapperProps) => { }: ListWrapperProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const plugins = useMarketplaceContext(v => v.plugins) const plugins = useMarketplaceContext(v => v.plugins)
const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal)
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient) const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient) const marketplaceCollectionPluginsMapFromClient = useMarketplaceContext(v => v.marketplaceCollectionPluginsMapFromClient)
const isLoading = useMarketplaceContext(v => v.isLoading) const isLoading = useMarketplaceContext(v => v.isLoading)
const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections) const isSuccessCollections = useMarketplaceContext(v => v.isSuccessCollections)
const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins) const handleQueryPlugins = useMarketplaceContext(v => v.handleQueryPlugins)
const page = useMarketplaceContext(v => v.page) const page = useMarketplaceContext(v => v.page)
const handleMoreClick = useMarketplaceContext(v => v.handleMoreClick)
useEffect(() => { useEffect(() => {
if (!marketplaceCollectionsFromClient?.length && isSuccessCollections) if (!marketplaceCollectionsFromClient?.length && isSuccessCollections)
@ -39,7 +41,7 @@ const ListWrapper = ({
{ {
plugins && ( plugins && (
<div className='top-5 flex items-center mb-4 pt-3'> <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> <div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown /> <SortDropdown />
</div> </div>
@ -60,6 +62,7 @@ const ListWrapper = ({
plugins={plugins} plugins={plugins}
showInstallButton={showInstallButton} showInstallButton={showInstallButton}
locale={locale} locale={locale}
onMoreClick={handleMoreClick}
/> />
) )
} }

@ -53,22 +53,22 @@ const SortDropdown = () => {
> >
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <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'> <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')} {t('plugin.marketplace.sortBy')}
</span> </span>
<span className='mr-1 system-sm-medium'> <span className='mr-1 system-sm-medium text-text-primary'>
{selectedOption.text} {selectedOption.text}
</span> </span>
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' /> <RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent> <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 => ( options.map(option => (
<div <div
key={`${option.value}-${option.order}`} 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 })} onClick={() => handleSortChange({ sortBy: option.value, sortOrder: option.order })}
> >
{option.text} {option.text}

@ -1,5 +1,11 @@
import type { Plugin } from '../types' import type { Plugin } from '../types'
export type SearchParamsFromCollection = {
query?: string
sort_by?: string
sort_order?: string
}
export type MarketplaceCollection = { export type MarketplaceCollection = {
name: string name: string
label: Record<string, string> label: Record<string, string>
@ -7,6 +13,8 @@ export type MarketplaceCollection = {
rule: string rule: string
created_at: string created_at: string
updated_at: string updated_at: string
searchable?: boolean
search_params?: SearchParamsFromCollection
} }
export type MarketplaceCollectionsResponse = { 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 { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks' 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 type { EndpointListItem } from '../types'
import EndpointModal from './endpoint-modal' import EndpointModal from './endpoint-modal'
import { NAME_FIELD } from './utils' import { NAME_FIELD } from './utils'
import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema' 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 ActionButton from '@/app/components/base/action-button'
import CopyBtn from '@/app/components/base/copy-btn'
import Confirm from '@/app/components/base/confirm' import Confirm from '@/app/components/base/confirm'
import Indicator from '@/app/components/header/indicator' import Indicator from '@/app/components/header/indicator'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import Tooltip from '@/app/components/base/tooltip'
import { import {
useDeleteEndpoint, useDeleteEndpoint,
useDisableEndpoint, useDisableEndpoint,
@ -111,6 +113,25 @@ const EndpointCard = ({
state, 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 ( return (
<div className='p-0.5 bg-background-section-burn rounded-xl'> <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'> <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='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 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> <div title={`${data.url}${endpoint.path}`} className='truncate'>{`${data.url}${endpoint.path}`}</div>
<CopyBtn <Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
className='hidden shrink-0 ml-2 group-hover/item:block' <ActionButton className='hidden shrink-0 ml-2 group-hover/item:flex' onClick={() => handleCopy(`${data.url}${endpoint.path}`)}>
value={`${data.url}${endpoint.path}`} <CopyIcon className='w-3.5 h-3.5 text-text-tertiary' />
isPlain </ActionButton>
/> </Tooltip>
</div> </div>
</div> </div>
))} ))}

@ -1,5 +1,6 @@
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { import {
RiAddLine, RiAddLine,
@ -19,6 +20,8 @@ import {
useInvalidateEndpointList, useInvalidateEndpointList,
} from '@/service/use-endpoints' } from '@/service/use-endpoints'
import type { PluginDetail } from '@/app/components/plugins/types' import type { PluginDetail } from '@/app/components/plugins/types'
import { LanguagesSupported } from '@/i18n/language'
import I18n from '@/context/i18n'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
@ -26,6 +29,7 @@ type Props = {
} }
const EndpointList = ({ detail }: Props) => { const EndpointList = ({ detail }: Props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { locale } = useContext(I18n)
const pluginUniqueID = detail.plugin_unique_identifier const pluginUniqueID = detail.plugin_unique_identifier
const declaration = detail.declaration.endpoint const declaration = detail.declaration.endpoint
const showTopBorder = detail.declaration.tool const showTopBorder = detail.declaration.tool
@ -74,9 +78,8 @@ const EndpointList = ({ detail }: Props) => {
<RiApps2AddLine className='w-4 h-4 text-text-tertiary' /> <RiApps2AddLine className='w-4 h-4 text-text-tertiary' />
</div> </div>
<div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsTip')}</div> <div className='text-text-tertiary system-xs-regular'>{t('plugin.detailPanel.endpointsTip')}</div>
{/* TODO endpoints doc link */}
<a <a
href='' href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/' : ''}guides/api-documentation/endpoint`}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
> >

@ -8,7 +8,7 @@ type IPluginListProps = {
const PluginList: FC<IPluginListProps> = ({ pluginList }) => { const PluginList: FC<IPluginListProps> = ({ pluginList }) => {
return ( return (
<div className='pb-3 bg-white'> <div className='pb-3'>
<div className='grid grid-cols-2 gap-3'> <div className='grid grid-cols-2 gap-3'>
{pluginList.map(plugin => ( {pluginList.map(plugin => (
<PluginItem <PluginItem

@ -36,7 +36,7 @@ const ProviderCard: FC<Props> = ({
const { locale } = useI18N() const { locale } = useI18N()
return ( 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 */} {/* Header */}
<div className="flex"> <div className="flex">
<Icon src={payload.icon} /> <Icon src={payload.icon} />
@ -61,7 +61,7 @@ const ProviderCard: FC<Props> = ({
))} ))}
</div> </div>
<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 <Button
className='grow' className='grow'

@ -6,8 +6,8 @@ const Empty = () => {
return ( return (
<div className='flex flex-col items-center'> <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="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='mb-1 text-[13px] font-medium text-text-secondary leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div>
<div className='text-[13px] text-gray-500 leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div> <div className='text-[13px] text-text-tertiary leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div>
</div> </div>
) )
} }

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import type { Credential } from '@/app/components/tools/types' import type { Credential } from '@/app/components/tools/types'
import Input from '@/app/components/base/input'
import Drawer from '@/app/components/base/drawer-plus' import Drawer from '@/app/components/base/drawer-plus'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Radio from '@/app/components/base/radio/ui' import Radio from '@/app/components/base/radio/ui'
@ -16,7 +17,6 @@ type Props = {
onChange: (credential: Credential) => void onChange: (credential: Credential) => void
onHide: () => void onHide: () => void
} }
const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
type ItemProps = { type ItemProps = {
text: string text: string
@ -28,11 +28,11 @@ type ItemProps = {
const SelectItem: FC<ItemProps> = ({ text, value, isChecked, onClick }) => { const SelectItem: FC<ItemProps> = ({ text, value, isChecked, onClick }) => {
return ( return (
<div <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)} onClick={() => onClick(value)}
> >
<Radio isChecked={isChecked} /> <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> </div>
) )
} }
@ -55,12 +55,12 @@ const ConfigCredential: FC<Props> = ({
panelClassName='mt-2 !w-[520px] h-fit' panelClassName='mt-2 !w-[520px] h-fit'
maxWidthClassName='!max-w-[520px]' maxWidthClassName='!max-w-[520px]'
height={'fit-content'} height={'fit-content'}
headerClassName='!border-b-black/5' headerClassName='!border-b-divider-regular'
body={ body={
<div className='pt-2 px-6'> <div className='pt-2 px-6'>
<div className='space-y-4'> <div className='space-y-4'>
<div> <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'> <div className='flex space-x-3'>
<SelectItem <SelectItem
text={t('tools.createTool.authMethod.types.none')} text={t('tools.createTool.authMethod.types.none')}
@ -84,7 +84,8 @@ const ConfigCredential: FC<Props> = ({
</div> </div>
{tempCredential.auth_type === AuthType.apiKey && ( {tempCredential.auth_type === AuthType.apiKey && (
<> <>
<div className={keyClassNames}>{t('tools.createTool.authHeaderPrefix.title')}</div> <div>
<div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authHeaderPrefix.title')}</div>
<div className='flex space-x-3'> <div className='flex space-x-3'>
<SelectItem <SelectItem
text={t('tools.createTool.authHeaderPrefix.types.basic')} text={t('tools.createTool.authHeaderPrefix.types.basic')}
@ -105,31 +106,30 @@ const ConfigCredential: FC<Props> = ({
onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })} onClick={value => setTempCredential({ ...tempCredential, api_key_header_prefix: value as AuthHeaderPrefix })}
/> />
</div> </div>
</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')} {t('tools.createTool.authMethod.key')}
<Tooltip <Tooltip
popupContent={ popupContent={
<div className='w-[261px] text-gray-500'> <div className='w-[261px] text-text-tertiary'>
{t('tools.createTool.authMethod.keyTooltip')} {t('tools.createTool.authMethod.keyTooltip')}
</div> </div>
} }
triggerClassName='ml-0.5 w-4 h-4' triggerClassName='ml-0.5 w-4 h-4'
/> />
</div> </div>
<input <Input
value={tempCredential.api_key_header} value={tempCredential.api_key_header}
onChange={e => setTempCredential({ ...tempCredential, api_key_header: e.target.value })} 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')!} placeholder={t('tools.createTool.authMethod.types.apiKeyPlaceholder')!}
/> />
</div> </div>
<div> <div>
<div className={keyClassNames}>{t('tools.createTool.authMethod.value')}</div> <div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.authMethod.value')}</div>
<input <Input
value={tempCredential.api_key_value} value={tempCredential.api_key_value}
onChange={e => setTempCredential({ ...tempCredential, api_key_value: e.target.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')!} placeholder={t('tools.createTool.authMethod.types.apiValuePlaceholder')!}
/> />
</div> </div>

@ -10,6 +10,7 @@ import {
import Toast from '../../base/toast' import Toast from '../../base/toast'
import examples from './examples' import examples from './examples'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import { importSchemaFromURL } from '@/service/tools' import { importSchemaFromURL } from '@/service/tools'
type Props = { type Props = {
@ -63,14 +64,14 @@ const GetSchema: FC<Props> = ({
onClick={() => { setShowImportFromUrl(!showImportFromUrl) }} onClick={() => { setShowImportFromUrl(!showImportFromUrl) }}
> >
<RiAddLine className='w-3 h-3' /> <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> </Button>
{showImportFromUrl && ( {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'> <div className='relative'>
<input <Input
type='text' 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')!} placeholder={t('tools.createTool.importFromUrlPlaceHolder')!}
value={importUrl} value={importUrl}
onChange={e => setImportUrl(e.target.value)} onChange={e => setImportUrl(e.target.value)}
@ -95,11 +96,11 @@ const GetSchema: FC<Props> = ({
className='space-x-1' className='space-x-1'
onClick={() => { setShowExamples(!showExamples) }} 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' /> <RiArrowDownSLine className='w-3 h-3' />
</Button> </Button>
{showExamples && ( {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 => ( {examples.map(item => (
<div <div
key={item.key} key={item.key}
@ -107,7 +108,7 @@ const GetSchema: FC<Props> = ({
onChange(item.content) onChange(item.content)
setShowExamples(false) 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}`)} {t(`tools.createTool.exampleOptions.${item.key}`)}
</div> </div>

@ -3,8 +3,9 @@ import type { FC } from 'react'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDebounce, useGetState } from 'ahooks' import { useDebounce, useGetState } from 'ahooks'
import { RiSettings2Line } from '@remixicon/react'
import produce from 'immer' 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 type { Credential, CustomCollectionBackend, CustomParamSchema, Emoji } from '../types'
import { AuthHeaderPrefix, AuthType } from '../types' import { AuthHeaderPrefix, AuthType } from '../types'
import GetSchema from './get-schema' import GetSchema from './get-schema'
@ -21,7 +22,6 @@ import { parseParamsSchema } from '@/service/tools'
import LabelSelector from '@/app/components/tools/labels/selector' import LabelSelector from '@/app/components/tools/labels/selector'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
const fieldNameClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
type Props = { type Props = {
positionLeft?: boolean positionLeft?: boolean
payload: any payload: any
@ -189,12 +189,12 @@ const EditCustomCollectionModal: FC<Props> = ({
panelClassName='mt-2 !w-[640px]' panelClassName='mt-2 !w-[640px]'
maxWidthClassName='!max-w-[640px]' maxWidthClassName='!max-w-[640px]'
height='calc(100vh - 16px)' height='calc(100vh - 16px)'
headerClassName='!border-b-black/5' headerClassName='!border-b-divider-regular'
body={ body={
<div className='flex flex-col h-full'> <div className='flex flex-col h-full'>
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'> <div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
<div> <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'> <div className='flex items-center justify-between gap-3'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> <AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} />
<Input <Input
@ -214,12 +214,12 @@ const EditCustomCollectionModal: FC<Props> = ({
<div className='select-none'> <div className='select-none'>
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<div className='flex 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='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-black/5'></div> <div className='mx-2 w-px h-3 bg-divider-regular'></div>
<a <a
href="https://swagger.io/specification/" href="https://swagger.io/specification/"
target='_blank' rel='noopener noreferrer' 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> <div className='text-xs font-normal'>{t('tools.createTool.viewSchemaSpec')}</div>
<LinkExternal02 className='w-3 h-3' /> <LinkExternal02 className='w-3 h-3' />
@ -238,11 +238,11 @@ const EditCustomCollectionModal: FC<Props> = ({
{/* Available Tools */} {/* Available Tools */}
<div> <div>
<div className={fieldNameClassNames}>{t('tools.createTool.availableTools.title')}</div> <div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.availableTools.title')}</div>
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'> <div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'> <table className='w-full system-xs-regular text-text-secondary'>
<thead className='text-gray-500 uppercase'> <thead className='text-text-tertiary uppercase'>
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-gray-200')}> <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">{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 w-[236px]">{t('tools.createTool.availableTools.description')}</th>
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th> <th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th>
@ -252,9 +252,9 @@ const EditCustomCollectionModal: FC<Props> = ({
</thead> </thead>
<tbody> <tbody>
{paramsSchemas.map((item, index) => ( {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">{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">{item.method}</td>
<td className="p-2 pl-3">{getPath(item.server_url)}</td> <td className="p-2 pl-3">{getPath(item.server_url)}</td>
<td className="p-2 pl-3 w-[62px]"> <td className="p-2 pl-3 w-[62px]">
@ -277,22 +277,22 @@ const EditCustomCollectionModal: FC<Props> = ({
{/* Authorization method */} {/* Authorization method */}
<div> <div>
<div className={fieldNameClassNames}>{t('tools.createTool.authMethod.title')}</div> <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-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}> <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='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div> <div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' /> <RiSettings2Line className='w-4 h-4 text-text-secondary' />
</div> </div>
</div> </div>
{/* Labels */} {/* Labels */}
<div> <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} /> <LabelSelector value={labels} onChange={handleLabelSelect} />
</div> </div>
{/* Privacy Policy */} {/* Privacy Policy */}
<div> <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 <Input
value={customCollection.privacy_policy} value={customCollection.privacy_policy}
onChange={(e) => { onChange={(e) => {
@ -305,7 +305,7 @@ const EditCustomCollectionModal: FC<Props> = ({
</div> </div>
<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 <Input
value={customCollection.custom_disclaimer} value={customCollection.custom_disclaimer}
onChange={(e) => { onChange={(e) => {
@ -318,10 +318,10 @@ const EditCustomCollectionModal: FC<Props> = ({
</div> </div>
</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 && ( 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 '> <div className='flex space-x-2 '>

@ -21,7 +21,6 @@ import Toast from '@/app/components/base/toast'
import Modal from '../../base/modal' import Modal from '../../base/modal'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
const fieldNameClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
type Props = { type Props = {
positionLeft?: boolean positionLeft?: boolean
payload: any payload: any
@ -187,12 +186,12 @@ const EditCustomCollectionModal: FC<Props> = ({
className='!p-0 !max-w-[630px] !h-[calc(100vh-16px)]' className='!p-0 !max-w-[630px] !h-[calc(100vh-16px)]'
> >
<div className='flex flex-col h-full'> <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')} {t('tools.createTool.title')}
</div> </div>
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'> <div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
<div> <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'> <div className='flex items-center justify-between gap-3'>
<AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> <AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} />
<Input <Input
@ -212,12 +211,12 @@ const EditCustomCollectionModal: FC<Props> = ({
<div className='select-none'> <div className='select-none'>
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<div className='flex 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='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-black/5'></div> <div className='mx-2 w-px h-3 bg-divider-regular'></div>
<a <a
href="https://swagger.io/specification/" href="https://swagger.io/specification/"
target='_blank' rel='noopener noreferrer' 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> <div className='text-xs font-normal'>{t('tools.createTool.viewSchemaSpec')}</div>
<LinkExternal02 className='w-3 h-3' /> <LinkExternal02 className='w-3 h-3' />
@ -236,11 +235,11 @@ const EditCustomCollectionModal: FC<Props> = ({
{/* Available Tools */} {/* Available Tools */}
<div> <div>
<div className={fieldNameClassNames}>{t('tools.createTool.availableTools.title')}</div> <div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.availableTools.title')}</div>
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'> <div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'> <table className='w-full system-xs-regular text-text-secondary'>
<thead className='text-gray-500 uppercase'> <thead className='text-text-tertiary uppercase'>
<tr className={cn(paramsSchemas.length > 0 && 'border-b', 'border-gray-200')}> <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">{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 w-[236px]">{t('tools.createTool.availableTools.description')}</th>
<th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th> <th className="p-2 pl-3 font-medium">{t('tools.createTool.availableTools.method')}</th>
@ -250,9 +249,9 @@ const EditCustomCollectionModal: FC<Props> = ({
</thead> </thead>
<tbody> <tbody>
{paramsSchemas.map((item, index) => ( {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">{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">{item.method}</td>
<td className="p-2 pl-3">{getPath(item.server_url)}</td> <td className="p-2 pl-3">{getPath(item.server_url)}</td>
<td className="p-2 pl-3 w-[62px]"> <td className="p-2 pl-3 w-[62px]">
@ -275,22 +274,22 @@ const EditCustomCollectionModal: FC<Props> = ({
{/* Authorization method */} {/* Authorization method */}
<div> <div>
<div className={fieldNameClassNames}>{t('tools.createTool.authMethod.title')}</div> <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-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}> <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='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div> <div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${credential.auth_type}`)}</div>
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' /> <Settings01 className='w-4 h-4 text-text-secondary' />
</div> </div>
</div> </div>
{/* Labels */} {/* Labels */}
<div> <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} /> <LabelSelector value={labels} onChange={handleLabelSelect} />
</div> </div>
{/* Privacy Policy */} {/* Privacy Policy */}
<div> <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 <Input
value={customCollection.privacy_policy} value={customCollection.privacy_policy}
onChange={(e) => { onChange={(e) => {
@ -303,7 +302,7 @@ const EditCustomCollectionModal: FC<Props> = ({
</div> </div>
<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 <Input
value={customCollection.custom_disclaimer} value={customCollection.custom_disclaimer}
onChange={(e) => { onChange={(e) => {
@ -316,10 +315,10 @@ const EditCustomCollectionModal: FC<Props> = ({
</div> </div>
</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 && ( 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 '> <div className='flex space-x-2 '>

@ -3,10 +3,11 @@ import type { FC } from 'react'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' 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 ConfigCredentials from './config-credentials'
import { AuthType, type Credential, type CustomCollectionBackend, type CustomParamSchema } from '@/app/components/tools/types' import { AuthType, type Credential, type CustomCollectionBackend, type CustomParamSchema } from '@/app/components/tools/types'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import Drawer from '@/app/components/base/drawer-plus' import Drawer from '@/app/components/base/drawer-plus'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import { testAPIAvailable } from '@/service/tools' import { testAPIAvailable } from '@/service/tools'
@ -19,8 +20,6 @@ type Props = {
onHide: () => void onHide: () => void
} }
const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900'
const TestApi: FC<Props> = ({ const TestApi: FC<Props> = ({
positionCenter, positionCenter,
customCollection, customCollection,
@ -65,39 +64,40 @@ const TestApi: FC<Props> = ({
panelClassName='mt-2 !w-[600px]' panelClassName='mt-2 !w-[600px]'
maxWidthClassName='!max-w-[600px]' maxWidthClassName='!max-w-[600px]'
height='calc(100vh - 16px)' height='calc(100vh - 16px)'
headerClassName='!border-b-black/5' headerClassName='!border-b-divider-regular'
body={ body={
<div className='pt-2 px-6 overflow-y-auto'> <div className='pt-2 px-6 overflow-y-auto'>
<div className='space-y-4'> <div className='space-y-4'>
<div> <div>
<div className={keyClassNames}>{t('tools.createTool.authMethod.title')}</div> <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-gray-100 rounded-lg cursor-pointer' onClick={() => setCredentialsModalShow(true)}> <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='text-sm font-normal text-gray-900'>{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}</div> <div className='system-xs-regular text-text-primary'>{t(`tools.createTool.authMethod.types.${tempCredential.auth_type}`)}</div>
<Settings01 className='w-4 h-4 text-gray-700 opacity-60' /> <RiSettings2Line className='w-4 h-4 text-text-secondary' />
</div> </div>
</div> </div>
<div> <div>
<div className={keyClassNames}>{t('tools.test.parametersValue')}</div> <div className='py-2 system-sm-medium text-text-primary'>{t('tools.test.parametersValue')}</div>
<div className='rounded-lg border border-gray-200'> <div className='rounded-lg border border-divider-regular'>
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'> <table className='w-full system-xs-regular text-text-secondary font-normal'>
<thead className='text-gray-500 uppercase'> <thead className='text-text-tertiary uppercase'>
<tr className='border-b border-gray-200'> <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.parameters')}</th>
<th className="p-2 pl-3 font-medium">{t('tools.test.value')}</th> <th className="p-2 pl-3 font-medium">{t('tools.test.value')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{parameters.map((item, index) => ( {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"> <td className="py-2 pl-3 pr-2.5">
{item.label[language]} {item.label[language]}
</td> </td>
<td className=""> <td className="">
<input <Input
value={parametersValue[item.name] || ''} value={parametersValue[item.name] || ''}
onChange={e => setParametersValue({ ...parametersValue, [item.name]: e.target.value })} 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> </td>
</tr> </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> <Button variant='primary' className=' mt-4 w-full h-10' onClick={handleTest}>{t('tools.test.title')}</Button>
<div className='mt-6'> <div className='mt-6'>
<div className='flex items-center space-x-3'> <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 className='grow w-0 h-px bg-[rgb(243, 244, 246)]'></div>
</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'> <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-gray-400'>{t('tools.test.testResultPlaceholder')}</span>} {result || <span className='text-text-quaternary'>{t('tools.test.testResultPlaceholder')}</span>}
</div> </div>
</div> </div>
</div> </div>

@ -67,24 +67,23 @@ const LabelFilter: FC<LabelFilterProps> = ({
className='block' className='block'
> >
<div className={cn( <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', '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 && '!bg-gray-300 hover:bg-gray-300', !open && !!value.length && 'shadow-xs',
!open && !!value.length && '!bg-white/80 shadow-xs !border-black/5 hover:!bg-gray-200', open && !!value.length && 'shadow-xs',
open && !!value.length && '!bg-gray-200 !border-black/5 shadow-xs hover:!bg-gray-200',
)}> )}>
<div className='p-[1px]'> <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>
<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 && t('common.tag.placeholder')}
{!!value.length && currentLabel?.label} {!!value.length && currentLabel?.label}
</div> </div>
{value.length > 1 && ( {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 && ( {!value.length && (
<div className='p-[1px]'> <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> </div>
)} )}
{!!value.length && ( {!!value.length && (
@ -92,14 +91,14 @@ const LabelFilter: FC<LabelFilterProps> = ({
e.stopPropagation() e.stopPropagation()
onChange([]) 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>
)} )}
</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1002]'> <PortalToFollowElemContent className='z-[1002]'>
<div className='relative w-[240px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'> <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 border-b-[0.5px] border-black/5'> <div className='p-2'>
<Input <Input
showLeftIcon showLeftIcon
showClearIcon showClearIcon
@ -112,17 +111,17 @@ const LabelFilter: FC<LabelFilterProps> = ({
{filteredLabelList.map(label => ( {filteredLabelList.map(label => (
<div <div
key={label.name} 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)} onClick={() => selectLabel(label)}
> >
<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>
{value.includes(label.name) && <Check className='shrink-0 w-4 h-4 text-primary-600' />} {value.includes(label.name) && <Check className='shrink-0 w-4 h-4 text-text-accent' />}
</div> </div>
))} ))}
{!filteredLabelList.length && ( {!filteredLabelList.length && (
<div className='p-3 flex flex-col items-center gap-1'> <div className='p-3 flex flex-col items-center gap-1'>
<Tag03 className='h-6 w-6 text-gray-300' /> <Tag03 className='h-6 w-6 text-text-quaternary' />
<div className='text-gray-500 text-xs leading-[14px]'>{t('common.tag.noTag')}</div> <div className='text-text-tertiary text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
</div> </div>
)} )}
</div> </div>

@ -66,21 +66,21 @@ const LabelSelector: FC<LabelSelectorProps> = ({
className='block' className='block'
> >
<div className={cn( <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', '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 && '!bg-gray-200 hover:bg-gray-200', 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 && t('tools.createTool.toolInput.labelPlaceholder')}
{!!value.length && selectedLabels} {!!value.length && selectedLabels}
</div> </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' /> <RiArrowDownSLine className='h-4 w-4' />
</div> </div>
</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1040]'> <PortalToFollowElemContent className='z-[1040]'>
<div className='relative w-[591px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg'> <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-black/5'> <div className='p-2 border-b-[0.5px] border-divider-regular'>
<Input <Input
showLeftIcon showLeftIcon
showClearIcon showClearIcon
@ -93,7 +93,7 @@ const LabelSelector: FC<LabelSelectorProps> = ({
{filteredLabelList.map(label => ( {filteredLabelList.map(label => (
<div <div
key={label.name} 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)} onClick={() => selectLabel(label)}
> >
<Checkbox <Checkbox
@ -101,13 +101,13 @@ const LabelSelector: FC<LabelSelectorProps> = ({
checked={value.includes(label.name)} checked={value.includes(label.name)}
onCheck={() => { }} 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> </div>
))} ))}
{!filteredLabelList.length && ( {!filteredLabelList.length && (
<div className='p-3 flex flex-col items-center gap-1'> <div className='p-3 flex flex-col items-center gap-1'>
<Tag03 className='h-6 w-6 text-gray-300' /> <Tag03 className='h-6 w-6 text-text-quaternary' />
<div className='text-gray-500 text-xs leading-[14px]'>{t('common.tag.noTag')}</div> <div className='text-text-tertiary text-xs leading-[14px]'>{t('common.tag.noTag')}</div>
</div> </div>
)} )}
</div> </div>

@ -63,13 +63,13 @@ const ProviderList = () => {
return ( 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 <div
ref={containerRef} 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( <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', currentProvider && 'pr-6',
)}> )}>
<TabSliderNew <TabSliderNew
@ -96,6 +96,7 @@ const ProviderList = () => {
{(filteredCollectionList.length > 0 || activeTab !== 'builtin') && ( {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && (
<div className={cn( <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', '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} />} {activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />}
{filteredCollectionList.map(collection => ( {filteredCollectionList.map(collection => (

@ -45,16 +45,16 @@ const Contribute = ({ onRefreshData }: Props) => {
return ( return (
<> <>
{isCurrentWorkspaceManager && ( {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='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-white' onClick={() => setIsShowEditCustomCollectionModal(true)}> <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='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'> <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-gray-500 group-hover:text-primary-600'/> <RiAddLine className='w-4 h-4 text-text-tertiary group-hover:text-text-accent'/>
</div> </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> </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'> <a href={linkUrl} target='_blank' rel='noopener noreferrer' className='flex items-center space-x-1'>
<BookOpen01 className='shrink-0 w-3 h-3' /> <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> <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] && ( {!!collection.description[language] && (
<Description text={collection.description[language]} descriptionLineRows={2}></Description> <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 && ( {collection.type === CollectionType.custom && !isDetailLoading && (
<Button <Button
className={cn('shrink-0 my-3 w-full')} className={cn('shrink-0 my-3 w-full')}
onClick={() => setIsShowEditCustomCollectionModal(true)} onClick={() => setIsShowEditCustomCollectionModal(true)}
> >
<Settings01 className='mr-1 w-4 h-4 text-gray-500' /> <Settings01 className='mr-1 w-4 h-4 text-text-tertiary' />
<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> </Button>
)} )}
{collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && (
@ -276,8 +276,8 @@ const ProviderDetail = ({
variant='primary' variant='primary'
className={cn('shrink-0 my-3 w-[183px]')} 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'> <a className='flex items-center text-text-primary' 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> <div className='system-sm-medium'>{t('tools.openInStudio')}</div>
<LinkExternal02 className='ml-1 w-4 h-4' /> <LinkExternal02 className='ml-1 w-4 h-4' />
</a> </a>
</Button> </Button>
@ -286,7 +286,7 @@ const ProviderDetail = ({
onClick={() => setIsShowEditWorkflowToolModal(true)} onClick={() => setIsShowEditWorkflowToolModal(true)}
disabled={!isCurrentWorkspaceManager} 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> </Button>
</> </>
)} )}
@ -319,7 +319,7 @@ const ProviderDetail = ({
<div className='text-text-secondary system-sm-semibold-uppercase'> <div className='text-text-secondary system-sm-semibold-uppercase'>
<span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span> <span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span>
<span className='px-1'>·</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> </div>
<Button <Button
variant='primary' variant='primary'

@ -71,11 +71,11 @@ const ConfigCredential: FC<Props> = ({
onHide={onCancel} onHide={onCancel}
title={t('tools.auth.setupModalTitle') as string} title={t('tools.auth.setupModalTitle') as string}
titleDescription={t('tools.auth.setupModalTitleDescription') 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]' maxWidthClassName='!max-w-[420px]'
height='calc(100vh - 64px)' height='calc(100vh - 64px)'
contentClassName='!bg-gray-100' contentClassName='!bg-components-panel-bg'
headerClassName='!border-b-black/5' headerClassName='!border-b-divider-subtle'
body={ body={
<div className='px-6 py-3 h-full'> <div className='px-6 py-3 h-full'>
@ -92,12 +92,12 @@ const ConfigCredential: FC<Props> = ({
isEditMode={true} isEditMode={true}
showOnVariableMap={{}} showOnVariableMap={{}}
validating={false} validating={false}
inputClassName='!bg-gray-50' inputClassName='!bg-components-input-bg-normal'
fieldMoreInfo={item => item.url fieldMoreInfo={item => item.url
? (<a ? (<a
href={item.url} href={item.url}
target='_blank' rel='noopener noreferrer' 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')} {t('tools.howToGet')}
<LinkExternal02 className='ml-1 w-3 h-3' /> <LinkExternal02 className='ml-1 w-3 h-3' />

@ -2,7 +2,6 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiCloseLine } from '@remixicon/react' import { RiCloseLine } from '@remixicon/react'
import s from './style.module.css'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal' import Modal from '@/app/components/base/modal'
@ -19,24 +18,24 @@ const ConfirmModal = ({ show, onConfirm, onClose }: ConfirmModalProps) => {
return ( return (
<Modal <Modal
className={cn('p-8 max-w-[600px] w-[600px]', s.bg)} className={cn('p-8 max-w-[600px] w-[600px]')}
isShow={show} isShow={show}
onClose={() => { }} onClose={() => { }}
> >
<div className='absolute right-4 top-4 p-2 cursor-pointer' onClick={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>
<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)]' /> <AlertTriangle className='w-6 h-6 text-[rgb(247,144,9)]' />
</div> </div>
<div className='relative mt-3 text-xl font-semibold leading-[30px] text-gray-900'>{t('tools.createTool.confirmTitle')}</div> <div className='relative mt-3 text-xl font-semibold leading-[30px] text-text-primary'>{t('tools.createTool.confirmTitle')}</div>
<div className='my-1 text-gray-500 text-sm leading-5'> <div className='my-1 text-text-tertiary text-sm leading-5'>
{t('tools.createTool.confirmTip')} {t('tools.createTool.confirmTip')}
</div> </div>
<div className='pt-6 flex justify-end items-center'> <div className='pt-6 flex justify-end items-center'>
<div className='flex items-center'> <div className='flex items-center'>
<Button className='mr-2' onClick={onClose}>{t('common.operation.cancel')}</Button> <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>
</div> </div>
</Modal> </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]' panelClassName='mt-2 !w-[640px]'
maxWidthClassName='!max-w-[640px]' maxWidthClassName='!max-w-[640px]'
height='calc(100vh - 16px)' height='calc(100vh - 16px)'
headerClassName='!border-b-black/5' headerClassName='!border-b-divider'
body={ body={
<div className='flex flex-col h-full'> <div className='flex flex-col h-full'>
<div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'> <div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'>
{/* name & icon */} {/* name & icon */}
<div> <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'> <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} /> <AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' iconType='emoji' icon={emoji.content} background={emoji.background} />
<Input <Input
@ -143,7 +143,7 @@ const WorkflowToolAsModal: FC<Props> = ({
</div> </div>
{/* name for tool call */} {/* name for tool call */}
<div> <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> {t('tools.createTool.nameForToolCall')} <span className='ml-1 text-red-500'>*</span>
<Tooltip <Tooltip
popupContent={ popupContent={
@ -165,7 +165,7 @@ const WorkflowToolAsModal: FC<Props> = ({
</div> </div>
{/* description */} {/* description */}
<div> <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 <Textarea
placeholder={t('tools.createTool.descriptionPlaceholder') || ''} placeholder={t('tools.createTool.descriptionPlaceholder') || ''}
value={description} value={description}
@ -174,11 +174,11 @@ const WorkflowToolAsModal: FC<Props> = ({
</div> </div>
{/* Tool Input */} {/* Tool Input */}
<div> <div>
<div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.title')}</div> <div className='py-2 system-sm-medium text-text-primary'>{t('tools.createTool.toolInput.title')}</div>
<div className='rounded-lg border border-gray-200 w-full overflow-x-auto'> <div className='rounded-lg border border-divider-regular w-full overflow-x-auto'>
<table className='w-full leading-[18px] text-xs text-gray-700 font-normal'> <table className='w-full leading-[18px] text-xs text-text-secondary font-normal'>
<thead className='text-gray-500 uppercase'> <thead className='text-text-tertiary uppercase'>
<tr className='border-b border-gray-200'> <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-[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 w-[102px]">{t('tools.createTool.toolInput.method')}</th>
<th className="p-2 pl-3 font-medium">{t('tools.createTool.toolInput.description')}</th> <th className="p-2 pl-3 font-medium">{t('tools.createTool.toolInput.description')}</th>
@ -186,22 +186,22 @@ const WorkflowToolAsModal: FC<Props> = ({
</thead> </thead>
<tbody> <tbody>
{parameters.map((item, index) => ( {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]"> <td className="p-2 pl-3 max-w-[156px]">
<div className='text-[13px] leading-[18px]'> <div className='text-[13px] leading-[18px]'>
<div title={item.name} className='flex'> <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> <span className='shrink-0 pl-1 text-[#ec4a0a] text-xs leading-[18px]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span>
</div> </div>
<div className='text-gray-500'>{item.type}</div> <div className='text-text-tertiary'>{item.type}</div>
</div> </div>
</td> </td>
<td> <td>
{item.name === '__image' && ( {item.name === '__image' && (
<div className={cn( <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')} {t('tools.createTool.toolInput.methodParameter')}
</div> </div>
</div> </div>
@ -210,10 +210,10 @@ const WorkflowToolAsModal: FC<Props> = ({
<MethodSelector value={item.form} onChange={value => handleParameterChange('form', value, index)} /> <MethodSelector value={item.form} onChange={value => handleParameterChange('form', value, index)} />
)} )}
</td> </td>
<td className="p-2 pl-3 text-gray-500 w-[236px]"> <td className="p-2 pl-3 text-text-tertiary w-[236px]">
<input <input
type='text' 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')!} placeholder={t('tools.createTool.toolInput.descriptionPlaceholder')!}
value={item.description} value={item.description}
onChange={e => handleParameterChange('description', e.target.value, index)} onChange={e => handleParameterChange('description', e.target.value, index)}
@ -227,12 +227,12 @@ const WorkflowToolAsModal: FC<Props> = ({
</div> </div>
{/* Tags */} {/* Tags */}
<div> <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} /> <LabelSelector value={labels} onChange={handleLabelSelect} />
</div> </div>
{/* Privacy Policy */} {/* Privacy Policy */}
<div> <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 <Input
className='h-10' className='h-10'
value={privacyPolicy} value={privacyPolicy}
@ -240,9 +240,9 @@ const WorkflowToolAsModal: FC<Props> = ({
placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} /> placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} />
</div> </div>
</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 && ( {!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 '> <div className='flex space-x-2 '>
<Button onClick={onHide}>{t('common.operation.cancel')}</Button> <Button onClick={onHide}>{t('common.operation.cancel')}</Button>

@ -34,37 +34,37 @@ const MethodSelector: FC<MethodSelectorProps> = ({
className='block' className='block'
> >
<div className={cn( <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', '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-gray-100 hover:bg-gray-100', 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')} {value === 'llm' ? t('tools.createTool.toolInput.methodParameter') : t('tools.createTool.toolInput.methodSetting')}
</div> </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' /> <RiArrowDownSLine className='h-4 w-4' />
</div> </div>
</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1040]'> <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='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='flex item-center gap-1'>
<div className='shrink-0 w-4 h-4'> <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>
<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>
<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>
<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='flex item-center gap-1'>
<div className='shrink-0 w-4 h-4'> <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>
<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>
<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> </div>
</div> </div>

@ -192,6 +192,7 @@ export const useWorkflowRun = () => {
const newNodes = produce(nodes, (draft) => { const newNodes = produce(nodes, (draft) => {
draft.forEach((node) => { draft.forEach((node) => {
node.data._waitingRun = true node.data._waitingRun = true
node.data._runningBranchId = undefined
}) })
}) })
setNodes(newNodes) setNodes(newNodes)

@ -1,9 +1,17 @@
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import produce from 'immer' import produce from 'immer'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import type { OutputVar } from '../../code/types' import type {
import type { ValueSelector } from '@/app/components/workflow/types' CodeNodeType,
import { VarType } from '@/app/components/workflow/types' OutputVar,
} from '../../code/types'
import type {
ValueSelector,
} from '@/app/components/workflow/types'
import {
BlockEnum,
VarType,
} from '@/app/components/workflow/types'
import { import {
useWorkflow, useWorkflow,
} from '@/app/components/workflow/hooks' } from '@/app/components/workflow/hooks'

@ -52,6 +52,12 @@ const useConfig = (id: string, payload: IterationNodeType) => {
[VarType.number]: VarType.arrayNumber, [VarType.number]: VarType.arrayNumber,
[VarType.object]: VarType.arrayObject, [VarType.object]: VarType.arrayObject,
[VarType.file]: VarType.arrayFile, [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 } as Record<VarType, VarType>)[outputItemType] || VarType.arrayString
}) })
setInputs(newInputs) setInputs(newInputs)

@ -59,20 +59,12 @@ const InputVarList: FC<Props> = ({
const newValue = produce(value, (draft: ToolVarInputs) => { const newValue = produce(value, (draft: ToolVarInputs) => {
const target = draft[variable] const target = draft[variable]
if (target) { if (target) {
if (!isSupportConstantValue || varKindType === VarKindType.variable) { target.type = varKindType
if (isSupportConstantValue) target.value = varValue
target.type = VarKindType.variable
target.value = varValue as ValueSelector
}
else {
target.type = VarKindType.constant
target.value = varValue as string
}
} }
else { else {
draft[variable] = { draft[variable] = {
type: VarKindType.variable, type: varKindType,
value: varValue, value: varValue,
} }
} }
@ -170,7 +162,7 @@ const InputVarList: FC<Props> = ({
value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])} value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])}
onChange={handleNotMixedTypeChange(variable)} onChange={handleNotMixedTypeChange(variable)}
onOpen={handleOpen(index)} onOpen={handleOpen(index)}
defaultVarKindType={isNumber ? VarKindType.constant : VarKindType.variable} defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
isSupportConstantValue={isSupportConstantValue} isSupportConstantValue={isSupportConstantValue}
filterVar={isNumber ? filterVar : undefined} filterVar={isNumber ? filterVar : undefined}
availableVars={isSelect ? availableVars : undefined} availableVars={isSelect ? availableVars : undefined}

@ -214,8 +214,13 @@ const useConfig = (id: string, payload: ToolNodeType) => {
.map(k => inputs.tool_parameters[k]) .map(k => inputs.tool_parameters[k])
const varInputs = getInputVars(hadVarParams.map((p) => { 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 ValueSelector).join('.')}#}}`
}
return p.value as string return p.value as string
})) }))

@ -161,6 +161,7 @@ const translation = {
newlyReleased: 'Newly Released', newlyReleased: 'Newly Released',
firstReleased: 'First Released', firstReleased: 'First Released',
}, },
viewMore: 'View more',
}, },
task: { task: {
installing: 'Installing {{installingLength}} plugins, 0 done.', installing: 'Installing {{installingLength}} plugins, 0 done.',

@ -161,6 +161,7 @@ const translation = {
newlyReleased: '最新发布', newlyReleased: '最新发布',
firstReleased: '首次发布', firstReleased: '首次发布',
}, },
viewMore: '查看更多',
}, },
task: { task: {
installing: '{{installingLength}} 个插件安装中0 已完成', installing: '{{installingLength}} 个插件安装中0 已完成',

@ -1,6 +1,6 @@
{ {
"name": "dify-web", "name": "dify-web",
"version": "0.13.2", "version": "0.14.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=18.17.0" "node": ">=18.17.0"
@ -39,7 +39,7 @@
"@next/mdx": "^14.0.4", "@next/mdx": "^14.0.4",
"@octokit/core": "^6.1.2", "@octokit/core": "^6.1.2",
"@octokit/request-error": "^6.1.5", "@octokit/request-error": "^6.1.5",
"@remixicon/react": "^4.3.0", "@remixicon/react": "^4.5.0",
"@sentry/react": "^7.54.0", "@sentry/react": "^7.54.0",
"@sentry/utils": "^7.54.0", "@sentry/utils": "^7.54.0",
"@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.js": "^3.2.4",
@ -91,6 +91,8 @@
"react-multi-email": "^1.0.25", "react-multi-email": "^1.0.25",
"react-papaparse": "^4.4.0", "react-papaparse": "^4.4.0",
"react-slider": "^2.0.6", "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-sortablejs": "^6.1.4",
"react-syntax-highlighter": "^15.6.1", "react-syntax-highlighter": "^15.6.1",
"react-tooltip": "5.8.3", "react-tooltip": "5.8.3",

File diff suppressed because it is too large Load Diff

@ -66,6 +66,7 @@ const config = {
// => @media (min-width: 600px) { ... } // => @media (min-width: 600px) { ... }
pc: '769px', pc: '769px',
// => @media (min-width: 769px) { ... } // => @media (min-width: 769px) { ... }
'2k': '2560px',
}, },
boxShadow: { boxShadow: {
'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)', 'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)',

@ -2242,10 +2242,10 @@
classcat "^5.0.3" classcat "^5.0.3"
zustand "^4.4.1" zustand "^4.4.1"
"@remixicon/react@^4.3.0": "@remixicon/react@^4.5.0":
version "4.3.0" version "4.5.0"
resolved "https://registry.npmjs.org/@remixicon/react/-/react-4.3.0.tgz#8ab34d03fccca53bf66f87c6e3f943ef5c65684f" resolved "https://registry.yarnpkg.com/@remixicon/react/-/react-4.5.0.tgz#5600d122ee4995bff2c4442cb056eeb4f11ecb5a"
integrity sha512-mAVDn8pAa9dURltGwiYrf7bPIqjG4ZAnCUHfjpgz3g+HLSDNXOaJ67Z5wmjVB5KMGpp9JbbTN5vsp2z+ajVLWg== integrity sha512-Xr20SxMpRNlgXZnoF5BCMyZuQEhXY3yJCyms8kxB/vJCCiV1nWdiO48XqRG5LBd1192iSHC4m658AIWi6rmBFg==
"@rgrove/parse-xml@^4.1.0": "@rgrove/parse-xml@^4.1.0":
version "4.1.0" version "4.1.0"

Loading…
Cancel
Save