Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox

# Conflicts:
#	api/controllers/console/app/app.py
#	web/eslint-suppressions.json
#	web/eslint.config.mjs
This commit is contained in:
yyh
2026-02-06 14:40:44 +08:00
177 changed files with 886 additions and 686 deletions

View File

@ -1 +0,0 @@
../../.agents/skills/component-refactoring

View File

@ -1 +0,0 @@
../../.agents/skills/frontend-code-review

View File

@ -1 +0,0 @@
../../.agents/skills/frontend-testing

View File

@ -1 +0,0 @@
../../.agents/skills/orpc-contract-first

View File

@ -79,29 +79,6 @@ jobs:
find . -name "*.py" -type f -exec sed -i.bak -E 's/"([^"]+)" \| None/Optional["\1"]/g; s/'"'"'([^'"'"']+)'"'"' \| None/Optional['"'"'\1'"'"']/g' {} \;
find . -name "*.py.bak" -type f -delete
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: web/package.json
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: ./web/pnpm-lock.yaml
- name: Install web dependencies
run: |
cd web
pnpm install --frozen-lockfile
- name: ESLint autofix
run: |
cd web
pnpm lint:fix || true
# mdformat breaks YAML front matter in markdown files. Add --exclude for directories containing YAML front matter.
- name: mdformat
run: |

View File

@ -4,8 +4,7 @@ on:
workflow_run:
workflows: ["Build and Push API & Web"]
branches:
- "feat/hitl-frontend"
- "feat/hitl-backend"
- "feat/hitl"
types:
- completed
@ -14,10 +13,7 @@ jobs:
runs-on: ubuntu-latest
if: |
github.event.workflow_run.conclusion == 'success' &&
(
github.event.workflow_run.head_branch == 'feat/hitl-frontend' ||
github.event.workflow_run.head_branch == 'feat/hitl-backend'
)
github.event.workflow_run.head_branch == 'feat/hitl'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1

View File

@ -102,6 +102,8 @@ forbidden_modules =
core.trigger
core.variables
ignore_imports =
core.workflow.nodes.agent.agent_node -> core.db.session_factory
core.workflow.nodes.agent.agent_node -> models.tools
core.workflow.nodes.loop.loop_node -> core.app.workflow.node_factory
core.workflow.graph_engine.command_channels.redis_channel -> extensions.ext_redis
core.workflow.workflow_entry -> core.app.workflow.layers.observability
@ -136,7 +138,6 @@ ignore_imports =
core.workflow.nodes.llm.llm_utils -> models.provider
core.workflow.nodes.llm.llm_utils -> services.credit_pool_service
core.workflow.nodes.llm.node -> core.tools.signature
core.workflow.nodes.template_transform.template_transform_node -> configs
core.workflow.nodes.tool.tool_node -> core.callback_handler.workflow_tool_callback_handler
core.workflow.nodes.tool.tool_node -> core.tools.tool_engine
core.workflow.nodes.tool.tool_node -> core.tools.tool_manager

View File

@ -740,8 +740,10 @@ def upgrade_db():
click.echo(click.style("Database migration successful!", fg="green"))
except Exception:
except Exception as e:
logger.exception("Failed to execute database migration")
click.echo(click.style(f"Database migration failed: {e}", fg="red"))
raise SystemExit(1)
finally:
lock.release()
else:

View File

@ -1,3 +1,4 @@
import logging
import uuid
from datetime import datetime
from enum import StrEnum
@ -56,6 +57,8 @@ ALLOW_CREATE_APP_MODES = ["chat", "agent-chat", "advanced-chat", "workflow", "co
register_enum_models(console_ns, IconType)
_logger = logging.getLogger(__name__)
class RuntimeType(StrEnum):
CLASSIC = "classic"
@ -513,6 +516,7 @@ class AppListApi(Resource):
select(Workflow).where(
Workflow.version == Workflow.VERSION_DRAFT,
Workflow.app_id.in_(workflow_capable_app_ids),
Workflow.tenant_id == current_tenant_id,
)
)
.scalars()
@ -528,12 +532,14 @@ class AppListApi(Resource):
if workflow.get_feature(WorkflowFeatures.SANDBOX).enabled:
sandbox_app_ids.add(str(workflow.app_id))
node_id = None
try:
for _, node_data in workflow.walk_nodes():
for node_id, node_data in workflow.walk_nodes():
if node_data.get("type") in trigger_node_types:
draft_trigger_app_ids.add(str(workflow.app_id))
break
except Exception:
_logger.exception("error while walking nodes, workflow_id=%s, node_id=%s", workflow.id, node_id)
continue
for app in app_pagination.items:

View File

@ -47,6 +47,7 @@ class DifyNodeFactory(NodeFactory):
code_providers: Sequence[type[CodeNodeProvider]] | None = None,
code_limits: CodeNodeLimits | None = None,
template_renderer: Jinja2TemplateRenderer | None = None,
template_transform_max_output_length: int | None = None,
http_request_http_client: HttpClientProtocol | None = None,
http_request_tool_file_manager_factory: Callable[[], ToolFileManager] = ToolFileManager,
http_request_file_manager: FileManagerProtocol | None = None,
@ -68,6 +69,9 @@ class DifyNodeFactory(NodeFactory):
max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH,
)
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
self._template_transform_max_output_length = (
template_transform_max_output_length or dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
)
self._http_request_http_client = http_request_http_client or ssrf_proxy
self._http_request_tool_file_manager_factory = http_request_tool_file_manager_factory
self._http_request_file_manager = http_request_file_manager or file_manager
@ -122,6 +126,7 @@ class DifyNodeFactory(NodeFactory):
graph_init_params=self.graph_init_params,
graph_runtime_state=self.graph_runtime_state,
template_renderer=self._template_renderer,
max_output_length=self._template_transform_max_output_length,
)
if node_type == NodeType.HTTP_REQUEST:

View File

@ -6,7 +6,8 @@ from yarl import URL
from configs import dify_config
from core.helper.download import download_with_size_limit
from core.plugin.entities.marketplace import MarketplacePluginDeclaration
from core.plugin.entities.marketplace import MarketplacePluginDeclaration, MarketplacePluginSnapshot
from extensions.ext_redis import redis_client
marketplace_api_url = URL(str(dify_config.MARKETPLACE_API_URL))
logger = logging.getLogger(__name__)
@ -43,28 +44,37 @@ def batch_fetch_plugin_by_ids(plugin_ids: list[str]) -> list[dict]:
return data.get("data", {}).get("plugins", [])
def batch_fetch_plugin_manifests_ignore_deserialization_error(
plugin_ids: list[str],
) -> Sequence[MarketplacePluginDeclaration]:
if len(plugin_ids) == 0:
return []
url = str(marketplace_api_url / "api/v1/plugins/batch")
response = httpx.post(url, json={"plugin_ids": plugin_ids}, headers={"X-Dify-Version": dify_config.project.version})
response.raise_for_status()
result: list[MarketplacePluginDeclaration] = []
for plugin in response.json()["data"]["plugins"]:
try:
result.append(MarketplacePluginDeclaration.model_validate(plugin))
except Exception:
logger.exception(
"Failed to deserialize marketplace plugin manifest for %s", plugin.get("plugin_id", "unknown")
)
return result
def record_install_plugin_event(plugin_unique_identifier: str):
url = str(marketplace_api_url / "api/v1/stats/plugins/install_count")
response = httpx.post(url, json={"unique_identifier": plugin_unique_identifier})
response.raise_for_status()
def fetch_global_plugin_manifest(cache_key_prefix: str, cache_ttl: int) -> None:
"""
Fetch all plugin manifests from marketplace and cache them in Redis.
This should be called once per check cycle to populate the instance-level cache.
Args:
cache_key_prefix: Redis key prefix for caching plugin manifests
cache_ttl: Cache TTL in seconds
Raises:
httpx.HTTPError: If the HTTP request fails
Exception: If any other error occurs during fetching or caching
"""
url = str(marketplace_api_url / "api/v1/dist/plugins/manifest.json")
response = httpx.get(url, headers={"X-Dify-Version": dify_config.project.version}, timeout=30)
response.raise_for_status()
raw_json = response.json()
plugins_data = raw_json.get("plugins", [])
# Parse and cache all plugin snapshots
for plugin_data in plugins_data:
plugin_snapshot = MarketplacePluginSnapshot.model_validate(plugin_data)
redis_client.setex(
name=f"{cache_key_prefix}{plugin_snapshot.plugin_id}",
time=cache_ttl,
value=plugin_snapshot.model_dump_json(),
)

View File

@ -1,4 +1,4 @@
from pydantic import BaseModel, Field, model_validator
from pydantic import BaseModel, Field, computed_field, model_validator
from core.model_runtime.entities.provider_entities import ProviderEntity
from core.plugin.entities.endpoint import EndpointProviderDeclaration
@ -48,3 +48,15 @@ class MarketplacePluginDeclaration(BaseModel):
if "tool" in data and not data["tool"]:
del data["tool"]
return data
class MarketplacePluginSnapshot(BaseModel):
org: str
name: str
latest_version: str
latest_package_identifier: str
latest_package_url: str
@computed_field
def plugin_id(self) -> str:
return f"{self.org}/{self.name}"

View File

@ -117,7 +117,7 @@ class ArrayPromptMessageVariable(ArrayPromptMessageSegment, ArrayVariable):
class RAGPipelineVariable(BaseModel):
belong_to_node_id: str = Field(description="belong to which node id, shared means public")
type: str = Field(description="variable type, text-input, paragraph, select, number, file, file-list")
type: str = Field(description="variable type, text-input, paragraph, select, number, file, file-list")
label: str = Field(description="label")
description: str | None = Field(description="description", default="")
variable: str = Field(description="variable key", default="")

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import json
from collections.abc import Generator, Mapping, Sequence
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, Union, cast
from packaging.version import Version
from pydantic import ValidationError
@ -11,6 +11,7 @@ from sqlalchemy.orm import Session
from core.agent.entities import AgentToolEntity
from core.agent.plugin_entities import AgentStrategyParameter
from core.db.session_factory import session_factory
from core.file import File, FileTransferMethod
from core.memory.base import BaseMemory
from core.memory.node_token_buffer_memory import NodeTokenBufferMemory
@ -58,6 +59,12 @@ from factories import file_factory
from factories.agent_factory import get_plugin_agent_strategy
from models import ToolFile
from models.model import Conversation
from models.tools import (
ApiToolProvider,
BuiltinToolProvider,
MCPToolProvider,
WorkflowToolProvider,
)
from services.tools.builtin_tools_manage_service import BuiltinToolManageService
from .exc import (
@ -272,7 +279,7 @@ class AgentNode(Node[AgentNodeData]):
value = cast(list[dict[str, Any]], value)
tool_value = []
for tool in value:
provider_type = ToolProviderType(tool.get("type", ToolProviderType.BUILT_IN))
provider_type = self._infer_tool_provider_type(tool, self.tenant_id)
setting_params = tool.get("settings", {})
parameters = tool.get("parameters", {})
manual_input_params = [key for key, value in parameters.items() if value is not None]
@ -921,3 +928,34 @@ class AgentNode(Node[AgentNodeData]):
llm_usage=llm_usage,
)
)
@staticmethod
def _infer_tool_provider_type(tool_config: dict[str, Any], tenant_id: str) -> ToolProviderType:
provider_type_str = tool_config.get("type")
if provider_type_str:
return ToolProviderType(provider_type_str)
provider_id = tool_config.get("provider_name")
if not provider_id:
return ToolProviderType.BUILT_IN
with session_factory.create_session() as session:
provider_map: dict[
type[Union[WorkflowToolProvider, MCPToolProvider, ApiToolProvider, BuiltinToolProvider]],
ToolProviderType,
] = {
WorkflowToolProvider: ToolProviderType.WORKFLOW,
MCPToolProvider: ToolProviderType.MCP,
ApiToolProvider: ToolProviderType.API,
BuiltinToolProvider: ToolProviderType.BUILT_IN,
}
for provider_model, provider_type in provider_map.items():
stmt = select(provider_model).where(
provider_model.id == provider_id,
provider_model.tenant_id == tenant_id,
)
if session.scalar(stmt):
return provider_type
raise AgentNodeError(f"Tool provider with ID '{provider_id}' not found.")

View File

@ -1,7 +1,6 @@
from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any
from configs import dify_config
from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus
from core.workflow.node_events import NodeRunResult
from core.workflow.nodes.base.node import Node
@ -16,12 +15,13 @@ if TYPE_CHECKING:
from core.workflow.entities import GraphInitParams
from core.workflow.runtime import GraphRuntimeState
MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = dify_config.TEMPLATE_TRANSFORM_MAX_LENGTH
DEFAULT_TEMPLATE_TRANSFORM_MAX_OUTPUT_LENGTH = 400_000
class TemplateTransformNode(Node[TemplateTransformNodeData]):
node_type = NodeType.TEMPLATE_TRANSFORM
_template_renderer: Jinja2TemplateRenderer
_max_output_length: int
def __init__(
self,
@ -31,6 +31,7 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
graph_runtime_state: "GraphRuntimeState",
*,
template_renderer: Jinja2TemplateRenderer | None = None,
max_output_length: int | None = None,
) -> None:
super().__init__(
id=id,
@ -40,6 +41,10 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
)
self._template_renderer = template_renderer or CodeExecutorJinja2TemplateRenderer()
if max_output_length is not None and max_output_length <= 0:
raise ValueError("max_output_length must be a positive integer")
self._max_output_length = max_output_length or DEFAULT_TEMPLATE_TRANSFORM_MAX_OUTPUT_LENGTH
@classmethod
def get_default_config(cls, filters: Mapping[str, object] | None = None) -> Mapping[str, object]:
"""
@ -69,11 +74,11 @@ class TemplateTransformNode(Node[TemplateTransformNodeData]):
except TemplateRenderError as e:
return NodeRunResult(inputs=variables, status=WorkflowNodeExecutionStatus.FAILED, error=str(e))
if len(rendered) > MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH:
if len(rendered) > self._max_output_length:
return NodeRunResult(
inputs=variables,
status=WorkflowNodeExecutionStatus.FAILED,
error=f"Output length exceeds {MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH} characters",
error=f"Output length exceeds {self._max_output_length} characters",
)
return NodeRunResult(

View File

@ -10,6 +10,10 @@ import models as models
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
def _is_pg(conn):
return conn.dialect.name == "postgresql"
# revision identifiers, used by Alembic.
revision = '7df29de0f6be'
down_revision = '03ea244985ce'
@ -19,16 +23,31 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tenant_credit_pools',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('pool_type', sa.String(length=40), server_default='trial', nullable=False),
sa.Column('quota_limit', sa.BigInteger(), nullable=False),
sa.Column('quota_used', sa.BigInteger(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='tenant_credit_pool_pkey')
)
conn = op.get_bind()
if _is_pg(conn):
op.create_table('tenant_credit_pools',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('pool_type', sa.String(length=40), server_default='trial', nullable=False),
sa.Column('quota_limit', sa.BigInteger(), nullable=False),
sa.Column('quota_used', sa.BigInteger(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='tenant_credit_pool_pkey')
)
else:
# For MySQL and other databases, UUID should be generated at application level
op.create_table('tenant_credit_pools',
sa.Column('id', models.types.StringUUID(), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('pool_type', sa.String(length=40), server_default='trial', nullable=False),
sa.Column('quota_limit', sa.BigInteger(), nullable=False),
sa.Column('quota_used', sa.BigInteger(), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.func.current_timestamp(), nullable=False),
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.current_timestamp(), nullable=False),
sa.PrimaryKeyConstraint('id', name='tenant_credit_pool_pkey')
)
with op.batch_alter_table('tenant_credit_pools', schema=None) as batch_op:
batch_op.create_index('tenant_credit_pool_pool_type_idx', ['pool_type'], unique=False)
batch_op.create_index('tenant_credit_pool_tenant_id_idx', ['tenant_id'], unique=False)

View File

@ -2264,7 +2264,9 @@ class TenantCreditPool(TypeBase):
sa.Index("tenant_credit_pool_pool_type_idx", "pool_type"),
)
id: Mapped[str] = mapped_column(StringUUID, primary_key=True, server_default=text("uuid_generate_v4()"), init=False)
id: Mapped[str] = mapped_column(
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
pool_type: Mapped[str] = mapped_column(String(40), nullable=False, default="trial", server_default="trial")
quota_limit: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)

View File

@ -1,16 +1,24 @@
import logging
import math
import time
import click
import app
from core.helper.marketplace import fetch_global_plugin_manifest
from extensions.ext_database import db
from models.account import TenantPluginAutoUpgradeStrategy
from tasks import process_tenant_plugin_autoupgrade_check_task as check_task
logger = logging.getLogger(__name__)
AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL = 15 * 60 # 15 minutes
MAX_CONCURRENT_CHECK_TASKS = 20
# Import cache constants from the task module
CACHE_REDIS_KEY_PREFIX = check_task.CACHE_REDIS_KEY_PREFIX
CACHE_REDIS_TTL = check_task.CACHE_REDIS_TTL
@app.celery.task(queue="plugin")
def check_upgradable_plugin_task():
@ -40,6 +48,22 @@ def check_upgradable_plugin_task():
) # make sure all strategies are checked in this interval
batch_interval_time = (AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL / batch_chunk_count) if batch_chunk_count > 0 else 0
if total_strategies == 0:
click.echo(click.style("no strategies to process, skipping plugin manifest fetch.", fg="green"))
return
# Fetch and cache all plugin manifests before processing tenants
# This reduces load on marketplace from 300k requests to 1 request per check cycle
logger.info("fetching global plugin manifest from marketplace")
try:
fetch_global_plugin_manifest(CACHE_REDIS_KEY_PREFIX, CACHE_REDIS_TTL)
logger.info("successfully fetched and cached global plugin manifest")
except Exception as e:
logger.exception("failed to fetch global plugin manifest")
click.echo(click.style(f"failed to fetch global plugin manifest: {e}", fg="red"))
click.echo(click.style("skipping plugin upgrade check for this cycle", fg="yellow"))
return
for i in range(0, total_strategies, MAX_CONCURRENT_CHECK_TASKS):
batch_strategies = strategies[i : i + MAX_CONCURRENT_CHECK_TASKS]
for strategy in batch_strategies:

View File

@ -6,8 +6,8 @@ import typing
import click
from celery import shared_task
from core.helper import marketplace
from core.helper.marketplace import MarketplacePluginDeclaration
from core.helper.marketplace import record_install_plugin_event
from core.plugin.entities.marketplace import MarketplacePluginSnapshot
from core.plugin.entities.plugin import PluginInstallationSource
from core.plugin.impl.plugin import PluginInstaller
from extensions.ext_redis import redis_client
@ -16,7 +16,7 @@ from models.account import TenantPluginAutoUpgradeStrategy
logger = logging.getLogger(__name__)
RETRY_TIMES_OF_ONE_PLUGIN_IN_ONE_TENANT = 3
CACHE_REDIS_KEY_PREFIX = "plugin_autoupgrade_check_task:cached_plugin_manifests:"
CACHE_REDIS_KEY_PREFIX = "plugin_autoupgrade_check_task:cached_plugin_snapshot:"
CACHE_REDIS_TTL = 60 * 60 # 1 hour
@ -25,11 +25,11 @@ def _get_redis_cache_key(plugin_id: str) -> str:
return f"{CACHE_REDIS_KEY_PREFIX}{plugin_id}"
def _get_cached_manifest(plugin_id: str) -> typing.Union[MarketplacePluginDeclaration, None, bool]:
def _get_cached_manifest(plugin_id: str) -> typing.Union[MarketplacePluginSnapshot, None, bool]:
"""
Get cached plugin manifest from Redis.
Returns:
- MarketplacePluginDeclaration: if found in cache
- MarketplacePluginSnapshot: if found in cache
- None: if cached as not found (marketplace returned no result)
- False: if not in cache at all
"""
@ -43,76 +43,31 @@ def _get_cached_manifest(plugin_id: str) -> typing.Union[MarketplacePluginDeclar
if cached_json is None:
return None
return MarketplacePluginDeclaration.model_validate(cached_json)
return MarketplacePluginSnapshot.model_validate(cached_json)
except Exception:
logger.exception("Failed to get cached manifest for plugin %s", plugin_id)
return False
def _set_cached_manifest(plugin_id: str, manifest: typing.Union[MarketplacePluginDeclaration, None]) -> None:
"""
Cache plugin manifest in Redis.
Args:
plugin_id: The plugin ID
manifest: The manifest to cache, or None if not found in marketplace
"""
try:
key = _get_redis_cache_key(plugin_id)
if manifest is None:
# Cache the fact that this plugin was not found
redis_client.setex(key, CACHE_REDIS_TTL, json.dumps(None))
else:
# Cache the manifest data
redis_client.setex(key, CACHE_REDIS_TTL, manifest.model_dump_json())
except Exception:
# If Redis fails, continue without caching
# traceback.print_exc()
logger.exception("Failed to set cached manifest for plugin %s", plugin_id)
def marketplace_batch_fetch_plugin_manifests(
plugin_ids_plain_list: list[str],
) -> list[MarketplacePluginDeclaration]:
"""Fetch plugin manifests with Redis caching support."""
cached_manifests: dict[str, typing.Union[MarketplacePluginDeclaration, None]] = {}
not_cached_plugin_ids: list[str] = []
) -> list[MarketplacePluginSnapshot]:
"""
Fetch plugin manifests from Redis cache only.
This function assumes fetch_global_plugin_manifest() has been called
to pre-populate the cache with all marketplace plugins.
"""
result: list[MarketplacePluginSnapshot] = []
# Check Redis cache for each plugin
for plugin_id in plugin_ids_plain_list:
cached_result = _get_cached_manifest(plugin_id)
if cached_result is False:
# Not in cache, need to fetch
not_cached_plugin_ids.append(plugin_id)
else:
# Either found manifest or cached as None (not found in marketplace)
# At this point, cached_result is either MarketplacePluginDeclaration or None
if isinstance(cached_result, bool):
# This should never happen due to the if condition above, but for type safety
continue
cached_manifests[plugin_id] = cached_result
if not isinstance(cached_result, MarketplacePluginSnapshot):
# cached_result is False (not in cache) or None (cached as not found)
logger.warning("plugin %s not found in cache, skipping", plugin_id)
continue
# Fetch uncached plugins from marketplace
if not_cached_plugin_ids:
manifests = marketplace.batch_fetch_plugin_manifests_ignore_deserialization_error(not_cached_plugin_ids)
# Cache the fetched manifests
for manifest in manifests:
cached_manifests[manifest.plugin_id] = manifest
_set_cached_manifest(manifest.plugin_id, manifest)
# Cache plugins that were not found in marketplace
fetched_plugin_ids = {manifest.plugin_id for manifest in manifests}
for plugin_id in not_cached_plugin_ids:
if plugin_id not in fetched_plugin_ids:
cached_manifests[plugin_id] = None
_set_cached_manifest(plugin_id, None)
# Build result list from cached manifests
result: list[MarketplacePluginDeclaration] = []
for plugin_id in plugin_ids_plain_list:
cached_manifest: typing.Union[MarketplacePluginDeclaration, None] = cached_manifests.get(plugin_id)
if cached_manifest is not None:
result.append(cached_manifest)
result.append(cached_result)
return result
@ -211,7 +166,7 @@ def process_tenant_plugin_autoupgrade_check_task(
# execute upgrade
new_unique_identifier = manifest.latest_package_identifier
marketplace.record_install_plugin_event(new_unique_identifier)
record_install_plugin_event(new_unique_identifier)
click.echo(
click.style(
f"Upgrade plugin: {original_unique_identifier} -> {new_unique_identifier}",

View File

@ -0,0 +1,197 @@
from unittest.mock import MagicMock, patch
import pytest
from core.tools.entities.tool_entities import ToolProviderType
from core.workflow.nodes.agent.agent_node import AgentNode
class TestInferToolProviderType:
"""Test cases for AgentNode._infer_tool_provider_type method."""
def test_infer_type_from_config_workflow(self):
"""Test inferring workflow provider type from config."""
tool_config = {
"type": "workflow",
"provider_name": "workflow-provider-id",
}
tenant_id = "test-tenant"
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.WORKFLOW
def test_infer_type_from_config_builtin(self):
"""Test inferring builtin provider type from config."""
tool_config = {
"type": "builtin",
"provider_name": "builtin-provider-id",
}
tenant_id = "test-tenant"
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.BUILT_IN
def test_infer_type_from_config_api(self):
"""Test inferring API provider type from config."""
tool_config = {
"type": "api",
"provider_name": "api-provider-id",
}
tenant_id = "test-tenant"
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.API
def test_infer_type_from_config_mcp(self):
"""Test inferring MCP provider type from config."""
tool_config = {
"type": "mcp",
"provider_name": "mcp-provider-id",
}
tenant_id = "test-tenant"
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.MCP
def test_infer_type_invalid_config_value_raises_error(self):
"""Test that invalid type value in config raises ValueError."""
tool_config = {
"type": "invalid-type",
"provider_name": "workflow-provider-id",
}
tenant_id = "test-tenant"
with pytest.raises(ValueError):
AgentNode._infer_tool_provider_type(tool_config, tenant_id)
def test_infer_workflow_type_from_database(self):
"""Test inferring workflow provider type from database."""
tool_config = {
"provider_name": "workflow-provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# First query (WorkflowToolProvider) returns a result
mock_session.scalar.return_value = True
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.WORKFLOW
# Should only query once (after finding WorkflowToolProvider)
assert mock_session.scalar.call_count == 1
def test_infer_mcp_type_from_database(self):
"""Test inferring MCP provider type from database."""
tool_config = {
"provider_name": "mcp-provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# First query (WorkflowToolProvider) returns None
# Second query (MCPToolProvider) returns a result
mock_session.scalar.side_effect = [None, True]
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.MCP
assert mock_session.scalar.call_count == 2
def test_infer_api_type_from_database(self):
"""Test inferring API provider type from database."""
tool_config = {
"provider_name": "api-provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# First query (WorkflowToolProvider) returns None
# Second query (MCPToolProvider) returns None
# Third query (ApiToolProvider) returns a result
mock_session.scalar.side_effect = [None, None, True]
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.API
assert mock_session.scalar.call_count == 3
def test_infer_builtin_type_from_database(self):
"""Test inferring builtin provider type from database."""
tool_config = {
"provider_name": "builtin-provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# First three queries return None
# Fourth query (BuiltinToolProvider) returns a result
mock_session.scalar.side_effect = [None, None, None, True]
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.BUILT_IN
assert mock_session.scalar.call_count == 4
def test_infer_type_default_when_not_found(self):
"""Test raising AgentNodeError when provider is not found in database."""
tool_config = {
"provider_name": "unknown-provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# All queries return None
mock_session.scalar.return_value = None
# Current implementation raises AgentNodeError when provider not found
from core.workflow.nodes.agent.exc import AgentNodeError
with pytest.raises(AgentNodeError, match="Tool provider with ID 'unknown-provider-id' not found"):
AgentNode._infer_tool_provider_type(tool_config, tenant_id)
def test_infer_type_default_when_no_provider_name(self):
"""Test defaulting to BUILT_IN when provider_name is missing."""
tool_config = {}
tenant_id = "test-tenant"
result = AgentNode._infer_tool_provider_type(tool_config, tenant_id)
assert result == ToolProviderType.BUILT_IN
def test_infer_type_database_exception_propagates(self):
"""Test that database exception propagates (current implementation doesn't catch it)."""
tool_config = {
"provider_name": "provider-id",
}
tenant_id = "test-tenant"
with patch("core.db.session_factory.session_factory.create_session") as mock_create_session:
mock_session = MagicMock()
mock_create_session.return_value.__enter__.return_value = mock_session
# Database query raises exception
mock_session.scalar.side_effect = Exception("Database error")
# Current implementation doesn't catch exceptions, so it propagates
with pytest.raises(Exception, match="Database error"):
AgentNode._infer_tool_provider_type(tool_config, tenant_id)

View File

@ -217,7 +217,6 @@ class TestTemplateTransformNode:
@patch(
"core.workflow.nodes.template_transform.template_transform_node.CodeExecutorJinja2TemplateRenderer.render_template"
)
@patch("core.workflow.nodes.template_transform.template_transform_node.MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH", 10)
def test_run_output_length_exceeds_limit(
self, mock_execute, basic_node_data, mock_graph, mock_graph_runtime_state, graph_init_params
):
@ -231,6 +230,7 @@ class TestTemplateTransformNode:
graph_init_params=graph_init_params,
graph=mock_graph,
graph_runtime_state=mock_graph_runtime_state,
max_output_length=10,
)
result = node._run()

View File

@ -57,7 +57,7 @@ const RangeSelector: FC<Props> = ({
{selected && (
<span
className={cn(
'absolute left-2 top-[9px] flex items-center text-text-accent',
'absolute left-2 top-[9px] flex items-center text-text-accent',
)}
>
<RiCheckLine className="h-4 w-4" aria-hidden="true" />

View File

@ -166,7 +166,7 @@ export default function AccountPage() {
<div className="mb-8">
<div className={titleClassName}>{t('account.name', { ns: 'common' })}</div>
<div className="mt-2 flex w-full items-center justify-between gap-2">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled ">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled">
<span className="pl-1">{userProfile.name}</span>
</div>
<div className="system-sm-medium cursor-pointer rounded-lg bg-components-button-tertiary-bg px-3 py-2 text-components-button-tertiary-text" onClick={handleEditName}>
@ -177,7 +177,7 @@ export default function AccountPage() {
<div className="mb-8">
<div className={titleClassName}>{t('account.email', { ns: 'common' })}</div>
<div className="mt-2 flex w-full items-center justify-between gap-2">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled ">
<div className="system-sm-regular flex-1 rounded-lg bg-components-input-bg-normal p-2 text-components-input-text-filled">
<span className="pl-1">{userProfile.email}</span>
</div>
{systemFeatures.enable_change_email && (

View File

@ -450,7 +450,7 @@ const AppPublisher = ({
<p className="system-xs-medium text-text-tertiary">{t('publishApp.title', { ns: 'app' })}</p>
</div>
<div
className="flex h-8 cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2 hover:bg-primary-50 hover:text-text-accent"
className="flex h-8 cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2 hover:bg-primary-50 hover:text-text-accent"
onClick={() => {
setShowAppAccessControl(true)
}}

View File

@ -35,7 +35,7 @@ const ConfirmAddVar: FC<IConfirmAddVarProps> = ({
// }, mainContentRef)
return (
<div
className="absolute inset-0 flex items-center justify-center rounded-xl"
className="absolute inset-0 flex items-center justify-center rounded-xl"
style={{
backgroundColor: 'rgba(35, 56, 118, 0.2)',
}}

View File

@ -28,7 +28,7 @@ const MessageTypeSelector: FC<Props> = ({
className={cn(showOption && 'bg-indigo-100', 'flex h-7 cursor-pointer items-center space-x-0.5 rounded-lg pl-1.5 pr-1 text-indigo-800')}
>
<div className="text-sm font-semibold uppercase">{value}</div>
<ChevronSelectorVertical className="h-3 w-3 " />
<ChevronSelectorVertical className="h-3 w-3" />
</div>
{showOption && (
<div className="absolute top-[30px] z-10 rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg">

View File

@ -87,7 +87,7 @@ const ConfigSelect: FC<IConfigSelectProps> = ({
<div
onClick={() => { onChange([...options, '']) }}
className="mt-1 flex h-9 cursor-pointer items-center gap-2 rounded-lg bg-components-button-tertiary-bg px-3 text-components-button-tertiary-text hover:bg-components-button-tertiary-bg-hover"
className="mt-1 flex h-9 cursor-pointer items-center gap-2 rounded-lg bg-components-button-tertiary-bg px-3 text-components-button-tertiary-text hover:bg-components-button-tertiary-bg-hover"
>
<RiAddLine className="h-4 w-4" />
<div className="system-sm-medium text-[13px]">{t('variableConfig.addOption', { ns: 'appDebug' })}</div>

View File

@ -11,7 +11,7 @@ export type IInputTypeIconProps = {
}
const IconMap = (type: IInputTypeIconProps['type'], className: string) => {
const classNames = `w-3.5 h-3.5 ${className}`
const classNames = `h-3.5 w-3.5 ${className}`
const icons = {
string: (
<InputVarTypeIcon type={InputVarType.textInput} className={classNames} />

View File

@ -33,7 +33,7 @@ const SelectTypeItem: FC<ISelectTypeItemProps> = ({
<div
className={cn(
'flex h-[58px] flex-col items-center justify-center space-y-1 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg text-text-secondary',
selected ? 'system-xs-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs' : ' system-xs-regular cursor-pointer hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs',
selected ? 'system-xs-medium border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg shadow-xs' : 'system-xs-regular cursor-pointer hover:border-components-option-card-option-border-hover hover:bg-components-option-card-option-bg-hover hover:shadow-xs',
)}
onClick={onClick}
>

View File

@ -66,7 +66,7 @@ const VarItem: FC<ItemProps> = ({
</div>
<div
data-testid="var-item-delete-btn"
className="flex h-6 w-6 cursor-pointer items-center justify-center text-text-tertiary hover:text-text-destructive"
className="flex h-6 w-6 cursor-pointer items-center justify-center text-text-tertiary hover:text-text-destructive"
onClick={onRemove}
onMouseOver={() => setIsDeleting(true)}
onMouseLeave={() => setIsDeleting(false)}

View File

@ -100,7 +100,7 @@ const ConfigVision: FC = () => {
selected={file?.image?.detail === Resolution.high}
onSelect={noop}
className={cn(
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
file?.image?.detail !== Resolution.high && 'hover:border-components-option-card-option-border',
)}
/>
@ -109,7 +109,7 @@ const ConfigVision: FC = () => {
selected={file?.image?.detail === Resolution.low}
onSelect={noop}
className={cn(
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
'cursor-not-allowed rounded-lg px-3 hover:shadow-none',
file?.image?.detail !== Resolution.low && 'hover:border-components-option-card-option-border',
)}
/>

View File

@ -45,7 +45,7 @@ const ParamConfigContent: FC = () => {
<div className="text-base font-semibold leading-6 text-text-primary">{t('vision.visionSettings.title', { ns: 'appDebug' })}</div>
<div className="space-y-6 pt-3">
<div>
<div className="mb-2 flex items-center space-x-1">
<div className="mb-2 flex items-center space-x-1">
<div className="text-[13px] font-semibold leading-[18px] text-text-secondary">{t('vision.visionSettings.resolution', { ns: 'appDebug' })}</div>
<Tooltip
popupContent={(

View File

@ -267,7 +267,7 @@ const AgentTools: FC = () => {
needsDelay={false}
>
<div
className="cursor-pointer rounded-md p-1 hover:bg-black/5"
className="cursor-pointer rounded-md p-1 hover:bg-black/5"
onClick={() => {
setCurrentTool(item)
setIsShowSettingTool(true)

View File

@ -246,7 +246,7 @@ const SettingBuiltInTool: FC<Props> = ({
{isInfoActive ? infoUI : settingUI}
{!readonly && !isInfoActive && (
<div className="flex shrink-0 justify-end space-x-2 rounded-b-[10px] bg-components-panel-bg py-2">
<Button className="flex h-8 items-center !px-3 !text-[13px] font-medium " onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
<Button className="flex h-8 items-center !px-3 !text-[13px] font-medium" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
<Button className="flex h-8 items-center !px-3 !text-[13px] font-medium" variant="primary" disabled={!isValid} onClick={() => onSave?.(tempSetting)}>{t('operation.save', { ns: 'common' })}</Button>
</div>
)}

View File

@ -96,7 +96,7 @@ const Editor: FC<Props> = ({
)}
</div>
</div>
<div className={cn(editorHeight, ' min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}>
<div className={cn(editorHeight, 'min-h-[102px] overflow-y-auto px-4 text-sm text-gray-700')}>
<PromptEditor
className={editorHeight}
value={value}

View File

@ -45,7 +45,7 @@ const SelectItem: FC<ItemProps> = ({ text, value, Icon, isChecked, description,
onClick={() => !disabled && onClick(value)}
>
<div className="flex items-center justify-between">
<div className="flex items-center ">
<div className="flex items-center">
<div className="mr-3 rounded-lg bg-indigo-50 p-1">
<Icon className="h-4 w-4 text-indigo-600" />
</div>
@ -84,7 +84,7 @@ const AssistantTypePicker: FC<Props> = ({
<>
<div className="my-4 h-px bg-gray-100"></div>
<div
className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'rounded-xl bg-gray-50 p-3 pr-4 ')}
className={cn(isAgent ? 'group cursor-pointer hover:bg-primary-50' : 'opacity-30', 'rounded-xl bg-gray-50 p-3 pr-4')}
onClick={() => {
if (isAgent) {
setOpen(false)
@ -93,7 +93,7 @@ const AssistantTypePicker: FC<Props> = ({
}}
>
<div className="flex items-center justify-between">
<div className="flex items-center ">
<div className="flex items-center">
<div className="mr-3 rounded-lg bg-gray-200 p-1 group-hover:bg-white">
<Settings04 className="h-4 w-4 text-gray-600 group-hover:text-[#155EEF]" />
</div>

View File

@ -27,7 +27,7 @@ const IdeaOutput: FC<Props> = ({
return (
<div className="mt-4 text-[0px]">
<div
className="mb-1.5 flex cursor-pointer items-center text-sm font-medium leading-5 text-text-primary"
className="mb-1.5 flex cursor-pointer items-center text-sm font-medium leading-5 text-text-primary"
onClick={toggleFoldIdeaOutput}
>
<div className="system-sm-semibold-uppercase mr-1 text-text-secondary">{t(`${i18nPrefix}.idealOutput`, { ns: 'appDebug' })}</div>

View File

@ -243,7 +243,7 @@ export const GetCodeGeneratorResModal: FC<IGetCodeGeneratorResProps> = (
disabled={isLoading}
>
<Generator className="h-4 w-4" />
<span className="text-xs font-semibold ">{t('codegen.generate', { ns: 'appDebug' })}</span>
<span className="text-xs font-semibold">{t('codegen.generate', { ns: 'appDebug' })}</span>
</Button>
</div>
</div>

View File

@ -14,7 +14,7 @@ const ContrlBtnGroup: FC<IContrlBtnGroupProps> = ({ onSave, onReset }) => {
const { t } = useTranslation()
return (
<div className="fixed bottom-0 left-[224px] h-[64px] w-[519px]">
<div className={`${s.ctrlBtn} flex h-full items-center gap-2 bg-white pl-4`}>
<div className={`${s.ctrlBtn} flex h-full items-center gap-2 bg-white pl-4`}>
<Button variant="primary" onClick={onSave} data-testid="apply-btn">{t('operation.applyConfig', { ns: 'appDebug' })}</Button>
<Button onClick={onReset} data-testid="reset-btn">{t('operation.resetConfig', { ns: 'appDebug' })}</Button>
</div>

View File

@ -14,7 +14,7 @@ const ContextVar: FC<Props> = (props) => {
const currItem = options.find(item => item.value === value)
const notSetVar = !currItem
return (
<div className={cn(notSetVar ? 'rounded-bl-xl rounded-br-xl border-[#FEF0C7] bg-[#FEF0C7]' : 'border-components-panel-border-subtle', 'flex h-12 items-center justify-between border-t px-3 ')}>
<div className={cn(notSetVar ? 'rounded-bl-xl rounded-br-xl border-[#FEF0C7] bg-[#FEF0C7]' : 'border-components-panel-border-subtle', 'flex h-12 items-center justify-between border-t px-3')}>
<div className="flex shrink-0 items-center space-x-1">
<div className="p-1">
<BracketsX className="h-4 w-4 text-text-accent" />

View File

@ -57,11 +57,11 @@ const VarPicker: FC<Props> = ({
<PortalToFollowElemTrigger className={cn(triggerClassName)} onClick={() => setOpen(v => !v)}>
<div className={cn(
className,
notSetVar ? 'border-[#FEDF89] bg-[#FFFCF5] text-[#DC6803]' : ' border-components-button-secondary-border text-text-accent hover:bg-components-button-secondary-bg',
notSetVar ? 'border-[#FEDF89] bg-[#FFFCF5] text-[#DC6803]' : 'border-components-button-secondary-border text-text-accent hover:bg-components-button-secondary-bg',
open ? 'bg-components-button-secondary-bg' : 'bg-transparent',
`
flex h-8 cursor-pointer items-center justify-center space-x-1 rounded-lg border px-2 text-[13px]
font-medium shadow-xs
flex h-8 cursor-pointer items-center justify-center space-x-1 rounded-lg border px-2 text-[13px]
font-medium shadow-xs
`,
)}
>
@ -82,7 +82,7 @@ const VarPicker: FC<Props> = ({
<PortalToFollowElemContent style={{ zIndex: 1000 }}>
{options.length > 0
? (
<div className="max-h-[50vh] w-[240px] overflow-y-auto rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg">
<div className="max-h-[50vh] w-[240px] overflow-y-auto rounded-lg border border-components-panel-border bg-components-panel-bg p-1 shadow-lg">
{options.map(({ name, value, type }, index) => (
<div
key={index}

View File

@ -126,7 +126,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
{hasNoData && (
<div
className="mt-6 flex h-[128px] items-center justify-center space-x-1 rounded-lg border text-[13px]"
className="mt-6 flex h-[128px] items-center justify-center space-x-1 rounded-lg border text-[13px]"
style={{
background: 'rgba(0, 0, 0, 0.02)',
borderColor: 'rgba(0, 0, 0, 0.02',
@ -195,7 +195,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({
)}
{!isLoading && (
<div className="mt-8 flex items-center justify-between">
<div className="text-sm font-medium text-text-secondary">
<div className="text-sm font-medium text-text-secondary">
{selected.length > 0 && `${selected.length} ${t('feature.dataSet.selected', { ns: 'appDebug' })}`}
</div>
<div className="flex space-x-2">

View File

@ -1031,8 +1031,8 @@ const Configuration: FC = () => {
<Config />
</div>
{!isMobile && (
<div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className="flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg ">
<div className="relative flex h-full w-1/2 grow flex-col overflow-y-auto" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className="flex grow flex-col rounded-tl-2xl border-l-[0.5px] border-t-[0.5px] border-components-panel-border bg-chatbot-bg">
<Debug
isAPIKeySet={isAPIKeySet}
onSetting={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MODEL_PROVIDER })}

View File

@ -217,7 +217,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
<AppIcon
size="large"
onClick={() => { setShowEmojiPicker(true) }}
className="!h-9 !w-9 cursor-pointer rounded-lg border-[0.5px] border-components-panel-border "
className="!h-9 !w-9 cursor-pointer rounded-lg border-[0.5px] border-components-panel-border"
icon={localeData.icon}
background={localeData.icon_background}
/>

View File

@ -130,7 +130,7 @@ const Tools = () => {
className="flex h-7 cursor-pointer items-center px-3 text-xs font-medium text-gray-700"
onClick={() => handleOpenExternalDataToolModal({}, -1)}
>
<RiAddLine className="mr-[5px] h-3.5 w-3.5 " />
<RiAddLine className="mr-[5px] h-3.5 w-3.5" />
{t('operation.add', { ns: 'common' })}
</div>
</div>

View File

@ -34,7 +34,7 @@ const AppCard = ({
}
}, [setShowTryAppPanel, app.category])
return (
<div className={cn('group relative flex h-[132px] cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs hover:shadow-lg')}>
<div className={cn('group relative flex h-[132px] cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 shadow-xs hover:shadow-lg')}>
<div className="flex shrink-0 grow-0 items-center gap-3 pb-2">
<div className="relative shrink-0">
<AppIcon

View File

@ -128,7 +128,7 @@ const Uploader: FC<Props> = ({
</div>
)}
{file && (
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', ' hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className={cn('group flex items-center rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', 'hover:bg-components-panel-on-panel-item-bg-hover')}>
<div className="flex items-center justify-center p-3">
<YamlIcon className="h-6 w-6 shrink-0" />
</div>

View File

@ -29,7 +29,7 @@ const APIKeyInfoPanel: FC = () => {
return null
return (
<div className={cn('border-components-panel-border bg-components-panel-bg', 'relative mb-6 rounded-2xl border p-8 shadow-md ')}>
<div className={cn('border-components-panel-border bg-components-panel-bg', 'relative mb-6 rounded-2xl border p-8 shadow-md')}>
<div className={cn('text-[24px] font-semibold text-text-primary', isCloud ? 'flex h-8 items-center space-x-1' : 'mb-6 leading-8')}>
{isCloud && <em-emoji id="😀" />}
{isCloud
@ -56,7 +56,7 @@ const APIKeyInfoPanel: FC = () => {
</Button>
{!isCloud && (
<a
className="mt-2 flex h-[26px] items-center space-x-1 p-1 text-xs font-medium text-[#155EEF]"
className="mt-2 flex h-[26px] items-center space-x-1 p-1 text-xs font-medium text-[#155EEF]"
href="https://cloud.dify.ai/apps"
target="_blank"
rel="noopener noreferrer"
@ -67,7 +67,7 @@ const APIKeyInfoPanel: FC = () => {
)}
<div
onClick={() => setIsShow(false)}
className="absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center "
className="absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center"
>
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
</div>

View File

@ -321,7 +321,7 @@ function AppCard({
<div className="flex flex-col items-start justify-center self-stretch">
<div className="system-xs-medium pb-1 text-text-tertiary">{t('publishApp.title', { ns: 'app' })}</div>
<div
className="flex h-9 w-full cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2"
className="flex h-9 w-full cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal py-1 pl-2.5 pr-2"
onClick={handleClickAccessControl}
>
<div className="flex grow items-center gap-x-1.5 pr-1">

View File

@ -170,7 +170,7 @@ const Embedded = ({ siteInfo, isShow, onClose, appBaseUrl, accessToken, classNam
</div>
)}
<div className={cn('inline-flex w-full flex-col items-start justify-start rounded-lg border-[0.5px] border-components-panel-border bg-background-section', 'mt-6')}>
<div className="inline-flex items-center justify-start gap-2 self-stretch rounded-t-lg bg-background-section-burn py-1 pl-3 pr-1">
<div className="inline-flex items-center justify-start gap-2 self-stretch rounded-t-lg bg-background-section-burn py-1 pl-3 pr-1">
<div className="system-sm-medium shrink-0 grow text-text-secondary">
{t(`${prefixEmbedded}.${option}`, { ns: 'appOverview' })}
</div>

View File

@ -25,7 +25,7 @@ const ResultTab = ({
<div className="flex flex-col gap-2">
{data?.files.map((item: any) => (
<div key={item.varName} className="system-xs-regular flex flex-col gap-1">
<div className="py-1 text-text-tertiary ">{item.varName}</div>
<div className="py-1 text-text-tertiary">{item.varName}</div>
<FileList
files={item.list}
showDeleteAction={false}

View File

@ -18,7 +18,7 @@ const NoData: FC<INoDataProps> = ({
const { t } = useTranslation()
return (
<div className="rounded-xl bg-background-section-burn p-6 ">
<div className="rounded-xl bg-background-section-burn p-6">
<div className="flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg-alt shadow-lg backdrop-blur-sm">
<RiBookmark3Line className="h-4 w-4 text-text-accent" />
</div>

View File

@ -35,7 +35,7 @@ const Alert: React.FC<Props> = ({
<div
className="relative flex space-x-1 overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg-blur p-3 shadow-lg"
>
<div className={cn('pointer-events-none absolute inset-0 bg-gradient-to-r opacity-[0.4]', bgVariants({ type }))}>
<div className={cn('pointer-events-none absolute inset-0 bg-gradient-to-r opacity-[0.4]', bgVariants({ type }))}>
</div>
<div className="flex h-6 w-6 items-center justify-center">
<RiInformation2Fill className="text-text-accent" />

View File

@ -26,17 +26,17 @@ export type AppIconProps = {
onClick?: () => void
}
const appIconVariants = cva(
'flex items-center justify-center relative grow-0 shrink-0 overflow-hidden leading-none border-[0.5px] border-divider-regular',
'relative flex shrink-0 grow-0 items-center justify-center overflow-hidden border-[0.5px] border-divider-regular leading-none',
{
variants: {
size: {
xs: 'w-4 h-4 text-xs rounded-[4px]',
tiny: 'w-6 h-6 text-base rounded-md',
small: 'w-8 h-8 text-xl rounded-lg',
medium: 'w-9 h-9 text-[22px] rounded-[10px]',
large: 'w-10 h-10 text-[24px] rounded-[10px]',
xl: 'w-12 h-12 text-[28px] rounded-xl',
xxl: 'w-14 h-14 text-[32px] rounded-2xl',
xs: 'h-4 w-4 rounded-[4px] text-xs',
tiny: 'h-6 w-6 rounded-md text-base',
small: 'h-8 w-8 rounded-lg text-xl',
medium: 'h-9 w-9 rounded-[10px] text-[22px]',
large: 'h-10 w-10 rounded-[10px] text-[24px]',
xl: 'h-12 w-12 rounded-xl text-[28px]',
xxl: 'h-14 w-14 rounded-2xl text-[32px]',
},
rounded: {
true: 'rounded-full',
@ -53,13 +53,13 @@ const EditIconWrapperVariants = cva(
{
variants: {
size: {
xs: 'w-4 h-4 rounded-[4px]',
tiny: 'w-6 h-6 rounded-md',
small: 'w-8 h-8 rounded-lg',
medium: 'w-9 h-9 rounded-[10px]',
large: 'w-10 h-10 rounded-[10px]',
xl: 'w-12 h-12 rounded-xl',
xxl: 'w-14 h-14 rounded-2xl',
xs: 'h-4 w-4 rounded-[4px]',
tiny: 'h-6 w-6 rounded-md',
small: 'h-8 w-8 rounded-lg',
medium: 'h-9 w-9 rounded-[10px]',
large: 'h-10 w-10 rounded-[10px]',
xl: 'h-12 w-12 rounded-xl',
xxl: 'h-14 w-14 rounded-2xl',
},
rounded: {
true: 'rounded-full',

View File

@ -69,7 +69,7 @@ const AutoHeightTextarea = (
(
<div className={`relative ${wrapperClassName}`}>
<div
className={cn(className, 'invisible overflow-y-auto whitespace-pre-wrap break-all')}
className={cn(className, 'invisible overflow-y-auto whitespace-pre-wrap break-all')}
style={{
minHeight,
maxHeight,

View File

@ -63,7 +63,7 @@ const BlockInput: FC<IBlockInputProps> = ({
}, [isEditing])
const style = cn({
'block px-4 py-2 w-full h-full text-sm text-gray-900 outline-0 border-0 break-all': true,
'block h-full w-full break-all border-0 px-4 py-2 text-sm text-gray-900 outline-0': true,
'block-input--editing': isEditing,
})
@ -121,7 +121,7 @@ const BlockInput: FC<IBlockInputProps> = ({
const editAreaClassName = 'focus:outline-none bg-transparent text-sm'
const textAreaContent = (
<div className={cn(readonly ? 'max-h-[180px] pb-5' : 'h-[180px]', ' overflow-y-auto')} onClick={() => !readonly && setIsEditing(true)}>
<div className={cn(readonly ? 'max-h-[180px] pb-5' : 'h-[180px]', 'overflow-y-auto')} onClick={() => !readonly && setIsEditing(true)}>
{isEditing
? (
<div className="h-full px-4 py-2">

View File

@ -46,7 +46,7 @@ const Operation: FC<Props> = ({
>
<div className={cn('flex cursor-pointer items-center rounded-lg p-1.5 pl-2 text-text-secondary hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
<div className="system-md-semibold">{title}</div>
<RiArrowDownSLine className="h-4 w-4 " />
<RiArrowDownSLine className="h-4 w-4" />
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-50">

View File

@ -7,8 +7,8 @@ import { cn } from '@/utils/classnames'
const dividerVariants = cva('', {
variants: {
type: {
horizontal: 'w-full h-[0.5px] my-2 ',
vertical: 'w-[1px] h-full mx-2',
horizontal: 'my-2 h-[0.5px] w-full',
vertical: 'mx-2 h-full w-[1px]',
},
bgStyle: {
gradient: 'bg-gradient-to-r from-divider-regular to-background-gradient-mask-transparent',

View File

@ -1,4 +1,3 @@
/* eslint-disable tailwindcss/classnames-order */
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import Effect from '.'
@ -29,8 +28,8 @@ type Story = StoryObj<typeof meta>
export const Playground: Story = {
render: () => (
<div className="relative h-40 w-72 overflow-hidden rounded-2xl border border-divider-subtle bg-background-default-subtle">
<Effect className="top-6 left-8" />
<Effect className="top-14 right-10 bg-util-colors-purple-brand-purple-brand-500" />
<Effect className="left-8 top-6" />
<Effect className="bg-util-colors-purple-brand-purple-brand-500 right-10 top-14" />
<div className="absolute inset-x-0 bottom-4 flex justify-center text-xs text-text-secondary">
Accent glow
</div>

View File

@ -175,7 +175,7 @@ const OpeningSettingModal = ({
{tempSuggestedQuestions.length < MAX_QUESTION_NUM && (
<div
onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }}
className="mt-1 flex h-9 cursor-pointer items-center gap-2 rounded-lg bg-components-button-tertiary-bg px-3 text-components-button-tertiary-text hover:bg-components-button-tertiary-bg-hover"
className="mt-1 flex h-9 cursor-pointer items-center gap-2 rounded-lg bg-components-button-tertiary-bg px-3 text-components-button-tertiary-text hover:bg-components-button-tertiary-bg-hover"
>
<RiAddLine className="h-4 w-4" />
<div className="system-sm-medium text-[13px]">{t('variableConfig.addOption', { ns: 'appDebug' })}</div>

View File

@ -38,7 +38,7 @@ const DialogWrapper = ({
<DialogPanel className={cn(
'relative flex h-0 w-[420px] grow flex-col overflow-hidden border-components-panel-border bg-components-panel-bg-alt p-0 text-left align-middle shadow-xl transition-all',
inWorkflow ? 'rounded-l-2xl border-b-[0.5px] border-l-[0.5px] border-t-[0.5px]' : 'rounded-2xl border-[0.5px]',
'data-[closed]:scale-95 data-[closed]:opacity-0',
'data-[closed]:scale-95 data-[closed]:opacity-0',
'data-[enter]:scale-100 data-[enter]:opacity-100 data-[enter]:duration-300 data-[enter]:ease-out',
'data-[leave]:scale-95 data-[leave]:opacity-0 data-[leave]:duration-200 data-[leave]:ease-in',
className,

View File

@ -9,7 +9,7 @@ import Tooltip from '../tooltip'
import ImageRender from './image-render'
const FileThumbVariants = cva(
'flex items-center justify-center cursor-pointer',
'flex cursor-pointer items-center justify-center',
{
variants: {
size: {

View File

@ -86,7 +86,7 @@ const FileListInLog = ({ fileList, isExpanded = false, noBorder = false, noPaddi
<div className="flex flex-col gap-3">
{fileList.map(item => (
<div key={item.varName} className="system-xs-regular flex flex-col gap-1">
<div className="py-1 text-text-tertiary ">{item.varName}</div>
<div className="py-1 text-text-tertiary">{item.varName}</div>
{item.list.map(file => (
<FileItem
key={file.id}

View File

@ -82,7 +82,7 @@ const FileImageItem = ({
showDownloadAction && (
<div className="absolute inset-0.5 z-10 hidden bg-background-overlay-alt bg-opacity-[0.3] group-hover/file-image:block">
<div
className="absolute bottom-0.5 right-0.5 flex h-6 w-6 items-center justify-center rounded-lg bg-components-actionbar-bg shadow-md"
className="absolute bottom-0.5 right-0.5 flex h-6 w-6 items-center justify-center rounded-lg bg-components-actionbar-bg shadow-md"
onClick={(e) => {
e.stopPropagation()
downloadUrl({ url: download_url || '', fileName: name, target: '_blank' })

View File

@ -13,8 +13,8 @@ export const inputVariants = cva(
{
variants: {
size: {
regular: 'px-3 radius-md system-sm-regular',
large: 'px-4 radius-lg system-md-regular',
regular: 'radius-md system-sm-regular px-3',
large: 'radius-lg system-md-regular px-4',
},
},
defaultVariants: {

View File

@ -32,7 +32,7 @@ const LikedItem = ({
<div className={cn('relative h-6 w-6 rounded-md')}>
<AppIcon size="tiny" iconType={detail.icon_type} icon={detail.icon} background={detail.icon_background} imageUrl={detail.icon_url} />
</div>
{!isMobile && <div className={cn(' system-sm-medium ml-2 truncate text-text-primary')}>{detail?.name || '--'}</div>}
{!isMobile && <div className={cn('system-sm-medium ml-2 truncate text-text-primary')}>{detail?.name || '--'}</div>}
</div>
<div className="system-2xs-medium-uppercase shrink-0 text-text-tertiary group-hover/link-item:hidden">{appTypeMap[detail.mode]}</div>
<RiArrowRightUpLine className="hidden h-4 w-4 text-text-tertiary group-hover/link-item:block" />

View File

@ -484,8 +484,8 @@ const Flowchart = (props: FlowchartProps) => {
'text-gray-300': currentTheme === Theme.dark,
}),
themeToggle: cn('flex h-10 w-10 items-center justify-center rounded-full shadow-md backdrop-blur-sm transition-all duration-300', {
'bg-white/80 hover:bg-white hover:shadow-lg text-gray-700 border border-gray-200': currentTheme === Theme.light,
'bg-slate-800/80 hover:bg-slate-700 hover:shadow-lg text-yellow-300 border border-slate-600': currentTheme === Theme.dark,
'border border-gray-200 bg-white/80 text-gray-700 hover:bg-white hover:shadow-lg': currentTheme === Theme.light,
'border border-slate-600 bg-slate-800/80 text-yellow-300 hover:bg-slate-700 hover:shadow-lg': currentTheme === Theme.dark,
}),
}

View File

@ -13,7 +13,7 @@ export enum NodeStatusEnum {
}
const nodeStatusVariants = cva(
'flex items-center gap-1 rounded-md px-2 py-1 system-xs-medium',
'system-xs-medium flex items-center gap-1 rounded-md px-2 py-1',
{
variants: {
status: {

View File

@ -23,7 +23,7 @@ export const PromptMenuItem = memo(({
className={`
flex h-6 cursor-pointer items-center rounded-md px-3 hover:bg-state-base-hover
${isSelected && !disabled && '!bg-state-base-hover'}
${disabled ? 'cursor-not-allowed opacity-30' : 'cursor-pointer hover:bg-state-base-hover'}
${disabled ? 'cursor-not-allowed opacity-30' : ''}
`}
tabIndex={-1}
ref={setRefElement}

View File

@ -44,7 +44,7 @@ const ContextBlockComponent: FC<ContextBlockComponentProps> = ({
<div
className={`
group inline-flex h-6 items-center rounded-[5px] border border-transparent bg-[#F4F3FF] pl-1 pr-0.5 text-[#6938EF] hover:bg-[#EBE9FE]
${open ? 'bg-[#EBE9FE]' : 'bg-[#F4F3FF]'}
${open ? 'bg-[#EBE9FE]' : ''}
${isSelected && '!border-[#9B8AFB]'}
`}
ref={ref}

View File

@ -29,7 +29,7 @@ const CurrentBlockComponent: FC<CurrentBlockComponentProps> = ({
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-util-colors-violet-violet-600 hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
isSelected ? 'border-state-accent-solid bg-state-accent-hover' : 'border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()

View File

@ -25,7 +25,7 @@ const ErrorMessageBlockComponent: FC<Props> = ({
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-util-colors-orange-dark-orange-dark-600 hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
isSelected ? 'border-state-accent-solid bg-state-accent-hover' : 'border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()

View File

@ -25,7 +25,7 @@ const LastRunBlockComponent: FC<Props> = ({
<div
className={cn(
'group/wrap relative mx-0.5 flex h-[18px] select-none items-center rounded-[5px] border pl-0.5 pr-[3px] text-text-accent hover:border-state-accent-solid hover:bg-state-accent-hover',
isSelected ? ' border-state-accent-solid bg-state-accent-hover' : ' border-components-panel-border-subtle bg-components-badge-white-to-dark',
isSelected ? 'border-state-accent-solid bg-state-accent-hover' : 'border-components-panel-border-subtle bg-components-badge-white-to-dark',
)}
onClick={(e) => {
e.stopPropagation()

View File

@ -36,7 +36,7 @@ export default function Select({
leaveTo="transform opacity-0 scale-95"
>
<MenuItems className="absolute right-0 z-10 mt-2 w-[200px] origin-top-right divide-y divide-divider-regular rounded-md bg-components-panel-bg shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 ">
<div className="px-1 py-1">
{items.map((item) => {
return (
<MenuItem key={item.value}>

View File

@ -33,7 +33,7 @@ const Slider: React.FC<ISliderProps> = ({
max={max || 100}
step={step || 1}
className={cn('slider relative', className)}
thumbClassName={cn('absolute top-[-9px] h-5 w-2 rounded-[3px] border-[0.5px] border-components-slider-knob-border bg-components-slider-knob shadow-sm focus:outline-none', !disabled && 'cursor-pointer', thumbClassName)}
thumbClassName={cn('absolute top-[-9px] h-5 w-2 rounded-[3px] border-[0.5px] border-components-slider-knob-border bg-components-slider-knob shadow-sm focus:outline-none', !disabled && 'cursor-pointer', thumbClassName)}
trackClassName={cn('h-0.5 rounded-full', 'slider-track', trackClassName)}
onChange={onChange}
/>

View File

@ -61,7 +61,7 @@ const Switch = (
setEnabled(checked)
onChange?.(checked)
}}
className={cn(wrapStyle[size], enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked', 'relative inline-flex shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out', disabled ? '!cursor-not-allowed !opacity-50' : '', size === 'xs' && 'rounded-sm', className)}
className={cn(wrapStyle[size], enabled ? 'bg-components-toggle-bg' : 'bg-components-toggle-bg-unchecked', 'relative inline-flex shrink-0 cursor-pointer rounded-[5px] border-2 border-transparent transition-colors duration-200 ease-in-out', disabled ? '!cursor-not-allowed !opacity-50' : '', size === 'xs' && 'rounded-sm', className)}
>
<span
aria-hidden="true"

View File

@ -26,7 +26,7 @@ const Item: FC<ItemProps> = ({
<div
key={option.value}
className={cn(
'relative pb-2.5 ',
'relative pb-2.5',
!isActive && 'cursor-pointer',
smallItem ? 'system-sm-semibold-uppercase' : 'system-xl-semibold',
className,
@ -61,7 +61,7 @@ const TabSlider: FC<Props> = ({
smallItem,
}) => {
return (
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
<div className={cn(className, !noBorderBottom && 'border-b border-divider-subtle', 'flex space-x-6')}>
{options.map(option => (
<Item
isActive={option.value === value}

View File

@ -70,7 +70,7 @@ const TagManagementModal = ({ show, type }: TagManagementModalProps) => {
</div>
<div className="mt-3 flex flex-wrap gap-2">
<input
className="w-[100px] shrink-0 appearance-none rounded-lg border border-dashed border-divider-regular bg-transparent px-2 py-1 text-sm leading-5 text-text-secondary caret-primary-600 outline-none placeholder:text-text-quaternary focus:border-solid"
className="w-[100px] shrink-0 appearance-none rounded-lg border border-dashed border-divider-regular bg-transparent px-2 py-1 text-sm leading-5 text-text-secondary caret-primary-600 outline-none placeholder:text-text-quaternary focus:border-solid"
placeholder={t('tag.addNew', { ns: 'common' }) || ''}
autoFocus
value={name}

View File

@ -9,9 +9,9 @@ const textareaVariants = cva(
{
variants: {
size: {
small: 'py-1 rounded-md system-xs-regular',
regular: 'px-3 rounded-md system-sm-regular',
large: 'px-4 rounded-lg system-md-regular',
small: 'system-xs-regular rounded-md py-1',
regular: 'system-sm-regular rounded-md px-3',
large: 'system-md-regular rounded-lg px-4',
},
},
defaultVariants: {

View File

@ -191,7 +191,7 @@ const VoiceInput = ({
{
startRecord && (
<div
className="mr-1 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-primary-100"
className="mr-1 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-primary-100"
onClick={handleStopRecorder}
>
<StopCircle className="h-5 w-5 text-primary-600" />
@ -201,7 +201,7 @@ const VoiceInput = ({
{
startConvert && (
<div
className="mr-1 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-gray-200"
className="mr-1 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-gray-200"
onClick={onCancel}
>
<RiCloseLine className="h-4 w-4 text-gray-500" />

View File

@ -82,7 +82,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
{/* Noise Effect */}
{STYLE_MAP[plan].noise}
<div className="flex flex-col px-5 py-4">
<div className=" flex flex-col gap-y-6 px-1 pt-10">
<div className="flex flex-col gap-y-6 px-1 pt-10">
{STYLE_MAP[plan].icon}
<div className="flex min-h-[104px] flex-col gap-y-2">
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`, { ns: 'billing' })}</div>

View File

@ -293,7 +293,7 @@ const CustomWebAppBrand = () => {
<div className="mb-1 py-2">
<div className="h-2 w-20 rounded-sm bg-text-quaternary opacity-20"></div>
</div>
<div className="h-16 w-full rounded-lg bg-components-input-bg-normal "></div>
<div className="h-16 w-full rounded-lg bg-components-input-bg-normal"></div>
</div>
<div className="flex items-center justify-between px-4 py-3">
<Button size="small">

View File

@ -19,7 +19,7 @@ const Header = () => {
variant="secondary-accent"
className="size-9 rounded-full p-0"
>
<RiArrowLeftLine className="size-5 " />
<RiArrowLeftLine className="size-5" />
</Button>
</Link>
</div>

View File

@ -15,7 +15,7 @@ const UpgradeCard: FC = () => {
}, [setShowPricingModal])
return (
<div className="flex items-center justify-between rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg py-3 pl-4 pr-3.5 shadow-xs backdrop-blur-[5px] ">
<div className="flex items-center justify-between rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg py-3 pl-4 pr-3.5 shadow-xs backdrop-blur-[5px]">
<div>
<div className="title-md-semi-bold bg-[linear-gradient(92deg,_var(--components-input-border-active-prompt-1,_#0BA5EC)_0%,_var(--components-input-border-active-prompt-2,_#155AEF)_99.21%)] bg-clip-text text-transparent">{t('upgrade.uploadMultipleFiles.title', { ns: 'billing' })}</div>
<div className="system-xs-regular text-text-tertiary">{t('upgrade.uploadMultipleFiles.description', { ns: 'billing' })}</div>

View File

@ -32,7 +32,7 @@ const Options: FC<Props> = ({
}
}, [payload, onChange])
return (
<div className={cn(className, ' space-y-2')}>
<div className={cn(className, 'space-y-2')}>
<CheckboxWithLabel
label={t(`${I18N_PREFIX}.crawlSubPage`, { ns: 'datasetCreation' })}
isChecked={payload.crawl_sub_pages}

View File

@ -32,7 +32,7 @@ const Options: FC<Props> = ({
}
}, [payload, onChange])
return (
<div className={cn(className, ' space-y-2')}>
<div className={cn(className, 'space-y-2')}>
<CheckboxWithLabel
label={t(`${I18N_PREFIX}.crawlSubPage`, { ns: 'datasetCreation' })}
isChecked={payload.crawl_sub_pages}

View File

@ -32,7 +32,7 @@ const Options: FC<Props> = ({
}
}, [payload, onChange])
return (
<div className={cn(className, ' space-y-2')}>
<div className={cn(className, 'space-y-2')}>
<CheckboxWithLabel
label={t(`${I18N_PREFIX}.crawlSubPage`, { ns: 'datasetCreation' })}
isChecked={payload.crawl_sub_pages}

View File

@ -14,7 +14,6 @@ const ErrorMessage = ({
errorMsg,
}: ErrorMessageProps) => {
return (
// eslint-disable-next-line tailwindcss/migration-from-tailwind-2
<div className={cn(
'flex gap-x-0.5 rounded-xl border-[0.5px] border-components-panel-border bg-opacity-40 bg-toast-error-bg p-2 shadow-xs shadow-shadow-shadow-3',
className,

View File

@ -41,7 +41,7 @@ const LeftHeader = ({
variant="secondary-accent"
className="absolute -left-11 top-3.5 size-9 rounded-full p-0"
>
<RiArrowLeftLine className="size-5 " />
<RiArrowLeftLine className="size-5" />
</Button>
</Link>
)}

View File

@ -22,7 +22,7 @@ const WebsitePreview = ({
<div className="flex grow flex-col gap-y-1">
<div className="system-2xs-semibold-uppercase">{t('addDocuments.stepOne.preview', { ns: 'datasetPipeline' })}</div>
<div className="title-md-semi-bold text-tex-primary">{currentWebsite.title}</div>
<div className="system-xs-medium flex gap-x-1 text-text-tertiary">
<div className="system-xs-medium flex gap-x-1 text-text-tertiary">
<RiGlobalLine className="size-3.5" />
<span className="uppercase" title={currentWebsite.source_url}>{currentWebsite.source_url}</span>
<span>·</span>

View File

@ -204,7 +204,7 @@ const CSVUploader: FC<Props> = ({
/>
<div ref={dropRef}>
{!file && (
<div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}>
<div className={cn('flex h-20 items-center rounded-xl border border-dashed border-components-panel-border bg-components-panel-bg-blur text-sm font-normal', dragging && 'border border-divider-subtle bg-components-panel-on-panel-item-bg-hover')}>
<div className="flex w-full items-center justify-center space-x-2">
<CSVIcon className="shrink-0" />
<div className="text-text-secondary">

View File

@ -34,7 +34,7 @@ const LeftHeader = ({
onClick={navigateBack}
aria-label={t('operation.back', { ns: 'common' })}
>
<RiArrowLeftLine className="size-5 " />
<RiArrowLeftLine className="size-5" />
</Button>
<Effect className="left-8 top-[-34px] opacity-20" />
</div>

View File

@ -20,7 +20,7 @@ const ChildChunks: FC<Props> = ({
className={!isShowAll ? 'line-clamp-2 break-all' : ''}
>
<div className="relative top-[-2px] inline-flex items-center">
<div className="system-2xs-semibold-uppercase flex h-[20.5px] items-center bg-state-accent-solid px-1 text-text-primary-on-surface">
<div className="system-2xs-semibold-uppercase flex h-[20.5px] items-center bg-state-accent-solid px-1 text-text-primary-on-surface">
C-
{position}
</div>

View File

@ -47,11 +47,11 @@ const Records = ({
return (
<div className="grow overflow-y-auto">
<table className="w-full border-collapse border-0 text-[13px] leading-4 text-text-secondary ">
<thead className="sticky top-0 h-7 text-xs font-medium uppercase leading-7 text-text-tertiary backdrop-blur-[5px]">
<table className="w-full border-collapse border-0 text-[13px] leading-4 text-text-secondary">
<thead className="sticky top-0 h-7 text-xs font-medium uppercase leading-7 text-text-tertiary backdrop-blur-[5px]">
<tr>
<td className="rounded-l-lg bg-background-section-burn pl-3">{t('table.header.queryContent', { ns: 'datasetHitTesting' })}</td>
<td className="w-[128px] bg-background-section-burn pl-3">{t('table.header.source', { ns: 'datasetHitTesting' })}</td>
<td className="w-[128px] bg-background-section-burn pl-3">{t('table.header.source', { ns: 'datasetHitTesting' })}</td>
<td className="w-48 rounded-r-lg bg-background-section-burn pl-3">
<div
className="flex cursor-pointer items-center"

View File

@ -49,7 +49,7 @@ const EditMetadatabatchItem: FC<Props> = ({
className={
cn(
'cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive',
isDeleted && 'cursor-default bg-state-destructive-hover text-text-destructive',
isDeleted && 'cursor-default bg-state-destructive-hover text-text-destructive',
)
}
onClick={() => onRemove(payload.id)}

View File

@ -22,7 +22,7 @@ const InputHasSetMultipleValue: FC<Props> = ({
{!readOnly && (
<div className="cursor-pointer rounded-[4px] p-px text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary">
<RiCloseLine
className="size-3.5 "
className="size-3.5"
onClick={onClear}
/>
</div>

View File

@ -75,7 +75,7 @@ const Item: FC<ItemProps> = ({
>
<div
className={cn(
'flex h-8 items-center justify-between px-2',
'flex h-8 items-center justify-between px-2',
disabled && 'opacity-30', // not include border and bg
)}
>

View File

@ -47,7 +47,7 @@ const SelectMetadata: FC<Props> = ({
return (
<div
key={item.id}
className="mx-1 flex h-6 cursor-pointer items-center justify-between rounded-md px-3 hover:bg-state-base-hover"
className="mx-1 flex h-6 cursor-pointer items-center justify-between rounded-md px-3 hover:bg-state-base-hover"
onClick={() => onSelect({
id: item.id,
name: item.name,
@ -70,7 +70,7 @@ const SelectMetadata: FC<Props> = ({
<RiAddLine className="size-3.5" />
<div className="system-sm-medium">{t(`${i18nPrefix}.newAction`, { ns: 'dataset' })}</div>
</div>
<div className="flex h-6 items-center text-text-secondary ">
<div className="flex h-6 items-center text-text-secondary">
<div className="mr-[3px] h-3 w-px bg-divider-regular"></div>
<div className="flex h-full cursor-pointer items-center rounded-md px-1.5 hover:bg-state-base-hover" onClick={onManage}>
<div className="system-sm-medium mr-1">{t(`${i18nPrefix}.manageAction`, { ns: 'dataset' })}</div>

View File

@ -85,7 +85,7 @@ const InfoGroup: FC<Props> = ({
onSave={data => onAdd?.(data)}
onManage={handleMangeMetadata}
/>
{list.length > 0 && <Divider className="my-3 " bgStyle="gradient" />}
{list.length > 0 && <Divider className="my-3" bgStyle="gradient" />}
</div>
)}
{list.map((item, i) => (
@ -99,7 +99,7 @@ const InfoGroup: FC<Props> = ({
value={item.value}
onChange={value => onChange?.({ ...item, value })}
/>
<div className="shrink-0 cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive">
<div className="shrink-0 cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive">
<RiDeleteBinLine className="size-4" onClick={() => onDelete?.(item)} />
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More