diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 65f413af85..82ba95444f 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -26,6 +26,7 @@ jobs: - name: ast-grep run: | uvx --from ast-grep-cli sg --pattern 'db.session.query($WHATEVER).filter($HERE)' --rewrite 'db.session.query($WHATEVER).where($HERE)' -l py --update-all + uvx --from ast-grep-cli sg --pattern 'session.query($WHATEVER).filter($HERE)' --rewrite 'session.query($WHATEVER).where($HERE)' -l py --update-all - name: mdformat run: | uvx mdformat . diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 7cd43d2a97..b6c9131c08 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -44,21 +44,10 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' run: uv sync --project api --dev - - name: Ruff check - if: steps.changed-files.outputs.any_changed == 'true' - run: | - uv run --directory api ruff --version - uv run --directory api ruff check ./ - uv run --directory api ruff format --check ./ - - name: Dotenv check if: steps.changed-files.outputs.any_changed == 'true' run: uv run --project api dotenv-linter ./api/.env.example ./web/.env.example - - name: Lint hints - if: failure() - run: echo "Please run 'dev/reformat' to fix the fixable linting errors." - web-style: name: Web Style runs-on: ubuntu-latest diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index fd86191a07..f0605a37f9 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -130,7 +130,7 @@ class MessageFeedbackApi(Resource): message_id = str(args["message_id"]) - message = db.session.query(Message).filter(Message.id == message_id, Message.app_id == app_model.id).first() + message = db.session.query(Message).where(Message.id == message_id, Message.app_id == app_model.id).first() if not message: raise NotFound("Message Not Exists.") diff --git a/api/core/app/app_config/features/more_like_this/manager.py b/api/core/app/app_config/features/more_like_this/manager.py index 496e1beeec..5d5c5ffd7f 100644 --- a/api/core/app/app_config/features/more_like_this/manager.py +++ b/api/core/app/app_config/features/more_like_this/manager.py @@ -1,3 +1,16 @@ +from pydantic import BaseModel, ConfigDict, Field, ValidationError + + +class MoreLikeThisConfig(BaseModel): + enabled: bool = False + model_config = ConfigDict(extra="allow") + + +class AppConfigModel(BaseModel): + more_like_this: MoreLikeThisConfig = Field(default_factory=MoreLikeThisConfig) + model_config = ConfigDict(extra="allow") + + class MoreLikeThisConfigManager: @classmethod def convert(cls, config: dict) -> bool: @@ -6,31 +19,14 @@ class MoreLikeThisConfigManager: :param config: model config args """ - more_like_this = False - more_like_this_dict = config.get("more_like_this") - if more_like_this_dict: - if more_like_this_dict.get("enabled"): - more_like_this = True - - return more_like_this + validated_config, _ = cls.validate_and_set_defaults(config) + return AppConfigModel.model_validate(validated_config).more_like_this.enabled @classmethod def validate_and_set_defaults(cls, config: dict) -> tuple[dict, list[str]]: - """ - Validate and set defaults for more like this feature - - :param config: app model config args - """ - if not config.get("more_like_this"): - config["more_like_this"] = {"enabled": False} - - if not isinstance(config["more_like_this"], dict): - raise ValueError("more_like_this must be of dict type") - - if "enabled" not in config["more_like_this"] or not config["more_like_this"]["enabled"]: - config["more_like_this"]["enabled"] = False - - if not isinstance(config["more_like_this"]["enabled"], bool): - raise ValueError("enabled in more_like_this must be of boolean type") - - return config, ["more_like_this"] + try: + return AppConfigModel.model_validate(config).model_dump(), ["more_like_this"] + except ValidationError as e: + raise ValueError( + "more_like_this must be of dict type and enabled in more_like_this must be of boolean type" + ) diff --git a/api/schedule/check_upgradable_plugin_task.py b/api/schedule/check_upgradable_plugin_task.py index e27391b558..08a5cfce79 100644 --- a/api/schedule/check_upgradable_plugin_task.py +++ b/api/schedule/check_upgradable_plugin_task.py @@ -20,7 +20,7 @@ def check_upgradable_plugin_task(): strategies = ( db.session.query(TenantPluginAutoUpgradeStrategy) - .filter( + .where( TenantPluginAutoUpgradeStrategy.upgrade_time_of_day >= now_seconds_of_day, TenantPluginAutoUpgradeStrategy.upgrade_time_of_day < now_seconds_of_day + AUTO_UPGRADE_MINIMAL_CHECKING_INTERVAL, diff --git a/api/schedule/clean_workflow_runlogs_precise.py b/api/schedule/clean_workflow_runlogs_precise.py index 75057983f6..1a0362ec38 100644 --- a/api/schedule/clean_workflow_runlogs_precise.py +++ b/api/schedule/clean_workflow_runlogs_precise.py @@ -93,7 +93,7 @@ def _delete_batch_with_retry(workflow_run_ids: list[str], attempt_count: int) -> with db.session.begin_nested(): message_data = ( db.session.query(Message.id, Message.conversation_id) - .filter(Message.workflow_run_id.in_(workflow_run_ids)) + .where(Message.workflow_run_id.in_(workflow_run_ids)) .all() ) message_id_list = [msg.id for msg in message_data] diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 45b246af1e..6603063c22 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -282,7 +282,7 @@ class AppAnnotationService: annotations_to_delete = ( db.session.query(MessageAnnotation, AppAnnotationSetting) .outerjoin(AppAnnotationSetting, MessageAnnotation.app_id == AppAnnotationSetting.app_id) - .filter(MessageAnnotation.id.in_(annotation_ids)) + .where(MessageAnnotation.id.in_(annotation_ids)) .all() ) @@ -493,7 +493,7 @@ class AppAnnotationService: def clear_all_annotations(cls, app_id: str) -> dict: app = ( db.session.query(App) - .filter(App.id == app_id, App.tenant_id == current_user.current_tenant_id, App.status == "normal") + .where(App.id == app_id, App.tenant_id == current_user.current_tenant_id, App.status == "normal") .first() ) diff --git a/api/services/clear_free_plan_tenant_expired_logs.py b/api/services/clear_free_plan_tenant_expired_logs.py index b28afcaa41..de00e74637 100644 --- a/api/services/clear_free_plan_tenant_expired_logs.py +++ b/api/services/clear_free_plan_tenant_expired_logs.py @@ -62,7 +62,7 @@ class ClearFreePlanTenantExpiredLogs: # Query records related to expired messages records = ( session.query(model) - .filter( + .where( model.message_id.in_(batch_message_ids), # type: ignore ) .all() @@ -101,7 +101,7 @@ class ClearFreePlanTenantExpiredLogs: except Exception: logger.exception("Failed to save %s records", table_name) - session.query(model).filter( + session.query(model).where( model.id.in_(record_ids), # type: ignore ).delete(synchronize_session=False) @@ -295,7 +295,7 @@ class ClearFreePlanTenantExpiredLogs: with Session(db.engine).no_autoflush as session: workflow_app_logs = ( session.query(WorkflowAppLog) - .filter( + .where( WorkflowAppLog.tenant_id == tenant_id, WorkflowAppLog.created_at < datetime.datetime.now() - datetime.timedelta(days=days), ) @@ -321,9 +321,9 @@ class ClearFreePlanTenantExpiredLogs: workflow_app_log_ids = [workflow_app_log.id for workflow_app_log in workflow_app_logs] # delete workflow app logs - session.query(WorkflowAppLog).filter( - WorkflowAppLog.id.in_(workflow_app_log_ids), - ).delete(synchronize_session=False) + session.query(WorkflowAppLog).where(WorkflowAppLog.id.in_(workflow_app_log_ids)).delete( + synchronize_session=False + ) session.commit() click.echo( diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index c66fac1b87..0d10aa15dd 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -2346,7 +2346,7 @@ class SegmentService: def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset): segments = ( db.session.query(DocumentSegment.index_node_id, DocumentSegment.word_count) - .filter( + .where( DocumentSegment.id.in_(segment_ids), DocumentSegment.dataset_id == dataset.id, DocumentSegment.document_id == document.id, diff --git a/api/services/plugin/plugin_auto_upgrade_service.py b/api/services/plugin/plugin_auto_upgrade_service.py index 3774050445..174bed488d 100644 --- a/api/services/plugin/plugin_auto_upgrade_service.py +++ b/api/services/plugin/plugin_auto_upgrade_service.py @@ -10,7 +10,7 @@ class PluginAutoUpgradeService: with Session(db.engine) as session: return ( session.query(TenantPluginAutoUpgradeStrategy) - .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) .first() ) @@ -26,7 +26,7 @@ class PluginAutoUpgradeService: with Session(db.engine) as session: exist_strategy = ( session.query(TenantPluginAutoUpgradeStrategy) - .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) .first() ) if not exist_strategy: @@ -54,7 +54,7 @@ class PluginAutoUpgradeService: with Session(db.engine) as session: exist_strategy = ( session.query(TenantPluginAutoUpgradeStrategy) - .filter(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) + .where(TenantPluginAutoUpgradeStrategy.tenant_id == tenant_id) .first() ) if not exist_strategy: diff --git a/api/tests/test_containers_integration_tests/services/test_annotation_service.py b/api/tests/test_containers_integration_tests/services/test_annotation_service.py index 92d93d601e..4184420880 100644 --- a/api/tests/test_containers_integration_tests/services/test_annotation_service.py +++ b/api/tests/test_containers_integration_tests/services/test_annotation_service.py @@ -674,7 +674,7 @@ class TestAnnotationService: history = ( db.session.query(AppAnnotationHitHistory) - .filter( + .where( AppAnnotationHitHistory.annotation_id == annotation.id, AppAnnotationHitHistory.message_id == message_id ) .first() diff --git a/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py b/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py index fc614b2296..d83983d0ff 100644 --- a/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py +++ b/api/tests/test_containers_integration_tests/services/test_app_dsl_service.py @@ -166,7 +166,7 @@ class TestAppDslService: assert result.imported_dsl_version == "" # Verify no app was created in database - apps_count = db_session_with_containers.query(App).filter(App.tenant_id == account.current_tenant_id).count() + apps_count = db_session_with_containers.query(App).where(App.tenant_id == account.current_tenant_id).count() assert apps_count == 1 # Only the original test app def test_import_app_missing_yaml_url(self, db_session_with_containers, mock_external_service_dependencies): @@ -191,7 +191,7 @@ class TestAppDslService: assert result.imported_dsl_version == "" # Verify no app was created in database - apps_count = db_session_with_containers.query(App).filter(App.tenant_id == account.current_tenant_id).count() + apps_count = db_session_with_containers.query(App).where(App.tenant_id == account.current_tenant_id).count() assert apps_count == 1 # Only the original test app def test_import_app_invalid_import_mode(self, db_session_with_containers, mock_external_service_dependencies): @@ -215,7 +215,7 @@ class TestAppDslService: ) # Verify no app was created in database - apps_count = db_session_with_containers.query(App).filter(App.tenant_id == account.current_tenant_id).count() + apps_count = db_session_with_containers.query(App).where(App.tenant_id == account.current_tenant_id).count() assert apps_count == 1 # Only the original test app def test_export_dsl_chat_app_success(self, db_session_with_containers, mock_external_service_dependencies): diff --git a/api/tests/test_containers_integration_tests/services/tools/__init__.py b/api/tests/test_containers_integration_tests/services/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/test_containers_integration_tests/services/tools/test_api_tools_manage_service.py b/api/tests/test_containers_integration_tests/services/tools/test_api_tools_manage_service.py new file mode 100644 index 0000000000..a412bdccf8 --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/tools/test_api_tools_manage_service.py @@ -0,0 +1,550 @@ +from unittest.mock import patch + +import pytest +from faker import Faker + +from models.account import Account, Tenant +from models.tools import ApiToolProvider +from services.tools.api_tools_manage_service import ApiToolManageService + + +class TestApiToolManageService: + """Integration tests for ApiToolManageService using testcontainers.""" + + @pytest.fixture + def mock_external_service_dependencies(self): + """Mock setup for external service dependencies.""" + with ( + patch("services.tools.api_tools_manage_service.ToolLabelManager") as mock_tool_label_manager, + patch("services.tools.api_tools_manage_service.create_tool_provider_encrypter") as mock_encrypter, + patch("services.tools.api_tools_manage_service.ApiToolProviderController") as mock_provider_controller, + ): + # Setup default mock returns + mock_tool_label_manager.update_tool_labels.return_value = None + mock_encrypter.return_value = (mock_encrypter, None) + mock_encrypter.encrypt.return_value = {"encrypted": "credentials"} + mock_provider_controller.from_db.return_value = mock_provider_controller + mock_provider_controller.load_bundled_tools.return_value = None + + yield { + "tool_label_manager": mock_tool_label_manager, + "encrypter": mock_encrypter, + "provider_controller": mock_provider_controller, + } + + def _create_test_account_and_tenant(self, db_session_with_containers, mock_external_service_dependencies): + """ + Helper method to create a test account and tenant for testing. + + Args: + db_session_with_containers: Database session from testcontainers infrastructure + mock_external_service_dependencies: Mock dependencies + + Returns: + tuple: (account, tenant) - Created account and tenant instances + """ + fake = Faker() + + # Create account + account = Account( + email=fake.email(), + name=fake.name(), + interface_language="en-US", + status="active", + ) + + from extensions.ext_database import db + + db.session.add(account) + db.session.commit() + + # Create tenant for the account + tenant = Tenant( + name=fake.company(), + status="normal", + ) + db.session.add(tenant) + db.session.commit() + + # Create tenant-account join + from models.account import TenantAccountJoin, TenantAccountRole + + join = TenantAccountJoin( + tenant_id=tenant.id, + account_id=account.id, + role=TenantAccountRole.OWNER.value, + current=True, + ) + db.session.add(join) + db.session.commit() + + # Set current tenant for account + account.current_tenant = tenant + + return account, tenant + + def _create_test_openapi_schema(self): + """Helper method to create a test OpenAPI schema.""" + return """ + { + "openapi": "3.0.0", + "info": { + "title": "Test API", + "version": "1.0.0", + "description": "Test API for testing purposes" + }, + "servers": [ + { + "url": "https://api.example.com", + "description": "Production server" + } + ], + "paths": { + "/test": { + "get": { + "operationId": "testOperation", + "summary": "Test operation", + "responses": { + "200": { + "description": "Success" + } + } + } + } + } + } + """ + + def test_parser_api_schema_success( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful parsing of API schema. + + This test verifies: + - Proper schema parsing with valid OpenAPI schema + - Correct credentials schema generation + - Proper warning handling + - Return value structure + """ + # Arrange: Create test schema + schema = self._create_test_openapi_schema() + + # Act: Parse the schema + result = ApiToolManageService.parser_api_schema(schema) + + # Assert: Verify the result structure + assert result is not None + assert "schema_type" in result + assert "parameters_schema" in result + assert "credentials_schema" in result + assert "warning" in result + + # Verify credentials schema structure + credentials_schema = result["credentials_schema"] + assert len(credentials_schema) == 3 + + # Check auth_type field + auth_type_field = next(field for field in credentials_schema if field["name"] == "auth_type") + assert auth_type_field["required"] is True + assert auth_type_field["default"] == "none" + assert len(auth_type_field["options"]) == 2 + + # Check api_key_header field + api_key_header_field = next(field for field in credentials_schema if field["name"] == "api_key_header") + assert api_key_header_field["required"] is False + assert api_key_header_field["default"] == "api_key" + + # Check api_key_value field + api_key_value_field = next(field for field in credentials_schema if field["name"] == "api_key_value") + assert api_key_value_field["required"] is False + assert api_key_value_field["default"] == "" + + def test_parser_api_schema_invalid_schema( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test parsing of invalid API schema. + + This test verifies: + - Proper error handling for invalid schemas + - Correct exception type and message + - Error propagation from underlying parser + """ + # Arrange: Create invalid schema + invalid_schema = "invalid json schema" + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.parser_api_schema(invalid_schema) + + assert "invalid schema" in str(exc_info.value) + + def test_parser_api_schema_malformed_json( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test parsing of malformed JSON schema. + + This test verifies: + - Proper error handling for malformed JSON + - Correct exception type and message + - Error propagation from JSON parsing + """ + # Arrange: Create malformed JSON schema + malformed_schema = '{"openapi": "3.0.0", "info": {"title": "Test", "version": "1.0.0"}, "paths": {}}' + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.parser_api_schema(malformed_schema) + + assert "invalid schema" in str(exc_info.value) + + def test_convert_schema_to_tool_bundles_success( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful conversion of schema to tool bundles. + + This test verifies: + - Proper schema conversion with valid OpenAPI schema + - Correct tool bundles generation + - Proper schema type detection + - Return value structure + """ + # Arrange: Create test schema + schema = self._create_test_openapi_schema() + + # Act: Convert schema to tool bundles + tool_bundles, schema_type = ApiToolManageService.convert_schema_to_tool_bundles(schema) + + # Assert: Verify the result structure + assert tool_bundles is not None + assert isinstance(tool_bundles, list) + assert len(tool_bundles) > 0 + assert schema_type is not None + assert isinstance(schema_type, str) + + # Verify tool bundle structure + tool_bundle = tool_bundles[0] + assert hasattr(tool_bundle, "operation_id") + assert tool_bundle.operation_id == "testOperation" + + def test_convert_schema_to_tool_bundles_with_extra_info( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful conversion of schema to tool bundles with extra info. + + This test verifies: + - Proper schema conversion with extra info parameter + - Correct tool bundles generation + - Extra info handling + - Return value structure + """ + # Arrange: Create test schema and extra info + schema = self._create_test_openapi_schema() + extra_info = {"description": "Custom description", "version": "2.0.0"} + + # Act: Convert schema to tool bundles with extra info + tool_bundles, schema_type = ApiToolManageService.convert_schema_to_tool_bundles(schema, extra_info) + + # Assert: Verify the result structure + assert tool_bundles is not None + assert isinstance(tool_bundles, list) + assert len(tool_bundles) > 0 + assert schema_type is not None + assert isinstance(schema_type, str) + + def test_convert_schema_to_tool_bundles_invalid_schema( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test conversion of invalid schema to tool bundles. + + This test verifies: + - Proper error handling for invalid schemas + - Correct exception type and message + - Error propagation from underlying parser + """ + # Arrange: Create invalid schema + invalid_schema = "invalid schema content" + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.convert_schema_to_tool_bundles(invalid_schema) + + assert "invalid schema" in str(exc_info.value) + + def test_create_api_tool_provider_success( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful creation of API tool provider. + + This test verifies: + - Proper provider creation with valid parameters + - Correct database state after creation + - Proper relationship establishment + - External service integration + - Return value correctness + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + provider_name = fake.company() + icon = {"type": "emoji", "value": "🔧"} + credentials = {"auth_type": "none", "api_key_header": "X-API-Key", "api_key_value": ""} + schema_type = "openapi" + schema = self._create_test_openapi_schema() + privacy_policy = "https://example.com/privacy" + custom_disclaimer = "Custom disclaimer text" + labels = ["test", "api"] + + # Act: Create API tool provider + result = ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + # Assert: Verify the result + assert result == {"result": "success"} + + # Verify database state + from extensions.ext_database import db + + provider = ( + db.session.query(ApiToolProvider) + .filter(ApiToolProvider.tenant_id == tenant.id, ApiToolProvider.name == provider_name) + .first() + ) + + assert provider is not None + assert provider.name == provider_name + assert provider.tenant_id == tenant.id + assert provider.user_id == account.id + assert provider.schema_type_str == schema_type + assert provider.privacy_policy == privacy_policy + assert provider.custom_disclaimer == custom_disclaimer + + # Verify mock interactions + mock_external_service_dependencies["tool_label_manager"].update_tool_labels.assert_called_once() + mock_external_service_dependencies["encrypter"].assert_called_once() + mock_external_service_dependencies["provider_controller"].from_db.assert_called_once() + mock_external_service_dependencies["provider_controller"].load_bundled_tools.assert_called_once() + + def test_create_api_tool_provider_duplicate_name( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test creation of API tool provider with duplicate name. + + This test verifies: + - Proper error handling for duplicate provider names + - Correct exception type and message + - Database constraint enforcement + """ + # Arrange: Create test data and existing provider + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + provider_name = fake.company() + icon = {"type": "emoji", "value": "🔧"} + credentials = {"auth_type": "none"} + schema_type = "openapi" + schema = self._create_test_openapi_schema() + privacy_policy = "https://example.com/privacy" + custom_disclaimer = "Custom disclaimer text" + labels = ["test"] + + # Create first provider + ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + # Act & Assert: Try to create duplicate provider + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + assert f"provider {provider_name} already exists" in str(exc_info.value) + + def test_create_api_tool_provider_invalid_schema_type( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test creation of API tool provider with invalid schema type. + + This test verifies: + - Proper error handling for invalid schema types + - Correct exception type and message + - Schema type validation + """ + # Arrange: Create test data with invalid schema type + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + provider_name = fake.company() + icon = {"type": "emoji", "value": "🔧"} + credentials = {"auth_type": "none"} + schema_type = "invalid_type" + schema = self._create_test_openapi_schema() + privacy_policy = "https://example.com/privacy" + custom_disclaimer = "Custom disclaimer text" + labels = ["test"] + + # Act & Assert: Try to create provider with invalid schema type + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + assert "invalid schema type" in str(exc_info.value) + + def test_create_api_tool_provider_missing_auth_type( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test creation of API tool provider with missing auth type. + + This test verifies: + - Proper error handling for missing auth type + - Correct exception type and message + - Credentials validation + """ + # Arrange: Create test data with missing auth type + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + provider_name = fake.company() + icon = {"type": "emoji", "value": "🔧"} + credentials = {} # Missing auth_type + schema_type = "openapi" + schema = self._create_test_openapi_schema() + privacy_policy = "https://example.com/privacy" + custom_disclaimer = "Custom disclaimer text" + labels = ["test"] + + # Act & Assert: Try to create provider with missing auth type + with pytest.raises(ValueError) as exc_info: + ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + assert "auth_type is required" in str(exc_info.value) + + def test_create_api_tool_provider_with_api_key_auth( + self, flask_req_ctx_with_containers, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful creation of API tool provider with API key authentication. + + This test verifies: + - Proper provider creation with API key auth + - Correct credentials handling + - Proper authentication type processing + """ + # Arrange: Create test data with API key auth + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + provider_name = fake.company() + icon = {"type": "emoji", "value": "🔑"} + credentials = {"auth_type": "api_key", "api_key_header": "X-API-Key", "api_key_value": fake.uuid4()} + schema_type = "openapi" + schema = self._create_test_openapi_schema() + privacy_policy = "https://example.com/privacy" + custom_disclaimer = "Custom disclaimer text" + labels = ["api_key", "secure"] + + # Act: Create API tool provider + result = ApiToolManageService.create_api_tool_provider( + user_id=account.id, + tenant_id=tenant.id, + provider_name=provider_name, + icon=icon, + credentials=credentials, + schema_type=schema_type, + schema=schema, + privacy_policy=privacy_policy, + custom_disclaimer=custom_disclaimer, + labels=labels, + ) + + # Assert: Verify the result + assert result == {"result": "success"} + + # Verify database state + from extensions.ext_database import db + + provider = ( + db.session.query(ApiToolProvider) + .filter(ApiToolProvider.tenant_id == tenant.id, ApiToolProvider.name == provider_name) + .first() + ) + + assert provider is not None + assert provider.name == provider_name + assert provider.tenant_id == tenant.id + assert provider.user_id == account.id + assert provider.schema_type_str == schema_type + + # Verify mock interactions + mock_external_service_dependencies["encrypter"].assert_called_once() + mock_external_service_dependencies["provider_controller"].from_db.assert_called_once() diff --git a/api/tests/test_containers_integration_tests/services/tools/test_mcp_tools_manage_service.py b/api/tests/test_containers_integration_tests/services/tools/test_mcp_tools_manage_service.py new file mode 100644 index 0000000000..0fcaf86711 --- /dev/null +++ b/api/tests/test_containers_integration_tests/services/tools/test_mcp_tools_manage_service.py @@ -0,0 +1,1277 @@ +from unittest.mock import patch + +import pytest +from faker import Faker + +from core.tools.entities.tool_entities import ToolProviderType +from models.account import Account, Tenant +from models.tools import MCPToolProvider +from services.tools.mcp_tools_manage_service import UNCHANGED_SERVER_URL_PLACEHOLDER, MCPToolManageService + + +class TestMCPToolManageService: + """Integration tests for MCPToolManageService using testcontainers.""" + + @pytest.fixture + def mock_external_service_dependencies(self): + """Mock setup for external service dependencies.""" + with ( + patch("services.tools.mcp_tools_manage_service.encrypter") as mock_encrypter, + patch("services.tools.mcp_tools_manage_service.ToolTransformService") as mock_tool_transform_service, + ): + # Setup default mock returns + mock_encrypter.encrypt_token.return_value = "encrypted_server_url" + mock_tool_transform_service.mcp_provider_to_user_provider.return_value = { + "id": "test_id", + "name": "test_name", + "type": ToolProviderType.MCP, + } + + yield { + "encrypter": mock_encrypter, + "tool_transform_service": mock_tool_transform_service, + } + + def _create_test_account_and_tenant(self, db_session_with_containers, mock_external_service_dependencies): + """ + Helper method to create a test account and tenant for testing. + + Args: + db_session_with_containers: Database session from testcontainers infrastructure + mock_external_service_dependencies: Mock dependencies + + Returns: + tuple: (account, tenant) - Created account and tenant instances + """ + fake = Faker() + + # Create account + account = Account( + email=fake.email(), + name=fake.name(), + interface_language="en-US", + status="active", + ) + + from extensions.ext_database import db + + db.session.add(account) + db.session.commit() + + # Create tenant for the account + tenant = Tenant( + name=fake.company(), + status="normal", + ) + db.session.add(tenant) + db.session.commit() + + # Create tenant-account join + from models.account import TenantAccountJoin, TenantAccountRole + + join = TenantAccountJoin( + tenant_id=tenant.id, + account_id=account.id, + role=TenantAccountRole.OWNER.value, + current=True, + ) + db.session.add(join) + db.session.commit() + + # Set current tenant for account + account.current_tenant = tenant + + return account, tenant + + def _create_test_mcp_provider( + self, db_session_with_containers, mock_external_service_dependencies, tenant_id, user_id + ): + """ + Helper method to create a test MCP tool provider for testing. + + Args: + db_session_with_containers: Database session from testcontainers infrastructure + mock_external_service_dependencies: Mock dependencies + tenant_id: Tenant ID for the provider + user_id: User ID who created the provider + + Returns: + MCPToolProvider: Created MCP tool provider instance + """ + fake = Faker() + + # Create MCP tool provider + mcp_provider = MCPToolProvider( + tenant_id=tenant_id, + name=fake.company(), + server_identifier=fake.uuid4(), + server_url="encrypted_server_url", + server_url_hash=fake.sha256(), + user_id=user_id, + authed=False, + tools="[]", + icon='{"content": "🤖", "background": "#FF6B6B"}', + timeout=30.0, + sse_read_timeout=300.0, + ) + + from extensions.ext_database import db + + db.session.add(mcp_provider) + db.session.commit() + + return mcp_provider + + def test_get_mcp_provider_by_provider_id_success( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful retrieval of MCP provider by provider ID. + + This test verifies: + - Proper retrieval of MCP provider by ID + - Correct tenant isolation + - Proper error handling for non-existent providers + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + + # Act: Execute the method under test + result = MCPToolManageService.get_mcp_provider_by_provider_id(mcp_provider.id, tenant.id) + + # Assert: Verify the expected outcomes + assert result is not None + assert result.id == mcp_provider.id + assert result.name == mcp_provider.name + assert result.tenant_id == tenant.id + assert result.user_id == account.id + + # Verify database state + from extensions.ext_database import db + + db.session.refresh(result) + assert result.id is not None + assert result.server_identifier == mcp_provider.server_identifier + + def test_get_mcp_provider_by_provider_id_not_found( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when MCP provider is not found by provider ID. + + This test verifies: + - Proper error handling for non-existent provider IDs + - Correct exception type and message + - Tenant isolation enforcement + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + non_existent_id = fake.uuid4() + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.get_mcp_provider_by_provider_id(non_existent_id, tenant.id) + + def test_get_mcp_provider_by_provider_id_tenant_isolation( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test tenant isolation when retrieving MCP provider by provider ID. + + This test verifies: + - Proper tenant isolation enforcement + - Providers from other tenants are not accessible + - Security boundaries are maintained + """ + # Arrange: Create test data for two tenants + fake = Faker() + account1, tenant1 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + account2, tenant2 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider in tenant1 + mcp_provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant1.id, account1.id + ) + + # Act & Assert: Verify tenant isolation + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.get_mcp_provider_by_provider_id(mcp_provider1.id, tenant2.id) + + def test_get_mcp_provider_by_server_identifier_success( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful retrieval of MCP provider by server identifier. + + This test verifies: + - Proper retrieval of MCP provider by server identifier + - Correct tenant isolation + - Proper error handling for non-existent server identifiers + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + + # Act: Execute the method under test + result = MCPToolManageService.get_mcp_provider_by_server_identifier(mcp_provider.server_identifier, tenant.id) + + # Assert: Verify the expected outcomes + assert result is not None + assert result.id == mcp_provider.id + assert result.server_identifier == mcp_provider.server_identifier + assert result.tenant_id == tenant.id + assert result.user_id == account.id + + # Verify database state + from extensions.ext_database import db + + db.session.refresh(result) + assert result.id is not None + assert result.name == mcp_provider.name + + def test_get_mcp_provider_by_server_identifier_not_found( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when MCP provider is not found by server identifier. + + This test verifies: + - Proper error handling for non-existent server identifiers + - Correct exception type and message + - Tenant isolation enforcement + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + non_existent_identifier = fake.uuid4() + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.get_mcp_provider_by_server_identifier(non_existent_identifier, tenant.id) + + def test_get_mcp_provider_by_server_identifier_tenant_isolation( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test tenant isolation when retrieving MCP provider by server identifier. + + This test verifies: + - Proper tenant isolation enforcement + - Providers from other tenants are not accessible by server identifier + - Security boundaries are maintained + """ + # Arrange: Create test data for two tenants + fake = Faker() + account1, tenant1 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + account2, tenant2 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider in tenant1 + mcp_provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant1.id, account1.id + ) + + # Act & Assert: Verify tenant isolation + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.get_mcp_provider_by_server_identifier(mcp_provider1.server_identifier, tenant2.id) + + def test_create_mcp_provider_success(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test successful creation of MCP provider. + + This test verifies: + - Proper MCP provider creation with all required fields + - Correct database state after creation + - Proper relationship establishment + - External service integration + - Return value correctness + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Setup mocks for provider creation + mock_external_service_dependencies["encrypter"].encrypt_token.return_value = "encrypted_server_url" + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.return_value = { + "id": "new_provider_id", + "name": "Test MCP Provider", + "type": ToolProviderType.MCP, + } + + # Act: Execute the method under test + result = MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider", + server_url="https://example.com/mcp", + user_id=account.id, + icon="🤖", + icon_type="emoji", + icon_background="#FF6B6B", + server_identifier="test_identifier_123", + timeout=30.0, + sse_read_timeout=300.0, + ) + + # Assert: Verify the expected outcomes + assert result is not None + assert result["name"] == "Test MCP Provider" + assert result["type"] == ToolProviderType.MCP + + # Verify database state + from extensions.ext_database import db + + created_provider = ( + db.session.query(MCPToolProvider) + .filter(MCPToolProvider.tenant_id == tenant.id, MCPToolProvider.name == "Test MCP Provider") + .first() + ) + + assert created_provider is not None + assert created_provider.server_identifier == "test_identifier_123" + assert created_provider.timeout == 30.0 + assert created_provider.sse_read_timeout == 300.0 + assert created_provider.authed is False + assert created_provider.tools == "[]" + + # Verify mock interactions + mock_external_service_dependencies["encrypter"].encrypt_token.assert_called_once_with( + tenant.id, "https://example.com/mcp" + ) + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.assert_called_once() + + def test_create_mcp_provider_duplicate_name(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test error handling when creating MCP provider with duplicate name. + + This test verifies: + - Proper error handling for duplicate provider names + - Correct exception type and message + - Database integrity constraints + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create first provider + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider", + server_url="https://example1.com/mcp", + user_id=account.id, + icon="🤖", + icon_type="emoji", + icon_background="#FF6B6B", + server_identifier="test_identifier_1", + timeout=30.0, + sse_read_timeout=300.0, + ) + + # Act & Assert: Verify proper error handling for duplicate name + with pytest.raises(ValueError, match="MCP tool Test MCP Provider already exists"): + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider", # Duplicate name + server_url="https://example2.com/mcp", + user_id=account.id, + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="test_identifier_2", + timeout=45.0, + sse_read_timeout=400.0, + ) + + def test_create_mcp_provider_duplicate_server_url( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when creating MCP provider with duplicate server URL. + + This test verifies: + - Proper error handling for duplicate server URLs + - Correct exception type and message + - URL hash uniqueness enforcement + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create first provider + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider 1", + server_url="https://example.com/mcp", + user_id=account.id, + icon="🤖", + icon_type="emoji", + icon_background="#FF6B6B", + server_identifier="test_identifier_1", + timeout=30.0, + sse_read_timeout=300.0, + ) + + # Act & Assert: Verify proper error handling for duplicate server URL + with pytest.raises(ValueError, match="MCP tool https://example.com/mcp already exists"): + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider 2", + server_url="https://example.com/mcp", # Duplicate URL + user_id=account.id, + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="test_identifier_2", + timeout=45.0, + sse_read_timeout=400.0, + ) + + def test_create_mcp_provider_duplicate_server_identifier( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when creating MCP provider with duplicate server identifier. + + This test verifies: + - Proper error handling for duplicate server identifiers + - Correct exception type and message + - Server identifier uniqueness enforcement + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create first provider + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider 1", + server_url="https://example1.com/mcp", + user_id=account.id, + icon="🤖", + icon_type="emoji", + icon_background="#FF6B6B", + server_identifier="test_identifier_123", + timeout=30.0, + sse_read_timeout=300.0, + ) + + # Act & Assert: Verify proper error handling for duplicate server identifier + with pytest.raises(ValueError, match="MCP tool test_identifier_123 already exists"): + MCPToolManageService.create_mcp_provider( + tenant_id=tenant.id, + name="Test MCP Provider 2", + server_url="https://example2.com/mcp", + user_id=account.id, + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="test_identifier_123", # Duplicate identifier + timeout=45.0, + sse_read_timeout=400.0, + ) + + def test_retrieve_mcp_tools_success(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test successful retrieval of MCP tools for a tenant. + + This test verifies: + - Proper retrieval of all MCP providers for a tenant + - Correct ordering by name + - Proper transformation of providers to user entities + - Empty list handling for tenants with no providers + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create multiple MCP providers + provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + provider1.name = "Alpha Provider" + + provider2 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + provider2.name = "Beta Provider" + + provider3 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + provider3.name = "Gamma Provider" + + from extensions.ext_database import db + + db.session.commit() + + # Setup mock for transformation service + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.side_effect = [ + {"id": provider1.id, "name": provider1.name, "type": ToolProviderType.MCP}, + {"id": provider2.id, "name": provider2.name, "type": ToolProviderType.MCP}, + {"id": provider3.id, "name": provider3.name, "type": ToolProviderType.MCP}, + ] + + # Act: Execute the method under test + result = MCPToolManageService.retrieve_mcp_tools(tenant.id, for_list=True) + + # Assert: Verify the expected outcomes + assert result is not None + assert len(result) == 3 + + # Verify correct ordering by name + assert result[0]["name"] == "Alpha Provider" + assert result[1]["name"] == "Beta Provider" + assert result[2]["name"] == "Gamma Provider" + + # Verify mock interactions + assert ( + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.call_count == 3 + ) + + def test_retrieve_mcp_tools_empty_list(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test retrieval of MCP tools when tenant has no providers. + + This test verifies: + - Proper handling of empty provider lists + - Correct return value for tenants with no MCP tools + - No transformation service calls for empty lists + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # No MCP providers created for this tenant + + # Act: Execute the method under test + result = MCPToolManageService.retrieve_mcp_tools(tenant.id, for_list=False) + + # Assert: Verify the expected outcomes + assert result is not None + assert len(result) == 0 + assert isinstance(result, list) + + # Verify no transformation service calls for empty list + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.assert_not_called() + + def test_retrieve_mcp_tools_tenant_isolation(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test tenant isolation when retrieving MCP tools. + + This test verifies: + - Proper tenant isolation enforcement + - Providers from other tenants are not accessible + - Security boundaries are maintained + """ + # Arrange: Create test data for two tenants + fake = Faker() + account1, tenant1 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + account2, tenant2 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider in tenant1 + provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant1.id, account1.id + ) + + # Create MCP provider in tenant2 + provider2 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant2.id, account2.id + ) + + # Setup mock for transformation service + mock_external_service_dependencies["tool_transform_service"].mcp_provider_to_user_provider.side_effect = [ + {"id": provider1.id, "name": provider1.name, "type": ToolProviderType.MCP}, + {"id": provider2.id, "name": provider2.name, "type": ToolProviderType.MCP}, + ] + + # Act: Execute the method under test for both tenants + result1 = MCPToolManageService.retrieve_mcp_tools(tenant1.id, for_list=True) + result2 = MCPToolManageService.retrieve_mcp_tools(tenant2.id, for_list=True) + + # Assert: Verify tenant isolation + assert len(result1) == 1 + assert len(result2) == 1 + assert result1[0]["id"] == provider1.id + assert result2[0]["id"] == provider2.id + + def test_list_mcp_tool_from_remote_server_success( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful listing of MCP tools from remote server. + + This test verifies: + - Proper connection to remote MCP server + - Correct tool listing and database update + - Proper authentication state management + - Return value correctness + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + mcp_provider.server_url = "encrypted_server_url" + mcp_provider.authed = False + mcp_provider.tools = "[]" + + from extensions.ext_database import db + + db.session.commit() + + # Mock the decrypted_server_url property to avoid encryption issues + with patch("models.tools.encrypter") as mock_encrypter: + mock_encrypter.decrypt_token.return_value = "https://example.com/mcp" + + # Mock MCPClient and its context manager + mock_tools = [ + type( + "MockTool", (), {"model_dump": lambda self: {"name": "test_tool_1", "description": "Test tool 1"}} + )(), + type( + "MockTool", (), {"model_dump": lambda self: {"name": "test_tool_2", "description": "Test tool 2"}} + )(), + ] + + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + # Setup mock client + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.return_value = mock_tools + + # Act: Execute the method under test + result = MCPToolManageService.list_mcp_tool_from_remote_server(tenant.id, mcp_provider.id) + + # Assert: Verify the expected outcomes + assert result is not None + assert result.id == mcp_provider.id + assert result.name == mcp_provider.name + assert result.type == ToolProviderType.MCP + # Note: server_url is mocked, so we skip that assertion to avoid encryption issues + + # Verify database state was updated + db.session.refresh(mcp_provider) + assert mcp_provider.authed is True + assert mcp_provider.tools != "[]" + assert mcp_provider.updated_at is not None + + # Verify mock interactions + mock_mcp_client.assert_called_once_with( + "https://example.com/mcp", mcp_provider.id, tenant.id, authed=False, for_list=True + ) + + def test_list_mcp_tool_from_remote_server_auth_error( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when MCP server requires authentication. + + This test verifies: + - Proper error handling for authentication errors + - Correct exception type and message + - Database state remains unchanged + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + mcp_provider.server_url = "encrypted_server_url" + mcp_provider.authed = False + mcp_provider.tools = "[]" + + from extensions.ext_database import db + + db.session.commit() + + # Mock the decrypted_server_url property to avoid encryption issues + with patch("models.tools.encrypter") as mock_encrypter: + mock_encrypter.decrypt_token.return_value = "https://example.com/mcp" + + # Mock MCPClient to raise authentication error + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + from core.mcp.error import MCPAuthError + + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.side_effect = MCPAuthError("Authentication required") + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="Please auth the tool first"): + MCPToolManageService.list_mcp_tool_from_remote_server(tenant.id, mcp_provider.id) + + # Verify database state was not changed + db.session.refresh(mcp_provider) + assert mcp_provider.authed is False + assert mcp_provider.tools == "[]" + + def test_list_mcp_tool_from_remote_server_connection_error( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test error handling when MCP server connection fails. + + This test verifies: + - Proper error handling for connection errors + - Correct exception type and message + - Database state remains unchanged + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + mcp_provider.server_url = "encrypted_server_url" + mcp_provider.authed = False + mcp_provider.tools = "[]" + + from extensions.ext_database import db + + db.session.commit() + + # Mock the decrypted_server_url property to avoid encryption issues + with patch("models.tools.encrypter") as mock_encrypter: + mock_encrypter.decrypt_token.return_value = "https://example.com/mcp" + + # Mock MCPClient to raise connection error + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + from core.mcp.error import MCPError + + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.side_effect = MCPError("Connection failed") + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="Failed to connect to MCP server: Connection failed"): + MCPToolManageService.list_mcp_tool_from_remote_server(tenant.id, mcp_provider.id) + + # Verify database state was not changed + db.session.refresh(mcp_provider) + assert mcp_provider.authed is False + assert mcp_provider.tools == "[]" + + def test_delete_mcp_tool_success(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test successful deletion of MCP tool. + + This test verifies: + - Proper deletion of MCP provider from database + - Correct tenant isolation enforcement + - Database state after deletion + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + + # Verify provider exists + from extensions.ext_database import db + + assert db.session.query(MCPToolProvider).filter_by(id=mcp_provider.id).first() is not None + + # Act: Execute the method under test + MCPToolManageService.delete_mcp_tool(tenant.id, mcp_provider.id) + + # Assert: Verify the expected outcomes + # Provider should be deleted from database + deleted_provider = db.session.query(MCPToolProvider).filter_by(id=mcp_provider.id).first() + assert deleted_provider is None + + def test_delete_mcp_tool_not_found(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test error handling when deleting non-existent MCP tool. + + This test verifies: + - Proper error handling for non-existent provider IDs + - Correct exception type and message + - Tenant isolation enforcement + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + non_existent_id = fake.uuid4() + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.delete_mcp_tool(tenant.id, non_existent_id) + + def test_delete_mcp_tool_tenant_isolation(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test tenant isolation when deleting MCP tool. + + This test verifies: + - Proper tenant isolation enforcement + - Providers from other tenants cannot be deleted + - Security boundaries are maintained + """ + # Arrange: Create test data for two tenants + fake = Faker() + account1, tenant1 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + account2, tenant2 = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider in tenant1 + mcp_provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant1.id, account1.id + ) + + # Act & Assert: Verify tenant isolation + with pytest.raises(ValueError, match="MCP tool not found"): + MCPToolManageService.delete_mcp_tool(tenant2.id, mcp_provider1.id) + + # Verify provider still exists in tenant1 + from extensions.ext_database import db + + assert db.session.query(MCPToolProvider).filter_by(id=mcp_provider1.id).first() is not None + + def test_update_mcp_provider_success(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test successful update of MCP provider. + + This test verifies: + - Proper update of MCP provider fields + - Correct database state after update + - Proper handling of unchanged server URL + - External service integration + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + original_name = mcp_provider.name + original_icon = mcp_provider.icon + + from extensions.ext_database import db + + db.session.commit() + + # Act: Execute the method under test + MCPToolManageService.update_mcp_provider( + tenant_id=tenant.id, + provider_id=mcp_provider.id, + name="Updated MCP Provider", + server_url=UNCHANGED_SERVER_URL_PLACEHOLDER, # Use placeholder for unchanged URL + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="updated_identifier_123", + timeout=45.0, + sse_read_timeout=400.0, + ) + + # Assert: Verify the expected outcomes + db.session.refresh(mcp_provider) + assert mcp_provider.name == "Updated MCP Provider" + assert mcp_provider.server_identifier == "updated_identifier_123" + assert mcp_provider.timeout == 45.0 + assert mcp_provider.sse_read_timeout == 400.0 + assert mcp_provider.updated_at is not None + + # Verify icon was updated + import json + + icon_data = json.loads(mcp_provider.icon) + assert icon_data["content"] == "🚀" + assert icon_data["background"] == "#4ECDC4" + + def test_update_mcp_provider_with_server_url_change( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful update of MCP provider with server URL change. + + This test verifies: + - Proper handling of server URL changes + - Correct reconnection logic + - Database state updates + - External service integration + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + + from extensions.ext_database import db + + db.session.commit() + + # Mock the reconnection method + with patch.object(MCPToolManageService, "_re_connect_mcp_provider") as mock_reconnect: + mock_reconnect.return_value = { + "authed": True, + "tools": '[{"name": "test_tool"}]', + "encrypted_credentials": "{}", + } + + # Act: Execute the method under test + MCPToolManageService.update_mcp_provider( + tenant_id=tenant.id, + provider_id=mcp_provider.id, + name="Updated MCP Provider", + server_url="https://new-example.com/mcp", + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="updated_identifier_123", + timeout=45.0, + sse_read_timeout=400.0, + ) + + # Assert: Verify the expected outcomes + db.session.refresh(mcp_provider) + assert mcp_provider.name == "Updated MCP Provider" + assert mcp_provider.server_identifier == "updated_identifier_123" + assert mcp_provider.timeout == 45.0 + assert mcp_provider.sse_read_timeout == 400.0 + assert mcp_provider.updated_at is not None + + # Verify reconnection was called + mock_reconnect.assert_called_once_with("https://new-example.com/mcp", mcp_provider.id, tenant.id) + + def test_update_mcp_provider_duplicate_name(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test error handling when updating MCP provider with duplicate name. + + This test verifies: + - Proper error handling for duplicate provider names + - Correct exception type and message + - Database integrity constraints + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create two MCP providers + provider1 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + provider1.name = "First Provider" + + provider2 = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + provider2.name = "Second Provider" + + from extensions.ext_database import db + + db.session.commit() + + # Act & Assert: Verify proper error handling for duplicate name + with pytest.raises(ValueError, match="MCP tool First Provider already exists"): + MCPToolManageService.update_mcp_provider( + tenant_id=tenant.id, + provider_id=provider2.id, + name="First Provider", # Duplicate name + server_url=UNCHANGED_SERVER_URL_PLACEHOLDER, + icon="🚀", + icon_type="emoji", + icon_background="#4ECDC4", + server_identifier="unique_identifier", + timeout=45.0, + sse_read_timeout=400.0, + ) + + def test_update_mcp_provider_credentials_success( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test successful update of MCP provider credentials. + + This test verifies: + - Proper encryption of credentials + - Correct database state after update + - Authentication state management + - External service integration + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + mcp_provider.encrypted_credentials = '{"existing_key": "existing_value"}' + mcp_provider.authed = False + mcp_provider.tools = "[]" + + from extensions.ext_database import db + + db.session.commit() + + # Mock the provider controller and encryption + with ( + patch("services.tools.mcp_tools_manage_service.MCPToolProviderController") as mock_controller, + patch("services.tools.mcp_tools_manage_service.ProviderConfigEncrypter") as mock_encrypter, + ): + # Setup mocks + mock_controller_instance = mock_controller._from_db.return_value + mock_controller_instance.get_credentials_schema.return_value = [] + + mock_encrypter_instance = mock_encrypter.return_value + mock_encrypter_instance.encrypt.return_value = {"new_key": "encrypted_value"} + + # Act: Execute the method under test + MCPToolManageService.update_mcp_provider_credentials( + mcp_provider=mcp_provider, credentials={"new_key": "new_value"}, authed=True + ) + + # Assert: Verify the expected outcomes + db.session.refresh(mcp_provider) + assert mcp_provider.authed is True + assert mcp_provider.updated_at is not None + + # Verify credentials were encrypted and merged + import json + + credentials = json.loads(mcp_provider.encrypted_credentials) + assert "existing_key" in credentials + assert "new_key" in credentials + + def test_update_mcp_provider_credentials_not_authed( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test update of MCP provider credentials when not authenticated. + + This test verifies: + - Proper handling of non-authenticated state + - Tools list is cleared when not authenticated + - Credentials are still updated + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Create MCP provider + mcp_provider = self._create_test_mcp_provider( + db_session_with_containers, mock_external_service_dependencies, tenant.id, account.id + ) + mcp_provider.encrypted_credentials = '{"existing_key": "existing_value"}' + mcp_provider.authed = True + mcp_provider.tools = '[{"name": "test_tool"}]' + + from extensions.ext_database import db + + db.session.commit() + + # Mock the provider controller and encryption + with ( + patch("services.tools.mcp_tools_manage_service.MCPToolProviderController") as mock_controller, + patch("services.tools.mcp_tools_manage_service.ProviderConfigEncrypter") as mock_encrypter, + ): + # Setup mocks + mock_controller_instance = mock_controller._from_db.return_value + mock_controller_instance.get_credentials_schema.return_value = [] + + mock_encrypter_instance = mock_encrypter.return_value + mock_encrypter_instance.encrypt.return_value = {"new_key": "encrypted_value"} + + # Act: Execute the method under test + MCPToolManageService.update_mcp_provider_credentials( + mcp_provider=mcp_provider, credentials={"new_key": "new_value"}, authed=False + ) + + # Assert: Verify the expected outcomes + db.session.refresh(mcp_provider) + assert mcp_provider.authed is False + assert mcp_provider.tools == "[]" + assert mcp_provider.updated_at is not None + + def test_re_connect_mcp_provider_success(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test successful reconnection to MCP provider. + + This test verifies: + - Proper connection to remote MCP server + - Correct tool listing and return value + - Proper error handling for authentication errors + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Mock MCPClient and its context manager + mock_tools = [ + type("MockTool", (), {"model_dump": lambda self: {"name": "test_tool_1", "description": "Test tool 1"}})(), + type("MockTool", (), {"model_dump": lambda self: {"name": "test_tool_2", "description": "Test tool 2"}})(), + ] + + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + # Setup mock client + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.return_value = mock_tools + + # Act: Execute the method under test + result = MCPToolManageService._re_connect_mcp_provider( + "https://example.com/mcp", "test_provider_id", tenant.id + ) + + # Assert: Verify the expected outcomes + assert result is not None + assert result["authed"] is True + assert result["tools"] is not None + assert result["encrypted_credentials"] == "{}" + + # Verify tools were properly serialized + import json + + tools_data = json.loads(result["tools"]) + assert len(tools_data) == 2 + assert tools_data[0]["name"] == "test_tool_1" + assert tools_data[1]["name"] == "test_tool_2" + + # Verify mock interactions + mock_mcp_client.assert_called_once_with( + "https://example.com/mcp", "test_provider_id", tenant.id, authed=False, for_list=True + ) + + def test_re_connect_mcp_provider_auth_error(self, db_session_with_containers, mock_external_service_dependencies): + """ + Test reconnection to MCP provider when authentication fails. + + This test verifies: + - Proper handling of authentication errors + - Correct return value for failed authentication + - Tools list is cleared + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Mock MCPClient to raise authentication error + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + from core.mcp.error import MCPAuthError + + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.side_effect = MCPAuthError("Authentication required") + + # Act: Execute the method under test + result = MCPToolManageService._re_connect_mcp_provider( + "https://example.com/mcp", "test_provider_id", tenant.id + ) + + # Assert: Verify the expected outcomes + assert result is not None + assert result["authed"] is False + assert result["tools"] == "[]" + assert result["encrypted_credentials"] == "{}" + + def test_re_connect_mcp_provider_connection_error( + self, db_session_with_containers, mock_external_service_dependencies + ): + """ + Test reconnection to MCP provider when connection fails. + + This test verifies: + - Proper error handling for connection errors + - Correct exception type and message + """ + # Arrange: Create test data + fake = Faker() + account, tenant = self._create_test_account_and_tenant( + db_session_with_containers, mock_external_service_dependencies + ) + + # Mock MCPClient to raise connection error + with patch("services.tools.mcp_tools_manage_service.MCPClient") as mock_mcp_client: + from core.mcp.error import MCPError + + mock_client_instance = mock_mcp_client.return_value.__enter__.return_value + mock_client_instance.list_tools.side_effect = MCPError("Connection failed") + + # Act & Assert: Verify proper error handling + with pytest.raises(ValueError, match="Failed to re-connect MCP server: Connection failed"): + MCPToolManageService._re_connect_mcp_provider("https://example.com/mcp", "test_provider_id", tenant.id) diff --git a/api/tests/unit_tests/services/test_clear_free_plan_tenant_expired_logs.py b/api/tests/unit_tests/services/test_clear_free_plan_tenant_expired_logs.py index dd2bc21814..5099362e00 100644 --- a/api/tests/unit_tests/services/test_clear_free_plan_tenant_expired_logs.py +++ b/api/tests/unit_tests/services/test_clear_free_plan_tenant_expired_logs.py @@ -57,7 +57,7 @@ class TestClearFreePlanTenantExpiredLogs: def test_clear_message_related_tables_no_records_found(self, mock_session, sample_message_ids): """Test when no related records are found.""" with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage: - mock_session.query.return_value.filter.return_value.all.return_value = [] + mock_session.query.return_value.where.return_value.all.return_value = [] ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) @@ -70,7 +70,7 @@ class TestClearFreePlanTenantExpiredLogs: ): """Test when records are found and have to_dict method.""" with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage: - mock_session.query.return_value.filter.return_value.all.return_value = sample_records + mock_session.query.return_value.where.return_value.all.return_value = sample_records ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) @@ -101,7 +101,7 @@ class TestClearFreePlanTenantExpiredLogs: records.append(record) # Mock records for first table only, empty for others - mock_session.query.return_value.filter.return_value.all.side_effect = [ + mock_session.query.return_value.where.return_value.all.side_effect = [ records, [], [], @@ -123,13 +123,13 @@ class TestClearFreePlanTenantExpiredLogs: with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage: mock_storage.save.side_effect = Exception("Storage error") - mock_session.query.return_value.filter.return_value.all.return_value = sample_records + mock_session.query.return_value.where.return_value.all.return_value = sample_records # Should not raise exception ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) # Should still delete records even if backup fails - assert mock_session.query.return_value.filter.return_value.delete.called + assert mock_session.query.return_value.where.return_value.delete.called def test_clear_message_related_tables_serialization_error_continues(self, mock_session, sample_message_ids): """Test that method continues even when record serialization fails.""" @@ -138,30 +138,30 @@ class TestClearFreePlanTenantExpiredLogs: record.id = "record-1" record.to_dict.side_effect = Exception("Serialization error") - mock_session.query.return_value.filter.return_value.all.return_value = [record] + mock_session.query.return_value.where.return_value.all.return_value = [record] # Should not raise exception ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) # Should still delete records even if serialization fails - assert mock_session.query.return_value.filter.return_value.delete.called + assert mock_session.query.return_value.where.return_value.delete.called def test_clear_message_related_tables_deletion_called(self, mock_session, sample_message_ids, sample_records): """Test that deletion is called for found records.""" with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage: - mock_session.query.return_value.filter.return_value.all.return_value = sample_records + mock_session.query.return_value.where.return_value.all.return_value = sample_records ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) # Should call delete for each table that has records - assert mock_session.query.return_value.filter.return_value.delete.called + assert mock_session.query.return_value.where.return_value.delete.called def test_clear_message_related_tables_logging_output( self, mock_session, sample_message_ids, sample_records, capsys ): """Test that logging output is generated.""" with patch("services.clear_free_plan_tenant_expired_logs.storage") as mock_storage: - mock_session.query.return_value.filter.return_value.all.return_value = sample_records + mock_session.query.return_value.where.return_value.all.return_value = sample_records ClearFreePlanTenantExpiredLogs._clear_message_related_tables(mock_session, "tenant-123", sample_message_ids) diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 0e695e4fca..a779999983 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -135,7 +135,7 @@ services: # Set the redis password when startup redis server. command: redis-server --requirepass ${REDIS_PASSWORD:-difyai123456} healthcheck: - test: [ 'CMD', 'redis-cli', 'ping' ] + test: [ 'CMD-SHELL', 'redis-cli -a ${REDIS_PASSWORD:-difyai123456} ping | grep -q PONG' ] # The DifySandbox sandbox: diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index 9f7cc72586..dc451e10ca 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -41,7 +41,7 @@ services: ports: - "${EXPOSE_REDIS_PORT:-6379}:6379" healthcheck: - test: [ "CMD", "redis-cli", "ping" ] + test: [ 'CMD-SHELL', 'redis-cli -a ${REDIS_PASSWORD:-difyai123456} ping | grep -q PONG' ] # The DifySandbox sandbox: diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index a7e72e2cb8..5a78d42f98 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -719,7 +719,7 @@ services: # Set the redis password when startup redis server. command: redis-server --requirepass ${REDIS_PASSWORD:-difyai123456} healthcheck: - test: [ 'CMD', 'redis-cli', 'ping' ] + test: [ 'CMD-SHELL', 'redis-cli -a ${REDIS_PASSWORD:-difyai123456} ping | grep -q PONG' ] # The DifySandbox sandbox: diff --git a/web/__tests__/xss-fix-verification.test.tsx b/web/__tests__/xss-fix-verification.test.tsx deleted file mode 100644 index 2fa5ab3c05..0000000000 --- a/web/__tests__/xss-fix-verification.test.tsx +++ /dev/null @@ -1,212 +0,0 @@ -/** - * XSS Fix Verification Test - * - * This test verifies that the XSS vulnerability in check-code pages has been - * properly fixed by replacing dangerouslySetInnerHTML with safe React rendering. - */ - -import React from 'react' -import { cleanup, render } from '@testing-library/react' -import '@testing-library/jest-dom' - -// Mock i18next with the new safe translation structure -jest.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (key: string) => { - if (key === 'login.checkCode.tipsPrefix') - return 'We send a verification code to ' - - return key - }, - }), -})) - -// Mock Next.js useSearchParams -jest.mock('next/navigation', () => ({ - useSearchParams: () => ({ - get: (key: string) => { - if (key === 'email') - return 'test@example.com' - return null - }, - }), -})) - -// Fixed CheckCode component implementation (current secure version) -const SecureCheckCodeComponent = ({ email }: { email: string }) => { - const { t } = require('react-i18next').useTranslation() - - return ( -
-

Check Code

-

- - {t('login.checkCode.tipsPrefix')} - {email} - -

-
- ) -} - -// Vulnerable implementation for comparison (what we fixed) -const VulnerableCheckCodeComponent = ({ email }: { email: string }) => { - const mockTranslation = (key: string, params?: any) => { - if (key === 'login.checkCode.tips' && params?.email) - return `We send a verification code to ${params.email}` - - return key - } - - return ( -
-

Check Code

-

- -

-
- ) -} - -describe('XSS Fix Verification - Check Code Pages Security', () => { - afterEach(() => { - cleanup() - }) - - const maliciousEmail = 'test@example.com' - - it('should securely render email with HTML characters as text (FIXED VERSION)', () => { - console.log('\n🔒 Security Fix Verification Report') - console.log('===================================') - - const { container } = render() - - const spanElement = container.querySelector('span') - const strongElement = container.querySelector('strong') - const scriptElements = container.querySelectorAll('script') - - console.log('\n✅ Fixed Implementation Results:') - console.log('- Email rendered in strong tag:', strongElement?.textContent) - console.log('- HTML tags visible as text:', strongElement?.textContent?.includes('', - 'normal@email.com', - ] - - testCases.forEach((testEmail, index) => { - const { container } = render() - - const strongElement = container.querySelector('strong') - const scriptElements = container.querySelectorAll('script') - const imgElements = container.querySelectorAll('img') - const divElements = container.querySelectorAll('div:not([data-testid])') - - console.log(`\n📧 Test Case ${index + 1}: ${testEmail.substring(0, 20)}...`) - console.log(` - Script elements: ${scriptElements.length}`) - console.log(` - Img elements: ${imgElements.length}`) - console.log(` - Malicious divs: ${divElements.length - 1}`) // -1 for container div - console.log(` - Text content: ${strongElement?.textContent === testEmail ? 'SAFE' : 'ISSUE'}`) - - // All should be safe - expect(scriptElements).toHaveLength(0) - expect(imgElements).toHaveLength(0) - expect(strongElement?.textContent).toBe(testEmail) - }) - - console.log('\n✅ All test cases passed - secure rendering confirmed') - }) - - it('should validate the translation structure is secure', () => { - console.log('\n🔍 Translation Security Analysis') - console.log('=================================') - - const { t } = require('react-i18next').useTranslation() - const prefix = t('login.checkCode.tipsPrefix') - - console.log('- Translation key used: login.checkCode.tipsPrefix') - console.log('- Translation value:', prefix) - console.log('- Contains HTML tags:', prefix.includes('<')) - console.log('- Pure text content:', !prefix.includes('<') && !prefix.includes('>')) - - // Verify translation is plain text - expect(prefix).toBe('We send a verification code to ') - expect(prefix).not.toContain('<') - expect(prefix).not.toContain('>') - expect(typeof prefix).toBe('string') - - console.log('\n✅ Translation structure is secure - no HTML content') - }) - - it('should confirm React automatic escaping works correctly', () => { - console.log('\n⚡ React Security Mechanism Test') - console.log('=================================') - - // Test React's automatic escaping with various inputs - const dangerousInputs = [ - '', - '', - '">', - '\'>alert(3)', - '
click
', - ] - - dangerousInputs.forEach((input, index) => { - const TestComponent = () => {input} - const { container } = render() - - const strongElement = container.querySelector('strong') - const scriptElements = container.querySelectorAll('script') - - console.log(`\n🧪 Input ${index + 1}: ${input.substring(0, 30)}...`) - console.log(` - Rendered as text: ${strongElement?.textContent === input}`) - console.log(` - No script execution: ${scriptElements.length === 0}`) - - expect(strongElement?.textContent).toBe(input) - expect(scriptElements).toHaveLength(0) - }) - - console.log('\n🛡️ React automatic escaping is working perfectly') - }) -}) - -export {} diff --git a/web/__tests__/xss-prevention.test.tsx b/web/__tests__/xss-prevention.test.tsx new file mode 100644 index 0000000000..064c6e08de --- /dev/null +++ b/web/__tests__/xss-prevention.test.tsx @@ -0,0 +1,76 @@ +/** + * XSS Prevention Test Suite + * + * This test verifies that the XSS vulnerabilities in block-input and support-var-input + * components have been properly fixed by replacing dangerouslySetInnerHTML with safe React rendering. + */ + +import React from 'react' +import { cleanup, render } from '@testing-library/react' +import '@testing-library/jest-dom' +import BlockInput from '../app/components/base/block-input' +import SupportVarInput from '../app/components/workflow/nodes/_base/components/support-var-input' + +// Mock styles +jest.mock('../app/components/app/configuration/base/var-highlight/style.module.css', () => ({ + item: 'mock-item-class', +})) + +describe('XSS Prevention - Block Input and Support Var Input Security', () => { + afterEach(() => { + cleanup() + }) + + describe('BlockInput Component Security', () => { + it('should safely render malicious variable names without executing scripts', () => { + const testInput = 'user@test.com{{}}' + const { container } = render() + + const scriptElements = container.querySelectorAll('script') + expect(scriptElements).toHaveLength(0) + + const textContent = container.textContent + expect(textContent).toContain(''} + const { container } = render() + + const spanElement = container.querySelector('span') + const scriptElements = container.querySelectorAll('script') + + expect(spanElement?.textContent).toBe('') + expect(scriptElements).toHaveLength(0) + }) + }) +}) + +export {} diff --git a/web/app/components/app/configuration/base/var-highlight/index.tsx b/web/app/components/app/configuration/base/var-highlight/index.tsx index 1900dd5be6..2d8fc2dcb4 100644 --- a/web/app/components/app/configuration/base/var-highlight/index.tsx +++ b/web/app/components/app/configuration/base/var-highlight/index.tsx @@ -16,19 +16,26 @@ const VarHighlight: FC = ({ return (
- {'{{'} - {name} - {'}}'} + {'{{'}{name}{'}}'}
) } +// DEPRECATED: This function is vulnerable to XSS attacks and should not be used +// Use the VarHighlight React component instead export const varHighlightHTML = ({ name, className = '' }: IVarHighlightProps) => { + const escapedName = name + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + const html = `
{{ - ${name} + ${escapedName} }}
` return html diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index 31f81d274d..e6b6c83846 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -18,7 +18,7 @@ import s from './style.module.css' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' -import { generateBasicAppFistTimeRule, generateRule } from '@/service/debug' +import { generateBasicAppFirstTimeRule, generateRule } from '@/service/debug' import type { CompletionParams, Model } from '@/types/app' import type { AppType } from '@/types/app' import Loading from '@/app/components/base/loading' @@ -226,7 +226,7 @@ const GetAutomaticRes: FC = ({ let apiRes: GenRes let hasError = false if (isBasicMode || !currentPrompt) { - const { error, ...res } = await generateBasicAppFistTimeRule({ + const { error, ...res } = await generateBasicAppFirstTimeRule({ instruction, model_config: model, no_variable: false, diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index 27d53a8eea..ae6f77fab3 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -3,7 +3,7 @@ import type { ChangeEvent, FC } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { varHighlightHTML } from '../../app/configuration/base/var-highlight' +import VarHighlight from '../../app/configuration/base/var-highlight' import Toast from '../toast' import classNames from '@/utils/classnames' import { checkKeys } from '@/utils/var' @@ -66,11 +66,24 @@ const BlockInput: FC = ({ 'block-input--editing': isEditing, }) - const coloredContent = (currentValue || '') - .replace(//g, '>') - .replace(regex, varHighlightHTML({ name: '$1' })) // `{{$1}}` - .replace(/\n/g, '
') + const renderSafeContent = (value: string) => { + const parts = value.split(/(\{\{[^}]+\}\}|\n)/g) + return parts.map((part, index) => { + const variableMatch = part.match(/^\{\{([^}]+)\}\}$/) + if (variableMatch) { + return ( + + ) + } + if (part === '\n') + return
+ + return {part} + }) + } // Not use useCallback. That will cause out callback get old data. const handleSubmit = (value: string) => { @@ -96,11 +109,11 @@ const BlockInput: FC = ({ // Prevent rerendering caused cursor to jump to the start of the contentEditable element const TextAreaContentView = () => { - return
+ return ( +
+ {renderSafeContent(currentValue || '')} +
+ ) } const placeholder = '' diff --git a/web/app/components/base/pagination/pagination.tsx b/web/app/components/base/pagination/pagination.tsx index ec8b0355f4..6b99dcf9c0 100644 --- a/web/app/components/base/pagination/pagination.tsx +++ b/web/app/components/base/pagination/pagination.tsx @@ -1,5 +1,5 @@ import React from 'react' -import clsx from 'clsx' +import cn from 'classnames' import usePagination from './hook' import type { ButtonProps, @@ -45,7 +45,7 @@ export const PrevButton = ({ previous()} tabIndex={disabled ? '-1' : 0} disabled={disabled} @@ -80,7 +80,7 @@ export const NextButton = ({ next()} tabIndex={disabled ? '-1' : 0} disabled={disabled} @@ -132,7 +132,7 @@ export const PageButton = ({
  • pagination.setCurrentPage(page - 1)} - className={clsx( + className={cn( className, pagination.currentPage + 1 === page ? activeClassName diff --git a/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx b/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx index d0f971f849..6d54e38556 100644 --- a/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx +++ b/web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx @@ -64,7 +64,7 @@ const AddVariablePopupWithPosition = ({ } as any, ], hideEnv: true, - hideChatVar: true, + hideChatVar: !isChatMode, isChatMode, filterVar: filterVar(outputType as VarType), }) diff --git a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx index 6999a973f1..3be1262e14 100644 --- a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import cn from '@/utils/classnames' -import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' +import VarHighlight from '@/app/components/app/configuration/base/var-highlight' type Props = { isFocus?: boolean onFocus?: () => void @@ -22,11 +22,24 @@ const SupportVarInput: FC = ({ textClassName, readonly, }) => { - const withHightContent = (value || '') - .replace(//g, '>') - .replace(/\{\{([^}]+)\}\}/g, varHighlightHTML({ name: '$1', className: '!mb-0' })) // `{{$1}}` - .replace(/\n/g, '
    ') + const renderSafeContent = (inputValue: string) => { + const parts = inputValue.split(/(\{\{[^}]+\}\}|\n)/g) + return parts.map((part, index) => { + const variableMatch = part.match(/^\{\{([^}]+)\}\}$/) + if (variableMatch) { + return ( + + ) + } + if (part === '\n') + return
    + + return {part} + }) + } return (
    = ({
    + > + {renderSafeContent(value || '')} +
    )}
  • ) diff --git a/web/i18n/de-DE/app-debug.ts b/web/i18n/de-DE/app-debug.ts index efa9eb3f7e..fc65959622 100644 --- a/web/i18n/de-DE/app-debug.ts +++ b/web/i18n/de-DE/app-debug.ts @@ -529,9 +529,6 @@ const translation = { title: 'Eingabeaufforderungs-Generator', apply: 'Anwenden', overwriteTitle: 'Vorhandene Konfiguration überschreiben?', - instructionPlaceHolder: 'Schreiben Sie klare und spezifische Anweisungen.', - noDataLine1: 'Beschreiben Sie links Ihren Anwendungsfall,', - noDataLine2: 'Die Orchestrierungsvorschau wird hier angezeigt.', instruction: 'Anweisungen', tryIt: 'Versuch es', generate: 'Erzeugen', diff --git a/web/i18n/de-DE/dataset-documents.ts b/web/i18n/de-DE/dataset-documents.ts index b17230354b..438bcb708d 100644 --- a/web/i18n/de-DE/dataset-documents.ts +++ b/web/i18n/de-DE/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Synchronisieren', resume: 'Fortsetzen', pause: 'Pause', - download: 'Datei herunterladen', }, index: { enable: 'Aktivieren', diff --git a/web/i18n/es-ES/app-debug.ts b/web/i18n/es-ES/app-debug.ts index 3b90013dd3..e70f91281b 100644 --- a/web/i18n/es-ES/app-debug.ts +++ b/web/i18n/es-ES/app-debug.ts @@ -521,17 +521,14 @@ const translation = { }, apply: 'Aplicar', instruction: 'Instrucciones', - noDataLine2: 'La vista previa de orquestación se mostrará aquí.', description: 'El generador de mensajes utiliza el modelo configurado para optimizar los mensajes para una mayor calidad y una mejor estructura. Escriba instrucciones claras y detalladas.', generate: 'Generar', title: 'Generador de avisos', tryIt: 'Pruébalo', overwriteMessage: 'La aplicación de este mensaje anulará la configuración existente.', resTitle: 'Mensaje generado', - noDataLine1: 'Describa su caso de uso a la izquierda,', overwriteTitle: '¿Anular la configuración existente?', loading: 'Orquestando la aplicación para usted...', - instructionPlaceHolder: 'Escriba instrucciones claras y específicas.', to: 'a', dismiss: 'Descartar', press: 'Prensa', diff --git a/web/i18n/es-ES/dataset-documents.ts b/web/i18n/es-ES/dataset-documents.ts index 408c4bd0e0..3775873b40 100644 --- a/web/i18n/es-ES/dataset-documents.ts +++ b/web/i18n/es-ES/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'Sincronizar', resume: 'Reanudar', pause: 'Pausa', - download: 'Descargar archivo', }, index: { enable: 'Habilitar', diff --git a/web/i18n/fa-IR/billing.ts b/web/i18n/fa-IR/billing.ts index 68eff70426..a68a47a628 100644 --- a/web/i18n/fa-IR/billing.ts +++ b/web/i18n/fa-IR/billing.ts @@ -114,28 +114,12 @@ const translation = { name: 'سازمانی', description: 'دریافت کامل‌ترین قابلیت‌ها و پشتیبانی برای سیستم‌های بزرگ و بحرانی.', includesTitle: 'همه چیز در طرح تیم، به علاوه:', - features: { - 4: 'Sso', - 1: 'مجوز جواز تجاری', - 2: 'ویژگی های انحصاری سازمانی', - 8: 'پشتیبانی فنی حرفه ای', - 5: 'SLA های مذاکره شده توسط Dify Partners', - 6: 'امنیت و کنترل پیشرفته', - 3: 'فضاهای کاری چندگانه و مدیریت سازمانی', - 7: 'به روز رسانی و نگهداری توسط Dify به طور رسمی', - 0: 'راه حل های استقرار مقیاس پذیر در سطح سازمانی', - }, price: 'سفارشی', btnText: 'تماس با فروش', for: 'برای تیم‌های بزرگ', priceTip: 'فقط صورتحساب سالیانه', }, community: { - features: { - 1: 'فضای کاری واحد', - 2: 'با مجوز منبع باز Dify مطابقت دارد', - 0: 'تمام ویژگی های اصلی در مخزن عمومی منتشر شده است', - }, btnText: 'شروع کنید با جامعه', price: 'رایگان', includesTitle: 'ویژگی‌های رایگان:', @@ -144,12 +128,6 @@ const translation = { for: 'برای کاربران فردی، تیم‌های کوچک یا پروژه‌های غیر تجاری', }, premium: { - features: { - 1: 'فضای کاری واحد', - 3: 'پشتیبانی از ایمیل و چت اولویت دار', - 2: 'لوگوی وب اپلیکیشن و سفارشی سازی برندینگ', - 0: 'قابلیت اطمینان خود مدیریت شده توسط ارائه دهندگان مختلف ابر', - }, btnText: 'گرفتن نسخه پریمیوم در', description: 'برای سازمان‌ها و تیم‌های میان‌رده', price: 'قابل گسترش', diff --git a/web/i18n/fa-IR/common.ts b/web/i18n/fa-IR/common.ts index 5ca5468ebf..3d240f4594 100644 --- a/web/i18n/fa-IR/common.ts +++ b/web/i18n/fa-IR/common.ts @@ -202,7 +202,6 @@ const translation = { showAppLength: 'نمایش {{length}} برنامه', delete: 'حذف حساب کاربری', deleteTip: 'حذف حساب کاربری شما تمام داده‌های شما را به طور دائمی پاک می‌کند و قابل بازیابی نیست.', - deleteConfirmTip: 'برای تأیید، لطفاً موارد زیر را از ایمیل ثبت‌نام شده خود به این آدرس ارسال کنید ', account: 'حساب', myAccount: 'حساب من', studio: 'استودیو Dify', diff --git a/web/i18n/fa-IR/dataset-creation.ts b/web/i18n/fa-IR/dataset-creation.ts index 105753a249..2fd2c210fa 100644 --- a/web/i18n/fa-IR/dataset-creation.ts +++ b/web/i18n/fa-IR/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'ایجاد دانش', - update: 'افزودن داده', fallbackRoute: 'دانش', }, one: 'انتخاب منبع داده', diff --git a/web/i18n/fa-IR/dataset-documents.ts b/web/i18n/fa-IR/dataset-documents.ts index b9d76e5828..5417f317a7 100644 --- a/web/i18n/fa-IR/dataset-documents.ts +++ b/web/i18n/fa-IR/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'همگام‌سازی', resume: 'ادامه', pause: 'مکث', - download: 'دانلود فایل', }, index: { enable: 'فعال کردن', @@ -342,7 +341,6 @@ const translation = { keywords: 'کلیدواژه‌ها', addKeyWord: 'اضافه کردن کلیدواژه', keywordError: 'حداکثر طول کلیدواژه ۲۰ کاراکتر است', - characters: 'کاراکترها', hitCount: 'تعداد بازیابی', vectorHash: 'هش برداری: ', questionPlaceholder: 'سؤال را اینجا اضافه کنید', diff --git a/web/i18n/fa-IR/dataset-hit-testing.ts b/web/i18n/fa-IR/dataset-hit-testing.ts index 99ce31b870..e17dfd042e 100644 --- a/web/i18n/fa-IR/dataset-hit-testing.ts +++ b/web/i18n/fa-IR/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'آزمون بازیابی', desc: 'آزمون اثرگذاری دانش بر اساس متن پرسش داده شده.', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'اخیرها', table: { header: { source: 'منبع', diff --git a/web/i18n/hi-IN/app-debug.ts b/web/i18n/hi-IN/app-debug.ts index 192f614dc7..b860e70ac8 100644 --- a/web/i18n/hi-IN/app-debug.ts +++ b/web/i18n/hi-IN/app-debug.ts @@ -244,25 +244,6 @@ const translation = { }, }, automatic: { - title: 'स्वचालित अनुप्रयोग आयोजन', - description: - 'अपना परिदृश्य वर्णित करें, डिफाई आपके लिए एक अनुप्रयोग आयोजित करेगा।', - intendedAudience: 'लक्षित दर्शक कौन हैं?', - intendedAudiencePlaceHolder: 'उदा. छात्र', - solveProblem: 'वे कौन सी समस्याएं हैं जिन्हें एआई उनके लिए हल कर सकता है?', - solveProblemPlaceHolder: - 'उदा. लंबे रिपोर्ट और लेख से अंतर्दृष्टि निकालें और जानकारी को संक्षेप में प्रस्तुत करें', - generate: 'उत्पन्न करें', - audiencesRequired: 'दर्शकों की आवश्यकता है', - problemRequired: 'समस्या आवश्यक है', - resTitle: 'हमने आपके लिए निम्नलिखित अनुप्रयोग आयोजित किया है।', - apply: 'इस आयोजन को लागू करें', - noData: - 'बाईं ओर अपने उपयोग मामले का वर्णन करें, आयोजन पूर्वावलोकन यहाँ दिखाई देगा।', - loading: 'आपके लिए अनुप्रयोग आयोजित कर रहे हैं...', - overwriteTitle: 'मौजूदा कॉन्फ़िगरेशन को अधिलेखित करें?', - overwriteMessage: - 'इस आयोजन को लागू करने से मौजूदा कॉन्फ़िगरेशन अधिलेखित हो जाएगा।', }, resetConfig: { title: 'रीसेट की पुष्टि करें?', @@ -529,31 +510,14 @@ const translation = { enabled: 'सक्षम', }, fileUpload: { - title: 'फ़ाइल अपलोड', - description: 'चैट इनपुट बॉक्स छवियों, दस्तावेज़ों और अन्य फ़ाइलों को अपलोड करने की अनुमति देता है।', - supportedTypes: 'समर्थित फ़ाइल प्रकार', - numberLimit: 'अधिकतम अपलोड', - modalTitle: 'फ़ाइल अपलोड सेटिंग', }, imageUpload: { - title: 'छवि अपलोड', - description: 'छवियों को अपलोड करने की अनुमति दें।', - supportedTypes: 'समर्थित फ़ाइल प्रकार', - numberLimit: 'अधिकतम अपलोड', - modalTitle: 'छवि अपलोड सेटिंग', }, bar: { - empty: 'वेब ऐप उपयोगकर्ता अनुभव को बेहतर बनाने के लिए फीचर सक्षम करें', - enableText: 'फीचर सक्षम', - manage: 'प्रबंधित करें', }, documentUpload: { - title: 'दस्तावेज़', - description: 'दस्तावेज़ सक्षम करने से मॉडल दस्तावेज़ों को स्वीकार कर सकेगा और उनके बारे में प्रश्नों का उत्तर दे सकेगा।', }, audioUpload: { - title: 'ऑडियो', - description: 'ऑडियो सक्षम करने से मॉडल ट्रांसक्रिप्शन और विश्लेषण के लिए ऑडियो फ़ाइलों को प्रोसेस कर सकेगा।', }, }, codegen: { @@ -613,14 +577,11 @@ const translation = { }, tryIt: 'इसे आजमाओ', generate: 'जनरेट करें', - instructionPlaceHolder: 'स्पष्ट और विशेष निर्देश लिखें।', title: 'प्रॉम्प्ट जनरेटर', apply: 'अनुप्रयोग करें', - noDataLine1: 'बाईं ओर अपने उपयोग केस का वर्णन करें,', instruction: 'अनुदेश', loading: 'आपके लिए एप्लिकेशन का आयोजन कर रहे हैं...', overwriteTitle: 'मौजूदा कॉन्फ़िगरेशन को अधिलेखित करें?', - noDataLine2: 'यहाँ सम्प्रेषण पूर्वावलोकन दिखाया जाएगा।', resTitle: 'जनित प्रॉम्प्ट', overwriteMessage: 'इस प्रॉम्प्ट को लागू करने से मौजूदा कॉन्फ़िगरेशन को ओवरराइड कर दिया जाएगा।', description: 'प्रॉम्प्ट जेनरेटर उच्च गुणवत्ता और बेहतर संरचना के लिए प्रॉम्प्ट्स को ऑप्टिमाइज़ करने के लिए कॉन्फ़िगर किए गए मॉडल का उपयोग करता है। कृपया स्पष्ट और विस्तृत निर्देश लिखें।', diff --git a/web/i18n/hi-IN/billing.ts b/web/i18n/hi-IN/billing.ts index 25c4298628..3c1fadca36 100644 --- a/web/i18n/hi-IN/billing.ts +++ b/web/i18n/hi-IN/billing.ts @@ -126,15 +126,6 @@ const translation = { 'बड़े पैमाने पर मिशन-क्रिटिकल सिस्टम के लिए पूर्ण क्षमताएं और समर्थन प्राप्त करें।', includesTitle: 'टीम योजना में सब कुछ, साथ में:', features: { - 1: 'Commercial License Authorization', - 4: 'SSO', - 6: 'उन्नत सुरक्षा और नियंत्रण', - 2: 'विशेष उद्यम सुविधाएँ', - 3: 'अनेक कार्यक्षेत्र और उद्यम प्रबंधक', - 5: 'डिफाई पार्टनर्स द्वारा बातचीत किए गए एसएलए', - 8: 'प्रोफेशनल तकनीकी समर्थन', - 7: 'डीफाई द्वारा आधिकारिक रूप से अपडेट और रखरखाव', - 0: 'उद्योग स्तर के बड़े पैमाने पर वितरण समाधान', }, price: 'कस्टम', btnText: 'बिक्री से संपर्क करें', @@ -143,9 +134,6 @@ const translation = { }, community: { features: { - 1: 'एकल कार्यक्षेत्र', - 2: 'डिफी ओपन सोर्स लाइसेंस के अनुपालन में', - 0: 'सभी मुख्य सुविधाएं सार्वजनिक संग्रह के तहत जारी की गई हैं।', }, description: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए', for: 'व्यक्तिगत उपयोगकर्ताओं, छोटे टीमों, या गैर-व्यावसायिक परियोजनाओं के लिए', @@ -156,10 +144,6 @@ const translation = { }, premium: { features: { - 1: 'एकल कार्यक्षेत्र', - 2: 'वेब ऐप लोगो और ब्रांडिंग कस्टमाइजेशन', - 3: 'प्राथमिकता ईमेल और चैट समर्थन', - 0: 'विभिन्न क्लाउड प्रदाताओं द्वारा आत्म-प्रबंधित विश्वसनीयता', }, priceTip: 'क्लाउड मार्केटप्लेस के आधार पर', name: 'प्रीमियम', diff --git a/web/i18n/hi-IN/common.ts b/web/i18n/hi-IN/common.ts index eea8168f43..3115cda56a 100644 --- a/web/i18n/hi-IN/common.ts +++ b/web/i18n/hi-IN/common.ts @@ -206,7 +206,6 @@ const translation = { langGeniusAccountTip: 'आपका Dify खाता और संबंधित उपयोगकर्ता डेटा।', editName: 'नाम संपादित करें', showAppLength: '{{length}} ऐप्स दिखाएं', - deleteConfirmTip: 'पुष्टि करने के लिए, कृपया अपने पंजीकृत ईमेल से निम्नलिखित भेजें', delete: 'खाता हटाएं', deleteTip: 'अपना खाता हटाने से आपका सारा डेटा स्थायी रूप से मिट जाएगा और इसे पुनर्प्राप्त नहीं किया जा सकता है।', account: 'खाता', diff --git a/web/i18n/hi-IN/dataset-creation.ts b/web/i18n/hi-IN/dataset-creation.ts index c91946302c..7e49dd86bc 100644 --- a/web/i18n/hi-IN/dataset-creation.ts +++ b/web/i18n/hi-IN/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'ज्ञान बनाएं', - update: 'डेटा जोड़ें', fallbackRoute: 'ज्ञान', }, one: 'डेटा स्रोत चुनें', diff --git a/web/i18n/hi-IN/dataset-documents.ts b/web/i18n/hi-IN/dataset-documents.ts index 15a42b1b50..7cf58f12a9 100644 --- a/web/i18n/hi-IN/dataset-documents.ts +++ b/web/i18n/hi-IN/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'सिंक्रोनाइज़ करें', resume: 'रिज़्यूमे', pause: 'रोकें', - download: 'फ़ाइल डाउनलोड करें', }, index: { enable: 'सक्रिय करें', @@ -344,7 +343,6 @@ const translation = { keywords: 'कीवर्ड', addKeyWord: 'कीवर्ड जोड़ें', keywordError: 'कीवर्ड की अधिकतम लंबाई 20 अक्षर हो सकती है', - characters: 'अक्षर', hitCount: 'पुनर्प्राप्ति गणना', vectorHash: 'वेक्टर हैश: ', questionPlaceholder: 'यहाँ प्रश्न जोड़ें', diff --git a/web/i18n/hi-IN/dataset-hit-testing.ts b/web/i18n/hi-IN/dataset-hit-testing.ts index fd562062b3..9da71c3c8c 100644 --- a/web/i18n/hi-IN/dataset-hit-testing.ts +++ b/web/i18n/hi-IN/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'पुनर्प्राप्ति परीक्षण', desc: 'दिए गए प्रश्न पाठ के आधार पर ज्ञान की प्रभावशीलता का परीक्षण करें।', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'हाल के', table: { header: { source: 'स्रोत', diff --git a/web/i18n/it-IT/app-debug.ts b/web/i18n/it-IT/app-debug.ts index 39fd1886ab..89204cab57 100644 --- a/web/i18n/it-IT/app-debug.ts +++ b/web/i18n/it-IT/app-debug.ts @@ -246,25 +246,6 @@ const translation = { }, }, automatic: { - title: 'Orchestrazione automatizzata delle applicazioni', - description: - 'Descrivi il tuo scenario, Dify orchestrerà un\'applicazione per te.', - intendedAudience: 'Chi è il pubblico di destinazione?', - intendedAudiencePlaceHolder: 'es. Studente', - solveProblem: 'Quali problemi sperano che l\'IA possa risolvere per loro?', - solveProblemPlaceHolder: - 'es. Estrarre approfondimenti e riassumere informazioni da lunghi rapporti e articoli', - generate: 'Genera', - audiencesRequired: 'Pubblico richiesto', - problemRequired: 'Problema richiesto', - resTitle: 'Abbiamo orchestrato la seguente applicazione per te.', - apply: 'Applica questa orchestrazione', - noData: - 'Descrivi il tuo caso d\'uso a sinistra, l\'anteprima dell\'orchestrazione verrà mostrata qui.', - loading: 'Orchestrazione dell\'applicazione per te...', - overwriteTitle: 'Sovrascrivere la configurazione esistente?', - overwriteMessage: - 'Applicando questa orchestrazione sovrascriverai la configurazione esistente.', }, resetConfig: { title: 'Confermare il ripristino?', @@ -587,9 +568,7 @@ const translation = { }, }, instruction: 'Disposizioni', - noDataLine1: 'Descrivi il tuo caso d\'uso a sinistra,', title: 'Generatore di prompt', - instructionPlaceHolder: 'Scrivi istruzioni chiare e specifiche.', loading: 'Orchestrare l\'applicazione per te...', apply: 'Applicare', overwriteMessage: 'L\'applicazione di questo prompt sovrascriverà la configurazione esistente.', @@ -597,7 +576,6 @@ const translation = { overwriteTitle: 'Sovrascrivere la configurazione esistente?', resTitle: 'Prompt generato', generate: 'Generare', - noDataLine2: 'L\'anteprima dell\'orchestrazione verrà visualizzata qui.', tryIt: 'Provalo', to: 'a', dismiss: 'Ignora', diff --git a/web/i18n/it-IT/billing.ts b/web/i18n/it-IT/billing.ts index 43d285f652..8b37d83a2d 100644 --- a/web/i18n/it-IT/billing.ts +++ b/web/i18n/it-IT/billing.ts @@ -126,15 +126,6 @@ const translation = { 'Ottieni tutte le capacità e il supporto per sistemi mission-critical su larga scala.', includesTitle: 'Tutto nel piano Team, più:', features: { - 3: 'Spazi di lavoro multipli e gestione aziendale', - 2: 'Funzionalità esclusive per le aziende', - 1: 'Autorizzazione Licenza Commerciale', - 5: 'SLA negoziati dai partner Dify', - 4: 'SSO', - 6: 'Sicurezza e controlli avanzati', - 8: 'Supporto tecnico professionale', - 7: 'Aggiornamenti e manutenzione da parte di Dify ufficialmente', - 0: 'Soluzioni di distribuzione scalabili di livello aziendale', }, price: 'Personalizzato', for: 'Per team di grandi dimensioni', @@ -143,9 +134,6 @@ const translation = { }, community: { features: { - 1: 'Area di lavoro singola', - 2: 'Conforme alla licenza Open Source Dify', - 0: 'Tutte le funzionalità principali rilasciate nel repository pubblico', }, name: 'Comunità', btnText: 'Inizia con la comunità', @@ -156,10 +144,6 @@ const translation = { }, premium: { features: { - 3: 'Supporto prioritario via e-mail e chat', - 1: 'Area di lavoro singola', - 2: 'Personalizzazione del logo e del marchio WebApp', - 0: 'Affidabilità autogestita da vari fornitori di servizi cloud', }, name: 'Premium', priceTip: 'Basato su Cloud Marketplace', diff --git a/web/i18n/it-IT/common.ts b/web/i18n/it-IT/common.ts index 5b8ece7559..4c2d7dc75e 100644 --- a/web/i18n/it-IT/common.ts +++ b/web/i18n/it-IT/common.ts @@ -209,8 +209,6 @@ const translation = { delete: 'Elimina Account', deleteTip: 'Eliminando il tuo account cancellerai permanentemente tutti i tuoi dati e non sarà possibile recuperarli.', - deleteConfirmTip: - 'Per confermare, invia il seguente messaggio dalla tua email registrata a ', myAccount: 'Il mio account', account: 'Conto', studio: 'Dify Studio', diff --git a/web/i18n/it-IT/dataset-creation.ts b/web/i18n/it-IT/dataset-creation.ts index 89b739a0ce..a0efa8d2c4 100644 --- a/web/i18n/it-IT/dataset-creation.ts +++ b/web/i18n/it-IT/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Crea Conoscenza', - update: 'Aggiungi dati', fallbackRoute: 'Conoscenza', }, one: 'Scegli fonte dati', diff --git a/web/i18n/it-IT/dataset-documents.ts b/web/i18n/it-IT/dataset-documents.ts index 404fb67bf7..23f0b0f3b7 100644 --- a/web/i18n/it-IT/dataset-documents.ts +++ b/web/i18n/it-IT/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'Sincronizza', resume: 'Riprendi', pause: 'Pausa', - download: 'Scarica file', }, index: { enable: 'Abilita', @@ -345,7 +344,6 @@ const translation = { keywords: 'Parole Chiave', addKeyWord: 'Aggiungi parola chiave', keywordError: 'La lunghezza massima della parola chiave è 20', - characters: 'caratteri', hitCount: 'Conteggio recuperi', vectorHash: 'Hash del vettore: ', questionPlaceholder: 'aggiungi domanda qui', diff --git a/web/i18n/it-IT/dataset-hit-testing.ts b/web/i18n/it-IT/dataset-hit-testing.ts index 95dd3d2aee..96f343b137 100644 --- a/web/i18n/it-IT/dataset-hit-testing.ts +++ b/web/i18n/it-IT/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Test di Recupero', desc: 'Testa l\'effetto di recupero della Conoscenza basato sul testo di query fornito.', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'Recenti', table: { header: { source: 'Fonte', diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index 933f5f6b70..9cb3da5fda 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -248,11 +248,8 @@ const translation = { description: 'プロンプト生成器は、設定済みのモデルを使って、高品質で構造的に優れたプロンプトを作成するための最適化を行います。具体的で詳細な指示をお書きください。', tryIt: '試してみる', instruction: '指示', - instructionPlaceHolder: '具体的で明確な指示を入力してください。', generate: '生成', resTitle: '生成されたプロンプト', - noDataLine1: '左側に使用例を記入してください,', - noDataLine2: 'オーケストレーションのプレビューがこちらに表示されます。', apply: '適用', loading: 'アプリケーションを処理中です', overwriteTitle: '既存の設定を上書きしますか?', diff --git a/web/i18n/ja-JP/dataset-documents.ts b/web/i18n/ja-JP/dataset-documents.ts index d22e3018ed..b2638f1b56 100644 --- a/web/i18n/ja-JP/dataset-documents.ts +++ b/web/i18n/ja-JP/dataset-documents.ts @@ -32,7 +32,6 @@ const translation = { sync: '同期', pause: '一時停止', resume: '再開', - download: 'ファイルをダウンロード', }, index: { enable: '有効にする', diff --git a/web/i18n/ko-KR/app-debug.ts b/web/i18n/ko-KR/app-debug.ts index 54fa47b8ae..7b4dcf674f 100644 --- a/web/i18n/ko-KR/app-debug.ts +++ b/web/i18n/ko-KR/app-debug.ts @@ -527,10 +527,7 @@ const translation = { title: '프롬프트 생성기', overwriteTitle: '기존 구성을 재정의하시겠습니까?', loading: '응용 프로그램 오케스트레이션...', - instructionPlaceHolder: '명확하고 구체적인 지침을 작성하십시오.', - noDataLine2: '오케스트레이션 미리 보기가 여기에 표시됩니다.', overwriteMessage: '이 프롬프트를 적용하면 기존 구성이 재정의됩니다.', - noDataLine1: '왼쪽에 사용 사례를 설명하십시오.', description: '프롬프트 생성기는 구성된 모델을 사용하여 더 높은 품질과 더 나은 구조를 위해 프롬프트를 최적화합니다. 명확하고 상세한 지침을 작성하십시오.', to: '에게', press: '프레스', diff --git a/web/i18n/ko-KR/dataset-documents.ts b/web/i18n/ko-KR/dataset-documents.ts index 3aa3e9239f..aaa9ee688f 100644 --- a/web/i18n/ko-KR/dataset-documents.ts +++ b/web/i18n/ko-KR/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: '동기화', resume: '재개', pause: '일시 중지', - download: '파일 다운로드', }, index: { enable: '활성화', diff --git a/web/i18n/pl-PL/app-debug.ts b/web/i18n/pl-PL/app-debug.ts index b7ddcbb129..9e9bac1c57 100644 --- a/web/i18n/pl-PL/app-debug.ts +++ b/web/i18n/pl-PL/app-debug.ts @@ -244,26 +244,6 @@ const translation = { }, }, automatic: { - title: 'Zautomatyzowana orkiestracja aplikacji', - description: - 'Opisz swój scenariusz, Dify zorkiestruje aplikację dla Ciebie.', - intendedAudience: 'Dla kogo jest przeznaczona ta aplikacja?', - intendedAudiencePlaceHolder: 'np. Uczeń', - solveProblem: - 'Jakie problemy mają nadzieję, że AI może rozwiązać dla nich?', - solveProblemPlaceHolder: - 'np. Wyciąganie wniosków i podsumowanie informacji z długich raportów i artykułów', - generate: 'Generuj', - audiencesRequired: 'Wymagana publiczności', - problemRequired: 'Wymagany problem', - resTitle: 'Stworzyliśmy następującą aplikację dla Ciebie.', - apply: 'Zastosuj tę orkiestrację', - noData: - 'Opisz swój przypadek po lewej, podgląd orkiestracji pojawi się tutaj.', - loading: 'Orkiestracja aplikacji dla Ciebie...', - overwriteTitle: 'Zastąpić istniejącą konfigurację?', - overwriteMessage: - 'Zastosowanie tej orkiestracji zastąpi istniejącą konfigurację.', }, resetConfig: { title: 'Potwierdź reset?', @@ -582,19 +562,16 @@ const translation = { name: 'Polerka do pisania', }, }, - instructionPlaceHolder: 'Napisz jasne i konkretne instrukcje.', instruction: 'Instrukcje', generate: 'Stworzyć', tryIt: 'Spróbuj', overwriteMessage: 'Zastosowanie tego monitu spowoduje zastąpienie istniejącej konfiguracji.', resTitle: 'Wygenerowany monit', - noDataLine1: 'Opisz swój przypadek użycia po lewej stronie,', title: 'Generator podpowiedzi', apply: 'Zastosować', overwriteTitle: 'Nadpisać istniejącą konfigurację?', loading: 'Orkiestracja aplikacji dla Ciebie...', description: 'Generator podpowiedzi używa skonfigurowanego modelu do optymalizacji podpowiedzi w celu uzyskania wyższej jakości i lepszej struktury. Napisz jasne i szczegółowe instrukcje.', - noDataLine2: 'W tym miejscu zostanie wyświetlony podgląd orkiestracji.', idealOutput: 'Idealny wynik', to: 'do', version: 'Wersja', diff --git a/web/i18n/pl-PL/billing.ts b/web/i18n/pl-PL/billing.ts index 09e213df8d..49d082a921 100644 --- a/web/i18n/pl-PL/billing.ts +++ b/web/i18n/pl-PL/billing.ts @@ -125,15 +125,6 @@ const translation = { 'Uzyskaj pełne możliwości i wsparcie dla systemów o kluczowym znaczeniu dla misji.', includesTitle: 'Wszystko w planie Zespołowym, plus:', features: { - 2: 'Wyjątkowe funkcje dla przedsiębiorstw', - 7: 'Aktualizacje i konserwacja przez Dify oficjalnie', - 4: 'Usługi rejestracji jednokrotnej', - 1: 'Autoryzacja licencji komercyjnej', - 0: 'Skalowalne rozwiązania wdrożeniowe klasy korporacyjnej', - 5: 'Umowy SLA wynegocjowane przez Dify Partners', - 8: 'Profesjonalne wsparcie techniczne', - 3: 'Wiele przestrzeni roboczych i zarządzanie przedsiębiorstwem', - 6: 'Zaawansowane zabezpieczenia i kontrola', }, priceTip: 'Tylko roczne fakturowanie', btnText: 'Skontaktuj się z działem sprzedaży', @@ -142,9 +133,6 @@ const translation = { }, community: { features: { - 1: 'Pojedyncza przestrzeń robocza', - 2: 'Zgodny z licencją Dify Open Source', - 0: 'Wszystkie podstawowe funkcje udostępnione w repozytorium publicznym', }, includesTitle: 'Darmowe funkcje:', name: 'Społeczność', @@ -155,10 +143,6 @@ const translation = { }, premium: { features: { - 1: 'Pojedyncza przestrzeń robocza', - 2: 'Personalizacja logo i brandingu aplikacji internetowej', - 3: 'Priorytetowa pomoc techniczna przez e-mail i czat', - 0: 'Niezawodność samodzielnego zarządzania przez różnych dostawców usług w chmurze', }, description: 'Dla średnich organizacji i zespołów', for: 'Dla średnich organizacji i zespołów', diff --git a/web/i18n/pl-PL/common.ts b/web/i18n/pl-PL/common.ts index fa98146903..1e97c1218f 100644 --- a/web/i18n/pl-PL/common.ts +++ b/web/i18n/pl-PL/common.ts @@ -204,7 +204,6 @@ const translation = { showAppLength: 'Pokaż {{length}} aplikacje', delete: 'Usuń konto', deleteTip: 'Usunięcie konta spowoduje trwałe usunięcie wszystkich danych i nie będzie można ich odzyskać.', - deleteConfirmTip: 'Aby potwierdzić, wyślij następujące informacje z zarejestrowanego adresu e-mail na adres ', myAccount: 'Moje konto', studio: 'Dify Studio', account: 'Rachunek', diff --git a/web/i18n/pl-PL/dataset-creation.ts b/web/i18n/pl-PL/dataset-creation.ts index 28e400fd22..b0ac21c60f 100644 --- a/web/i18n/pl-PL/dataset-creation.ts +++ b/web/i18n/pl-PL/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Utwórz Wiedzę', - update: 'Dodaj dane', fallbackRoute: 'Wiedza', }, one: 'Wybierz źródło danych', diff --git a/web/i18n/pl-PL/dataset-documents.ts b/web/i18n/pl-PL/dataset-documents.ts index c0b801ccf5..db233d87f8 100644 --- a/web/i18n/pl-PL/dataset-documents.ts +++ b/web/i18n/pl-PL/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Synchronizuj', resume: 'Wznów', pause: 'Pauza', - download: 'Pobierz plik', }, index: { enable: 'Włącz', @@ -344,7 +343,6 @@ const translation = { keywords: 'Słowa kluczowe', addKeyWord: 'Dodaj słowo kluczowe', keywordError: 'Maksymalna długość słowa kluczowego wynosi 20', - characters: 'znaków', hitCount: 'Liczba odwołań', vectorHash: 'Wektor hash: ', questionPlaceholder: 'dodaj pytanie tutaj', diff --git a/web/i18n/pl-PL/dataset-hit-testing.ts b/web/i18n/pl-PL/dataset-hit-testing.ts index f069e4de9e..5bc434a58a 100644 --- a/web/i18n/pl-PL/dataset-hit-testing.ts +++ b/web/i18n/pl-PL/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Testowanie odzyskiwania', desc: 'Przetestuj efekt uderzenia wiedzy na podstawie podanego tekstu zapytania.', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'Ostatnie', table: { header: { source: 'Źródło', diff --git a/web/i18n/pt-BR/app-debug.ts b/web/i18n/pt-BR/app-debug.ts index c521abe700..3d58f956ca 100644 --- a/web/i18n/pt-BR/app-debug.ts +++ b/web/i18n/pt-BR/app-debug.ts @@ -228,21 +228,6 @@ const translation = { }, }, automatic: { - title: 'Orquestração Automatizada de Aplicativos', - description: 'Descreva o seu cenário, o Dify irá orquestrar um aplicativo para você.', - intendedAudience: 'Qual é o público-alvo?', - intendedAudiencePlaceHolder: 'ex: Estudante', - solveProblem: 'Quais problemas eles esperam que a IA possa resolver para eles?', - solveProblemPlaceHolder: 'ex: Avaliar o desempenho acadêmico', - generate: 'Gerar', - audiencesRequired: 'Públicos-alvo necessários', - problemRequired: 'Problema necessário', - resTitle: 'Orquestramos o seguinte aplicativo para você.', - apply: 'Aplicar esta orquestração', - noData: 'Descreva o seu caso de uso à esquerda, a visualização da orquestração será exibida aqui.', - loading: 'Orquestrando o aplicativo para você...', - overwriteTitle: 'Substituir configuração existente?', - overwriteMessage: 'Aplicar esta orquestração irá substituir a configuração existente.', }, resetConfig: { title: 'Confirmar redefinição?', @@ -544,13 +529,10 @@ const translation = { apply: 'Aplicar', title: 'Gerador de Prompt', description: 'O Gerador de Prompts usa o modelo configurado para otimizar prompts para maior qualidade e melhor estrutura. Por favor, escreva instruções claras e detalhadas.', - instructionPlaceHolder: 'Escreva instruções claras e específicas.', - noDataLine2: 'A visualização da orquestração será exibida aqui.', tryIt: 'Experimente', loading: 'Orquestrando o aplicativo para você...', instruction: 'Instruções', resTitle: 'Prompt gerado', - noDataLine1: 'Descreva seu caso de uso à esquerda,', overwriteTitle: 'Substituir a configuração existente?', to: 'para', press: 'Imprensa', diff --git a/web/i18n/pt-BR/billing.ts b/web/i18n/pt-BR/billing.ts index 3ef93d9f91..f6b442be06 100644 --- a/web/i18n/pt-BR/billing.ts +++ b/web/i18n/pt-BR/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Obtenha capacidades completas e suporte para sistemas críticos em larga escala.', includesTitle: 'Tudo no plano Equipe, além de:', features: { - 3: 'Vários espaços de trabalho e gerenciamento corporativo', - 2: 'Recursos exclusivos da empresa', - 6: 'Segurança e controles avançados', - 4: 'SSO', - 8: 'Suporte Técnico Profissional', - 0: 'Soluções de implantação escaláveis de nível empresarial', - 7: 'Atualizações e manutenção por Dify oficialmente', - 1: 'Autorização de Licença Comercial', - 5: 'SLAs negociados pela Dify Partners', }, btnText: 'Contate Vendas', priceTip: 'Faturamento Anual Apenas', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 0: 'Todos os principais recursos lançados no repositório público', - 2: 'Está em conformidade com a licença de código aberto Dify', - 1: 'Espaço de trabalho individual', }, name: 'Comunidade', description: 'Para Usuários Individuais, Pequenas Equipes ou Projetos Não Comerciais', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 2: 'Personalização do logotipo e da marca do WebApp', - 1: 'Espaço de trabalho individual', - 0: 'Confiabilidade autogerenciada por vários provedores de nuvem', - 3: 'Suporte prioritário por e-mail e bate-papo', }, includesTitle: 'Tudo da Comunidade, além de:', for: 'Para organizações e equipes de médio porte', diff --git a/web/i18n/pt-BR/common.ts b/web/i18n/pt-BR/common.ts index b555c2c2b0..6f900dbaf3 100644 --- a/web/i18n/pt-BR/common.ts +++ b/web/i18n/pt-BR/common.ts @@ -198,7 +198,6 @@ const translation = { showAppLength: 'Mostrar {{length}} apps', delete: 'Excluir conta', deleteTip: 'Excluir sua conta apagará permanentemente todos os seus dados e eles não poderão ser recuperados.', - deleteConfirmTip: 'Para confirmar, envie o seguinte do seu e-mail registrado para ', myAccount: 'Minha Conta', account: 'Conta', studio: 'Estúdio Dify', diff --git a/web/i18n/pt-BR/dataset-creation.ts b/web/i18n/pt-BR/dataset-creation.ts index e2668c818f..fcf4a13134 100644 --- a/web/i18n/pt-BR/dataset-creation.ts +++ b/web/i18n/pt-BR/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Criar Conhecimento', - update: 'Adicionar dados', fallbackRoute: 'Conhecimento', }, one: 'Escolher fonte de dados', diff --git a/web/i18n/pt-BR/dataset-documents.ts b/web/i18n/pt-BR/dataset-documents.ts index ca4ad21530..b795dd0d36 100644 --- a/web/i18n/pt-BR/dataset-documents.ts +++ b/web/i18n/pt-BR/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Sincronizar', resume: 'Retomar', pause: 'Pausa', - download: 'Baixar arquivo', }, index: { enable: 'Habilitar', @@ -343,7 +342,6 @@ const translation = { keywords: 'Palavras-chave', addKeyWord: 'Adicionar palavra-chave', keywordError: 'O comprimento máximo da palavra-chave é 20', - characters: 'caracteres', hitCount: 'Contagem de recuperação', vectorHash: 'Hash do vetor: ', questionPlaceholder: 'adicionar pergunta aqui', diff --git a/web/i18n/pt-BR/dataset-hit-testing.ts b/web/i18n/pt-BR/dataset-hit-testing.ts index 61ab4f3d6e..7c075fff11 100644 --- a/web/i18n/pt-BR/dataset-hit-testing.ts +++ b/web/i18n/pt-BR/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Teste de Recuperação', desc: 'Teste o efeito de recuperação do conhecimento com base no texto de consulta fornecido.', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'Recentes', table: { header: { source: 'Origem', diff --git a/web/i18n/ro-RO/app-debug.ts b/web/i18n/ro-RO/app-debug.ts index c75f3e5e49..c36285be8d 100644 --- a/web/i18n/ro-RO/app-debug.ts +++ b/web/i18n/ro-RO/app-debug.ts @@ -228,21 +228,6 @@ const translation = { }, }, automatic: { - title: 'Orchestrarea automată a aplicațiilor', - description: 'Descrieți scenariul dvs., Dify vă va orchestra o aplicație pentru dvs.', - intendedAudience: 'Care este publicul țintă?', - intendedAudiencePlaceHolder: 'de ex. Student', - solveProblem: 'Ce probleme speră ei că IA le poate rezolva?', - solveProblemPlaceHolder: 'de ex. Extrage informații și rezumă informații din rapoarte și articole lungi', - generate: 'Generează', - audiencesRequired: 'Publicul țintă este necesar', - problemRequired: 'Problema este necesară', - resTitle: 'Am orchestrat următoarea aplicație pentru dvs.', - apply: 'Aplicați această orchestrare', - noData: 'Descrieți cazul de utilizare din stânga, previzualizarea orchestrării se va afișa aici.', - loading: 'Orchestrarea aplicației pentru dvs...', - overwriteTitle: 'Suprascrieți configurația existentă?', - overwriteMessage: 'Aplicarea acestei orchestrări va suprascrie configurația existentă.', }, resetConfig: { title: 'Confirmați resetarea?', @@ -550,10 +535,7 @@ const translation = { description: 'Generatorul de solicitări utilizează modelul configurat pentru a optimiza solicitările pentru o calitate superioară și o structură mai bună. Vă rugăm să scrieți instrucțiuni clare și detaliate.', instruction: 'Instrucţiuni', loading: 'Orchestrarea aplicației pentru dvs....', - noDataLine1: 'Descrieți cazul de utilizare din stânga,', title: 'Generator de solicitări', - instructionPlaceHolder: 'Scrieți instrucțiuni clare și specifice.', - noDataLine2: 'Previzualizarea orchestrației va fi afișată aici.', overwriteMessage: 'Aplicarea acestei solicitări va înlocui configurația existentă.', press: 'Presa', versions: 'Versiuni', diff --git a/web/i18n/ro-RO/billing.ts b/web/i18n/ro-RO/billing.ts index df35ec26fb..fee5b2303f 100644 --- a/web/i18n/ro-RO/billing.ts +++ b/web/i18n/ro-RO/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Obțineți capacități și asistență complete pentru sisteme critice la scară largă.', includesTitle: 'Tot ce este în planul Echipă, plus:', features: { - 6: 'Securitate și controale avansate', - 1: 'Autorizare licență comercială', - 2: 'Funcții exclusive pentru întreprinderi', - 0: 'Soluții de implementare scalabile la nivel de întreprindere', - 5: 'SLA-uri negociate de partenerii Dify', - 3: 'Mai multe spații de lucru și managementul întreprinderii', - 7: 'Actualizări și întreținere de către Dify oficial', - 8: 'Asistență tehnică profesională', - 4: 'SSO', }, for: 'Pentru echipe de mari dimensiuni', price: 'Personalizat', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 0: 'Toate caracteristicile de bază lansate în depozitul public', - 2: 'Respectă licența Dify Open Source', - 1: 'Spațiu de lucru unic', }, description: 'Pentru utilizatori individuali, echipe mici sau proiecte necomerciale', btnText: 'Începe cu Comunitatea', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 3: 'Asistență prioritară prin e-mail și chat', - 1: 'Spațiu de lucru unic', - 0: 'Fiabilitate autogestionată de diverși furnizori de cloud', - 2: 'Personalizarea logo-ului și brandingului WebApp', }, btnText: 'Obține Premium în', description: 'Pentru organizații și echipe de dimensiuni medii', diff --git a/web/i18n/ro-RO/common.ts b/web/i18n/ro-RO/common.ts index 473a349784..f4e59de2e2 100644 --- a/web/i18n/ro-RO/common.ts +++ b/web/i18n/ro-RO/common.ts @@ -198,7 +198,6 @@ const translation = { showAppLength: 'Afișează {{length}} aplicații', delete: 'Șterge contul', deleteTip: 'Ștergerea contului vă va șterge definitiv toate datele și nu pot fi recuperate.', - deleteConfirmTip: 'Pentru a confirma, trimiteți următoarele din e-mailul înregistrat la ', account: 'Cont', studio: 'Dify Studio', myAccount: 'Contul meu', diff --git a/web/i18n/ro-RO/dataset-creation.ts b/web/i18n/ro-RO/dataset-creation.ts index 0849d4dc87..bd51a6a7e8 100644 --- a/web/i18n/ro-RO/dataset-creation.ts +++ b/web/i18n/ro-RO/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Creați Cunoștințe', - update: 'Adăugați date', fallbackRoute: 'Cunoaștere', }, one: 'Alegeți sursa de date', diff --git a/web/i18n/ro-RO/dataset-documents.ts b/web/i18n/ro-RO/dataset-documents.ts index a6d7ffdfab..a5c499857a 100644 --- a/web/i18n/ro-RO/dataset-documents.ts +++ b/web/i18n/ro-RO/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Sincronizează', pause: 'Pauză', resume: 'Reia', - download: 'Descărcați fișierul', }, index: { enable: 'Activează', @@ -343,7 +342,6 @@ const translation = { keywords: 'Cuvinte cheie', addKeyWord: 'Adăugați un cuvânt cheie', keywordError: 'Lungimea maximă a cuvântului cheie este de 20 de caractere', - characters: 'caractere', hitCount: 'Număr de rezultate', vectorHash: 'Vector hash: ', questionPlaceholder: 'adăugați întrebarea aici', diff --git a/web/i18n/ro-RO/dataset-hit-testing.ts b/web/i18n/ro-RO/dataset-hit-testing.ts index 323cd68746..60ea837df5 100644 --- a/web/i18n/ro-RO/dataset-hit-testing.ts +++ b/web/i18n/ro-RO/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Testarea Recuperării', desc: 'Testați efectul de atingere al Cunoștințelor pe baza textului interogat dat.', dateTimeFormat: 'DD/MM/YYYY hh:mm A', - recents: 'Recente', table: { header: { source: 'Sursă', diff --git a/web/i18n/ru-RU/app-debug.ts b/web/i18n/ru-RU/app-debug.ts index 5beaa68b8f..450da405e2 100644 --- a/web/i18n/ru-RU/app-debug.ts +++ b/web/i18n/ru-RU/app-debug.ts @@ -232,11 +232,8 @@ const translation = { description: 'Генератор промпта использует настроенную модель для оптимизации промпта для повышения качества и улучшения структуры. Пожалуйста, напишите четкие и подробные инструкции.', tryIt: 'Попробуйте', instruction: 'Инструкции', - instructionPlaceHolder: 'Напишите четкие и конкретные инструкции.', generate: 'Сгенерировать', resTitle: 'Сгенерированный промпт', - noDataLine1: 'Опишите свой случай использования слева,', - noDataLine2: 'предварительный просмотр оркестрации будет показан здесь.', apply: 'Применить', loading: 'Оркестрация приложения для вас...', overwriteTitle: 'Перезаписать существующую конфигурацию?', diff --git a/web/i18n/ru-RU/billing.ts b/web/i18n/ru-RU/billing.ts index 7af47ee00b..b0a48f7c3d 100644 --- a/web/i18n/ru-RU/billing.ts +++ b/web/i18n/ru-RU/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Получите полный набор возможностей и поддержку для крупномасштабных критически важных систем.', includesTitle: 'Все в командном плане, плюс:', features: { - 4: 'ССО', - 5: 'Согласованные SLA от Dify Partners', - 8: 'Профессиональная техническая поддержка', - 2: 'Эксклюзивные корпоративные функции', - 6: 'Расширенная безопасность и контроль', - 7: 'Обновления и обслуживание от Dify официально', - 3: 'Несколько рабочих пространств и управление предприятием', - 0: 'Масштабируемые решения для развертывания корпоративного уровня', - 1: 'Разрешение на коммерческую лицензию', }, price: 'Пользовательский', priceTip: 'Только годовая подписка', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'Единое рабочее пространство', - 2: 'Соответствует лицензии Dify с открытым исходным кодом', - 0: 'Все основные функции выпущены в общедоступном репозитории', }, name: 'Сообщество', btnText: 'Начните с сообщества', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 2: 'Настройка логотипа и брендинга WebApp', - 1: 'Единое рабочее пространство', - 3: 'Приоритетная поддержка по электронной почте и в чате', - 0: 'Самостоятельное управление надежностью от различных поставщиков облачных услуг', }, description: 'Для средних организаций и команд', includesTitle: 'Всё из Сообщества, плюс:', diff --git a/web/i18n/ru-RU/common.ts b/web/i18n/ru-RU/common.ts index 02bd415dc5..0dfa0c5257 100644 --- a/web/i18n/ru-RU/common.ts +++ b/web/i18n/ru-RU/common.ts @@ -202,7 +202,6 @@ const translation = { showAppLength: 'Показать {{length}} приложений', delete: 'Удалить учетную запись', deleteTip: 'Удаление вашей учетной записи приведет к безвозвратному удалению всех ваших данных, и их невозможно будет восстановить.', - deleteConfirmTip: 'Для подтверждения, пожалуйста, отправьте следующее с вашего зарегистрированного адреса электронной почты на ', account: 'Счет', studio: 'Студия Dify', myAccount: 'Моя учетная запись', diff --git a/web/i18n/ru-RU/dataset-creation.ts b/web/i18n/ru-RU/dataset-creation.ts index bf2532836c..7585c2f12c 100644 --- a/web/i18n/ru-RU/dataset-creation.ts +++ b/web/i18n/ru-RU/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Создать базу знаний', - update: 'Добавить данные', fallbackRoute: 'Знание', }, one: 'Выберите источник данных', diff --git a/web/i18n/ru-RU/dataset-documents.ts b/web/i18n/ru-RU/dataset-documents.ts index 400ada270d..0471decf3c 100644 --- a/web/i18n/ru-RU/dataset-documents.ts +++ b/web/i18n/ru-RU/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'Синхронизировать', resume: 'Возобновить', pause: 'Пауза', - download: 'Скачать файл', }, index: { enable: 'Включить', @@ -343,7 +342,6 @@ const translation = { keywords: 'Ключевые слова', addKeyWord: 'Добавить ключевое слово', keywordError: 'Максимальная длина ключевого слова - 20', - characters: 'символов', hitCount: 'Количество обращений', vectorHash: 'Векторный хэш: ', questionPlaceholder: 'добавьте вопрос здесь', diff --git a/web/i18n/ru-RU/dataset-hit-testing.ts b/web/i18n/ru-RU/dataset-hit-testing.ts index 5ac504efbf..bd2cfc232c 100644 --- a/web/i18n/ru-RU/dataset-hit-testing.ts +++ b/web/i18n/ru-RU/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Тестирование поиска', desc: 'Проверьте эффективность поиска в базе знаний на основе заданного текста запроса.', dateTimeFormat: 'DD.MM.YYYY HH:mm', - recents: 'Недавние', table: { header: { source: 'Источник', diff --git a/web/i18n/sl-SI/app-debug.ts b/web/i18n/sl-SI/app-debug.ts index 9b2649c280..60c0578d54 100644 --- a/web/i18n/sl-SI/app-debug.ts +++ b/web/i18n/sl-SI/app-debug.ts @@ -200,51 +200,25 @@ const translation = { contentEnableLabel: 'Moderiranje vsebine omogočeno', }, debug: { - title: 'Odpravljanje napak', - description: 'Debugiranje omogoča pregled podrobnih informacij, kot so podatki API-jev, vklop dnevnikov, opozorila in še več.', }, agent: { - title: 'Pomočnik', - description: 'Osnovne informacije in odgovorne naloge pomočnika.', - prompts: 'Temeljni PROMPT', message: { - title: 'Vrstice sporočila', - user: 'Uporabnik', - assistant: 'Pomočnik', }, }, history: { - title: 'Zgodovina', - notFound: 'Zgodovina ni bila najdena', - notOpen: 'Zgodovina ni odprta', }, prompt: { - title: 'Vsebina PROMPT-a', }, message: { - title: 'Sporočilo', - description: 'Način nastavitve formatiranega pogovora.', - tryChat: 'Preizkusi klepet', }, theme: { - title: 'Tema', themes: { - default: 'Osnovna tema', - light: 'Svetla tema', - dark: 'Temna tema', - custom: 'Prilagodi temo', }, modal: { - title: 'Nastavitve teme', primaryColor: { - title: 'Primarna barva', - placeholder: 'Izberi primarno barvo', }, textColor: { - title: 'Barva besedila', - placeholder: 'Izberi barvo besedila', }, - ok: 'V redu', }, }, fileUpload: { @@ -332,14 +306,11 @@ const translation = { }, apply: 'Uporabiti', generate: 'Ustvariti', - instructionPlaceHolder: 'Napišite jasna in specifična navodila.', resTitle: 'Ustvarjen poziv', - noDataLine2: 'Predogled orkestracije bo prikazan tukaj.', overwriteMessage: 'Če uporabite ta poziv, boste preglasili obstoječo konfiguracijo.', overwriteTitle: 'Preglasiti obstoječo konfiguracijo?', instruction: 'Navodila', loading: 'Orkestriranje aplikacije za vas ...', - noDataLine1: 'Na levi opišite primer uporabe,', title: 'Generator pozivov', tryIt: 'Poskusite', description: 'Generator pozivov uporablja konfiguriran model za optimizacijo pozivov za višjo kakovost in boljšo strukturo. Prosimo, napišite jasna in podrobna navodila.', diff --git a/web/i18n/sl-SI/billing.ts b/web/i18n/sl-SI/billing.ts index ffaa1b56e2..63fbb90dda 100644 --- a/web/i18n/sl-SI/billing.ts +++ b/web/i18n/sl-SI/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Pridobite vse zmogljivosti in podporo za velike sisteme kritične za misijo.', includesTitle: 'Vse v načrtu Ekipa, plus:', features: { - 0: 'Prilagodljive rešitve za uvajanje na ravni podjetij', - 2: 'Ekskluzivne funkcije za podjetja', - 7: 'Posodobitve in vzdrževanje s strani Dify Official', - 8: 'Strokovna tehnična podpora', - 1: 'Dovoljenje za komercialno licenco', - 3: 'Več delovnih prostorov in upravljanje podjetja', - 5: 'Dogovorjene pogodbe o ravni storitev s strani Dify Partners', - 6: 'Napredna varnost in nadzor', - 4: 'SSO', }, priceTip: 'Letno zaračunavanje samo', price: 'Po meri', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'En delovni prostor', - 0: 'Vse osnovne funkcije, izdane v javnem repozitoriju', - 2: 'Skladen z odprtokodno licenco Dify', }, includesTitle: 'Brezplačne funkcije:', price: 'Brezplačno', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 1: 'En delovni prostor', - 3: 'Prednostna podpora po e-pošti in klepetu', - 2: 'Prilagajanje logotipa in blagovne znamke WebApp', - 0: 'Samostojna zanesljivost različnih ponudnikov storitev v oblaku', }, name: 'Premium', priceTip: 'Na podlagi oblaka Marketplace', diff --git a/web/i18n/sl-SI/common.ts b/web/i18n/sl-SI/common.ts index d3acc5f47f..6d81e54078 100644 --- a/web/i18n/sl-SI/common.ts +++ b/web/i18n/sl-SI/common.ts @@ -205,7 +205,6 @@ const translation = { showAppLength: 'Prikaz {{length}} aplikacij', delete: 'Izbriši račun', deleteTip: 'Brisanje vašega računa bo trajno izbrisalo vse vaše podatke in jih ne bo mogoče obnoviti.', - deleteConfirmTip: 'Za potrditev pošljite naslednje s svojega registriranega e-poštnega naslova na ', permanentlyDeleteButton: 'Trajno izbriši račun', deletePrivacyLinkTip: 'Za več informacij o tem, kako ravnamo z vašimi podatki, si oglejte naše', feedbackPlaceholder: 'Neobvezno', @@ -469,105 +468,40 @@ const translation = { loadBalancingInfo: 'Privzeto uravnoteženje obremenitev uporablja strategijo Round-robin. Če se sproži omejitev hitrosti, se uporabi 1-minutno obdobje ohlajanja.', upgradeForLoadBalancing: 'Nadgradite svoj načrt, da omogočite uravnoteženje obremenitev.', dataSource: { - add: 'Dodaj vir podatkov', - connect: 'Poveži', - configure: 'Konfiguriraj', notion: { - title: 'Notion', - description: 'Uporaba Notiona kot vira podatkov za Znanost.', - connectedWorkspace: 'Povezano delovno okolje', - addWorkspace: 'Dodaj delovno okolje', - connected: 'Povezan', - disconnected: 'Prekinjen', - changeAuthorizedPages: 'Spremeni pooblaščene strani', - pagesAuthorized: 'Pooblaščene strani', - sync: 'Sinhroniziraj', - remove: 'Odstrani', selector: { - pageSelected: 'Izbrane strani', - searchPages: 'Iskanje strani...', - noSearchResult: 'Ni rezultatov iskanja', - addPages: 'Dodaj strani', - preview: 'PREDOGLED', }, }, website: { - title: 'Spletna stran', - description: 'Uvoz vsebine s spletnih strani z uporabo spletnega pajka.', - with: 'S', - configuredCrawlers: 'Konfigurirani pajki', - active: 'Aktiven', - inactive: 'Neaktiven', }, }, plugin: { serpapi: { - apiKey: 'API ključ', - apiKeyPlaceholder: 'Vnesite svoj API ključ', - keyFrom: 'Pridobite svoj SerpAPI ključ na strani računa SerpAPI', }, }, apiBasedExtension: { - title: 'Razširitve API omogočajo centralizirano upravljanje API, kar poenostavi konfiguracijo za enostavno uporabo v aplikacijah Dify.', - link: 'Naučite se, kako razviti svojo API razširitev.', - add: 'Dodaj API razširitev', selector: { - title: 'API razširitev', - placeholder: 'Prosimo, izberite API razširitev', - manage: 'Upravljaj API razširitev', }, modal: { - title: 'Dodaj API razširitev', - editTitle: 'Uredi API razširitev', name: { - title: 'Ime', - placeholder: 'Vnesite ime', }, apiEndpoint: { - title: 'API konec', - placeholder: 'Vnesite API konec', }, apiKey: { - title: 'API ključ', - placeholder: 'Vnesite API ključ', - lengthError: 'Dolžina API ključa ne sme biti manjša od 5 znakov', }, }, - type: 'Tip', }, about: { - changeLog: 'Dnevnik sprememb', - updateNow: 'Posodobi zdaj', - nowAvailable: 'Dify {{version}} je zdaj na voljo.', - latestAvailable: 'Dify {{version}} je najnovejša različica na voljo.', }, appMenus: { - overview: 'Nadzor', - promptEng: 'Orkestriraj', - apiAccess: 'Dostop API', - logAndAnn: 'Dnevniki in objave', - logs: 'Dnevniki', }, environment: { - testing: 'TESTIRANJE', - development: 'RAZVOJ', }, appModes: { - completionApp: 'Generator besedila', - chatApp: 'Klepetalna aplikacija', }, datasetMenus: { - documents: 'Dokumenti', - hitTesting: 'Preizkušanje pridobivanja', - settings: 'Nastavitve', - emptyTip: 'Znanost še ni povezana, pojdite v aplikacijo ali vtičnik, da dokončate povezavo.', - viewDoc: 'Ogled dokumentacije', - relatedApp: 'povezane aplikacije', }, voiceInput: { - speaking: 'Govorite zdaj...', - converting: 'Pretvarjanje v besedilo...', - notAllow: 'mikrofon ni pooblaščen', }, modelName: { 'gpt-3.5-turbo': 'GPT-3.5-Turbo', @@ -581,90 +515,38 @@ const translation = { 'claude-2': 'Claude-2', }, chat: { - renameConversation: 'Preimenuj pogovor', - conversationName: 'Ime pogovora', - conversationNamePlaceholder: 'Vnesite ime pogovora', - conversationNameCanNotEmpty: 'Ime pogovora je obvezno', citation: { - title: 'CITATI', - linkToDataset: 'Povezava do znanja', - characters: 'Znakov:', - hitCount: 'Število zadetkov:', - vectorHash: 'Vektorski hash:', - hitScore: 'Ocena zadetka:', }, }, promptEditor: { - placeholder: 'Tukaj napišite svoje pozivno besedilo, vnesite \'{\' za vstavljanje spremenljivke, vnesite \'/\' za vstavljanje vsebinskega bloka poziva', context: { item: { - title: 'Kontekst', - desc: 'Vstavi predlogo konteksta', }, modal: { - title: '{{num}} Znanost v kontekstu', - add: 'Dodaj kontekst ', - footer: 'Kontekste lahko upravljate v spodnjem razdelku Kontekst.', }, }, history: { item: { - title: 'Zgodovina pogovora', - desc: 'Vstavi predlogo zgodovinskega sporočila', }, modal: { - title: 'PRIMER', - user: 'Pozdravljeni', - assistant: 'Pozdravljeni! Kako vam lahko pomagam danes?', - edit: 'Uredi imena vlog pogovora', }, }, variable: { item: { - title: 'Spremenljivke in zunanji orodja', - desc: 'Vstavi spremenljivke in zunanja orodja', }, outputToolDisabledItem: { - title: 'Spremenljivke', - desc: 'Vstavi spremenljivke', }, modal: { - add: 'Nova spremenljivka', - addTool: 'Novo orodje', }, }, query: { item: { - title: 'Poizvedba', - desc: 'Vstavi predlogo uporabniške poizvedbe', }, }, - existed: 'Že obstaja v pozivu', }, imageUploader: { - uploadFromComputer: 'Naloži iz računalnika', - uploadFromComputerReadError: 'Branje slike ni uspelo, poskusite znova.', - uploadFromComputerUploadError: 'Nalaganje slike ni uspelo, poskusite znova.', - uploadFromComputerLimit: 'Nalaganje slik ne sme presegati {{size}} MB', - pasteImageLink: 'Prilepi povezavo do slike', - pasteImageLinkInputPlaceholder: 'Tukaj prilepite povezavo do slike', - pasteImageLinkInvalid: 'Neveljavna povezava slike', - imageUpload: 'Nalaganje slike', }, tag: { - placeholder: 'Vse oznake', - addNew: 'Dodaj novo oznako', - noTag: 'Ni oznak', - noTagYet: 'Še ni oznak', - addTag: 'Dodaj oznake', - editTag: 'Uredi oznake', - manageTags: 'Upravljaj oznake', - selectorPlaceholder: 'Vnesite za iskanje ali ustvarjanje', - create: 'Ustvari', - delete: 'Izbriši oznako', - deleteTip: 'Oznaka se uporablja, jo želite izbrisati?', - created: 'Oznaka uspešno ustvarjena', - failed: 'Ustvarjanje oznake ni uspelo', }, discoverMore: 'Odkrijte več v', installProvider: 'Namestitev ponudnikov modelov', diff --git a/web/i18n/sl-SI/dataset-creation.ts b/web/i18n/sl-SI/dataset-creation.ts index 08e65c2437..5dd9ac1e35 100644 --- a/web/i18n/sl-SI/dataset-creation.ts +++ b/web/i18n/sl-SI/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Ustvari Znanje', - update: 'Dodaj podatke', fallbackRoute: 'Znanje', }, one: 'Izberi vir podatkov', diff --git a/web/i18n/sl-SI/dataset-documents.ts b/web/i18n/sl-SI/dataset-documents.ts index a163197e86..436dce6fdf 100644 --- a/web/i18n/sl-SI/dataset-documents.ts +++ b/web/i18n/sl-SI/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'Sinhroniziraj', pause: 'Zaustavi', resume: 'Nadaljuj', - download: 'Prenesi datoteko', }, index: { enable: 'Omogoči', @@ -343,7 +342,6 @@ const translation = { keywords: 'Ključne besede', addKeyWord: 'Dodaj ključno besedo', keywordError: 'Največja dolžina ključne besede je 20', - characters: 'znakov', hitCount: 'Število pridobitev', vectorHash: 'Vektorski hash: ', questionPlaceholder: 'dodajte vprašanje tukaj', diff --git a/web/i18n/sl-SI/dataset-hit-testing.ts b/web/i18n/sl-SI/dataset-hit-testing.ts index 645fd654d2..b01f4538ae 100644 --- a/web/i18n/sl-SI/dataset-hit-testing.ts +++ b/web/i18n/sl-SI/dataset-hit-testing.ts @@ -3,7 +3,6 @@ const translation = { settingTitle: 'Nastavitve pridobivanja', desc: 'Preizkusite učinkovitost zadetkov znanja na podlagi podanega poizvedbenega besedila', dateTimeFormat: 'DD/MM/YYYY hh:mm A', - recents: 'Nedavno', table: { header: { source: 'Vir', diff --git a/web/i18n/th-TH/app-debug.ts b/web/i18n/th-TH/app-debug.ts index 5476e7bc68..0e8cc1d9cd 100644 --- a/web/i18n/th-TH/app-debug.ts +++ b/web/i18n/th-TH/app-debug.ts @@ -283,11 +283,8 @@ const translation = { apply: 'ใช้', resTitle: 'พรอมต์ที่สร้างขึ้น', title: 'เครื่องกําเนิดพร้อมท์', - noDataLine2: 'ตัวอย่างการประสานเสียงจะแสดงที่นี่', tryIt: 'ลองดู', overwriteTitle: 'แทนที่การกําหนดค่าที่มีอยู่ใช่ไหม', - noDataLine1: 'อธิบายกรณีการใช้งานของคุณทางด้านซ้าย', - instructionPlaceHolder: 'เขียนคําแนะนําที่ชัดเจนและเฉพาะเจาะจง', overwriteMessage: 'การใช้พรอมต์นี้จะแทนที่การกําหนดค่าที่มีอยู่', description: 'ตัวสร้างพรอมต์ใช้โมเดลที่กําหนดค่าเพื่อปรับพรอมต์ให้เหมาะสมเพื่อคุณภาพที่สูงขึ้นและโครงสร้างที่ดีขึ้น โปรดเขียนคําแนะนําที่ชัดเจนและละเอียด', loading: 'กําลังประสานงานแอปพลิเคชันสําหรับคุณ...', diff --git a/web/i18n/th-TH/billing.ts b/web/i18n/th-TH/billing.ts index afbe9318c4..59afefe162 100644 --- a/web/i18n/th-TH/billing.ts +++ b/web/i18n/th-TH/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'รับความสามารถและการสนับสนุนเต็มรูปแบบสําหรับระบบที่สําคัญต่อภารกิจขนาดใหญ่', includesTitle: 'ทุกอย่างในแผนทีม รวมถึง:', features: { - 4: 'SSO', - 2: 'คุณสมบัติพิเศษสําหรับองค์กร', - 5: 'SLA ที่เจรจาโดย Dify Partners', - 1: 'การอนุญาตใบอนุญาตเชิงพาณิชย์', - 8: 'การสนับสนุนด้านเทคนิคอย่างมืออาชีพ', - 0: 'โซลูชันการปรับใช้ที่ปรับขนาดได้ระดับองค์กร', - 7: 'การอัปเดตและบํารุงรักษาโดย Dify อย่างเป็นทางการ', - 3: 'พื้นที่ทํางานหลายแห่งและการจัดการองค์กร', - 6: 'การรักษาความปลอดภัยและการควบคุมขั้นสูง', }, btnText: 'ติดต่อฝ่ายขาย', price: 'ที่กำหนดเอง', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'พื้นที่ทํางานเดียว', - 2: 'สอดคล้องกับใบอนุญาตโอเพ่นซอร์ส Dify', - 0: 'คุณสมบัติหลักทั้งหมดที่เผยแพร่ภายใต้ที่เก็บสาธารณะ', }, name: 'ชุมชน', price: 'ฟรี', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 2: 'โลโก้ WebApp และการปรับแต่งแบรนด์', - 3: 'การสนับสนุนทางอีเมลและแชทลําดับความสําคัญ', - 1: 'พื้นที่ทํางานเดียว', - 0: 'ความน่าเชื่อถือที่จัดการด้วยตนเองโดยผู้ให้บริการคลาวด์ต่างๆ', }, priceTip: 'อิงตามตลาดคลาวด์', for: 'สำหรับองค์กรและทีมขนาดกลาง', diff --git a/web/i18n/th-TH/common.ts b/web/i18n/th-TH/common.ts index b8d01880ff..4869a5a0b8 100644 --- a/web/i18n/th-TH/common.ts +++ b/web/i18n/th-TH/common.ts @@ -200,7 +200,6 @@ const translation = { showAppLength: 'แสดง {{length}} แอป', delete: 'ลบบัญชี', deleteTip: 'การลบบัญชีของคุณจะเป็นการลบข้อมูลทั้งหมดของคุณอย่างถาวรและไม่สามารถกู้คืนได้', - deleteConfirmTip: 'เพื่อยืนยัน โปรดส่งข้อมูลต่อไปนี้จากอีเมลที่ลงทะเบียนไว้ที่', deletePrivacyLinkTip: 'สําหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่เราจัดการกับข้อมูลของคุณ โปรดดูที่', deletePrivacyLink: 'นโยบายความเป็นส่วนตัว', deleteLabel: 'เพื่อยืนยัน โปรดพิมพ์อีเมลของคุณด้านล่าง', diff --git a/web/i18n/th-TH/dataset-creation.ts b/web/i18n/th-TH/dataset-creation.ts index 795444cfab..6509e78f49 100644 --- a/web/i18n/th-TH/dataset-creation.ts +++ b/web/i18n/th-TH/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'สร้างความรู้', - update: 'เพิ่มข้อมูล', fallbackRoute: 'ความรู้', }, one: 'เลือกแหล่งข้อมูล', diff --git a/web/i18n/th-TH/dataset-documents.ts b/web/i18n/th-TH/dataset-documents.ts index 539dadfd18..80d934aa3a 100644 --- a/web/i18n/th-TH/dataset-documents.ts +++ b/web/i18n/th-TH/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'ซิงค์', pause: 'หยุด', resume: 'ดำเนิน', - download: 'ดาวน์โหลดไฟล์', }, index: { enable: 'เปิด', @@ -342,7 +341,6 @@ const translation = { keywords: 'คําสําคัญ', addKeyWord: 'เพิ่มคําสําคัญ', keywordError: 'ความยาวสูงสุดของคําหลักคือ 20', - characters: 'อักขระ', hitCount: 'จํานวนการดึงข้อมูล', vectorHash: 'แฮชเวกเตอร์:', questionPlaceholder: 'เพิ่มคําถามที่นี่', diff --git a/web/i18n/th-TH/dataset-hit-testing.ts b/web/i18n/th-TH/dataset-hit-testing.ts index d04f2be2fc..03490899f2 100644 --- a/web/i18n/th-TH/dataset-hit-testing.ts +++ b/web/i18n/th-TH/dataset-hit-testing.ts @@ -3,7 +3,6 @@ const translation = { settingTitle: 'การตั้งค่าการดึงข้อมูล', desc: 'ทดสอบเอฟเฟกต์การตีของความรู้ตามข้อความแบบสอบถามที่กําหนด', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'ล่าสุด', table: { header: { source: 'ที่มา', diff --git a/web/i18n/tr-TR/app-debug.ts b/web/i18n/tr-TR/app-debug.ts index 782f65f19c..0f32eaefa4 100644 --- a/web/i18n/tr-TR/app-debug.ts +++ b/web/i18n/tr-TR/app-debug.ts @@ -232,11 +232,8 @@ const translation = { description: 'Prompt Oluşturucu, yapılandırılan modeli kullanarak promptları daha iyi kalite ve yapı için optimize eder. Lütfen açık ve ayrıntılı talimatlar yazın.', tryIt: 'Deneyin', instruction: 'Talimatlar', - instructionPlaceHolder: 'Açık ve belirli talimatlar yazın.', generate: 'Oluştur', resTitle: 'Oluşturulmuş Prompt', - noDataLine1: 'Kullanım durumunuzu solda açıklayın,', - noDataLine2: 'orkestrasyon önizlemesi burada görünecek.', apply: 'Uygula', loading: 'Uygulama orkestrasyonu yapılıyor...', overwriteTitle: 'Mevcut yapılandırmanın üzerine yazılsın mı?', diff --git a/web/i18n/tr-TR/billing.ts b/web/i18n/tr-TR/billing.ts index d85de6b5a2..ba80c49f78 100644 --- a/web/i18n/tr-TR/billing.ts +++ b/web/i18n/tr-TR/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Büyük ölçekli kritik sistemler için tam yetenekler ve destek.', includesTitle: 'Takım plandaki her şey, artı:', features: { - 8: 'Profesyonel Teknik Destek', - 1: 'Ticari Lisans Yetkilendirmesi', - 6: 'Gelişmiş Güvenlik ve Kontroller', - 5: 'Dify Partners tarafından müzakere edilen SLA\'lar', - 4: 'SSO', - 2: 'Özel Kurumsal Özellikler', - 0: 'Kurumsal Düzeyde Ölçeklenebilir Dağıtım Çözümleri', - 7: 'Resmi olarak Dify tarafından Güncellemeler ve Bakım', - 3: 'Çoklu Çalışma Alanları ve Kurumsal Yönetim', }, priceTip: 'Yıllık Faturalama Sadece', for: 'Büyük boyutlu Takımlar için', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'Tek Çalışma Alanı', - 0: 'Genel depo altında yayınlanan tüm temel özellikler', - 2: 'Dify Açık Kaynak Lisansı ile uyumludur', }, price: 'Ücretsiz', includesTitle: 'Ücretsiz Özellikler:', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 1: 'Tek Çalışma Alanı', - 0: 'Çeşitli Bulut Sağlayıcıları Tarafından Kendi Kendini Yöneten Güvenilirlik', - 2: 'WebApp Logosu ve Marka Özelleştirmesi', - 3: 'Öncelikli E-posta ve Sohbet Desteği', }, name: 'Premium', includesTitle: 'Topluluktan her şey, artı:', diff --git a/web/i18n/tr-TR/common.ts b/web/i18n/tr-TR/common.ts index 7dcebecff2..a5ea56f10e 100644 --- a/web/i18n/tr-TR/common.ts +++ b/web/i18n/tr-TR/common.ts @@ -202,7 +202,6 @@ const translation = { showAppLength: '{{length}} uygulamayı göster', delete: 'Hesabı Sil', deleteTip: 'Hesabınızı silmek tüm verilerinizi kalıcı olarak siler ve geri alınamaz.', - deleteConfirmTip: 'Onaylamak için, kayıtlı e-postanızdan şu adrese e-posta gönderin: ', account: 'Hesap', myAccount: 'Hesabım', studio: 'Dify Stüdyo', diff --git a/web/i18n/tr-TR/dataset-creation.ts b/web/i18n/tr-TR/dataset-creation.ts index 32fb8165eb..33c82b69f7 100644 --- a/web/i18n/tr-TR/dataset-creation.ts +++ b/web/i18n/tr-TR/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Bilgi Oluştur', - update: 'Veri ekle', fallbackRoute: 'Bilgi', }, one: 'Veri kaynağı seçin', diff --git a/web/i18n/tr-TR/dataset-documents.ts b/web/i18n/tr-TR/dataset-documents.ts index 984aad5a0a..0f5e4329a5 100644 --- a/web/i18n/tr-TR/dataset-documents.ts +++ b/web/i18n/tr-TR/dataset-documents.ts @@ -31,7 +31,6 @@ const translation = { sync: 'Senkronize et', pause: 'Duraklat', resume: 'Devam Et', - download: 'Dosyayı İndir', }, index: { enable: 'Etkinleştir', @@ -342,7 +341,6 @@ const translation = { keywords: 'Anahtar Kelimeler', addKeyWord: 'Anahtar kelime ekle', keywordError: 'Anahtar kelimenin maksimum uzunluğu 20', - characters: 'karakter', hitCount: 'Geri alım sayısı', vectorHash: 'Vektör hash: ', questionPlaceholder: 'soruyu buraya ekleyin', diff --git a/web/i18n/tr-TR/dataset-hit-testing.ts b/web/i18n/tr-TR/dataset-hit-testing.ts index d22df0d93e..9b1ea2dbc1 100644 --- a/web/i18n/tr-TR/dataset-hit-testing.ts +++ b/web/i18n/tr-TR/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Geri Alım Testi', desc: 'Verilen sorgu metnine göre Bilginin isabet etkisini test edin.', dateTimeFormat: 'GG/AA/YYYY ss:dd ÖÖ/ÖS', - recents: 'Sonuçlar', table: { header: { source: 'Kaynak', diff --git a/web/i18n/uk-UA/app-debug.ts b/web/i18n/uk-UA/app-debug.ts index 5bf7642c91..337da83e74 100644 --- a/web/i18n/uk-UA/app-debug.ts +++ b/web/i18n/uk-UA/app-debug.ts @@ -233,21 +233,6 @@ const translation = { }, }, automatic: { - title: 'Автоматизована оркестрація застосунків', - description: 'Опишіть свій сценарій, Dify збере для вас застосунок.', - intendedAudience: 'Хто є цільовою аудиторією?', - intendedAudiencePlaceHolder: 'напр. Студент', - solveProblem: 'Які проблеми вони сподіваються вирішити за допомогою AI?', - solveProblemPlaceHolder: 'напр. Оцінка успішності', - generate: 'Генерувати', - audiencesRequired: 'Необхідна аудиторія', - problemRequired: 'Необхідна проблема', - resTitle: 'Ми створили для вас такий застосунок.', - apply: 'Застосувати цю оркестрацію', - noData: 'Опишіть свій випадок використання зліва, тут буде показано попередній перегляд оркестрації.', - loading: 'Оркестрація програми для вас...', - overwriteTitle: 'Перезаписати існуючу конфігурацію?', - overwriteMessage: 'Застосування цієї оркестрації призведе до перезапису існуючої конфігурації.', }, resetConfig: { title: 'Підтвердіть скидання?', @@ -570,12 +555,9 @@ const translation = { apply: 'Застосовувати', tryIt: 'Спробуйте', overwriteTitle: 'Змінити існуючу конфігурацію?', - instructionPlaceHolder: 'Пишіть чіткі та конкретні інструкції.', loading: 'Оркестрування програми для вас...', - noDataLine1: 'Опишіть свій випадок використання зліва,', resTitle: 'Згенерований запит', title: 'Генератор підказок', - noDataLine2: 'Тут буде показано попередній перегляд оркестровки.', overwriteMessage: 'Застосування цього рядка замінить існуючу конфігурацію.', description: 'Генератор підказок використовує налаштовану модель для оптимізації запитів для кращої якості та кращої структури. Напишіть, будь ласка, зрозумілу та детальну інструкцію.', versions: 'Версії', diff --git a/web/i18n/uk-UA/billing.ts b/web/i18n/uk-UA/billing.ts index a048fe67cd..72fd9f6633 100644 --- a/web/i18n/uk-UA/billing.ts +++ b/web/i18n/uk-UA/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Отримайте повні можливості та підтримку для масштабних критично важливих систем.', includesTitle: 'Все, що входить до плану Team, плюс:', features: { - 4: 'Єдиний вхід', - 7: 'Оновлення та обслуговування від Dify Official', - 1: 'Авторизація комерційної ліцензії', - 8: 'Професійна технічна підтримка', - 2: 'Ексклюзивні функції підприємства', - 6: 'Розширені функції безпеки та керування', - 3: 'Кілька робочих областей і управління підприємством', - 5: 'Угода про рівень обслуговування за домовленістю від Dify Partners', - 0: 'Масштабовані рішення для розгортання корпоративного рівня', }, btnText: 'Зв\'язатися з відділом продажу', priceTip: 'Тільки річна оплата', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'Єдине робоче місце', - 2: 'Відповідає ліцензії Dify з відкритим вихідним кодом', - 0: 'Усі основні функції випущено в загальнодоступному репозиторії', }, btnText: 'Розпочніть з громади', includesTitle: 'Безкоштовні можливості:', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 1: 'Єдине робоче місце', - 2: 'Налаштування логотипу WebApp та брендингу', - 3: 'Пріоритетна підтримка електронною поштою та в чаті', - 0: 'Самокерована надійність різними хмарними провайдерами', }, description: 'Для середніх підприємств та команд', btnText: 'Отримайте Преміум у', diff --git a/web/i18n/uk-UA/common.ts b/web/i18n/uk-UA/common.ts index 550148ad32..c40b330eb4 100644 --- a/web/i18n/uk-UA/common.ts +++ b/web/i18n/uk-UA/common.ts @@ -198,7 +198,6 @@ const translation = { showAppLength: 'Показати {{length}} програм', delete: 'Видалити обліковий запис', deleteTip: 'Видалення вашого облікового запису призведе до остаточного видалення всіх ваших даних, і їх неможливо буде відновити.', - deleteConfirmTip: 'Щоб підтвердити, будь ласка, надішліть наступне з вашої зареєстрованої електронної пошти на ', account: 'Рахунок', studio: 'Студія Dify', myAccount: 'Особистий кабінет', diff --git a/web/i18n/uk-UA/dataset-creation.ts b/web/i18n/uk-UA/dataset-creation.ts index 8ea32c0d81..2685db70b4 100644 --- a/web/i18n/uk-UA/dataset-creation.ts +++ b/web/i18n/uk-UA/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Створити Знання', - update: 'Додати дані', fallbackRoute: 'Знання', }, one: 'Виберіть джерело даних', diff --git a/web/i18n/uk-UA/dataset-documents.ts b/web/i18n/uk-UA/dataset-documents.ts index f4a40081c5..fecc8fef47 100644 --- a/web/i18n/uk-UA/dataset-documents.ts +++ b/web/i18n/uk-UA/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Синхронізувати', pause: 'Пауза', resume: 'Продовжити', - download: 'Завантажити файл', }, index: { enable: 'Активувати', @@ -254,7 +253,6 @@ const translation = { cs: 'Чеська', th: 'Тайська', id: 'Індонезійська', - uk: 'Українська', }, categoryMap: { book: { @@ -343,7 +341,6 @@ const translation = { keywords: 'Ключові слова', addKeyWord: 'Додати ключове слово', keywordError: 'Максимальна довжина ключового слова – 20 символів', - characters: 'символів', hitCount: 'Кількість пошуку', vectorHash: 'Векторний хеш: ', questionPlaceholder: 'додайте запитання тут', diff --git a/web/i18n/uk-UA/dataset-hit-testing.ts b/web/i18n/uk-UA/dataset-hit-testing.ts index 3567c098f2..65f4f1d6c0 100644 --- a/web/i18n/uk-UA/dataset-hit-testing.ts +++ b/web/i18n/uk-UA/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Тестування вибірки', desc: 'Тестування ефективності пошуку знань на основі наданого текстового запиту.', dateTimeFormat: 'DD/MM/YYYY HH:mm A', - recents: 'Останні', table: { header: { source: 'Джерело', diff --git a/web/i18n/vi-VN/app-debug.ts b/web/i18n/vi-VN/app-debug.ts index bf34f04db5..9f6071da8e 100644 --- a/web/i18n/vi-VN/app-debug.ts +++ b/web/i18n/vi-VN/app-debug.ts @@ -228,21 +228,6 @@ const translation = { }, }, automatic: { - title: 'Tự động hóa triển khai ứng dụng', - description: 'Mô tả tình huống của bạn, Dify sẽ tự động hóa một ứng dụng cho bạn.', - intendedAudience: 'Đối tượng mục tiêu là ai?', - intendedAudiencePlaceHolder: 'Ví dụ: Sinh viên', - solveProblem: 'Họ hy vọng AI có thể giải quyết vấn đề gì?', - solveProblemPlaceHolder: 'Ví dụ: Đánh giá thành tích học tập', - generate: 'Tạo', - audiencesRequired: 'Yêu cầu nhập đối tượng mục tiêu', - problemRequired: 'Yêu cầu nhập vấn đề cần giải quyết', - resTitle: 'Chúng tôi đã tự động hóa ứng dụng sau đây cho bạn.', - apply: 'Áp dụng tự động hóa này', - noData: 'Mô tả tình huống sử dụng của bạn ở bên trái, xem trước tự động hóa sẽ hiển thị ở đây.', - loading: 'Đang tự động hóa ứng dụng cho bạn...', - overwriteTitle: 'Ghi đè cấu hình hiện tại?', - overwriteMessage: 'Áp dụng tự động hóa này sẽ ghi đè lên cấu hình hiện tại.', }, resetConfig: { title: 'Xác nhận đặt lại?', @@ -536,17 +521,14 @@ const translation = { }, generate: 'Đẻ ra', tryIt: 'Dùng thử', - noDataLine2: 'Bản xem trước Orchestration sẽ hiển thị ở đây.', apply: 'Áp dụng', instruction: 'Chỉ thị', title: 'Trình tạo nhắc nhở', resTitle: 'Lời nhắc được tạo', loading: 'Sắp xếp ứng dụng cho bạn...', - noDataLine1: 'Mô tả trường hợp sử dụng của bạn ở bên trái,', description: 'Trình tạo lời nhắc sử dụng mô hình được định cấu hình để tối ưu hóa lời nhắc cho chất lượng cao hơn và cấu trúc tốt hơn. Vui lòng viết hướng dẫn rõ ràng và chi tiết.', overwriteMessage: 'Áp dụng lời nhắc này sẽ ghi đè cấu hình hiện có.', overwriteTitle: 'Ghi đè cấu hình hiện có?', - instructionPlaceHolder: 'Viết hướng dẫn rõ ràng và cụ thể.', versions: 'Phiên bản', optimizationNote: 'Chú thích tối ưu hóa', to: 'đến', diff --git a/web/i18n/vi-VN/billing.ts b/web/i18n/vi-VN/billing.ts index 69035dc595..45c6529f74 100644 --- a/web/i18n/vi-VN/billing.ts +++ b/web/i18n/vi-VN/billing.ts @@ -115,15 +115,6 @@ const translation = { description: 'Nhận toàn bộ khả năng và hỗ trợ cho các hệ thống quan trọng cho nhiệm vụ quy mô lớn.', includesTitle: 'Tất cả trong kế hoạch Nhóm, cộng thêm:', features: { - 2: 'Các tính năng dành riêng cho doanh nghiệp', - 3: 'Nhiều không gian làm việc & quản lý doanh nghiệp', - 7: 'Cập nhật và bảo trì bởi Dify chính thức', - 4: 'SSO', - 8: 'Hỗ trợ kỹ thuật chuyên nghiệp', - 5: 'SLA được đàm phán bởi Dify Partners', - 1: 'Ủy quyền giấy phép thương mại', - 6: 'Bảo mật & Kiểm soát nâng cao', - 0: 'Giải pháp triển khai có thể mở rộng cấp doanh nghiệp', }, price: 'Tùy chỉnh', for: 'Dành cho các đội lớn', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 1: 'Không gian làm việc đơn', - 0: 'Tất cả các tính năng cốt lõi được phát hành trong kho lưu trữ công cộng', - 2: 'Tuân thủ Giấy phép nguồn mở Dify', }, description: 'Dành cho người dùng cá nhân, nhóm nhỏ hoặc các dự án phi thương mại', name: 'Cộng đồng', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 1: 'Không gian làm việc đơn', - 2: 'Logo WebApp & Tùy chỉnh thương hiệu', - 3: 'Hỗ trợ email & trò chuyện ưu tiên', - 0: 'Độ tin cậy tự quản lý của các nhà cung cấp đám mây khác nhau', }, comingSoon: 'Hỗ trợ Microsoft Azure & Google Cloud Sẽ Đến Sớm', priceTip: 'Dựa trên Thị trường Đám mây', diff --git a/web/i18n/vi-VN/common.ts b/web/i18n/vi-VN/common.ts index 384c4dbf61..60cf113ab2 100644 --- a/web/i18n/vi-VN/common.ts +++ b/web/i18n/vi-VN/common.ts @@ -198,7 +198,6 @@ const translation = { showAppLength: 'Hiển thị {{length}} ứng dụng', delete: 'Xóa tài khoản', deleteTip: 'Xóa tài khoản của bạn sẽ xóa vĩnh viễn tất cả dữ liệu của bạn và không thể khôi phục được.', - deleteConfirmTip: 'Để xác nhận, vui lòng gửi thông tin sau từ email đã đăng ký của bạn tới ', studio: 'Dify Studio', myAccount: 'Tài khoản của tôi', account: 'Tài khoản', diff --git a/web/i18n/vi-VN/dataset-creation.ts b/web/i18n/vi-VN/dataset-creation.ts index 39215fde68..63d44a93ea 100644 --- a/web/i18n/vi-VN/dataset-creation.ts +++ b/web/i18n/vi-VN/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: 'Tạo Kiến thức', - update: 'Thêm dữ liệu', fallbackRoute: 'Kiến thức', }, one: 'Chọn nguồn dữ liệu', diff --git a/web/i18n/vi-VN/dataset-documents.ts b/web/i18n/vi-VN/dataset-documents.ts index 1f514a1d6f..1833b00588 100644 --- a/web/i18n/vi-VN/dataset-documents.ts +++ b/web/i18n/vi-VN/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: 'Đồng bộ', pause: 'Tạm dừng', resume: 'Tiếp tục', - download: 'Tải xuống tập tin', }, index: { enable: 'Kích hoạt', @@ -342,7 +341,6 @@ const translation = { keywords: 'Từ khóa', addKeyWord: 'Thêm từ khóa', keywordError: 'Độ dài tối đa của từ khóa là 20', - characters: 'ký tự', hitCount: 'Số lần truy vấn', vectorHash: 'Mã băm vector: ', questionPlaceholder: 'thêm câu hỏi ở đây', diff --git a/web/i18n/vi-VN/dataset-hit-testing.ts b/web/i18n/vi-VN/dataset-hit-testing.ts index 02a2547938..a08532ae17 100644 --- a/web/i18n/vi-VN/dataset-hit-testing.ts +++ b/web/i18n/vi-VN/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: 'Kiểm tra truy vấn', desc: 'Kiểm tra hiệu quả truy xuất của Kiến thức dựa trên văn bản truy vấn đã cho.', dateTimeFormat: 'MM/DD/YYYY hh:mm A', - recents: 'Gần đây', table: { header: { source: 'Nguồn', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index bb64f41bf1..1610a766f6 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -240,8 +240,6 @@ const translation = { apply: '应用', applyChanges: '应用更改', resTitle: '生成的代码', - newNoDataLine1: '在左侧描述您的用例,点击生成查看响应。', - newNoDataLine2: '了解提示词设计', overwriteConfirmTitle: '是否覆盖现有代码?', overwriteConfirmMessage: '此操作将覆盖现有代码。您确定要继续吗?', }, diff --git a/web/i18n/zh-Hans/dataset-documents.ts b/web/i18n/zh-Hans/dataset-documents.ts index 15e3071e51..581bc851f7 100644 --- a/web/i18n/zh-Hans/dataset-documents.ts +++ b/web/i18n/zh-Hans/dataset-documents.ts @@ -32,7 +32,6 @@ const translation = { sync: '同步', pause: '暂停', resume: '恢复', - download: '下载文件', }, index: { enable: '启用中', diff --git a/web/i18n/zh-Hant/app-debug.ts b/web/i18n/zh-Hant/app-debug.ts index d92a3bfd4e..7668e61663 100644 --- a/web/i18n/zh-Hant/app-debug.ts +++ b/web/i18n/zh-Hant/app-debug.ts @@ -523,16 +523,13 @@ const translation = { }, overwriteMessage: '應用此提示將覆蓋現有配置。', tryIt: '試試看', - noDataLine1: '在左側描述您的用例,', instruction: '指示', description: '提示生成器使用配置的模型來優化提示,以獲得更高的品質和更好的結構。請寫出清晰詳細的說明。', generate: '生成', apply: '應用', - instructionPlaceHolder: '寫出清晰具體的說明。', overwriteTitle: '覆蓋現有配置?', title: '提示生成器', loading: '為您編排應用程式...', - noDataLine2: '業務流程預覽將在此處顯示。', resTitle: '生成的提示', latest: '最新', to: '到', diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts index bedf4550f8..f957bc4eab 100644 --- a/web/i18n/zh-Hant/billing.ts +++ b/web/i18n/zh-Hant/billing.ts @@ -115,15 +115,6 @@ const translation = { description: '獲得大規模關鍵任務系統的完整功能和支援。', includesTitle: 'Team 計劃中的一切,加上:', features: { - 8: '專業技術支持', - 3: '多個工作區和企業管理', - 0: '企業級可擴展部署解決方案', - 1: '商業許可證授權', - 7: 'Dify 官方更新和維護', - 6: '進階安全與控制', - 4: '單一登入', - 5: 'Dify 合作夥伴協商的 SLA', - 2: '獨家企業功能', }, price: '自訂', btnText: '聯繫銷售', @@ -132,9 +123,6 @@ const translation = { }, community: { features: { - 0: '所有核心功能在公共存儲庫下發布', - 1: '單一工作區', - 2: '符合 Dify 開源許可證', }, includesTitle: '免費功能:', btnText: '開始使用社區', @@ -145,10 +133,6 @@ const translation = { }, premium: { features: { - 3: '優先電子郵件和聊天支持', - 2: 'WebApp 標誌和品牌定制', - 0: '各種雲端供應商的自我管理可靠性', - 1: '單一工作區', }, for: '適用於中型組織和團隊', comingSoon: '微軟 Azure 與 Google Cloud 支持即將推出', diff --git a/web/i18n/zh-Hant/dataset-documents.ts b/web/i18n/zh-Hant/dataset-documents.ts index 7344db2df7..1b482f181f 100644 --- a/web/i18n/zh-Hant/dataset-documents.ts +++ b/web/i18n/zh-Hant/dataset-documents.ts @@ -30,7 +30,6 @@ const translation = { sync: '同步', resume: '恢復', pause: '暫停', - download: '下載檔案', }, index: { enable: '啟用中', diff --git a/web/package.json b/web/package.json index e579d688a3..a422c7fd6c 100644 --- a/web/package.json +++ b/web/package.json @@ -73,7 +73,6 @@ "ahooks": "^3.8.4", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", - "clsx": "^2.1.1", "cmdk": "^1.1.1", "copy-to-clipboard": "^3.3.3", "crypto-js": "^4.2.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index b3695a0b89..3dbbf4f070 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -141,9 +141,6 @@ importers: classnames: specifier: ^2.5.1 version: 2.5.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 cmdk: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -1724,144 +1721,170 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm64@1.2.0': resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.0': resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.0': resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.0': resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-arm64@1.2.0': resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.0': resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm64@0.34.3': resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.3': resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.3': resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.3': resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.3': resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-arm64@0.34.3': resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.3': resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -2145,24 +2168,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.5.0': resolution: {integrity: sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.5.0': resolution: {integrity: sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.5.0': resolution: {integrity: sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.5.0': resolution: {integrity: sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==} @@ -2384,36 +2411,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -3528,41 +3561,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} diff --git a/web/service/base.ts b/web/service/base.ts index 33aebf9cfb..5386f9b07b 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -467,7 +467,7 @@ export const ssePost = async ( onAgentLog, ) }).catch((e) => { - if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property')) + if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().includes('TypeError: Cannot assign to read only property')) Toast.notify({ type: 'error', message: e }) onError?.(e) }) diff --git a/web/service/debug.ts b/web/service/debug.ts index 20a4f0953f..fab2910c5e 100644 --- a/web/service/debug.ts +++ b/web/service/debug.ts @@ -80,7 +80,7 @@ export const fetchConversationMessages = (appId: string, conversation_id: string }) } -export const generateBasicAppFistTimeRule = (body: Record) => { +export const generateBasicAppFirstTimeRule = (body: Record) => { return post('/rule-generate', { body, })