mirror of https://github.com/langgenius/dify.git
Merge branch 'main' into feat-agent-mask
This commit is contained in:
commit
fda2e45c1f
|
|
@ -6,11 +6,10 @@ cd web && pnpm install
|
|||
pipx install uv
|
||||
|
||||
echo "alias start-api=\"cd $WORKSPACE_ROOT/api && uv run python -m flask run --host 0.0.0.0 --port=5001 --debug\"" >> ~/.bashrc
|
||||
echo "alias start-worker=\"cd $WORKSPACE_ROOT/api && uv run python -m celery -A app.celery worker -P threads -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion,plugin,workflow_storage\"" >> ~/.bashrc
|
||||
echo "alias start-worker=\"cd $WORKSPACE_ROOT/api && uv run python -m celery -A app.celery worker -P threads -c 1 --loglevel INFO -Q dataset,priority_dataset,priority_pipeline,pipeline,mail,ops_trace,app_deletion,plugin,workflow_storage,conversation,workflow,schedule_poller,schedule_executor,triggered_workflow_dispatcher,trigger_refresh_executor\"" >> ~/.bashrc
|
||||
echo "alias start-web=\"cd $WORKSPACE_ROOT/web && pnpm dev\"" >> ~/.bashrc
|
||||
echo "alias start-web-prod=\"cd $WORKSPACE_ROOT/web && pnpm build && pnpm start\"" >> ~/.bashrc
|
||||
echo "alias start-containers=\"cd $WORKSPACE_ROOT/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env up -d\"" >> ~/.bashrc
|
||||
echo "alias stop-containers=\"cd $WORKSPACE_ROOT/docker && docker-compose -f docker-compose.middleware.yaml -p dify --env-file middleware.env down\"" >> ~/.bashrc
|
||||
|
||||
source /home/vscode/.bashrc
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ class StreamableHTTPTransport:
|
|||
) -> bool:
|
||||
"""Handle an SSE event, returning True if the response is complete."""
|
||||
if sse.event == "message":
|
||||
# ping event send by server will be recognized as a message event with empty data by httpx-sse's SSEDecoder
|
||||
if not sse.data.strip():
|
||||
return False
|
||||
|
||||
try:
|
||||
message = JSONRPCMessage.model_validate_json(sse.data)
|
||||
logger.debug("SSE message: %s", message)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ class TenantIsolatedTaskQueue:
|
|||
serialized_data = wrapper.serialize()
|
||||
serialized_tasks.append(serialized_data)
|
||||
|
||||
if not serialized_tasks:
|
||||
return
|
||||
|
||||
redis_client.lpush(self._queue, *serialized_tasks)
|
||||
|
||||
def pull_tasks(self, count: int = 1) -> Sequence[Any]:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import io
|
|||
import json
|
||||
from collections.abc import Generator
|
||||
|
||||
from google.cloud import storage as google_cloud_storage
|
||||
from google.cloud import storage as google_cloud_storage # type: ignore
|
||||
|
||||
from configs import dify_config
|
||||
from extensions.storage.base_storage import BaseStorage
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from configs import dify_config
|
|||
from core.rag.index_processor.constant.built_in_field import BuiltInField, MetadataDataSource
|
||||
from core.rag.retrieval.retrieval_methods import RetrievalMethod
|
||||
from extensions.ext_storage import storage
|
||||
from models.base import TypeBase
|
||||
from services.entities.knowledge_entities.knowledge_entities import ParentMode, Rule
|
||||
|
||||
from .account import Account
|
||||
|
|
@ -906,17 +907,21 @@ class ChildChunk(Base):
|
|||
return db.session.query(DocumentSegment).where(DocumentSegment.id == self.segment_id).first()
|
||||
|
||||
|
||||
class AppDatasetJoin(Base):
|
||||
class AppDatasetJoin(TypeBase):
|
||||
__tablename__ = "app_dataset_joins"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="app_dataset_join_pkey"),
|
||||
sa.Index("app_dataset_join_app_dataset_idx", "dataset_id", "app_id"),
|
||||
)
|
||||
|
||||
id = mapped_column(StringUUID, primary_key=True, nullable=False, server_default=sa.text("uuid_generate_v4()"))
|
||||
app_id = mapped_column(StringUUID, nullable=False)
|
||||
dataset_id = mapped_column(StringUUID, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, server_default=sa.func.current_timestamp())
|
||||
id: Mapped[str] = mapped_column(
|
||||
StringUUID, primary_key=True, nullable=False, server_default=sa.text("uuid_generate_v4()"), init=False
|
||||
)
|
||||
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
dataset_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, nullable=False, server_default=sa.func.current_timestamp(), init=False
|
||||
)
|
||||
|
||||
@property
|
||||
def app(self):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "dify-api"
|
||||
version = "1.9.2"
|
||||
version = "1.10.0"
|
||||
requires-python = ">=3.11,<3.13"
|
||||
|
||||
dependencies = [
|
||||
|
|
@ -37,7 +37,7 @@ dependencies = [
|
|||
"numpy~=1.26.4",
|
||||
"openpyxl~=3.1.5",
|
||||
"opik~=1.8.72",
|
||||
"litellm==1.77.1", # Pinned to avoid madoka dependency issue
|
||||
"litellm==1.77.1", # Pinned to avoid madoka dependency issue
|
||||
"opentelemetry-api==1.27.0",
|
||||
"opentelemetry-distro==0.48b0",
|
||||
"opentelemetry-exporter-otlp==1.27.0",
|
||||
|
|
@ -79,7 +79,6 @@ dependencies = [
|
|||
"tiktoken~=0.9.0",
|
||||
"transformers~=4.56.1",
|
||||
"unstructured[docx,epub,md,ppt,pptx]~=0.16.1",
|
||||
"weave~=0.51.0",
|
||||
"yarl~=1.18.3",
|
||||
"webvtt-py~=0.5.1",
|
||||
"sseclient-py~=1.8.0",
|
||||
|
|
@ -90,6 +89,7 @@ dependencies = [
|
|||
"croniter>=6.0.0",
|
||||
"weaviate-client==4.17.0",
|
||||
"apscheduler>=3.11.0",
|
||||
"weave>=0.52.16",
|
||||
]
|
||||
# Before adding new dependency, consider place it in
|
||||
# alphabet order (a-z) and suitable group.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from typing import Any
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from core.app.app_config.entities import (
|
||||
DatasetEntity,
|
||||
|
|
@ -28,6 +28,12 @@ from models.model import App, AppMode, AppModelConfig
|
|||
from models.workflow import Workflow, WorkflowType
|
||||
|
||||
|
||||
class _NodeType(TypedDict):
|
||||
id: str
|
||||
position: None
|
||||
data: dict[str, Any]
|
||||
|
||||
|
||||
class WorkflowConverter:
|
||||
"""
|
||||
App Convert to Workflow Mode
|
||||
|
|
@ -217,7 +223,7 @@ class WorkflowConverter:
|
|||
|
||||
return app_config
|
||||
|
||||
def _convert_to_start_node(self, variables: list[VariableEntity]):
|
||||
def _convert_to_start_node(self, variables: list[VariableEntity]) -> _NodeType:
|
||||
"""
|
||||
Convert to Start Node
|
||||
:param variables: list of variables
|
||||
|
|
@ -235,7 +241,7 @@ class WorkflowConverter:
|
|||
|
||||
def _convert_to_http_request_node(
|
||||
self, app_model: App, variables: list[VariableEntity], external_data_variables: list[ExternalDataVariableEntity]
|
||||
) -> tuple[list[dict], dict[str, str]]:
|
||||
) -> tuple[list[_NodeType], dict[str, str]]:
|
||||
"""
|
||||
Convert API Based Extension to HTTP Request Node
|
||||
:param app_model: App instance
|
||||
|
|
@ -285,7 +291,7 @@ class WorkflowConverter:
|
|||
request_body_json = json.dumps(request_body)
|
||||
request_body_json = request_body_json.replace(r"\{\{", "{{").replace(r"\}\}", "}}")
|
||||
|
||||
http_request_node = {
|
||||
http_request_node: _NodeType = {
|
||||
"id": f"http_request_{index}",
|
||||
"position": None,
|
||||
"data": {
|
||||
|
|
@ -303,7 +309,7 @@ class WorkflowConverter:
|
|||
nodes.append(http_request_node)
|
||||
|
||||
# append code node for response body parsing
|
||||
code_node: dict[str, Any] = {
|
||||
code_node: _NodeType = {
|
||||
"id": f"code_{index}",
|
||||
"position": None,
|
||||
"data": {
|
||||
|
|
@ -326,7 +332,7 @@ class WorkflowConverter:
|
|||
|
||||
def _convert_to_knowledge_retrieval_node(
|
||||
self, new_app_mode: AppMode, dataset_config: DatasetEntity, model_config: ModelConfigEntity
|
||||
) -> dict | None:
|
||||
) -> _NodeType | None:
|
||||
"""
|
||||
Convert datasets to Knowledge Retrieval Node
|
||||
:param new_app_mode: new app mode
|
||||
|
|
@ -384,7 +390,7 @@ class WorkflowConverter:
|
|||
prompt_template: PromptTemplateEntity,
|
||||
file_upload: FileUploadConfig | None = None,
|
||||
external_data_variable_node_mapping: dict[str, str] | None = None,
|
||||
):
|
||||
) -> _NodeType:
|
||||
"""
|
||||
Convert to LLM Node
|
||||
:param original_app_mode: original app mode
|
||||
|
|
@ -561,7 +567,7 @@ class WorkflowConverter:
|
|||
|
||||
return template
|
||||
|
||||
def _convert_to_end_node(self):
|
||||
def _convert_to_end_node(self) -> _NodeType:
|
||||
"""
|
||||
Convert to End Node
|
||||
:return:
|
||||
|
|
@ -577,7 +583,7 @@ class WorkflowConverter:
|
|||
},
|
||||
}
|
||||
|
||||
def _convert_to_answer_node(self):
|
||||
def _convert_to_answer_node(self) -> _NodeType:
|
||||
"""
|
||||
Convert to Answer Node
|
||||
:return:
|
||||
|
|
@ -598,7 +604,7 @@ class WorkflowConverter:
|
|||
"""
|
||||
return {"id": f"{source}-{target}", "source": source, "target": target}
|
||||
|
||||
def _append_node(self, graph: dict, node: dict):
|
||||
def _append_node(self, graph: dict[str, Any], node: _NodeType):
|
||||
"""
|
||||
Append Node to Graph
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class TestTenantIsolatedTaskQueue:
|
|||
"""Test pushing empty task list."""
|
||||
sample_queue.push_tasks([])
|
||||
|
||||
mock_redis.lpush.assert_called_once_with("tenant_self_test-key_task_queue:tenant-123")
|
||||
mock_redis.lpush.assert_not_called()
|
||||
|
||||
@patch("core.rag.pipeline.queue.redis_client")
|
||||
def test_pull_tasks_default_count(self, mock_redis, sample_queue):
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ def test__convert_to_knowledge_retrieval_node_for_chatbot():
|
|||
node = WorkflowConverter()._convert_to_knowledge_retrieval_node(
|
||||
new_app_mode=new_app_mode, dataset_config=dataset_config, model_config=model_config
|
||||
)
|
||||
assert node is not None
|
||||
|
||||
assert node["data"]["type"] == "knowledge-retrieval"
|
||||
assert node["data"]["query_variable_selector"] == ["sys", "query"]
|
||||
|
|
@ -231,6 +232,7 @@ def test__convert_to_knowledge_retrieval_node_for_workflow_app():
|
|||
node = WorkflowConverter()._convert_to_knowledge_retrieval_node(
|
||||
new_app_mode=new_app_mode, dataset_config=dataset_config, model_config=model_config
|
||||
)
|
||||
assert node is not None
|
||||
|
||||
assert node["data"]["type"] == "knowledge-retrieval"
|
||||
assert node["data"]["query_variable_selector"] == ["start", dataset_config.retrieve_config.query_variable]
|
||||
|
|
|
|||
1598
api/uv.lock
1598
api/uv.lock
File diff suppressed because it is too large
Load Diff
|
|
@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -31,7 +31,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
|
||||
worker:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -58,7 +58,7 @@ services:
|
|||
# worker_beat service
|
||||
# Celery beat for scheduling periodic tasks.
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -76,7 +76,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.10.0-rc1
|
||||
image: langgenius/dify-web:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
@ -182,7 +182,7 @@ services:
|
|||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.4.0-local
|
||||
image: langgenius/dify-plugin-daemon:0.4.1-local
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
|
|||
|
|
@ -625,7 +625,7 @@ x-shared-env: &shared-api-worker-env
|
|||
services:
|
||||
# API service
|
||||
api:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -654,7 +654,7 @@ services:
|
|||
# worker service
|
||||
# The Celery worker for processing all queues (dataset, workflow, mail, etc.)
|
||||
worker:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -681,7 +681,7 @@ services:
|
|||
# worker_beat service
|
||||
# Celery beat for scheduling periodic tasks.
|
||||
worker_beat:
|
||||
image: langgenius/dify-api:1.10.0-rc1
|
||||
image: langgenius/dify-api:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
@ -699,7 +699,7 @@ services:
|
|||
|
||||
# Frontend web application.
|
||||
web:
|
||||
image: langgenius/dify-web:1.10.0-rc1
|
||||
image: langgenius/dify-web:1.10.0
|
||||
restart: always
|
||||
environment:
|
||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||
|
|
@ -805,7 +805,7 @@ services:
|
|||
|
||||
# plugin daemon
|
||||
plugin_daemon:
|
||||
image: langgenius/dify-plugin-daemon:0.4.0-local
|
||||
image: langgenius/dify-plugin-daemon:0.4.1-local
|
||||
restart: always
|
||||
environment:
|
||||
# Use the shared environment variables.
|
||||
|
|
|
|||
|
|
@ -37,18 +37,22 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
|||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const setAppDetail = useAppStore(state => state.setAppDetail)
|
||||
|
||||
const isWorkflowApp = appDetail?.mode === AppModeEnum.WORKFLOW
|
||||
const showMCPCard = isInPanel
|
||||
const showTriggerCard = isInPanel && appDetail?.mode === AppModeEnum.WORKFLOW
|
||||
const { data: currentWorkflow } = useAppWorkflow(appDetail?.mode === AppModeEnum.WORKFLOW ? appDetail.id : '')
|
||||
const hasTriggerNode = useMemo(() => {
|
||||
if (appDetail?.mode !== AppModeEnum.WORKFLOW)
|
||||
const showTriggerCard = isInPanel && isWorkflowApp
|
||||
const { data: currentWorkflow } = useAppWorkflow(isWorkflowApp ? appDetail.id : '')
|
||||
const hasTriggerNode = useMemo<boolean | null>(() => {
|
||||
if (!isWorkflowApp)
|
||||
return false
|
||||
const nodes = currentWorkflow?.graph?.nodes || []
|
||||
if (!currentWorkflow)
|
||||
return null
|
||||
const nodes = currentWorkflow.graph?.nodes || []
|
||||
return nodes.some((node) => {
|
||||
const nodeType = node.data?.type as BlockEnum | undefined
|
||||
return !!nodeType && isTriggerNode(nodeType)
|
||||
})
|
||||
}, [appDetail?.mode, currentWorkflow])
|
||||
}, [isWorkflowApp, currentWorkflow])
|
||||
const shouldRenderAppCards = !isWorkflowApp || hasTriggerNode === false
|
||||
|
||||
const updateAppDetail = async () => {
|
||||
try {
|
||||
|
|
@ -123,7 +127,7 @@ const CardView: FC<ICardViewProps> = ({ appId, isInPanel, className }) => {
|
|||
return (
|
||||
<div className={className || 'mb-6 grid w-full grid-cols-1 gap-6 xl:grid-cols-2'}>
|
||||
{
|
||||
!hasTriggerNode && (
|
||||
shouldRenderAppCards && (
|
||||
<>
|
||||
<AppCard
|
||||
appInfo={appDetail}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { getProcessedFilesFromResponse } from '@/app/components/base/file-upload
|
|||
import cn from '@/utils/classnames'
|
||||
import { noop } from 'lodash-es'
|
||||
import PromptLogModal from '../../base/prompt-log-modal'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
|
||||
type AppStoreState = ReturnType<typeof useAppStore.getState>
|
||||
type ConversationListItem = ChatConversationGeneralDetail | CompletionConversationGeneralDetail
|
||||
|
|
@ -779,15 +780,17 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
|||
}
|
||||
</div>
|
||||
{showMessageLogModal && (
|
||||
<MessageLogModal
|
||||
width={width}
|
||||
currentLogItem={currentLogItem}
|
||||
onCancel={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
defaultTab={currentLogModalActiveTab}
|
||||
/>
|
||||
<WorkflowContextProvider>
|
||||
<MessageLogModal
|
||||
width={width}
|
||||
currentLogItem={currentLogItem}
|
||||
onCancel={() => {
|
||||
setCurrentLogItem()
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
defaultTab={currentLogModalActiveTab}
|
||||
/>
|
||||
</WorkflowContextProvider>
|
||||
)}
|
||||
{!isChatMode && showPromptLogModal && (
|
||||
<PromptLogModal
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
|
||||
import { RiDiscordFill, RiDiscussLine, RiGithubFill } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type CustomLinkProps = {
|
||||
|
|
@ -38,6 +38,9 @@ const Footer = () => {
|
|||
<CustomLink href='https://discord.gg/FngNHpbcY7'>
|
||||
<RiDiscordFill className='h-5 w-5 text-text-tertiary' />
|
||||
</CustomLink>
|
||||
<CustomLink href='https://forum.dify.ai'>
|
||||
<RiDiscussLine className='h-5 w-5 text-text-tertiary' />
|
||||
</CustomLink>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="apps-2-line">
|
||||
<path id="Vector" d="M4.66602 7.6665C3.00916 7.6665 1.66602 6.32336 1.66602 4.6665C1.66602 3.00965 3.00916 1.6665 4.66602 1.6665C6.32287 1.6665 7.66602 3.00965 7.66602 4.6665C7.66602 6.32336 6.32287 7.6665 4.66602 7.6665ZM4.66602 14.3332C3.00916 14.3332 1.66602 12.99 1.66602 11.3332C1.66602 9.6763 3.00916 8.33317 4.66602 8.33317C6.32287 8.33317 7.66602 9.6763 7.66602 11.3332C7.66602 12.99 6.32287 14.3332 4.66602 14.3332ZM11.3327 7.6665C9.67582 7.6665 8.33268 6.32336 8.33268 4.6665C8.33268 3.00965 9.67582 1.6665 11.3327 1.6665C12.9895 1.6665 14.3327 3.00965 14.3327 4.6665C14.3327 6.32336 12.9895 7.6665 11.3327 7.6665ZM11.3327 14.3332C9.67582 14.3332 8.33268 12.99 8.33268 11.3332C8.33268 9.6763 9.67582 8.33317 11.3327 8.33317C12.9895 8.33317 14.3327 9.6763 14.3327 11.3332C14.3327 12.99 12.9895 14.3332 11.3327 14.3332ZM4.66602 6.33317C5.58649 6.33317 6.33268 5.58698 6.33268 4.6665C6.33268 3.74603 5.58649 2.99984 4.66602 2.99984C3.74554 2.99984 2.99935 3.74603 2.99935 4.6665C2.99935 5.58698 3.74554 6.33317 4.66602 6.33317ZM4.66602 12.9998C5.58649 12.9998 6.33268 12.2536 6.33268 11.3332C6.33268 10.4127 5.58649 9.6665 4.66602 9.6665C3.74554 9.6665 2.99935 10.4127 2.99935 11.3332C2.99935 12.2536 3.74554 12.9998 4.66602 12.9998ZM11.3327 6.33317C12.2531 6.33317 12.9993 5.58698 12.9993 4.6665C12.9993 3.74603 12.2531 2.99984 11.3327 2.99984C10.4122 2.99984 9.66602 3.74603 9.66602 4.6665C9.66602 5.58698 10.4122 6.33317 11.3327 6.33317ZM11.3327 12.9998C12.2531 12.9998 12.9993 12.2536 12.9993 11.3332C12.9993 10.4127 12.2531 9.6665 11.3327 9.6665C10.4122 9.6665 9.66602 10.4127 9.66602 11.3332C9.66602 12.2536 10.4122 12.9998 11.3327 12.9998Z" fill="#155EEF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.66602 14.3334C3.00916 14.3334 1.66602 12.9903 1.66602 11.3334C1.66602 9.67655 3.00916 8.33342 4.66602 8.33342C6.32287 8.33342 7.66602 9.67655 7.66602 11.3334C7.66602 12.9903 6.32287 14.3334 4.66602 14.3334ZM11.3327 7.66675C9.67582 7.66675 8.33268 6.3236 8.33268 4.66675C8.33268 3.00989 9.67582 1.66675 11.3327 1.66675C12.9895 1.66675 14.3327 3.00989 14.3327 4.66675C14.3327 6.3236 12.9895 7.66675 11.3327 7.66675ZM4.66602 13.0001C5.58649 13.0001 6.33268 12.2539 6.33268 11.3334C6.33268 10.4129 5.58649 9.66675 4.66602 9.66675C3.74554 9.66675 2.99935 10.4129 2.99935 11.3334C2.99935 12.2539 3.74554 13.0001 4.66602 13.0001ZM11.3327 6.33342C12.2531 6.33342 12.9993 5.58722 12.9993 4.66675C12.9993 3.74627 12.2531 3.00008 11.3327 3.00008C10.4122 3.00008 9.66602 3.74627 9.66602 4.66675C9.66602 5.58722 10.4122 6.33342 11.3327 6.33342ZM1.99935 5.33341C1.99935 3.49247 3.49174 2.00008 5.33268 2.00008H7.33268V3.33341H5.33268C4.22812 3.33341 3.33268 4.22885 3.33268 5.33341V7.33342H1.99935V5.33341ZM13.9993 8.66675H12.666V10.6667C12.666 11.7713 11.7706 12.6667 10.666 12.6667H8.66602V14.0001H10.666C12.5069 14.0001 13.9993 12.5077 13.9993 10.6667V8.66675Z" fill="#344054"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 2.66659H3.33333V13.3333H12.6667V5.33325H10V2.66659ZM2 1.99445C2 1.62929 2.29833 1.33325 2.66567 1.33325H10.6667L13.9998 4.66658L14 13.9949C14 14.3659 13.7034 14.6666 13.3377 14.6666H2.66227C2.29651 14.6666 2 14.3631 2 14.0054V1.99445ZM11.7713 7.99992L9.4142 10.3569L8.4714 9.41412L9.8856 7.99992L8.4714 6.58571L9.4142 5.6429L11.7713 7.99992ZM4.22877 7.99992L6.58579 5.6429L7.5286 6.58571L6.11438 7.99992L7.5286 9.41412L6.58579 10.3569L4.22877 7.99992Z" fill="#344054"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 586 B |
|
|
@ -0,0 +1,258 @@
|
|||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import React from 'react'
|
||||
|
||||
declare const require: any
|
||||
|
||||
type IconComponent = React.ComponentType<Record<string, unknown>>
|
||||
|
||||
type IconEntry = {
|
||||
name: string
|
||||
category: string
|
||||
path: string
|
||||
Component: IconComponent
|
||||
}
|
||||
|
||||
const iconContext = require.context('./src', true, /\.tsx$/)
|
||||
|
||||
const iconEntries: IconEntry[] = iconContext
|
||||
.keys()
|
||||
.filter((key: string) => !key.endsWith('.stories.tsx') && !key.endsWith('.spec.tsx'))
|
||||
.map((key: string) => {
|
||||
const mod = iconContext(key)
|
||||
const Component = mod.default as IconComponent | undefined
|
||||
if (!Component)
|
||||
return null
|
||||
|
||||
const relativePath = key.replace(/^\.\//, '')
|
||||
const path = `app/components/base/icons/src/${relativePath}`
|
||||
const parts = relativePath.split('/')
|
||||
const fileName = parts.pop() || ''
|
||||
const category = parts.length ? parts.join('/') : '(root)'
|
||||
const name = Component.displayName || fileName.replace(/\.tsx$/, '')
|
||||
|
||||
return {
|
||||
name,
|
||||
category,
|
||||
path,
|
||||
Component,
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as IconEntry[]
|
||||
|
||||
const sortedEntries = [...iconEntries].sort((a, b) => {
|
||||
if (a.category === b.category)
|
||||
return a.name.localeCompare(b.name)
|
||||
return a.category.localeCompare(b.category)
|
||||
})
|
||||
|
||||
const filterEntries = (entries: IconEntry[], query: string) => {
|
||||
const normalized = query.trim().toLowerCase()
|
||||
if (!normalized)
|
||||
return entries
|
||||
|
||||
return entries.filter(entry =>
|
||||
entry.name.toLowerCase().includes(normalized)
|
||||
|| entry.path.toLowerCase().includes(normalized)
|
||||
|| entry.category.toLowerCase().includes(normalized),
|
||||
)
|
||||
}
|
||||
|
||||
const groupByCategory = (entries: IconEntry[]) => entries.reduce((acc, entry) => {
|
||||
if (!acc[entry.category])
|
||||
acc[entry.category] = []
|
||||
|
||||
acc[entry.category].push(entry)
|
||||
return acc
|
||||
}, {} as Record<string, IconEntry[]>)
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
padding: 24,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 24,
|
||||
}
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
}
|
||||
|
||||
const controlsStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
flexWrap: 'wrap',
|
||||
}
|
||||
|
||||
const searchInputStyle: React.CSSProperties = {
|
||||
padding: '8px 12px',
|
||||
minWidth: 280,
|
||||
borderRadius: 6,
|
||||
border: '1px solid #d0d0d5',
|
||||
}
|
||||
|
||||
const toggleButtonStyle: React.CSSProperties = {
|
||||
padding: '8px 12px',
|
||||
borderRadius: 6,
|
||||
border: '1px solid #d0d0d5',
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
|
||||
const emptyTextStyle: React.CSSProperties = { color: '#5f5f66' }
|
||||
|
||||
const sectionStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
}
|
||||
|
||||
const gridStyle: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gap: 12,
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
|
||||
}
|
||||
|
||||
const cardStyle: React.CSSProperties = {
|
||||
border: '1px solid #e1e1e8',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
minHeight: 140,
|
||||
}
|
||||
|
||||
const previewBaseStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: 48,
|
||||
borderRadius: 6,
|
||||
}
|
||||
|
||||
const nameButtonBaseStyle: React.CSSProperties = {
|
||||
display: 'inline-flex',
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
font: 'inherit',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
fontWeight: 600,
|
||||
}
|
||||
|
||||
const PREVIEW_SIZE = 40
|
||||
|
||||
const IconGalleryStory = () => {
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [copiedPath, setCopiedPath] = React.useState<string | null>(null)
|
||||
const [previewTheme, setPreviewTheme] = React.useState<'light' | 'dark'>('light')
|
||||
|
||||
const filtered = React.useMemo(() => filterEntries(sortedEntries, query), [query])
|
||||
|
||||
const grouped = React.useMemo(() => groupByCategory(filtered), [filtered])
|
||||
|
||||
const categoryOrder = React.useMemo(
|
||||
() => Object.keys(grouped).sort((a, b) => a.localeCompare(b)),
|
||||
[grouped],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!copiedPath)
|
||||
return undefined
|
||||
|
||||
const timerId = window.setTimeout(() => {
|
||||
setCopiedPath(null)
|
||||
}, 1200)
|
||||
|
||||
return () => window.clearTimeout(timerId)
|
||||
}, [copiedPath])
|
||||
|
||||
const handleCopy = React.useCallback((text: string) => {
|
||||
navigator.clipboard?.writeText(text)
|
||||
.then(() => {
|
||||
setCopiedPath(text)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy icon path:', err)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<header style={headerStyle}>
|
||||
<h1 style={{ margin: 0 }}>Icon Gallery</h1>
|
||||
<p style={{ margin: 0, color: '#5f5f66' }}>
|
||||
Browse all icon components sourced from <code>app/components/base/icons/src</code>. Use the search bar
|
||||
to filter by name or path.
|
||||
</p>
|
||||
<div style={controlsStyle}>
|
||||
<input
|
||||
style={searchInputStyle}
|
||||
placeholder="Search icons"
|
||||
value={query}
|
||||
onChange={event => setQuery(event.target.value)}
|
||||
/>
|
||||
<span style={{ color: '#5f5f66' }}>{filtered.length} icons</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPreviewTheme(prev => (prev === 'light' ? 'dark' : 'light'))}
|
||||
style={toggleButtonStyle}
|
||||
>
|
||||
Toggle {previewTheme === 'light' ? 'dark' : 'light'} preview
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
{categoryOrder.length === 0 && (
|
||||
<p style={emptyTextStyle}>No icons match the current filter.</p>
|
||||
)}
|
||||
{categoryOrder.map(category => (
|
||||
<section key={category} style={sectionStyle}>
|
||||
<h2 style={{ margin: 0, fontSize: 18 }}>{category}</h2>
|
||||
<div style={gridStyle}>
|
||||
{grouped[category].map(entry => (
|
||||
<div key={entry.path} style={cardStyle}>
|
||||
<div
|
||||
style={{
|
||||
...previewBaseStyle,
|
||||
background: previewTheme === 'dark' ? '#1f2024' : '#fff',
|
||||
}}
|
||||
>
|
||||
<entry.Component style={{ width: PREVIEW_SIZE, height: PREVIEW_SIZE }} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleCopy(entry.path)}
|
||||
style={{
|
||||
...nameButtonBaseStyle,
|
||||
color: copiedPath === entry.path ? '#00754a' : '#24262c',
|
||||
}}
|
||||
>
|
||||
{copiedPath === entry.path ? 'Copied!' : entry.name}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta<typeof IconGalleryStory> = {
|
||||
title: 'Base/Icons/Icon Gallery',
|
||||
component: IconGalleryStory,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof IconGalleryStory>
|
||||
|
||||
export const All: Story = {
|
||||
render: () => <IconGalleryStory />,
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "apps-2-line"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"d": "M4.66602 7.6665C3.00916 7.6665 1.66602 6.32336 1.66602 4.6665C1.66602 3.00965 3.00916 1.6665 4.66602 1.6665C6.32287 1.6665 7.66602 3.00965 7.66602 4.6665C7.66602 6.32336 6.32287 7.6665 4.66602 7.6665ZM4.66602 14.3332C3.00916 14.3332 1.66602 12.99 1.66602 11.3332C1.66602 9.6763 3.00916 8.33317 4.66602 8.33317C6.32287 8.33317 7.66602 9.6763 7.66602 11.3332C7.66602 12.99 6.32287 14.3332 4.66602 14.3332ZM11.3327 7.6665C9.67582 7.6665 8.33268 6.32336 8.33268 4.6665C8.33268 3.00965 9.67582 1.6665 11.3327 1.6665C12.9895 1.6665 14.3327 3.00965 14.3327 4.6665C14.3327 6.32336 12.9895 7.6665 11.3327 7.6665ZM11.3327 14.3332C9.67582 14.3332 8.33268 12.99 8.33268 11.3332C8.33268 9.6763 9.67582 8.33317 11.3327 8.33317C12.9895 8.33317 14.3327 9.6763 14.3327 11.3332C14.3327 12.99 12.9895 14.3332 11.3327 14.3332ZM4.66602 6.33317C5.58649 6.33317 6.33268 5.58698 6.33268 4.6665C6.33268 3.74603 5.58649 2.99984 4.66602 2.99984C3.74554 2.99984 2.99935 3.74603 2.99935 4.6665C2.99935 5.58698 3.74554 6.33317 4.66602 6.33317ZM4.66602 12.9998C5.58649 12.9998 6.33268 12.2536 6.33268 11.3332C6.33268 10.4127 5.58649 9.6665 4.66602 9.6665C3.74554 9.6665 2.99935 10.4127 2.99935 11.3332C2.99935 12.2536 3.74554 12.9998 4.66602 12.9998ZM11.3327 6.33317C12.2531 6.33317 12.9993 5.58698 12.9993 4.6665C12.9993 3.74603 12.2531 2.99984 11.3327 2.99984C10.4122 2.99984 9.66602 3.74603 9.66602 4.6665C9.66602 5.58698 10.4122 6.33317 11.3327 6.33317ZM11.3327 12.9998C12.2531 12.9998 12.9993 12.2536 12.9993 11.3332C12.9993 10.4127 12.2531 9.6665 11.3327 9.6665C10.4122 9.6665 9.66602 10.4127 9.66602 11.3332C9.66602 12.2536 10.4122 12.9998 11.3327 12.9998Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Apps02"
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Apps02.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Apps02'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M4.66602 14.3334C3.00916 14.3334 1.66602 12.9903 1.66602 11.3334C1.66602 9.67655 3.00916 8.33342 4.66602 8.33342C6.32287 8.33342 7.66602 9.67655 7.66602 11.3334C7.66602 12.9903 6.32287 14.3334 4.66602 14.3334ZM11.3327 7.66675C9.67582 7.66675 8.33268 6.3236 8.33268 4.66675C8.33268 3.00989 9.67582 1.66675 11.3327 1.66675C12.9895 1.66675 14.3327 3.00989 14.3327 4.66675C14.3327 6.3236 12.9895 7.66675 11.3327 7.66675ZM4.66602 13.0001C5.58649 13.0001 6.33268 12.2539 6.33268 11.3334C6.33268 10.4129 5.58649 9.66675 4.66602 9.66675C3.74554 9.66675 2.99935 10.4129 2.99935 11.3334C2.99935 12.2539 3.74554 13.0001 4.66602 13.0001ZM11.3327 6.33342C12.2531 6.33342 12.9993 5.58722 12.9993 4.66675C12.9993 3.74627 12.2531 3.00008 11.3327 3.00008C10.4122 3.00008 9.66602 3.74627 9.66602 4.66675C9.66602 5.58722 10.4122 6.33342 11.3327 6.33342ZM1.99935 5.33341C1.99935 3.49247 3.49174 2.00008 5.33268 2.00008H7.33268V3.33341H5.33268C4.22812 3.33341 3.33268 4.22885 3.33268 5.33341V7.33342H1.99935V5.33341ZM13.9993 8.66675H12.666V10.6667C12.666 11.7713 11.7706 12.6667 10.666 12.6667H8.66602V14.0001H10.666C12.5069 14.0001 13.9993 12.5077 13.9993 10.6667V8.66675Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Exchange02"
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Exchange02.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Exchange02'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M10 2.66659H3.33333V13.3333H12.6667V5.33325H10V2.66659ZM2 1.99445C2 1.62929 2.29833 1.33325 2.66567 1.33325H10.6667L13.9998 4.66658L14 13.9949C14 14.3659 13.7034 14.6666 13.3377 14.6666H2.66227C2.29651 14.6666 2 14.3631 2 14.0054V1.99445ZM11.7713 7.99992L9.4142 10.3569L8.4714 9.41412L9.8856 7.99992L8.4714 6.58571L9.4142 5.6429L11.7713 7.99992ZM4.22877 7.99992L6.58579 5.6429L7.5286 6.58571L6.11438 7.99992L7.5286 9.41412L6.58579 10.3569L4.22877 7.99992Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "FileCode"
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './FileCode.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'FileCode'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
export { default as Apps02 } from './Apps02'
|
||||
export { default as BubbleX } from './BubbleX'
|
||||
export { default as Colors } from './Colors'
|
||||
export { default as DragHandle } from './DragHandle'
|
||||
export { default as Env } from './Env'
|
||||
export { default as Exchange02 } from './Exchange02'
|
||||
export { default as FileCode } from './FileCode'
|
||||
export { default as GlobalVariable } from './GlobalVariable'
|
||||
export { default as Icon3Dots } from './Icon3Dots'
|
||||
export { default as LongArrowLeft } from './LongArrowLeft'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useStore } from '@/app/components/app/store'
|
|||
import type { WorkflowRunDetailResponse } from '@/models/log'
|
||||
import type { NodeTracing, NodeTracingListResponse } from '@/types/workflow'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { WorkflowContextProvider } from '@/app/components/workflow/context'
|
||||
|
||||
const SAMPLE_APP_DETAIL = {
|
||||
id: 'app-demo-1',
|
||||
|
|
@ -143,10 +144,12 @@ const MessageLogPreview = (props: MessageLogModalProps) => {
|
|||
|
||||
return (
|
||||
<div className="relative min-h-[640px] w-full bg-background-default-subtle p-6">
|
||||
<MessageLogModal
|
||||
{...props}
|
||||
currentLogItem={mockCurrentLogItem}
|
||||
/>
|
||||
<WorkflowContextProvider>
|
||||
<MessageLogModal
|
||||
{...props}
|
||||
currentLogItem={mockCurrentLogItem}
|
||||
/>
|
||||
</WorkflowContextProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
|
|
@ -1,80 +0,0 @@
|
|||
'use client'
|
||||
import { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useMount } from 'ahooks'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Apps02 } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import { useStore as useLabelStore } from '@/app/components/tools/labels/store'
|
||||
import { fetchLabelList } from '@/service/tools'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onSelect: (type: string) => void
|
||||
}
|
||||
|
||||
const Icon = ({ svgString, active }: { svgString: string; active: boolean }) => {
|
||||
const svgRef = useRef<SVGSVGElement | null>(null)
|
||||
const SVGParser = (svg: string) => {
|
||||
if (!svg)
|
||||
return null
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(svg, 'image/svg+xml')
|
||||
return doc.documentElement
|
||||
}
|
||||
useMount(() => {
|
||||
const svgElement = SVGParser(svgString)
|
||||
if (svgRef.current && svgElement)
|
||||
svgRef.current.appendChild(svgElement)
|
||||
})
|
||||
return <svg className={cn('h-4 w-4 text-gray-700', active && '!text-primary-600')} ref={svgRef} />
|
||||
}
|
||||
|
||||
const Category = ({
|
||||
value,
|
||||
onSelect,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const language = getLanguage(locale)
|
||||
const labelList = useLabelStore(s => s.labelList)
|
||||
const setLabelList = useLabelStore(s => s.setLabelList)
|
||||
|
||||
useMount(() => {
|
||||
fetchLabelList().then((res) => {
|
||||
setLabelList(res)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<div className='px-3 py-0.5 text-xs font-medium leading-[18px] text-gray-500'>{t('tools.addToolModal.category').toLocaleUpperCase()}</div>
|
||||
<div className={cn('mb-0.5 flex cursor-pointer items-center rounded-lg p-1 pl-3 text-sm leading-5 text-gray-700 hover:bg-white', value === '' && '!bg-white font-medium !text-primary-600')} onClick={() => onSelect('')}>
|
||||
<Apps02 className='mr-2 h-4 w-4 shrink-0' />
|
||||
{t('tools.type.all')}
|
||||
</div>
|
||||
{labelList.map((label) => {
|
||||
const labelText = typeof label.label === 'string'
|
||||
? label.label
|
||||
: (label.label ? renderI18nObject(label.label, language) : '')
|
||||
return (
|
||||
<div
|
||||
key={label.name}
|
||||
title={labelText}
|
||||
className={cn('mb-0.5 flex cursor-pointer items-center overflow-hidden truncate rounded-lg p-1 pl-3 text-sm leading-5 text-gray-700 hover:bg-white', value === label.name && '!bg-white font-medium !text-primary-600')}
|
||||
onClick={() => onSelect(label.name)}
|
||||
>
|
||||
<div className='mr-2 h-4 w-4 shrink-0'>
|
||||
<Icon active={value === label.name} svgString={label.icon || ''} />
|
||||
</div>
|
||||
{labelText}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Category
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -1,258 +0,0 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
RiAddLine,
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react'
|
||||
import { useMount } from 'ahooks'
|
||||
import type { Collection, CustomCollectionBackend, Tool } from '../types'
|
||||
import type { CollectionType } from '../types'
|
||||
import Type from './type'
|
||||
import Category from './category'
|
||||
import Tools from './tools'
|
||||
import cn from '@/utils/classnames'
|
||||
import { basePath } from '@/utils/var'
|
||||
import I18n from '@/context/i18n'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Input from '@/app/components/base/input'
|
||||
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import {
|
||||
createCustomCollection,
|
||||
fetchAllBuiltInTools,
|
||||
fetchAllCustomTools,
|
||||
fetchAllWorkflowTools,
|
||||
removeBuiltInToolCredential,
|
||||
updateBuiltInToolCredential,
|
||||
} from '@/service/tools'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import type { ModelConfig } from '@/models/debug'
|
||||
|
||||
type Props = {
|
||||
onHide: () => void
|
||||
}
|
||||
// Add and Edit
|
||||
const AddToolModal: FC<Props> = ({
|
||||
onHide,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const [currentType, setCurrentType] = useState('builtin')
|
||||
const [currentCategory, setCurrentCategory] = useState('')
|
||||
const [keywords, setKeywords] = useState<string>('')
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
}
|
||||
const isMatchingKeywords = (text: string, keywords: string) => {
|
||||
return text.toLowerCase().includes(keywords.toLowerCase())
|
||||
}
|
||||
const [toolList, setToolList] = useState<ToolWithProvider[]>([])
|
||||
const [listLoading, setListLoading] = useState(true)
|
||||
const getAllTools = async () => {
|
||||
setListLoading(true)
|
||||
const buildInTools = await fetchAllBuiltInTools()
|
||||
if (basePath) {
|
||||
buildInTools.forEach((item) => {
|
||||
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
|
||||
item.icon = `${basePath}${item.icon}`
|
||||
})
|
||||
}
|
||||
const customTools = await fetchAllCustomTools()
|
||||
const workflowTools = await fetchAllWorkflowTools()
|
||||
const mergedToolList = [
|
||||
...buildInTools,
|
||||
...customTools,
|
||||
...workflowTools.filter((toolWithProvider) => {
|
||||
return !toolWithProvider.tools.some((tool) => {
|
||||
return !!tool.parameters.find(item => item.name === '__image')
|
||||
})
|
||||
}),
|
||||
]
|
||||
setToolList(mergedToolList)
|
||||
setListLoading(false)
|
||||
}
|
||||
const filteredList = useMemo(() => {
|
||||
return toolList.filter((toolWithProvider) => {
|
||||
if (currentType === 'all')
|
||||
return true
|
||||
else
|
||||
return toolWithProvider.type === currentType
|
||||
}).filter((toolWithProvider) => {
|
||||
if (!currentCategory)
|
||||
return true
|
||||
else
|
||||
return toolWithProvider.labels.includes(currentCategory)
|
||||
}).filter((toolWithProvider) => {
|
||||
return (
|
||||
isMatchingKeywords(toolWithProvider.name, keywords)
|
||||
|| toolWithProvider.tools.some((tool) => {
|
||||
return Object.values(tool.label).some((label) => {
|
||||
return isMatchingKeywords(label, keywords)
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
}, [currentType, currentCategory, toolList, keywords])
|
||||
|
||||
const {
|
||||
modelConfig,
|
||||
setModelConfig,
|
||||
} = useContext(ConfigContext)
|
||||
|
||||
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)
|
||||
const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
|
||||
await createCustomCollection(data)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
setIsShowEditCustomCollectionModal(false)
|
||||
getAllTools()
|
||||
}
|
||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
const [collection, setCollection] = useState<Collection>()
|
||||
const toolSelectHandle = (collection: Collection, tool: Tool) => {
|
||||
const parameters: Record<string, string> = {}
|
||||
if (tool.parameters) {
|
||||
tool.parameters.forEach((item) => {
|
||||
parameters[item.name] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
||||
draft.agentConfig.tools.push({
|
||||
provider_id: collection.id || collection.name,
|
||||
provider_type: collection.type as CollectionType,
|
||||
provider_name: collection.name,
|
||||
tool_name: tool.name,
|
||||
tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')],
|
||||
tool_parameters: parameters,
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
setModelConfig(nexModelConfig)
|
||||
}
|
||||
const authSelectHandle = (provider: Collection) => {
|
||||
setCollection(provider)
|
||||
setShowSettingAuth(true)
|
||||
}
|
||||
const updateBuiltinAuth = async (value: Record<string, any>) => {
|
||||
if (!collection)
|
||||
return
|
||||
await updateBuiltInToolCredential(collection.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await getAllTools()
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
const removeBuiltinAuth = async () => {
|
||||
if (!collection)
|
||||
return
|
||||
await removeBuiltInToolCredential(collection.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await getAllTools()
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
getAllTools()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
isOpen
|
||||
mask
|
||||
clickOutsideNotOpen
|
||||
onClose={onHide}
|
||||
footer={null}
|
||||
panelClassName={cn('mx-2 mb-3 mt-16 rounded-xl !p-0 sm:mr-2', 'mt-2 !w-[640px]', '!max-w-[640px]')}
|
||||
>
|
||||
<div
|
||||
className='flex w-full rounded-xl border-[0.5px] border-gray-200 bg-white shadow-xl'
|
||||
style={{
|
||||
height: 'calc(100vh - 16px)',
|
||||
}}
|
||||
>
|
||||
<div className='relative w-[200px] shrink-0 overflow-y-auto rounded-l-xl border-r-[0.5px] border-black/2 bg-gray-100 pb-3'>
|
||||
<div className='sticky left-0 right-0 top-0'>
|
||||
<div className='text-md sticky left-0 right-0 top-0 px-5 py-3 font-semibold text-gray-900'>{t('tools.addTool')}</div>
|
||||
<div className='px-3 pb-4 pt-2'>
|
||||
<Button variant='primary' className='w-[176px]' onClick={() => setIsShowEditCustomCollectionModal(true)}>
|
||||
<RiAddLine className='mr-1 h-4 w-4' />
|
||||
{t('tools.createCustomTool')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-2 py-1'>
|
||||
<Type value={currentType} onSelect={setCurrentType} />
|
||||
<Category value={currentCategory} onSelect={setCurrentCategory} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative grow overflow-y-auto rounded-r-xl bg-white'>
|
||||
<div className='sticky left-0 right-0 top-0 z-10 flex items-center gap-1 bg-white p-2'>
|
||||
<div className='grow'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
value={keywords}
|
||||
onChange={e => handleKeywordsChange(e.target.value)}
|
||||
onClear={() => handleKeywordsChange('')}
|
||||
/>
|
||||
</div>
|
||||
<div className='ml-2 mr-1 h-4 w-[1px] bg-gray-200'></div>
|
||||
<div className='cursor-pointer p-2' onClick={onHide}>
|
||||
<RiCloseLine className='h-4 w-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
{listLoading && (
|
||||
<div className='flex h-[200px] items-center justify-center bg-white'>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{!listLoading && (
|
||||
<Tools
|
||||
showWorkflowEmpty={currentType === 'workflow'}
|
||||
tools={filteredList}
|
||||
addedTools={(modelConfig?.agentConfig?.tools as any) || []}
|
||||
onSelect={toolSelectHandle}
|
||||
onAuthSetup={authSelectHandle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
{isShowEditCollectionToolModal && (
|
||||
<EditCustomToolModal
|
||||
positionLeft
|
||||
payload={null}
|
||||
onHide={() => setIsShowEditCustomCollectionModal(false)}
|
||||
onAdd={doCreateCustomToolCollection}
|
||||
/>
|
||||
)}
|
||||
{showSettingAuth && collection && (
|
||||
<ConfigCredential
|
||||
collection={collection}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={updateBuiltinAuth}
|
||||
onRemove={removeBuiltinAuth}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
export default React.memo(AddToolModal)
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { basePath } from '@/utils/var'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAddLine,
|
||||
} from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { Check } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Tag01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useStore as useLabelStore } from '@/app/components/tools/labels/store'
|
||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { AgentTool } from '@/types/app'
|
||||
import { MAX_TOOLS_NUM } from '@/config'
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
|
||||
const resolveI18nText = (value: TypeWithI18N | string | undefined, language: string): string => {
|
||||
if (!value)
|
||||
return ''
|
||||
return typeof value === 'string' ? value : renderI18nObject(value, language)
|
||||
}
|
||||
|
||||
type ToolsProps = {
|
||||
showWorkflowEmpty: boolean
|
||||
tools: ToolWithProvider[]
|
||||
addedTools: AgentTool[]
|
||||
onSelect: (provider: ToolWithProvider, tool: Tool) => void
|
||||
onAuthSetup: (provider: ToolWithProvider) => void
|
||||
}
|
||||
const Blocks = ({
|
||||
showWorkflowEmpty,
|
||||
tools,
|
||||
addedTools,
|
||||
onSelect,
|
||||
onAuthSetup,
|
||||
}: ToolsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const labelList = useLabelStore(s => s.labelList)
|
||||
const addable = addedTools.length < MAX_TOOLS_NUM
|
||||
|
||||
const renderGroup = useCallback((toolWithProvider: ToolWithProvider) => {
|
||||
const list = toolWithProvider.tools
|
||||
const needAuth = toolWithProvider.allow_delete && !toolWithProvider.is_team_authorization && toolWithProvider.type === CollectionType.builtIn
|
||||
|
||||
return (
|
||||
<div
|
||||
key={toolWithProvider.id}
|
||||
className='group mb-1 last-of-type:mb-0'
|
||||
>
|
||||
<div className='flex h-[22px] w-full items-center justify-between pl-3 pr-1 text-xs font-medium text-gray-500'>
|
||||
{resolveI18nText(toolWithProvider.label, language)}
|
||||
<a className='hidden cursor-pointer items-center group-hover:flex' href={`${basePath}/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 h-3 w-3' /></a>
|
||||
</div>
|
||||
{list.map((tool) => {
|
||||
const labelContent = (() => {
|
||||
if (!tool.labels)
|
||||
return ''
|
||||
return tool.labels.map((name) => {
|
||||
const label = labelList.find(item => item.name === name)
|
||||
return resolveI18nText(label?.label, language)
|
||||
}).filter(Boolean).join(', ')
|
||||
})()
|
||||
const added = !!addedTools?.find(v => v.provider_id === toolWithProvider.id && v.provider_type === toolWithProvider.type && v.tool_name === tool.name)
|
||||
return (
|
||||
<Tooltip
|
||||
key={tool.name}
|
||||
position='bottom'
|
||||
popupClassName='!p-0 !px-3 !py-2.5 !w-[210px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !bg-transparent !rounded-xl !shadow-lg translate-x-[108px]'
|
||||
popupContent={(
|
||||
<div>
|
||||
<BlockIcon
|
||||
size='md'
|
||||
className='mb-2'
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={toolWithProvider.icon}
|
||||
/>
|
||||
<div className='mb-1 text-sm leading-5 text-gray-900'>{resolveI18nText(tool.label, language)}</div>
|
||||
<div className='text-xs leading-[18px] text-gray-700'>{resolveI18nText(tool.description, language)}</div>
|
||||
{tool.labels?.length > 0 && (
|
||||
<div className='mt-1 flex shrink-0 items-center'>
|
||||
<div className='relative flex w-full items-center gap-1 rounded-md py-1 text-gray-500' title={labelContent}>
|
||||
<Tag01 className='h-3 w-3 shrink-0 text-gray-500' />
|
||||
<div className='grow truncate text-start text-xs font-normal leading-[18px]'>{labelContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className='group/item flex h-8 w-full cursor-pointer items-center rounded-lg pl-3 pr-1 hover:bg-gray-50'>
|
||||
<BlockIcon
|
||||
className={cn('mr-2 shrink-0', needAuth && 'opacity-30')}
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={toolWithProvider.icon}
|
||||
/>
|
||||
<div className={cn('grow truncate text-sm text-gray-900', needAuth && 'opacity-30')}>{resolveI18nText(tool.label, language)}</div>
|
||||
{!needAuth && added && (
|
||||
<div className='flex items-center gap-1 rounded-[6px] border border-gray-100 bg-white px-2 py-[3px] text-xs font-medium leading-[18px] text-gray-300'>
|
||||
<Check className='h-3 w-3' />
|
||||
{t('tools.addToolModal.added').toLocaleUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
{!needAuth && !added && addable && (
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
size='small'
|
||||
className={cn('hidden shrink-0 items-center group-hover/item:flex')}
|
||||
onClick={() => onSelect(toolWithProvider, tool)}
|
||||
>
|
||||
<RiAddLine className='h-3 w-3' />
|
||||
{t('tools.addToolModal.add').toLocaleUpperCase()}
|
||||
</Button>
|
||||
)}
|
||||
{needAuth && (
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
size='small'
|
||||
className={cn('hidden shrink-0 group-hover/item:flex')}
|
||||
onClick={() => onAuthSetup(toolWithProvider)}
|
||||
>{t('tools.auth.setup')}</Button>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}, [addable, language, t, labelList, addedTools, onAuthSetup, onSelect])
|
||||
|
||||
return (
|
||||
<div className='max-w-[440px] p-1 pb-6'>
|
||||
{!tools.length && !showWorkflowEmpty && (
|
||||
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
)}
|
||||
{!tools.length && showWorkflowEmpty && (
|
||||
<div className='pt-[280px]'>
|
||||
<Empty />
|
||||
</div>
|
||||
)}
|
||||
{!!tools.length && tools.map(renderGroup)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Blocks)
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
'use client'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Exchange02, FileCode } from '@/app/components/base/icons/src/vender/line/others'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onSelect: (type: string) => void
|
||||
}
|
||||
|
||||
const Types = ({
|
||||
value,
|
||||
onSelect,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<div className={cn('mb-0.5 flex cursor-pointer items-center rounded-lg p-1 pl-3 text-sm leading-5 hover:bg-white', value === 'builtin' && '!bg-white font-medium')} onClick={() => onSelect('builtin')}>
|
||||
<div className="mr-2 h-4 w-4 shrink-0 bg-[url('~@/app/components/tools/add-tool-modal/D.png')] bg-cover bg-no-repeat" />
|
||||
<span className={cn('text-gray-700', value === 'builtin' && '!text-primary-600')}>{t('tools.type.builtIn')}</span>
|
||||
</div>
|
||||
<div className={cn('mb-0.5 flex cursor-pointer items-center rounded-lg p-1 pl-3 text-sm leading-5 text-gray-700 hover:bg-white', value === 'api' && '!bg-white font-medium !text-primary-600')} onClick={() => onSelect('api')}>
|
||||
<FileCode className='mr-2 h-4 w-4 shrink-0' />
|
||||
{t('tools.type.custom')}
|
||||
</div>
|
||||
<div className={cn('mb-0.5 flex cursor-pointer items-center rounded-lg p-1 pl-3 text-sm leading-5 text-gray-700 hover:bg-white', value === 'workflow' && '!bg-white font-medium !text-primary-600')} onClick={() => onSelect('workflow')}>
|
||||
<Exchange02 className='mr-2 h-4 w-4 shrink-0' />
|
||||
{t('tools.type.workflow')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Types
|
||||
|
|
@ -11,7 +11,7 @@ import Input from '@/app/components/base/input'
|
|||
import ProviderDetail from '@/app/components/tools/provider/detail'
|
||||
import Empty from '@/app/components/plugins/marketplace/empty'
|
||||
import CustomCreateCard from '@/app/components/tools/provider/custom-create-card'
|
||||
import WorkflowToolEmpty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import WorkflowToolEmpty from '@/app/components/tools/provider/empty'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||
|
|
|
|||
|
|
@ -17,9 +17,17 @@ export const useAvailableNodesMetaData = () => {
|
|||
const isChatMode = useIsChatMode()
|
||||
const docLink = useDocLink()
|
||||
|
||||
const startNodeMetaData = useMemo(() => ({
|
||||
...StartDefault,
|
||||
metaData: {
|
||||
...StartDefault.metaData,
|
||||
isUndeletable: isChatMode, // start node is undeletable in chat mode, @use-nodes-interactions: handleNodeDelete function
|
||||
},
|
||||
}), [isChatMode])
|
||||
|
||||
const mergedNodesMetaData = useMemo(() => [
|
||||
...WORKFLOW_COMMON_NODES,
|
||||
StartDefault,
|
||||
startNodeMetaData,
|
||||
...(
|
||||
isChatMode
|
||||
? [AnswerDefault]
|
||||
|
|
@ -30,7 +38,7 @@ export const useAvailableNodesMetaData = () => {
|
|||
TriggerPluginDefault,
|
||||
]
|
||||
),
|
||||
], [isChatMode])
|
||||
], [isChatMode, startNodeMetaData])
|
||||
|
||||
const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => {
|
||||
const { metaData } = node
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import IndexBar, { groupItems } from './index-bar'
|
|||
import type { ToolDefaultValue, ToolValue } from './types'
|
||||
import type { ToolTypeEnum } from './types'
|
||||
import { ViewType } from './view-type-select'
|
||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import Empty from '@/app/components/tools/provider/empty'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import ToolListTreeView from './tool/tool-list-tree-view/list'
|
||||
import ToolListFlatView from './tool/tool-list-flat-view/list'
|
||||
|
|
|
|||
|
|
@ -249,6 +249,8 @@ export const useChecklistBeforePublish = () => {
|
|||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
const appMode = useAppStore.getState().appDetail?.mode
|
||||
const shouldCheckStartNode = appMode === AppModeEnum.WORKFLOW || appMode === AppModeEnum.ADVANCED_CHAT
|
||||
|
||||
const getCheckData = useCallback((data: CommonNodeType<{}>, datasets: DataSet[]) => {
|
||||
let checkData = data
|
||||
|
|
@ -366,17 +368,22 @@ export const useChecklistBeforePublish = () => {
|
|||
}
|
||||
}
|
||||
|
||||
if (!validNodes.find(n => n.id === node.id)) {
|
||||
const isStartNodeMeta = nodesExtraData?.[node.data.type as BlockEnum]?.metaData.isStart ?? false
|
||||
const canSkipConnectionCheck = shouldCheckStartNode ? isStartNodeMeta : true
|
||||
const isUnconnected = !validNodes.find(n => n.id === node.id)
|
||||
|
||||
if (isUnconnected && !canSkipConnectionCheck) {
|
||||
notify({ type: 'error', message: `[${node.data.title}] ${t('workflow.common.needConnectTip')}` })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const startNodesFiltered = nodes.filter(node => START_NODE_TYPES.includes(node.data.type as BlockEnum))
|
||||
|
||||
if (startNodesFiltered.length === 0) {
|
||||
notify({ type: 'error', message: t('workflow.common.needStartNode') })
|
||||
return false
|
||||
if (shouldCheckStartNode) {
|
||||
const startNodesFiltered = nodes.filter(node => START_NODE_TYPES.includes(node.data.type as BlockEnum))
|
||||
if (startNodesFiltered.length === 0) {
|
||||
notify({ type: 'error', message: t('workflow.common.needStartNode') })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isRequiredNodesType = Object.keys(nodesExtraData!).filter((key: any) => (nodesExtraData as any)[key].metaData.isRequired)
|
||||
|
|
@ -391,7 +398,7 @@ export const useChecklistBeforePublish = () => {
|
|||
}
|
||||
|
||||
return true
|
||||
}, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, workflowStore, buildInTools, customTools, workflowTools])
|
||||
}, [store, notify, t, language, nodesExtraData, strategyProviders, updateDatasetsDetail, getCheckData, workflowStore, buildInTools, customTools, workflowTools, shouldCheckStartNode])
|
||||
|
||||
return {
|
||||
handleCheckBeforePublish,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const AgentNode: FC<NodeProps<AgentNodeType>> = (props) => {
|
|||
})
|
||||
return tools
|
||||
}, [currentStrategy?.parameters, inputs.agent_parameters])
|
||||
return <div className='mb-1 space-y-1 px-3 py-1'>
|
||||
return <div className='mb-1 space-y-1 px-3'>
|
||||
{inputs.agent_strategy_name
|
||||
? <SettingItem
|
||||
label={t('workflow.nodes.agent.strategy.shortLabel')}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const NodeComponent: FC<NodeProps<DocExtractorNodeType>> = ({
|
|||
const isSystem = isSystemVar(variable)
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
return (
|
||||
<div className='relative px-3'>
|
||||
<div className='relative mb-1 px-3 py-1'>
|
||||
<div className='system-2xs-medium-uppercase mb-1 text-text-tertiary'>{t(`${i18nPrefix}.inputVar`)}</div>
|
||||
<VariableLabelInNode
|
||||
variables={variable}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Werkzeuge',
|
||||
createCustomTool: 'Eigenes Werkzeug erstellen',
|
||||
type: {
|
||||
all: 'Alle',
|
||||
builtIn: 'Integriert',
|
||||
custom: 'Benutzerdefiniert',
|
||||
workflow: 'Arbeitsablauf',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'Nach der Konfiguration der Anmeldeinformationen können alle Mitglieder im Arbeitsbereich dieses Werkzeug beim Orchestrieren von Anwendungen nutzen.',
|
||||
},
|
||||
includeToolNum: '{{num}} Werkzeuge inkludiert',
|
||||
addTool: 'Werkzeug hinzufügen',
|
||||
createTool: {
|
||||
title: 'Eigenes Werkzeug erstellen',
|
||||
editAction: 'Konfigurieren',
|
||||
|
|
@ -143,9 +141,7 @@ const translation = {
|
|||
addToolModal: {
|
||||
type: 'Art',
|
||||
category: 'Kategorie',
|
||||
add: 'hinzufügen',
|
||||
added: 'zugefügt',
|
||||
manageInTools: 'Verwalten in Tools',
|
||||
custom: {
|
||||
title: 'Kein benutzerdefiniertes Werkzeug verfügbar',
|
||||
tip: 'Benutzerdefiniertes Werkzeug erstellen',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Create Custom Tool',
|
||||
customToolTip: 'Learn more about Dify custom tools',
|
||||
type: {
|
||||
all: 'All',
|
||||
builtIn: 'Tools',
|
||||
custom: 'Custom',
|
||||
workflow: 'Workflow',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'After configuring credentials, all members within the workspace can use this tool when orchestrating applications.',
|
||||
},
|
||||
includeToolNum: '{{num}} {{action}} included',
|
||||
addTool: 'Add Tool',
|
||||
addToolModal: {
|
||||
type: 'type',
|
||||
category: 'category',
|
||||
add: 'add',
|
||||
added: 'added',
|
||||
manageInTools: 'Manage in Tools',
|
||||
custom: {
|
||||
title: 'No custom tool available',
|
||||
tip: 'Create a custom tool',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Crear Herramienta Personalizada',
|
||||
customToolTip: 'Aprende más sobre las herramientas personalizadas de Dify',
|
||||
type: {
|
||||
all: 'Todas',
|
||||
builtIn: 'Incorporadas',
|
||||
custom: 'Personalizadas',
|
||||
workflow: 'Flujo de Trabajo',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'Después de configurar las credenciales, todos los miembros dentro del espacio de trabajo pueden usar esta herramienta al orquestar aplicaciones.',
|
||||
},
|
||||
includeToolNum: '{{num}} herramientas incluidas',
|
||||
addTool: 'Agregar Herramienta',
|
||||
addToolModal: {
|
||||
type: 'tipo',
|
||||
category: 'categoría',
|
||||
add: 'agregar',
|
||||
added: 'agregada',
|
||||
manageInTools: 'Administrar en Herramientas',
|
||||
custom: {
|
||||
title: 'No hay herramienta personalizada disponible',
|
||||
tip: 'Crear una herramienta personalizada',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'ایجاد ابزار سفارشی',
|
||||
customToolTip: 'بیشتر در مورد ابزارهای سفارشی Dify بیاموزید',
|
||||
type: {
|
||||
all: 'همه',
|
||||
builtIn: 'سفارشی شده',
|
||||
custom: 'سفارشی',
|
||||
workflow: 'جریان کار',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'پس از پیکربندی اعتبارنامهها، همه اعضای موجود در فضای کاری میتوانند از این ابزار هنگام هماهنگی برنامهها استفاده کنند.',
|
||||
},
|
||||
includeToolNum: '{{num}} ابزار شامل شد',
|
||||
addTool: 'افزودن ابزار',
|
||||
addToolModal: {
|
||||
type: 'نوع',
|
||||
category: 'دستهبندی',
|
||||
add: 'افزودن',
|
||||
added: 'افزوده شد',
|
||||
manageInTools: 'مدیریت در ابزارها',
|
||||
custom: {
|
||||
title: 'هیچ ابزار سفارشی موجود نیست',
|
||||
tip: 'یک ابزار سفارشی ایجاد کنید',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Outils',
|
||||
createCustomTool: 'Créer un Outil Personnalisé',
|
||||
type: {
|
||||
all: 'Tout',
|
||||
builtIn: 'Intégré',
|
||||
custom: 'Personnalisé',
|
||||
workflow: 'Flux de travail',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'Après avoir configuré les identifiants, tous les membres de l\'espace de travail peuvent utiliser cet outil lors de l\'orchestration des applications.',
|
||||
},
|
||||
includeToolNum: '{{num}} outils inclus',
|
||||
addTool: 'Ajouter un outil',
|
||||
createTool: {
|
||||
title: 'Créer un Outil Personnalisé',
|
||||
editAction: 'Configurer',
|
||||
|
|
@ -143,9 +141,7 @@ const translation = {
|
|||
addToolModal: {
|
||||
type: 'type',
|
||||
added: 'supplémentaire',
|
||||
add: 'ajouter',
|
||||
category: 'catégorie',
|
||||
manageInTools: 'Gérer dans Outils',
|
||||
custom: {
|
||||
title: 'Aucun outil personnalisé disponible',
|
||||
tip: 'Créer un outil personnalisé',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'कस्टम उपकरण बनाएं',
|
||||
customToolTip: 'Dify कस्टम उपकरणों के बारे में और जानें',
|
||||
type: {
|
||||
all: 'सभी',
|
||||
builtIn: 'निर्मित',
|
||||
custom: 'कस्टम',
|
||||
workflow: 'कार्यप्रवाह',
|
||||
|
|
@ -22,13 +21,10 @@ const translation = {
|
|||
'प्रमाणिकरण कॉन्फ़िगर करने के बाद, कार्यस्थान के सभी सदस्य इस उपकरण का उपयोग कर सकेंगे।',
|
||||
},
|
||||
includeToolNum: '{{num}} उपकरण शामिल हैं',
|
||||
addTool: 'उपकरण जोड़ें',
|
||||
addToolModal: {
|
||||
type: 'प्रकार',
|
||||
category: 'श्रेणी',
|
||||
add: 'जोड़ें',
|
||||
added: 'जोड़ा गया',
|
||||
manageInTools: 'उपकरणों में प्रबंधित करें',
|
||||
custom: {
|
||||
title: 'कोई कस्टम टूल उपलब्ध नहीं है',
|
||||
tip: 'एक कस्टम टूल बनाएं',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const translation = {
|
||||
type: {
|
||||
all: 'Semua',
|
||||
workflow: 'Alur Kerja',
|
||||
builtIn: 'Perkakas',
|
||||
custom: 'Adat',
|
||||
|
|
@ -35,8 +34,6 @@ const translation = {
|
|||
category: 'golongan',
|
||||
type: 'jenis',
|
||||
added: 'Ditambahkan',
|
||||
add: 'tambah',
|
||||
manageInTools: 'Kelola di Alat',
|
||||
},
|
||||
createTool: {
|
||||
exampleOptions: {
|
||||
|
|
@ -240,7 +237,6 @@ const translation = {
|
|||
title: 'Perkakas',
|
||||
createCustomTool: 'Buat Alat Kustom',
|
||||
customToolTip: 'Pelajari alat kustom Dify lebih lanjut',
|
||||
addTool: 'Tambahkan Alat',
|
||||
author: 'Oleh',
|
||||
copyToolName: 'Salin Nama',
|
||||
howToGet: 'Cara mendapatkan',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Crea Strumento Personalizzato',
|
||||
customToolTip: 'Scopri di più sugli strumenti personalizzati di Dify',
|
||||
type: {
|
||||
all: 'Tutti',
|
||||
builtIn: 'Integrato',
|
||||
custom: 'Personalizzato',
|
||||
workflow: 'Flusso di lavoro',
|
||||
|
|
@ -22,13 +21,10 @@ const translation = {
|
|||
'Dopo aver configurato le credenziali, tutti i membri all\'interno del workspace possono utilizzare questo strumento durante l\'orchestrazione delle applicazioni.',
|
||||
},
|
||||
includeToolNum: '{{num}} strumenti inclusi',
|
||||
addTool: 'Aggiungi Strumento',
|
||||
addToolModal: {
|
||||
type: 'tipo',
|
||||
category: 'categoria',
|
||||
add: 'aggiungi',
|
||||
added: 'aggiunto',
|
||||
manageInTools: 'Gestisci in Strumenti',
|
||||
custom: {
|
||||
title: 'Nessuno strumento personalizzato disponibile',
|
||||
tip: 'Crea uno strumento personalizzato',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'カスタムツールを作成する',
|
||||
customToolTip: 'Dify カスタムツールの詳細',
|
||||
type: {
|
||||
all: 'すべて',
|
||||
builtIn: 'ツール',
|
||||
custom: 'カスタム',
|
||||
workflow: 'ワークフロー',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: '資格情報を構成した後、ワークスペース内のすべてのメンバーがアプリケーションのオーケストレーション時にこのツールを使用できます。',
|
||||
},
|
||||
includeToolNum: '{{num}}個のツールが含まれています',
|
||||
addTool: 'ツールを追加する',
|
||||
addToolModal: {
|
||||
type: 'タイプ',
|
||||
category: 'カテゴリー',
|
||||
add: '追加',
|
||||
added: '追加済',
|
||||
manageInTools: 'ツールリストに移動して管理する',
|
||||
custom: {
|
||||
title: 'カスタムツールはありません',
|
||||
tip: 'カスタムツールを作成する',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: '커스텀 도구 만들기',
|
||||
customToolTip: 'Dify 커스텀 도구에 대해 더 알아보기',
|
||||
type: {
|
||||
all: '모두',
|
||||
builtIn: '내장',
|
||||
custom: '커스텀',
|
||||
workflow: '워크플로우',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: '자격 증명을 구성한 후에 워크스페이스의 모든 멤버가 이 도구를 사용하여 애플리케이션을 조작할 수 있습니다.',
|
||||
},
|
||||
includeToolNum: '{{num}}개의 도구가 포함되어 있습니다',
|
||||
addTool: '도구 추가',
|
||||
addToolModal: {
|
||||
type: '타입',
|
||||
category: '카테고리',
|
||||
add: '추가',
|
||||
added: '추가됨',
|
||||
manageInTools: '도구에서 관리',
|
||||
custom: {
|
||||
title: '사용자 정의 도구 없음',
|
||||
tip: '사용자 정의 도구 생성',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Narzędzia',
|
||||
createCustomTool: 'Utwórz niestandardowe narzędzie',
|
||||
type: {
|
||||
all: 'Wszystkie',
|
||||
builtIn: 'Wbudowane',
|
||||
custom: 'Niestandardowe',
|
||||
workflow: 'Przepływ pracy',
|
||||
|
|
@ -21,7 +20,6 @@ const translation = {
|
|||
'Po skonfigurowaniu poświadczeń wszyscy członkowie w przestrzeni roboczej mogą używać tego narzędzia podczas projektowania aplikacji.',
|
||||
},
|
||||
includeToolNum: '{{num}} narzędzi zawarte',
|
||||
addTool: 'Dodaj narzędzie',
|
||||
createTool: {
|
||||
title: 'Utwórz niestandardowe narzędzie',
|
||||
editAction: 'Konfiguruj',
|
||||
|
|
@ -145,11 +143,9 @@ const translation = {
|
|||
notAuthorized: 'Narzędzie nieautoryzowane',
|
||||
howToGet: 'Jak uzyskać',
|
||||
addToolModal: {
|
||||
manageInTools: 'Zarządzanie w Narzędziach',
|
||||
added: 'Dodane',
|
||||
type: 'typ',
|
||||
category: 'kategoria',
|
||||
add: 'dodawać',
|
||||
custom: {
|
||||
title: 'Brak dostępnego narzędzia niestandardowego',
|
||||
tip: 'Utwórz narzędzie niestandardowe',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Ferramentas',
|
||||
createCustomTool: 'Criar Ferramenta Personalizada',
|
||||
type: {
|
||||
all: 'Todas',
|
||||
builtIn: 'Integradas',
|
||||
custom: 'Personalizadas',
|
||||
workflow: 'Fluxo de trabalho',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'Após configurar as credenciais, todos os membros do espaço de trabalho podem usar essa ferramenta ao orquestrar aplicativos.',
|
||||
},
|
||||
includeToolNum: '{{num}} ferramentas incluídas',
|
||||
addTool: 'Adicionar Ferramenta',
|
||||
createTool: {
|
||||
title: 'Criar Ferramenta Personalizada',
|
||||
editAction: 'Configurar',
|
||||
|
|
@ -143,9 +141,7 @@ const translation = {
|
|||
addToolModal: {
|
||||
category: 'categoria',
|
||||
type: 'tipo',
|
||||
add: 'adicionar',
|
||||
added: 'Adicionado',
|
||||
manageInTools: 'Gerenciar em Ferramentas',
|
||||
custom: {
|
||||
title: 'Nenhuma ferramenta personalizada disponível',
|
||||
tip: 'Crie uma ferramenta personalizada',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Instrumente',
|
||||
createCustomTool: 'Creează Instrument Personalizat',
|
||||
type: {
|
||||
all: 'Toate',
|
||||
builtIn: 'Incorporat',
|
||||
custom: 'Personalizat',
|
||||
workflow: 'Flux de lucru',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'După configurarea credențialelor, toți membrii din spațiul de lucru pot utiliza acest instrument la orchestrarea aplicațiilor.',
|
||||
},
|
||||
includeToolNum: '{{num}} instrumente incluse',
|
||||
addTool: 'Adaugă Instrument',
|
||||
createTool: {
|
||||
title: 'Creează Instrument Personalizat',
|
||||
editAction: 'Configurează',
|
||||
|
|
@ -143,8 +141,6 @@ const translation = {
|
|||
addToolModal: {
|
||||
added: 'adăugat',
|
||||
category: 'categorie',
|
||||
manageInTools: 'Gestionați în Instrumente',
|
||||
add: 'adăuga',
|
||||
type: 'tip',
|
||||
custom: {
|
||||
title: 'Niciun instrument personalizat disponibil',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Создать пользовательский инструмент',
|
||||
customToolTip: 'Узнать больше о пользовательских инструментах Dify',
|
||||
type: {
|
||||
all: 'Все',
|
||||
builtIn: 'Встроенные',
|
||||
custom: 'Пользовательские',
|
||||
workflow: 'Рабочий процесс',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'После настройки учетных данных все участники рабочего пространства смогут использовать этот инструмент при оркестровке приложений.',
|
||||
},
|
||||
includeToolNum: 'Включено {{num}} инструментов',
|
||||
addTool: 'Добавить инструмент',
|
||||
addToolModal: {
|
||||
type: 'тип',
|
||||
category: 'категория',
|
||||
add: 'добавить',
|
||||
added: 'добавлено',
|
||||
manageInTools: 'Управлять в инструментах',
|
||||
custom: {
|
||||
title: 'Нет доступного пользовательского инструмента',
|
||||
tip: 'Создать пользовательский инструмент',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Ustvari prilagojeno orodje',
|
||||
customToolTip: 'Izvedite več o prilagojenih orodjih Dify',
|
||||
type: {
|
||||
all: 'Vsa',
|
||||
builtIn: 'Vgrajena',
|
||||
custom: 'Prilagojena',
|
||||
workflow: 'Potek dela',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'Po konfiguraciji poverilnic bodo vsi člani znotraj delovnega prostora lahko uporabljali to orodje pri orkestraciji aplikacij.',
|
||||
},
|
||||
includeToolNum: 'Vključeno {{num}} orodij',
|
||||
addTool: 'Dodaj orodje',
|
||||
addToolModal: {
|
||||
type: 'tip',
|
||||
category: 'kategorija',
|
||||
add: 'dodaj',
|
||||
added: 'dodano',
|
||||
manageInTools: 'Upravljaj v Orodjih',
|
||||
custom: {
|
||||
title: 'Žiadne prispôsobené nástroje nie sú k dispozícii',
|
||||
tip: 'Vytvorte prispôsobený nástroj',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'สร้างเครื่องมือที่กําหนดเอง',
|
||||
customToolTip: 'เรียนรู้เพิ่มเติมเกี่ยวกับเครื่องมือแบบกําหนดเองของ Dify',
|
||||
type: {
|
||||
all: 'ทั้งหมด',
|
||||
builtIn: 'ในตัว',
|
||||
custom: 'ธรรมเนียม',
|
||||
workflow: 'เวิร์กโฟลว์',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'หลังจากกําหนดค่าข้อมูลประจําตัวแล้ว สมาชิกทั้งหมดภายในพื้นที่ทํางานสามารถใช้เครื่องมือนี้เมื่อประสานงานแอปพลิเคชันได้',
|
||||
},
|
||||
includeToolNum: '{{num}} รวมเครื่องมือ',
|
||||
addTool: 'เพิ่มเครื่องมือ',
|
||||
addToolModal: {
|
||||
type: 'ประเภท',
|
||||
category: 'ประเภท',
|
||||
add: 'เพิ่ม',
|
||||
added: 'เพิ่ม',
|
||||
manageInTools: 'จัดการในเครื่องมือ',
|
||||
custom: {
|
||||
title: 'ไม่มีเครื่องมือกำหนดเอง',
|
||||
tip: 'สร้างเครื่องมือกำหนดเอง',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: 'Özel Araç Oluştur',
|
||||
customToolTip: 'Dify özel araçları hakkında daha fazla bilgi edinin',
|
||||
type: {
|
||||
all: 'Hepsi',
|
||||
builtIn: 'Yerleşik',
|
||||
custom: 'Özel',
|
||||
workflow: 'Workflow',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: 'Kimlik bilgilerini yapılandırdıktan sonra, çalışma alanındaki tüm üyeler uygulamaları düzenlerken bu aracı kullanabilir.',
|
||||
},
|
||||
includeToolNum: '{{num}} araç dahil',
|
||||
addTool: 'Araç Ekle',
|
||||
addToolModal: {
|
||||
type: 'Tür',
|
||||
category: 'Kategori',
|
||||
add: 'Ekle',
|
||||
added: 'Eklendi',
|
||||
manageInTools: 'Araçlarda Yönet',
|
||||
custom: {
|
||||
title: 'Mevcut özel araç yok',
|
||||
tip: 'Özel bir araç oluşturun',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Інструменти',
|
||||
createCustomTool: 'Створити власний інструмент',
|
||||
type: {
|
||||
all: 'Усі',
|
||||
builtIn: 'Вбудовані',
|
||||
custom: 'Користувацькі',
|
||||
workflow: 'Робочий процес',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'Після налаштування облікових даних усі члени робочого простору можуть використовувати цей інструмент під час оркестрування програм.',
|
||||
},
|
||||
includeToolNum: '{{num}} інструмент(ів) включено',
|
||||
addTool: 'Додати інструмент ',
|
||||
createTool: {
|
||||
title: 'Створити власний інструмент',
|
||||
editAction: 'Налаштування',
|
||||
|
|
@ -142,10 +140,8 @@ const translation = {
|
|||
howToGet: 'Як отримати',
|
||||
addToolModal: {
|
||||
category: 'категорія',
|
||||
add: 'Додати',
|
||||
added: 'Додано',
|
||||
type: 'тип',
|
||||
manageInTools: 'Керування в інструментах',
|
||||
custom: {
|
||||
title: 'Немає доступного користувацького інструмента',
|
||||
tip: 'Створити користувацький інструмент',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: 'Công cụ',
|
||||
createCustomTool: 'Tạo công cụ tùy chỉnh',
|
||||
type: {
|
||||
all: 'Tất cả',
|
||||
builtIn: 'Tích hợp sẵn',
|
||||
custom: 'Tùy chỉnh',
|
||||
workflow: 'Quy trình làm việc',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: 'Sau khi cấu hình thông tin đăng nhập, tất cả thành viên trong không gian làm việc có thể sử dụng công cụ này khi triển khai ứng dụng.',
|
||||
},
|
||||
includeToolNum: 'Bao gồm {{num}} công cụ',
|
||||
addTool: 'Thêm công cụ',
|
||||
createTool: {
|
||||
title: 'Tạo công cụ tùy chỉnh',
|
||||
editAction: 'Cấu hình',
|
||||
|
|
@ -142,9 +140,7 @@ const translation = {
|
|||
howToGet: 'Cách nhận',
|
||||
addToolModal: {
|
||||
category: 'loại',
|
||||
manageInTools: 'Quản lý trong Công cụ',
|
||||
type: 'kiểu',
|
||||
add: 'thêm',
|
||||
added: 'Thêm',
|
||||
custom: {
|
||||
title: 'Không có công cụ tùy chỉnh nào',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ const translation = {
|
|||
createCustomTool: '创建自定义工具',
|
||||
customToolTip: '了解更多关于 Dify 自定义工具的信息',
|
||||
type: {
|
||||
all: '全部',
|
||||
builtIn: '工具',
|
||||
custom: '自定义',
|
||||
workflow: '工作流',
|
||||
|
|
@ -21,13 +20,10 @@ const translation = {
|
|||
setupModalTitleDescription: '配置凭据后,工作区中的所有成员都可以在编排应用程序时使用此工具。',
|
||||
},
|
||||
includeToolNum: '包含 {{num}} 个 {{action}}',
|
||||
addTool: '添加工具',
|
||||
addToolModal: {
|
||||
type: '类型',
|
||||
category: '类别',
|
||||
add: '添加',
|
||||
added: '已添加',
|
||||
manageInTools: '去工具列表管理',
|
||||
custom: {
|
||||
title: '没有可用的自定义工具',
|
||||
tip: '创建自定义工具',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ const translation = {
|
|||
title: '工具',
|
||||
createCustomTool: '建立自定義工具',
|
||||
type: {
|
||||
all: '全部',
|
||||
builtIn: '內建',
|
||||
custom: '自定義',
|
||||
workflow: '工作流',
|
||||
|
|
@ -20,7 +19,6 @@ const translation = {
|
|||
setupModalTitleDescription: '配置憑據後,工作區中的所有成員都可以在編排應用程式時使用此工具。',
|
||||
},
|
||||
includeToolNum: '包含 {{num}} 個工具',
|
||||
addTool: '新增工具',
|
||||
createTool: {
|
||||
title: '建立自定義工具',
|
||||
editAction: '編輯',
|
||||
|
|
@ -141,10 +139,8 @@ const translation = {
|
|||
notAuthorized: '工具未授權',
|
||||
howToGet: '如何獲取',
|
||||
addToolModal: {
|
||||
add: '加',
|
||||
type: '類型',
|
||||
added: '新增',
|
||||
manageInTools: '在工具中管理',
|
||||
category: '類別',
|
||||
custom: {
|
||||
title: '沒有可用的自訂工具',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "dify-web",
|
||||
"version": "1.10.0-rc1",
|
||||
"version": "1.10.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8",
|
||||
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c",
|
||||
"engines": {
|
||||
"node": ">=v22.11.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import type {
|
|||
WorkflowToolProviderRequest,
|
||||
WorkflowToolProviderResponse,
|
||||
} from '@/app/components/tools/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import type { Label } from '@/app/components/tools/labels/constant'
|
||||
import { buildProviderQuery } from './_tools_util'
|
||||
|
||||
export const fetchCollectionList = () => {
|
||||
|
|
@ -112,26 +110,6 @@ export const testAPIAvailable = (payload: any) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const fetchAllBuiltInTools = () => {
|
||||
return get<ToolWithProvider[]>('/workspaces/current/tools/builtin')
|
||||
}
|
||||
|
||||
export const fetchAllCustomTools = () => {
|
||||
return get<ToolWithProvider[]>('/workspaces/current/tools/api')
|
||||
}
|
||||
|
||||
export const fetchAllWorkflowTools = () => {
|
||||
return get<ToolWithProvider[]>('/workspaces/current/tools/workflow')
|
||||
}
|
||||
|
||||
export const fetchAllMCPTools = () => {
|
||||
return get<ToolWithProvider[]>('/workspaces/current/tools/mcp')
|
||||
}
|
||||
|
||||
export const fetchLabelList = () => {
|
||||
return get<Label[]>('/workspaces/current/tool-labels')
|
||||
}
|
||||
|
||||
export const createWorkflowToolProvider = (payload: WorkflowToolProviderRequest & { workflow_app_id: string }) => {
|
||||
return post('/workspaces/current/tool-provider/workflow/create', {
|
||||
body: { ...payload },
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ describe('formatNumberAbbreviated', () => {
|
|||
expect(formatNumberAbbreviated(1000000)).toBe('1M')
|
||||
expect(formatNumberAbbreviated(1500000)).toBe('1.5M')
|
||||
expect(formatNumberAbbreviated(2300000)).toBe('2.3M')
|
||||
expect(formatNumberAbbreviated(999999999)).toBe('1000M')
|
||||
expect(formatNumberAbbreviated(999999999)).toBe('1B')
|
||||
})
|
||||
|
||||
it('should format billions with B suffix', () => {
|
||||
|
|
@ -145,7 +145,7 @@ describe('formatNumberAbbreviated', () => {
|
|||
it('should handle edge cases', () => {
|
||||
expect(formatNumberAbbreviated(950)).toBe('950')
|
||||
expect(formatNumberAbbreviated(1001)).toBe('1k')
|
||||
expect(formatNumberAbbreviated(999999)).toBe('1000k')
|
||||
expect(formatNumberAbbreviated(999999)).toBe('1M')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -130,10 +130,20 @@ export const formatNumberAbbreviated = (num: number) => {
|
|||
|
||||
for (let i = 0; i < units.length; i++) {
|
||||
if (num >= units[i].value) {
|
||||
const formatted = (num / units[i].value).toFixed(1)
|
||||
const value = num / units[i].value
|
||||
let rounded = Math.round(value * 10) / 10
|
||||
let unitIndex = i
|
||||
|
||||
// If rounded value >= 1000, promote to next unit
|
||||
if (rounded >= 1000 && i > 0) {
|
||||
rounded = rounded / 1000
|
||||
unitIndex = i - 1
|
||||
}
|
||||
|
||||
const formatted = rounded.toFixed(1)
|
||||
return formatted.endsWith('.0')
|
||||
? `${Number.parseInt(formatted)}${units[i].symbol}`
|
||||
: `${formatted}${units[i].symbol}`
|
||||
? `${Number.parseInt(formatted)}${units[unitIndex].symbol}`
|
||||
: `${formatted}${units[unitIndex].symbol}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue