| @@ -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 . | |||
| @@ -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 | |||
| @@ -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.") | |||
| @@ -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" | |||
| ) | |||
| @@ -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, | |||
| @@ -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] | |||
| @@ -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() | |||
| ) | |||
| @@ -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( | |||
| @@ -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, | |||
| @@ -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: | |||
| @@ -674,7 +674,7 @@ class TestAnnotationService: | |||
| history = ( | |||
| db.session.query(AppAnnotationHitHistory) | |||
| .filter( | |||
| .where( | |||
| AppAnnotationHitHistory.annotation_id == annotation.id, AppAnnotationHitHistory.message_id == message_id | |||
| ) | |||
| .first() | |||
| @@ -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): | |||
| @@ -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() | |||
| @@ -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) | |||
| @@ -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: | |||
| @@ -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: | |||
| @@ -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: | |||
| @@ -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<script>alert("XSS")</script>' | |||
| return null | |||
| }, | |||
| }), | |||
| })) | |||
| // Fixed CheckCode component implementation (current secure version) | |||
| const SecureCheckCodeComponent = ({ email }: { email: string }) => { | |||
| const { t } = require('react-i18next').useTranslation() | |||
| return ( | |||
| <div> | |||
| <h1>Check Code</h1> | |||
| <p> | |||
| <span> | |||
| {t('login.checkCode.tipsPrefix')} | |||
| <strong>{email}</strong> | |||
| </span> | |||
| </p> | |||
| </div> | |||
| ) | |||
| } | |||
| // 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 <strong>${params.email}</strong>` | |||
| return key | |||
| } | |||
| return ( | |||
| <div> | |||
| <h1>Check Code</h1> | |||
| <p> | |||
| <span dangerouslySetInnerHTML={{ __html: mockTranslation('login.checkCode.tips', { email }) }}></span> | |||
| </p> | |||
| </div> | |||
| ) | |||
| } | |||
| describe('XSS Fix Verification - Check Code Pages Security', () => { | |||
| afterEach(() => { | |||
| cleanup() | |||
| }) | |||
| const maliciousEmail = 'test@example.com<script>alert("XSS")</script>' | |||
| it('should securely render email with HTML characters as text (FIXED VERSION)', () => { | |||
| console.log('\n🔒 Security Fix Verification Report') | |||
| console.log('===================================') | |||
| const { container } = render(<SecureCheckCodeComponent email={maliciousEmail} />) | |||
| 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('<script>')) | |||
| console.log('- Script elements created:', scriptElements.length) | |||
| console.log('- Full text content:', spanElement?.textContent) | |||
| // Verify secure behavior | |||
| expect(strongElement?.textContent).toBe(maliciousEmail) // Email rendered as text | |||
| expect(strongElement?.textContent).toContain('<script>') // HTML visible as text | |||
| expect(scriptElements).toHaveLength(0) // No script elements created | |||
| expect(spanElement?.textContent).toBe(`We send a verification code to ${maliciousEmail}`) | |||
| console.log('\n🎯 Security Status: SECURE - HTML automatically escaped by React') | |||
| }) | |||
| it('should demonstrate the vulnerability that was fixed (VULNERABLE VERSION)', () => { | |||
| const { container } = render(<VulnerableCheckCodeComponent email={maliciousEmail} />) | |||
| const spanElement = container.querySelector('span') | |||
| const strongElement = container.querySelector('strong') | |||
| const scriptElements = container.querySelectorAll('script') | |||
| console.log('\n⚠️ Previous Vulnerable Implementation:') | |||
| console.log('- HTML content:', spanElement?.innerHTML) | |||
| console.log('- Strong element text:', strongElement?.textContent) | |||
| console.log('- Script elements created:', scriptElements.length) | |||
| console.log('- Script content:', scriptElements[0]?.textContent) | |||
| // Verify vulnerability exists in old implementation | |||
| expect(scriptElements).toHaveLength(1) // Script element was created | |||
| expect(scriptElements[0]?.textContent).toBe('alert("XSS")') // Contains malicious code | |||
| expect(spanElement?.innerHTML).toContain('<script>') // Raw HTML in DOM | |||
| console.log('\n❌ Security Status: VULNERABLE - dangerouslySetInnerHTML creates script elements') | |||
| }) | |||
| it('should verify all affected components use the secure pattern', () => { | |||
| console.log('\n📋 Component Security Audit') | |||
| console.log('============================') | |||
| // Test multiple malicious inputs | |||
| const testCases = [ | |||
| 'user@test.com<img src=x onerror=alert(1)>', | |||
| 'test@evil.com<div onclick="alert(2)">click</div>', | |||
| 'admin@site.com<script>document.cookie="stolen"</script>', | |||
| 'normal@email.com', | |||
| ] | |||
| testCases.forEach((testEmail, index) => { | |||
| const { container } = render(<SecureCheckCodeComponent email={testEmail} />) | |||
| 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 = [ | |||
| '<script>alert("xss")</script>', | |||
| '<img src="x" onerror="alert(1)">', | |||
| '"><script>alert(2)</script>', | |||
| '\'>alert(3)</script>', | |||
| '<div onclick="alert(4)">click</div>', | |||
| ] | |||
| dangerousInputs.forEach((input, index) => { | |||
| const TestComponent = () => <strong>{input}</strong> | |||
| const { container } = render(<TestComponent />) | |||
| 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 {} | |||
| @@ -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{{<script>alert("XSS")</script>}}' | |||
| const { container } = render(<BlockInput value={testInput} readonly={true} />) | |||
| const scriptElements = container.querySelectorAll('script') | |||
| expect(scriptElements).toHaveLength(0) | |||
| const textContent = container.textContent | |||
| expect(textContent).toContain('<script>') | |||
| }) | |||
| it('should preserve legitimate variable highlighting', () => { | |||
| const legitimateInput = 'Hello {{userName}} welcome to {{appName}}' | |||
| const { container } = render(<BlockInput value={legitimateInput} readonly={true} />) | |||
| const textContent = container.textContent | |||
| expect(textContent).toContain('userName') | |||
| expect(textContent).toContain('appName') | |||
| }) | |||
| }) | |||
| describe('SupportVarInput Component Security', () => { | |||
| it('should safely render malicious variable names without executing scripts', () => { | |||
| const testInput = 'test@evil.com{{<img src=x onerror=alert(1)>}}' | |||
| const { container } = render(<SupportVarInput value={testInput} readonly={true} />) | |||
| const scriptElements = container.querySelectorAll('script') | |||
| const imgElements = container.querySelectorAll('img') | |||
| expect(scriptElements).toHaveLength(0) | |||
| expect(imgElements).toHaveLength(0) | |||
| const textContent = container.textContent | |||
| expect(textContent).toContain('<img') | |||
| }) | |||
| }) | |||
| describe('React Automatic Escaping Verification', () => { | |||
| it('should confirm React automatic escaping works correctly', () => { | |||
| const TestComponent = () => <span>{'<script>alert("xss")</script>'}</span> | |||
| const { container } = render(<TestComponent />) | |||
| const spanElement = container.querySelector('span') | |||
| const scriptElements = container.querySelectorAll('script') | |||
| expect(spanElement?.textContent).toBe('<script>alert("xss")</script>') | |||
| expect(scriptElements).toHaveLength(0) | |||
| }) | |||
| }) | |||
| }) | |||
| export {} | |||
| @@ -16,19 +16,26 @@ const VarHighlight: FC<IVarHighlightProps> = ({ | |||
| return ( | |||
| <div | |||
| key={name} | |||
| className={`${s.item} ${className} mb-2 flex h-5 items-center justify-center rounded-md px-1 text-xs font-medium text-primary-600`} | |||
| className={`${s.item} ${className} mb-2 inline-flex h-5 items-center justify-center rounded-md px-1 text-xs font-medium text-primary-600`} | |||
| > | |||
| <span className='opacity-60'>{'{{'}</span> | |||
| <span>{name}</span> | |||
| <span className='opacity-60'>{'}}'}</span> | |||
| <span className='opacity-60'>{'{{'}</span><span>{name}</span><span className='opacity-60'>{'}}'}</span> | |||
| </div> | |||
| ) | |||
| } | |||
| // 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, '"') | |||
| .replace(/'/g, ''') | |||
| const html = `<div class="${s.item} ${className} inline-flex mb-2 items-center justify-center px-1 rounded-md h-5 text-xs font-medium text-primary-600"> | |||
| <span class='opacity-60'>{{</span> | |||
| <span>${name}</span> | |||
| <span>${escapedName}</span> | |||
| <span class='opacity-60'>}}</span> | |||
| </div>` | |||
| return html | |||
| @@ -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<IGetAutomaticResProps> = ({ | |||
| 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, | |||
| @@ -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<IBlockInputProps> = ({ | |||
| 'block-input--editing': isEditing, | |||
| }) | |||
| const coloredContent = (currentValue || '') | |||
| .replace(/</g, '<') | |||
| .replace(/>/g, '>') | |||
| .replace(regex, varHighlightHTML({ name: '$1' })) // `<span class="${highLightClassName}">{{$1}}</span>` | |||
| .replace(/\n/g, '<br />') | |||
| const renderSafeContent = (value: string) => { | |||
| const parts = value.split(/(\{\{[^}]+\}\}|\n)/g) | |||
| return parts.map((part, index) => { | |||
| const variableMatch = part.match(/^\{\{([^}]+)\}\}$/) | |||
| if (variableMatch) { | |||
| return ( | |||
| <VarHighlight | |||
| key={`var-${index}`} | |||
| name={variableMatch[1]} | |||
| /> | |||
| ) | |||
| } | |||
| if (part === '\n') | |||
| return <br key={`br-${index}`} /> | |||
| return <span key={`text-${index}`}>{part}</span> | |||
| }) | |||
| } | |||
| // Not use useCallback. That will cause out callback get old data. | |||
| const handleSubmit = (value: string) => { | |||
| @@ -96,11 +109,11 @@ const BlockInput: FC<IBlockInputProps> = ({ | |||
| // Prevent rerendering caused cursor to jump to the start of the contentEditable element | |||
| const TextAreaContentView = () => { | |||
| return <div | |||
| className={classNames(style, className)} | |||
| dangerouslySetInnerHTML={{ __html: coloredContent }} | |||
| suppressContentEditableWarning={true} | |||
| /> | |||
| return ( | |||
| <div className={classNames(style, className)}> | |||
| {renderSafeContent(currentValue || '')} | |||
| </div> | |||
| ) | |||
| } | |||
| const placeholder = '' | |||
| @@ -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 = ({ | |||
| <as.type | |||
| {...buttonProps} | |||
| {...as.props} | |||
| className={clsx(className, as.props.className)} | |||
| className={cn(className, as.props.className)} | |||
| onClick={() => previous()} | |||
| tabIndex={disabled ? '-1' : 0} | |||
| disabled={disabled} | |||
| @@ -80,7 +80,7 @@ export const NextButton = ({ | |||
| <as.type | |||
| {...buttonProps} | |||
| {...as.props} | |||
| className={clsx(className, as.props.className)} | |||
| className={cn(className, as.props.className)} | |||
| onClick={() => next()} | |||
| tabIndex={disabled ? '-1' : 0} | |||
| disabled={disabled} | |||
| @@ -132,7 +132,7 @@ export const PageButton = ({ | |||
| <li key={page}> | |||
| <as.type | |||
| data-testid={ | |||
| clsx({ | |||
| cn({ | |||
| [`${dataTestIdActive}`]: | |||
| dataTestIdActive && pagination.currentPage + 1 === page, | |||
| [`${dataTestIdInactive}-${page}`]: | |||
| @@ -145,7 +145,7 @@ export const PageButton = ({ | |||
| pagination.setCurrentPage(page - 1) | |||
| }} | |||
| onClick={() => pagination.setCurrentPage(page - 1)} | |||
| className={clsx( | |||
| className={cn( | |||
| className, | |||
| pagination.currentPage + 1 === page | |||
| ? activeClassName | |||
| @@ -64,7 +64,7 @@ const AddVariablePopupWithPosition = ({ | |||
| } as any, | |||
| ], | |||
| hideEnv: true, | |||
| hideChatVar: true, | |||
| hideChatVar: !isChatMode, | |||
| isChatMode, | |||
| filterVar: filterVar(outputType as VarType), | |||
| }) | |||
| @@ -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<Props> = ({ | |||
| textClassName, | |||
| readonly, | |||
| }) => { | |||
| const withHightContent = (value || '') | |||
| .replace(/</g, '<') | |||
| .replace(/>/g, '>') | |||
| .replace(/\{\{([^}]+)\}\}/g, varHighlightHTML({ name: '$1', className: '!mb-0' })) // `<span class="${highLightClassName}">{{$1}}</span>` | |||
| .replace(/\n/g, '<br />') | |||
| const renderSafeContent = (inputValue: string) => { | |||
| const parts = inputValue.split(/(\{\{[^}]+\}\}|\n)/g) | |||
| return parts.map((part, index) => { | |||
| const variableMatch = part.match(/^\{\{([^}]+)\}\}$/) | |||
| if (variableMatch) { | |||
| return ( | |||
| <VarHighlight | |||
| key={`var-${index}`} | |||
| name={variableMatch[1]} | |||
| /> | |||
| ) | |||
| } | |||
| if (part === '\n') | |||
| return <br key={`br-${index}`} /> | |||
| return <span key={`text-${index}`}>{part}</span> | |||
| }) | |||
| } | |||
| return ( | |||
| <div | |||
| @@ -42,9 +55,9 @@ const SupportVarInput: FC<Props> = ({ | |||
| <div | |||
| className={cn(textClassName, 'h-full w-0 grow truncate whitespace-nowrap')} | |||
| title={value} | |||
| dangerouslySetInnerHTML={{ | |||
| __html: withHightContent, | |||
| }}></div> | |||
| > | |||
| {renderSafeContent(value || '')} | |||
| </div> | |||
| )} | |||
| </div> | |||
| ) | |||
| @@ -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', | |||
| @@ -30,7 +30,6 @@ const translation = { | |||
| sync: 'Synchronisieren', | |||
| resume: 'Fortsetzen', | |||
| pause: 'Pause', | |||
| download: 'Datei herunterladen', | |||
| }, | |||
| index: { | |||
| enable: 'Aktivieren', | |||
| @@ -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', | |||
| @@ -31,7 +31,6 @@ const translation = { | |||
| sync: 'Sincronizar', | |||
| resume: 'Reanudar', | |||
| pause: 'Pausa', | |||
| download: 'Descargar archivo', | |||
| }, | |||
| index: { | |||
| enable: 'Habilitar', | |||
| @@ -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: 'قابل گسترش', | |||
| @@ -202,7 +202,6 @@ const translation = { | |||
| showAppLength: 'نمایش {{length}} برنامه', | |||
| delete: 'حذف حساب کاربری', | |||
| deleteTip: 'حذف حساب کاربری شما تمام دادههای شما را به طور دائمی پاک میکند و قابل بازیابی نیست.', | |||
| deleteConfirmTip: 'برای تأیید، لطفاً موارد زیر را از ایمیل ثبتنام شده خود به این آدرس ارسال کنید ', | |||
| account: 'حساب', | |||
| myAccount: 'حساب من', | |||
| studio: 'استودیو Dify', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'ایجاد دانش', | |||
| update: 'افزودن داده', | |||
| fallbackRoute: 'دانش', | |||
| }, | |||
| one: 'انتخاب منبع داده', | |||
| @@ -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: 'سؤال را اینجا اضافه کنید', | |||
| @@ -2,7 +2,6 @@ const translation = { | |||
| title: 'آزمون بازیابی', | |||
| desc: 'آزمون اثرگذاری دانش بر اساس متن پرسش داده شده.', | |||
| dateTimeFormat: 'MM/DD/YYYY hh:mm A', | |||
| recents: 'اخیرها', | |||
| table: { | |||
| header: { | |||
| source: 'منبع', | |||
| @@ -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: 'प्रॉम्प्ट जेनरेटर उच्च गुणवत्ता और बेहतर संरचना के लिए प्रॉम्प्ट्स को ऑप्टिमाइज़ करने के लिए कॉन्फ़िगर किए गए मॉडल का उपयोग करता है। कृपया स्पष्ट और विस्तृत निर्देश लिखें।', | |||
| @@ -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: 'प्रीमियम', | |||
| @@ -206,7 +206,6 @@ const translation = { | |||
| langGeniusAccountTip: 'आपका Dify खाता और संबंधित उपयोगकर्ता डेटा।', | |||
| editName: 'नाम संपादित करें', | |||
| showAppLength: '{{length}} ऐप्स दिखाएं', | |||
| deleteConfirmTip: 'पुष्टि करने के लिए, कृपया अपने पंजीकृत ईमेल से निम्नलिखित भेजें', | |||
| delete: 'खाता हटाएं', | |||
| deleteTip: 'अपना खाता हटाने से आपका सारा डेटा स्थायी रूप से मिट जाएगा और इसे पुनर्प्राप्त नहीं किया जा सकता है।', | |||
| account: 'खाता', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'ज्ञान बनाएं', | |||
| update: 'डेटा जोड़ें', | |||
| fallbackRoute: 'ज्ञान', | |||
| }, | |||
| one: 'डेटा स्रोत चुनें', | |||
| @@ -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: 'यहाँ प्रश्न जोड़ें', | |||
| @@ -2,7 +2,6 @@ const translation = { | |||
| title: 'पुनर्प्राप्ति परीक्षण', | |||
| desc: 'दिए गए प्रश्न पाठ के आधार पर ज्ञान की प्रभावशीलता का परीक्षण करें।', | |||
| dateTimeFormat: 'MM/DD/YYYY hh:mm A', | |||
| recents: 'हाल के', | |||
| table: { | |||
| header: { | |||
| source: 'स्रोत', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Crea Conoscenza', | |||
| update: 'Aggiungi dati', | |||
| fallbackRoute: 'Conoscenza', | |||
| }, | |||
| one: 'Scegli fonte dati', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -248,11 +248,8 @@ const translation = { | |||
| description: 'プロンプト生成器は、設定済みのモデルを使って、高品質で構造的に優れたプロンプトを作成するための最適化を行います。具体的で詳細な指示をお書きください。', | |||
| tryIt: '試してみる', | |||
| instruction: '指示', | |||
| instructionPlaceHolder: '具体的で明確な指示を入力してください。', | |||
| generate: '生成', | |||
| resTitle: '生成されたプロンプト', | |||
| noDataLine1: '左側に使用例を記入してください,', | |||
| noDataLine2: 'オーケストレーションのプレビューがこちらに表示されます。', | |||
| apply: '適用', | |||
| loading: 'アプリケーションを処理中です', | |||
| overwriteTitle: '既存の設定を上書きしますか?', | |||
| @@ -32,7 +32,6 @@ const translation = { | |||
| sync: '同期', | |||
| pause: '一時停止', | |||
| resume: '再開', | |||
| download: 'ファイルをダウンロード', | |||
| }, | |||
| index: { | |||
| enable: '有効にする', | |||
| @@ -527,10 +527,7 @@ const translation = { | |||
| title: '프롬프트 생성기', | |||
| overwriteTitle: '기존 구성을 재정의하시겠습니까?', | |||
| loading: '응용 프로그램 오케스트레이션...', | |||
| instructionPlaceHolder: '명확하고 구체적인 지침을 작성하십시오.', | |||
| noDataLine2: '오케스트레이션 미리 보기가 여기에 표시됩니다.', | |||
| overwriteMessage: '이 프롬프트를 적용하면 기존 구성이 재정의됩니다.', | |||
| noDataLine1: '왼쪽에 사용 사례를 설명하십시오.', | |||
| description: '프롬프트 생성기는 구성된 모델을 사용하여 더 높은 품질과 더 나은 구조를 위해 프롬프트를 최적화합니다. 명확하고 상세한 지침을 작성하십시오.', | |||
| to: '에게', | |||
| press: '프레스', | |||
| @@ -30,7 +30,6 @@ const translation = { | |||
| sync: '동기화', | |||
| resume: '재개', | |||
| pause: '일시 중지', | |||
| download: '파일 다운로드', | |||
| }, | |||
| index: { | |||
| enable: '활성화', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Utwórz Wiedzę', | |||
| update: 'Dodaj dane', | |||
| fallbackRoute: 'Wiedza', | |||
| }, | |||
| one: 'Wybierz źródło danych', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Criar Conhecimento', | |||
| update: 'Adicionar dados', | |||
| fallbackRoute: 'Conhecimento', | |||
| }, | |||
| one: 'Escolher fonte de dados', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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ă', | |||
| @@ -232,11 +232,8 @@ const translation = { | |||
| description: 'Генератор промпта использует настроенную модель для оптимизации промпта для повышения качества и улучшения структуры. Пожалуйста, напишите четкие и подробные инструкции.', | |||
| tryIt: 'Попробуйте', | |||
| instruction: 'Инструкции', | |||
| instructionPlaceHolder: 'Напишите четкие и конкретные инструкции.', | |||
| generate: 'Сгенерировать', | |||
| resTitle: 'Сгенерированный промпт', | |||
| noDataLine1: 'Опишите свой случай использования слева,', | |||
| noDataLine2: 'предварительный просмотр оркестрации будет показан здесь.', | |||
| apply: 'Применить', | |||
| loading: 'Оркестрация приложения для вас...', | |||
| overwriteTitle: 'Перезаписать существующую конфигурацию?', | |||
| @@ -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: 'Всё из Сообщества, плюс:', | |||
| @@ -202,7 +202,6 @@ const translation = { | |||
| showAppLength: 'Показать {{length}} приложений', | |||
| delete: 'Удалить учетную запись', | |||
| deleteTip: 'Удаление вашей учетной записи приведет к безвозвратному удалению всех ваших данных, и их невозможно будет восстановить.', | |||
| deleteConfirmTip: 'Для подтверждения, пожалуйста, отправьте следующее с вашего зарегистрированного адреса электронной почты на ', | |||
| account: 'Счет', | |||
| studio: 'Студия Dify', | |||
| myAccount: 'Моя учетная запись', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Создать базу знаний', | |||
| update: 'Добавить данные', | |||
| fallbackRoute: 'Знание', | |||
| }, | |||
| one: 'Выберите источник данных', | |||
| @@ -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: 'добавьте вопрос здесь', | |||
| @@ -2,7 +2,6 @@ const translation = { | |||
| title: 'Тестирование поиска', | |||
| desc: 'Проверьте эффективность поиска в базе знаний на основе заданного текста запроса.', | |||
| dateTimeFormat: 'DD.MM.YYYY HH:mm', | |||
| recents: 'Недавние', | |||
| table: { | |||
| header: { | |||
| source: 'Источник', | |||
| @@ -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.', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Ustvari Znanje', | |||
| update: 'Dodaj podatke', | |||
| fallbackRoute: 'Znanje', | |||
| }, | |||
| one: 'Izberi vir podatkov', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -283,11 +283,8 @@ const translation = { | |||
| apply: 'ใช้', | |||
| resTitle: 'พรอมต์ที่สร้างขึ้น', | |||
| title: 'เครื่องกําเนิดพร้อมท์', | |||
| noDataLine2: 'ตัวอย่างการประสานเสียงจะแสดงที่นี่', | |||
| tryIt: 'ลองดู', | |||
| overwriteTitle: 'แทนที่การกําหนดค่าที่มีอยู่ใช่ไหม', | |||
| noDataLine1: 'อธิบายกรณีการใช้งานของคุณทางด้านซ้าย', | |||
| instructionPlaceHolder: 'เขียนคําแนะนําที่ชัดเจนและเฉพาะเจาะจง', | |||
| overwriteMessage: 'การใช้พรอมต์นี้จะแทนที่การกําหนดค่าที่มีอยู่', | |||
| description: 'ตัวสร้างพรอมต์ใช้โมเดลที่กําหนดค่าเพื่อปรับพรอมต์ให้เหมาะสมเพื่อคุณภาพที่สูงขึ้นและโครงสร้างที่ดีขึ้น โปรดเขียนคําแนะนําที่ชัดเจนและละเอียด', | |||
| loading: 'กําลังประสานงานแอปพลิเคชันสําหรับคุณ...', | |||
| @@ -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: 'สำหรับองค์กรและทีมขนาดกลาง', | |||
| @@ -200,7 +200,6 @@ const translation = { | |||
| showAppLength: 'แสดง {{length}} แอป', | |||
| delete: 'ลบบัญชี', | |||
| deleteTip: 'การลบบัญชีของคุณจะเป็นการลบข้อมูลทั้งหมดของคุณอย่างถาวรและไม่สามารถกู้คืนได้', | |||
| deleteConfirmTip: 'เพื่อยืนยัน โปรดส่งข้อมูลต่อไปนี้จากอีเมลที่ลงทะเบียนไว้ที่', | |||
| deletePrivacyLinkTip: 'สําหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่เราจัดการกับข้อมูลของคุณ โปรดดูที่', | |||
| deletePrivacyLink: 'นโยบายความเป็นส่วนตัว', | |||
| deleteLabel: 'เพื่อยืนยัน โปรดพิมพ์อีเมลของคุณด้านล่าง', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'สร้างความรู้', | |||
| update: 'เพิ่มข้อมูล', | |||
| fallbackRoute: 'ความรู้', | |||
| }, | |||
| one: 'เลือกแหล่งข้อมูล', | |||
| @@ -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: 'เพิ่มคําถามที่นี่', | |||
| @@ -3,7 +3,6 @@ const translation = { | |||
| settingTitle: 'การตั้งค่าการดึงข้อมูล', | |||
| desc: 'ทดสอบเอฟเฟกต์การตีของความรู้ตามข้อความแบบสอบถามที่กําหนด', | |||
| dateTimeFormat: 'MM/DD/YYYY hh:mm A', | |||
| recents: 'ล่าสุด', | |||
| table: { | |||
| header: { | |||
| source: 'ที่มา', | |||
| @@ -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ı?', | |||
| @@ -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ı:', | |||
| @@ -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', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Bilgi Oluştur', | |||
| update: 'Veri ekle', | |||
| fallbackRoute: 'Bilgi', | |||
| }, | |||
| one: 'Veri kaynağı seçin', | |||
| @@ -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', | |||
| @@ -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', | |||
| @@ -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: 'Версії', | |||
| @@ -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: 'Отримайте Преміум у', | |||
| @@ -198,7 +198,6 @@ const translation = { | |||
| showAppLength: 'Показати {{length}} програм', | |||
| delete: 'Видалити обліковий запис', | |||
| deleteTip: 'Видалення вашого облікового запису призведе до остаточного видалення всіх ваших даних, і їх неможливо буде відновити.', | |||
| deleteConfirmTip: 'Щоб підтвердити, будь ласка, надішліть наступне з вашої зареєстрованої електронної пошти на ', | |||
| account: 'Рахунок', | |||
| studio: 'Студія Dify', | |||
| myAccount: 'Особистий кабінет', | |||
| @@ -1,8 +1,6 @@ | |||
| const translation = { | |||
| steps: { | |||
| header: { | |||
| creation: 'Створити Знання', | |||
| update: 'Додати дані', | |||
| fallbackRoute: 'Знання', | |||
| }, | |||
| one: 'Виберіть джерело даних', | |||
| @@ -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: 'додайте запитання тут', | |||