diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index e424171019..aa7e68dbac 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -75,7 +75,7 @@ jobs: - name: Run Workflow run: poetry run -C api bash dev/pytest/pytest_workflow.sh - - name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma) + - name: Set up Vector Stores (Weaviate, Qdrant, PGVector, Milvus, PgVecto-RS, Chroma, MyScale) uses: hoverkraft-tech/compose-action@v2.0.0 with: compose-file: | @@ -89,5 +89,6 @@ jobs: pgvecto-rs pgvector chroma + myscale - name: Test Vector Stores run: poetry run -C api bash dev/pytest/pytest_vdb.sh diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 2678f23a77..407bd47d9b 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -48,18 +48,18 @@ jobs: platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ env.DOCKERHUB_USER }} password: ${{ env.DOCKERHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 diff --git a/api/.env.example b/api/.env.example index bdea20c5ff..eb0574e252 100644 --- a/api/.env.example +++ b/api/.env.example @@ -83,7 +83,7 @@ OCI_REGION=your-region WEB_API_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,* CONSOLE_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,* -# Vector database configuration, support: weaviate, qdrant, milvus, relyt, pgvecto_rs, pgvector, pgvector, chroma, opensearch, tidb_vector +# Vector database configuration, support: weaviate, qdrant, milvus, myscale, relyt, pgvecto_rs, pgvector, pgvector, chroma, opensearch, tidb_vector VECTOR_STORE=weaviate # Weaviate configuration @@ -106,6 +106,14 @@ MILVUS_USER=root MILVUS_PASSWORD=Milvus MILVUS_SECURE=false +# MyScale configuration +MYSCALE_HOST=127.0.0.1 +MYSCALE_PORT=8123 +MYSCALE_USER=default +MYSCALE_PASSWORD= +MYSCALE_DATABASE=default +MYSCALE_FTS_PARAMS= + # Relyt configuration RELYT_HOST=127.0.0.1 RELYT_PORT=5432 @@ -151,6 +159,16 @@ CHROMA_DATABASE=default_database CHROMA_AUTH_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider CHROMA_AUTH_CREDENTIALS=difyai123456 +# AnalyticDB configuration +ANALYTICDB_KEY_ID=your-ak +ANALYTICDB_KEY_SECRET=your-sk +ANALYTICDB_REGION_ID=cn-hangzhou +ANALYTICDB_INSTANCE_ID=gp-ab123456 +ANALYTICDB_ACCOUNT=testaccount +ANALYTICDB_PASSWORD=testpassword +ANALYTICDB_NAMESPACE=dify +ANALYTICDB_NAMESPACE_PASSWORD=difypassword + # OpenSearch configuration OPENSEARCH_HOST=127.0.0.1 OPENSEARCH_PORT=9200 @@ -237,6 +255,7 @@ WORKFLOW_CALL_MAX_DEPTH=5 # App configuration APP_MAX_EXECUTION_TIME=1200 +APP_MAX_ACTIVE_REQUESTS=0 # Plugin configuration PLUGIN_INNER_API_URL=http://127.0.0.1:5002 diff --git a/api/commands.py b/api/commands.py index cc49824b4f..6719539cc8 100644 --- a/api/commands.py +++ b/api/commands.py @@ -337,6 +337,14 @@ def migrate_knowledge_vector_database(): "vector_store": {"class_prefix": collection_name} } dataset.index_struct = json.dumps(index_struct_dict) + elif vector_type == VectorType.ANALYTICDB: + dataset_id = dataset.id + collection_name = Dataset.gen_collection_name_by_id(dataset_id) + index_struct_dict = { + "type": VectorType.ANALYTICDB, + "vector_store": {"class_prefix": collection_name} + } + dataset.index_struct = json.dumps(index_struct_dict) else: raise ValueError(f"Vector store {vector_type} is not supported.") diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 2012b56c62..565823e119 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -31,6 +31,10 @@ class AppExecutionConfig(BaseSettings): description='execution timeout in seconds for app execution', default=1200, ) + APP_MAX_ACTIVE_REQUESTS: NonNegativeInt = Field( + description='max active request per app, 0 means unlimited', + default=0, + ) class CodeExecutionSandboxConfig(BaseSettings): @@ -409,6 +413,11 @@ class DataSetConfig(BaseSettings): default=30, ) + DATASET_OPERATOR_ENABLED: bool = Field( + description='whether to enable dataset operator', + default=False, + ) + class WorkspaceConfig(BaseSettings): """ diff --git a/api/configs/middleware/__init__.py b/api/configs/middleware/__init__.py index d8a2fe683a..a32b70bdc7 100644 --- a/api/configs/middleware/__init__.py +++ b/api/configs/middleware/__init__.py @@ -10,8 +10,10 @@ from configs.middleware.storage.azure_blob_storage_config import AzureBlobStorag from configs.middleware.storage.google_cloud_storage_config import GoogleCloudStorageConfig from configs.middleware.storage.oci_storage_config import OCIStorageConfig from configs.middleware.storage.tencent_cos_storage_config import TencentCloudCOSStorageConfig +from configs.middleware.vdb.analyticdb_config import AnalyticdbConfig from configs.middleware.vdb.chroma_config import ChromaConfig from configs.middleware.vdb.milvus_config import MilvusConfig +from configs.middleware.vdb.myscale_config import MyScaleConfig from configs.middleware.vdb.opensearch_config import OpenSearchConfig from configs.middleware.vdb.oracle_config import OracleConfig from configs.middleware.vdb.pgvector_config import PGVectorConfig @@ -183,8 +185,10 @@ class MiddlewareConfig( # configs of vdb and vdb providers VectorStoreConfig, + AnalyticdbConfig, ChromaConfig, MilvusConfig, + MyScaleConfig, OpenSearchConfig, OracleConfig, PGVectorConfig, diff --git a/api/configs/middleware/vdb/analyticdb_config.py b/api/configs/middleware/vdb/analyticdb_config.py new file mode 100644 index 0000000000..db2899265e --- /dev/null +++ b/api/configs/middleware/vdb/analyticdb_config.py @@ -0,0 +1,44 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class AnalyticdbConfig(BaseModel): + """ + Configuration for connecting to AnalyticDB. + Refer to the following documentation for details on obtaining credentials: + https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/getting-started/create-an-instance-instances-with-vector-engine-optimization-enabled + """ + + ANALYTICDB_KEY_ID : Optional[str] = Field( + default=None, + description="The Access Key ID provided by Alibaba Cloud for authentication." + ) + ANALYTICDB_KEY_SECRET : Optional[str] = Field( + default=None, + description="The Secret Access Key corresponding to the Access Key ID for secure access." + ) + ANALYTICDB_REGION_ID : Optional[str] = Field( + default=None, + description="The region where the AnalyticDB instance is deployed (e.g., 'cn-hangzhou')." + ) + ANALYTICDB_INSTANCE_ID : Optional[str] = Field( + default=None, + description="The unique identifier of the AnalyticDB instance you want to connect to (e.g., 'gp-ab123456').." + ) + ANALYTICDB_ACCOUNT : Optional[str] = Field( + default=None, + description="The account name used to log in to the AnalyticDB instance." + ) + ANALYTICDB_PASSWORD : Optional[str] = Field( + default=None, + description="The password associated with the AnalyticDB account for authentication." + ) + ANALYTICDB_NAMESPACE : Optional[str] = Field( + default=None, + description="The namespace within AnalyticDB for schema isolation." + ) + ANALYTICDB_NAMESPACE_PASSWORD : Optional[str] = Field( + default=None, + description="The password for accessing the specified namespace within the AnalyticDB instance." + ) diff --git a/api/configs/middleware/vdb/myscale_config.py b/api/configs/middleware/vdb/myscale_config.py new file mode 100644 index 0000000000..e513cad0e8 --- /dev/null +++ b/api/configs/middleware/vdb/myscale_config.py @@ -0,0 +1,39 @@ +from typing import Optional + +from pydantic import BaseModel, Field, PositiveInt + + +class MyScaleConfig(BaseModel): + """ + MyScale configs + """ + + MYSCALE_HOST: Optional[str] = Field( + description='MyScale host', + default=None, + ) + + MYSCALE_PORT: Optional[PositiveInt] = Field( + description='MyScale port', + default=8123, + ) + + MYSCALE_USER: Optional[str] = Field( + description='MyScale user', + default=None, + ) + + MYSCALE_PASSWORD: Optional[str] = Field( + description='MyScale password', + default=None, + ) + + MYSCALE_DATABASE: Optional[str] = Field( + description='MyScale database name', + default=None, + ) + + MYSCALE_FTS_PARAMS: Optional[str] = Field( + description='MyScale fts index parameters', + default=None, + ) diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index dc812a15be..30888d0b71 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings): CURRENT_VERSION: str = Field( description='Dify version', - default='0.6.12-fix1', + default='0.6.13', ) COMMIT_SHA: str = Field( diff --git a/api/constants/tts_auto_play_timeout.py b/api/constants/tts_auto_play_timeout.py new file mode 100644 index 0000000000..d5ed30830a --- /dev/null +++ b/api/constants/tts_auto_play_timeout.py @@ -0,0 +1,4 @@ +TTS_AUTO_PLAY_TIMEOUT = 5 + +# sleep 20 ms ( 40ms => 1280 byte audio file,20ms => 640 byte audio file) +TTS_AUTO_PLAY_YIELD_CPU_TIME = 0.02 diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py index fb3205813d..6952940649 100644 --- a/api/controllers/console/app/app.py +++ b/api/controllers/console/app/app.py @@ -134,6 +134,7 @@ class AppApi(Resource): parser.add_argument('description', type=str, location='json') parser.add_argument('icon', type=str, location='json') parser.add_argument('icon_background', type=str, location='json') + parser.add_argument('max_active_requests', type=int, location='json') args = parser.parse_args() app_service = AppService() diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index 51322c92d3..1de08afa4e 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -81,15 +81,36 @@ class ChatMessageTextApi(Resource): @account_initialization_required @get_app_model def post(self, app_model): + from werkzeug.exceptions import InternalServerError + try: + parser = reqparse.RequestParser() + parser.add_argument('message_id', type=str, location='json') + parser.add_argument('text', type=str, location='json') + parser.add_argument('voice', type=str, location='json') + parser.add_argument('streaming', type=bool, location='json') + args = parser.parse_args() + + message_id = args.get('message_id', None) + text = args.get('text', None) + if (app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value] + and app_model.workflow + and app_model.workflow.features_dict): + text_to_speech = app_model.workflow.features_dict.get('text_to_speech') + voice = args.get('voice') if args.get('voice') else text_to_speech.get('voice') + else: + try: + voice = args.get('voice') if args.get('voice') else app_model.app_model_config.text_to_speech_dict.get( + 'voice') + except Exception: + voice = None response = AudioService.transcript_tts( app_model=app_model, - text=request.form['text'], - voice=request.form['voice'], - streaming=False + text=text, + message_id=message_id, + voice=voice ) - - return {'data': response.data.decode('latin1')} + return response except services.errors.app_model_config.AppModelConfigBrokenError: logging.exception("App model config broken.") raise AppUnavailableError() diff --git a/api/controllers/console/app/completion.py b/api/controllers/console/app/completion.py index 478ee9dfe7..61582536fd 100644 --- a/api/controllers/console/app/completion.py +++ b/api/controllers/console/app/completion.py @@ -19,7 +19,12 @@ from controllers.console.setup import setup_required from controllers.console.wraps import account_initialization_required from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import InvokeFrom -from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError +from core.errors.error import ( + AppInvokeQuotaExceededError, + ModelCurrentlyNotSupportError, + ProviderTokenNotInitError, + QuotaExceededError, +) from core.model_runtime.errors.invoke import InvokeError from libs import helper from libs.helper import uuid_value @@ -75,7 +80,7 @@ class CompletionMessageApi(Resource): raise ProviderModelCurrentlyNotSupportError() except InvokeError as e: raise CompletionRequestError(e.description) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") @@ -141,7 +146,7 @@ class ChatMessageApi(Resource): raise ProviderModelCurrentlyNotSupportError() except InvokeError as e: raise CompletionRequestError(e.description) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 08c2d47746..cadb75c547 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -13,6 +13,7 @@ from controllers.console.setup import setup_required from controllers.console.wraps import account_initialization_required from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import InvokeFrom +from core.errors.error import AppInvokeQuotaExceededError from fields.workflow_fields import workflow_fields from fields.workflow_run_fields import workflow_run_node_execution_fields from libs import helper @@ -279,7 +280,7 @@ class DraftWorkflowRunApi(Resource): ) return helper.compact_generate_response(response) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index fdd61b0a0c..70c506bb0e 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -25,7 +25,7 @@ from fields.document_fields import document_status_fields from libs.login import login_required from models.dataset import Dataset, Document, DocumentSegment from models.model import ApiToken, UploadFile -from services.dataset_service import DatasetService, DocumentService +from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService def _validate_name(name): @@ -85,6 +85,12 @@ class DatasetListApi(Resource): else: item['embedding_available'] = True + if item.get('permission') == 'partial_members': + part_users_list = DatasetPermissionService.get_dataset_partial_member_list(item['id']) + item.update({'partial_member_list': part_users_list}) + else: + item.update({'partial_member_list': []}) + response = { 'data': data, 'has_more': len(datasets) == limit, @@ -108,8 +114,8 @@ class DatasetListApi(Resource): help='Invalid indexing technique.') args = parser.parse_args() - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator + if not current_user.is_dataset_editor: raise Forbidden() try: @@ -140,6 +146,10 @@ class DatasetApi(Resource): except services.errors.account.NoPermissionError as e: raise Forbidden(str(e)) data = marshal(dataset, dataset_detail_fields) + if data.get('permission') == 'partial_members': + part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + data.update({'partial_member_list': part_users_list}) + # check embedding setting provider_manager = ProviderManager() configurations = provider_manager.get_configurations( @@ -163,6 +173,11 @@ class DatasetApi(Resource): data['embedding_available'] = False else: data['embedding_available'] = True + + if data.get('permission') == 'partial_members': + part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + data.update({'partial_member_list': part_users_list}) + return data, 200 @setup_required @@ -188,17 +203,21 @@ class DatasetApi(Resource): nullable=True, help='Invalid indexing technique.') parser.add_argument('permission', type=str, location='json', choices=( - 'only_me', 'all_team_members'), help='Invalid permission.') + 'only_me', 'all_team_members', 'partial_members'), help='Invalid permission.' + ) parser.add_argument('embedding_model', type=str, location='json', help='Invalid embedding model.') parser.add_argument('embedding_model_provider', type=str, location='json', help='Invalid embedding model provider.') parser.add_argument('retrieval_model', type=dict, location='json', help='Invalid retrieval model.') + parser.add_argument('partial_member_list', type=list, location='json', help='Invalid parent user list.') args = parser.parse_args() + data = request.get_json() - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: - raise Forbidden() + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + DatasetPermissionService.check_permission( + current_user, dataset, data.get('permission'), data.get('partial_member_list') + ) dataset = DatasetService.update_dataset( dataset_id_str, args, current_user) @@ -206,7 +225,20 @@ class DatasetApi(Resource): if dataset is None: raise NotFound("Dataset not found.") - return marshal(dataset, dataset_detail_fields), 200 + result_data = marshal(dataset, dataset_detail_fields) + tenant_id = current_user.current_tenant_id + + if data.get('partial_member_list') and data.get('permission') == 'partial_members': + DatasetPermissionService.update_partial_member_list( + tenant_id, dataset_id_str, data.get('partial_member_list') + ) + else: + DatasetPermissionService.clear_partial_member_list(dataset_id_str) + + partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + result_data.update({'partial_member_list': partial_member_list}) + + return result_data, 200 @setup_required @login_required @@ -215,11 +247,12 @@ class DatasetApi(Resource): dataset_id_str = str(dataset_id) # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + if not current_user.is_editor or current_user.is_dataset_operator: raise Forbidden() try: if DatasetService.delete_dataset(dataset_id_str, current_user): + DatasetPermissionService.clear_partial_member_list(dataset_id_str) return {'result': 'success'}, 204 else: raise NotFound("Dataset not found.") @@ -515,7 +548,7 @@ class DatasetRetrievalSettingApi(Resource): RetrievalMethod.SEMANTIC_SEARCH ] } - case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH: + case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH | VectorType.ANALYTICDB | VectorType.MYSCALE: return { 'retrieval_method': [ RetrievalMethod.SEMANTIC_SEARCH, @@ -539,7 +572,7 @@ class DatasetRetrievalSettingMockApi(Resource): RetrievalMethod.SEMANTIC_SEARCH ] } - case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH: + case VectorType.QDRANT | VectorType.WEAVIATE | VectorType.OPENSEARCH| VectorType.ANALYTICDB | VectorType.MYSCALE: return { 'retrieval_method': [ RetrievalMethod.SEMANTIC_SEARCH, @@ -569,6 +602,27 @@ class DatasetErrorDocs(Resource): }, 200 +class DatasetPermissionUserListApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self, dataset_id): + dataset_id_str = str(dataset_id) + dataset = DatasetService.get_dataset(dataset_id_str) + if dataset is None: + raise NotFound("Dataset not found.") + try: + DatasetService.check_dataset_permission(dataset, current_user) + except services.errors.account.NoPermissionError as e: + raise Forbidden(str(e)) + + partial_members_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + + return { + 'data': partial_members_list, + }, 200 + + api.add_resource(DatasetListApi, '/datasets') api.add_resource(DatasetApi, '/datasets/') api.add_resource(DatasetUseCheckApi, '/datasets//use-check') @@ -582,3 +636,4 @@ api.add_resource(DatasetApiDeleteApi, '/datasets/api-keys/') api.add_resource(DatasetApiBaseUrlApi, '/datasets/api-base-info') api.add_resource(DatasetRetrievalSettingApi, '/datasets/retrieval-setting') api.add_resource(DatasetRetrievalSettingMockApi, '/datasets/retrieval-setting/') +api.add_resource(DatasetPermissionUserListApi, '/datasets//permission-part-users') diff --git a/api/controllers/console/datasets/datasets_document.py b/api/controllers/console/datasets/datasets_document.py index b3a253c167..afe0ca7c69 100644 --- a/api/controllers/console/datasets/datasets_document.py +++ b/api/controllers/console/datasets/datasets_document.py @@ -228,7 +228,7 @@ class DatasetDocumentListApi(Resource): raise NotFound('Dataset not found.') # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + if not current_user.is_dataset_editor: raise Forbidden() try: @@ -294,6 +294,11 @@ class DatasetInitApi(Resource): parser.add_argument('retrieval_model', type=dict, required=False, nullable=False, location='json') args = parser.parse_args() + + # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator + if not current_user.is_dataset_editor: + raise Forbidden() + if args['indexing_technique'] == 'high_quality': try: model_manager = ModelManager() @@ -757,14 +762,18 @@ class DocumentStatusApi(DocumentResource): dataset = DatasetService.get_dataset(dataset_id) if dataset is None: raise NotFound("Dataset not found.") + + # The role of the current user in the ta table must be admin, owner, or editor + if not current_user.is_dataset_editor: + raise Forbidden() + # check user's model setting DatasetService.check_dataset_model_setting(dataset) - document = self.get_document(dataset_id, document_id) + # check user's permission + DatasetService.check_dataset_permission(dataset, current_user) - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: - raise Forbidden() + document = self.get_document(dataset_id, document_id) indexing_cache_key = 'document_{}_indexing'.format(document.id) cache_result = redis_client.get(indexing_cache_key) @@ -955,10 +964,11 @@ class DocumentRenameApi(DocumentResource): @account_initialization_required @marshal_with(document_fields) def post(self, dataset_id, document_id): - # The role of the current user in the ta table must be admin or owner - if not current_user.is_admin_or_owner: + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + if not current_user.is_dataset_editor: raise Forbidden() - + dataset = DatasetService.get_dataset(dataset_id) + DatasetService.check_dataset_operator_permission(current_user, dataset) parser = reqparse.RequestParser() parser.add_argument('name', type=str, required=True, nullable=False, location='json') args = parser.parse_args() diff --git a/api/controllers/console/explore/audio.py b/api/controllers/console/explore/audio.py index d869cd38ed..920b1d8383 100644 --- a/api/controllers/console/explore/audio.py +++ b/api/controllers/console/explore/audio.py @@ -19,6 +19,7 @@ from controllers.console.app.error import ( from controllers.console.explore.wraps import InstalledAppResource from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError +from models.model import AppMode from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -70,16 +71,33 @@ class ChatAudioApi(InstalledAppResource): class ChatTextApi(InstalledAppResource): def post(self, installed_app): - app_model = installed_app.app + from flask_restful import reqparse + app_model = installed_app.app try: + parser = reqparse.RequestParser() + parser.add_argument('message_id', type=str, required=False, location='json') + parser.add_argument('voice', type=str, location='json') + parser.add_argument('streaming', type=bool, location='json') + args = parser.parse_args() + + message_id = args.get('message_id') + if (app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value] + and app_model.workflow + and app_model.workflow.features_dict): + text_to_speech = app_model.workflow.features_dict.get('text_to_speech') + voice = args.get('voice') if args.get('voice') else text_to_speech.get('voice') + else: + try: + voice = args.get('voice') if args.get('voice') else app_model.app_model_config.text_to_speech_dict.get('voice') + except Exception: + voice = None response = AudioService.transcript_tts( app_model=app_model, - text=request.form['text'], - voice=request.form['voice'] if request.form.get('voice') else app_model.app_model_config.text_to_speech_dict.get('voice'), - streaming=False + message_id=message_id, + voice=voice ) - return {'data': response.data.decode('latin1')} + return response except services.errors.app_model_config.AppModelConfigBrokenError: logging.exception("App model config broken.") raise AppUnavailableError() @@ -108,3 +126,5 @@ class ChatTextApi(InstalledAppResource): api.add_resource(ChatAudioApi, '/installed-apps//audio-to-text', endpoint='installed_app_audio') api.add_resource(ChatTextApi, '/installed-apps//text-to-audio', endpoint='installed_app_text') +# api.add_resource(ChatTextApiWithMessageId, '/installed-apps//text-to-audio/message-id', +# endpoint='installed_app_text_with_message_id') diff --git a/api/controllers/console/tag/tags.py b/api/controllers/console/tag/tags.py index 55b212358d..004afaa531 100644 --- a/api/controllers/console/tag/tags.py +++ b/api/controllers/console/tag/tags.py @@ -36,7 +36,7 @@ class TagListApi(Resource): @account_initialization_required def post(self): # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + if not (current_user.is_editor or current_user.is_dataset_editor): raise Forbidden() parser = reqparse.RequestParser() @@ -68,7 +68,7 @@ class TagUpdateDeleteApi(Resource): def patch(self, tag_id): tag_id = str(tag_id) # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + if not (current_user.is_editor or current_user.is_dataset_editor): raise Forbidden() parser = reqparse.RequestParser() @@ -109,8 +109,8 @@ class TagBindingCreateApi(Resource): @login_required @account_initialization_required def post(self): - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + if not (current_user.is_editor or current_user.is_dataset_editor): raise Forbidden() parser = reqparse.RequestParser() @@ -134,8 +134,8 @@ class TagBindingDeleteApi(Resource): @login_required @account_initialization_required def post(self): - # The role of the current user in the ta table must be admin, owner, or editor - if not current_user.is_editor: + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + if not (current_user.is_editor or current_user.is_dataset_editor): raise Forbidden() parser = reqparse.RequestParser() diff --git a/api/controllers/console/workspace/members.py b/api/controllers/console/workspace/members.py index f404ca7efc..e8c88850a4 100644 --- a/api/controllers/console/workspace/members.py +++ b/api/controllers/console/workspace/members.py @@ -131,7 +131,20 @@ class MemberUpdateRoleApi(Resource): return {'result': 'success'} +class DatasetOperatorMemberListApi(Resource): + """List all members of current tenant.""" + + @setup_required + @login_required + @account_initialization_required + @marshal_with(account_with_role_list_fields) + def get(self): + members = TenantService.get_dataset_operator_members(current_user.current_tenant) + return {'result': 'success', 'accounts': members}, 200 + + api.add_resource(MemberListApi, '/workspaces/current/members') api.add_resource(MemberInviteEmailApi, '/workspaces/current/members/invite-email') api.add_resource(MemberCancelInviteApi, '/workspaces/current/members/') api.add_resource(MemberUpdateRoleApi, '/workspaces/current/members//update-role') +api.add_resource(DatasetOperatorMemberListApi, '/workspaces/current/dataset-operators') diff --git a/api/controllers/inner_api/wraps.py b/api/controllers/inner_api/wraps.py index 0793a67eec..5b2aaabe14 100644 --- a/api/controllers/inner_api/wraps.py +++ b/api/controllers/inner_api/wraps.py @@ -3,7 +3,7 @@ from functools import wraps from hashlib import sha1 from hmac import new as hmac_new -from flask import abort, current_app, request +from flask import abort, request from configs import dify_config from extensions.ext_database import db @@ -13,12 +13,12 @@ from models.model import EndUser def enterprise_inner_api_only(view): @wraps(view) def decorated(*args, **kwargs): - if not current_app.config['INNER_API']: + if not dify_config.INNER_API: abort(404) # get header 'X-Inner-Api-Key' inner_api_key = request.headers.get('X-Inner-Api-Key') - if not inner_api_key or inner_api_key != current_app.config['INNER_API_KEY']: + if not inner_api_key or inner_api_key != dify_config.INNER_API_KEY: abort(404) return view(*args, **kwargs) @@ -29,7 +29,7 @@ def enterprise_inner_api_only(view): def enterprise_inner_api_user_auth(view): @wraps(view) def decorated(*args, **kwargs): - if not current_app.config['INNER_API']: + if not dify_config.INNER_API: return view(*args, **kwargs) # get header 'X-Inner-Api-Key' diff --git a/api/controllers/service_api/app/app.py b/api/controllers/service_api/app/app.py index bccce9b55b..3b3cf1b026 100644 --- a/api/controllers/service_api/app/app.py +++ b/api/controllers/service_api/app/app.py @@ -1,7 +1,7 @@ -from flask import current_app from flask_restful import Resource, fields, marshal_with +from configs import dify_config from controllers.service_api import api from controllers.service_api.app.error import AppUnavailableError from controllers.service_api.wraps import validate_app_token @@ -78,7 +78,7 @@ class AppParameterApi(Resource): "transfer_methods": ["remote_url", "local_file"] }}), 'system_parameters': { - 'image_file_size_limit': current_app.config.get('UPLOAD_IMAGE_FILE_SIZE_LIMIT') + 'image_file_size_limit': dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT } } diff --git a/api/controllers/service_api/app/audio.py b/api/controllers/service_api/app/audio.py index 15c0a153b8..607d71598f 100644 --- a/api/controllers/service_api/app/audio.py +++ b/api/controllers/service_api/app/audio.py @@ -20,7 +20,7 @@ from controllers.service_api.app.error import ( from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError -from models.model import App, EndUser +from models.model import App, AppMode, EndUser from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -72,19 +72,30 @@ class AudioApi(Resource): class TextApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) def post(self, app_model: App, end_user: EndUser): - parser = reqparse.RequestParser() - parser.add_argument('text', type=str, required=True, nullable=False, location='json') - parser.add_argument('voice', type=str, location='json') - parser.add_argument('streaming', type=bool, required=False, nullable=False, location='json') - args = parser.parse_args() - try: + parser = reqparse.RequestParser() + parser.add_argument('message_id', type=str, required=False, location='json') + parser.add_argument('voice', type=str, location='json') + parser.add_argument('streaming', type=bool, location='json') + args = parser.parse_args() + + message_id = args.get('message_id') + if (app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value] + and app_model.workflow + and app_model.workflow.features_dict): + text_to_speech = app_model.workflow.features_dict.get('text_to_speech') + voice = args.get('voice') if args.get('voice') else text_to_speech.get('voice') + else: + try: + voice = args.get('voice') if args.get('voice') else app_model.app_model_config.text_to_speech_dict.get( + 'voice') + except Exception: + voice = None response = AudioService.transcript_tts( app_model=app_model, - text=args['text'], - end_user=end_user, - voice=args.get('voice'), - streaming=args['streaming'] + message_id=message_id, + end_user=end_user.external_user_id, + voice=voice ) return response diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index c1fdf249bb..2511f46baf 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -17,7 +17,12 @@ from controllers.service_api.app.error import ( from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import InvokeFrom -from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError +from core.errors.error import ( + AppInvokeQuotaExceededError, + ModelCurrentlyNotSupportError, + ProviderTokenNotInitError, + QuotaExceededError, +) from core.model_runtime.errors.invoke import InvokeError from libs import helper from libs.helper import uuid_value @@ -69,7 +74,7 @@ class CompletionApi(Resource): raise ProviderModelCurrentlyNotSupportError() except InvokeError as e: raise CompletionRequestError(e.description) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") @@ -132,7 +137,7 @@ class ChatApi(Resource): raise ProviderModelCurrentlyNotSupportError() except InvokeError as e: raise CompletionRequestError(e.description) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index 2830530db5..dd11949e84 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -14,7 +14,12 @@ from controllers.service_api.app.error import ( from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import InvokeFrom -from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError +from core.errors.error import ( + AppInvokeQuotaExceededError, + ModelCurrentlyNotSupportError, + ProviderTokenNotInitError, + QuotaExceededError, +) from core.model_runtime.errors.invoke import InvokeError from libs import helper from models.model import App, AppMode, EndUser @@ -59,7 +64,7 @@ class WorkflowRunApi(Resource): raise ProviderModelCurrentlyNotSupportError() except InvokeError as e: raise CompletionRequestError(e.description) - except ValueError as e: + except (ValueError, AppInvokeQuotaExceededError) as e: raise e except Exception as e: logging.exception("internal server error.") diff --git a/api/controllers/service_api/index.py b/api/controllers/service_api/index.py index 932388b562..c910063ebd 100644 --- a/api/controllers/service_api/index.py +++ b/api/controllers/service_api/index.py @@ -1,6 +1,6 @@ -from flask import current_app from flask_restful import Resource +from configs import dify_config from controllers.service_api import api @@ -9,7 +9,7 @@ class IndexApi(Resource): return { "welcome": "Dify OpenAPI", "api_version": "v1", - "server_version": current_app.config['CURRENT_VERSION'] + "server_version": dify_config.CURRENT_VERSION, } diff --git a/api/controllers/web/app.py b/api/controllers/web/app.py index 91d9015c33..f4db82552c 100644 --- a/api/controllers/web/app.py +++ b/api/controllers/web/app.py @@ -1,6 +1,6 @@ -from flask import current_app from flask_restful import fields, marshal_with +from configs import dify_config from controllers.web import api from controllers.web.error import AppUnavailableError from controllers.web.wraps import WebApiResource @@ -75,7 +75,7 @@ class AppParameterApi(WebApiResource): "transfer_methods": ["remote_url", "local_file"] }}), 'system_parameters': { - 'image_file_size_limit': current_app.config.get('UPLOAD_IMAGE_FILE_SIZE_LIMIT') + 'image_file_size_limit': dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT } } diff --git a/api/controllers/web/audio.py b/api/controllers/web/audio.py index 3fed7895e2..8be872f5f9 100644 --- a/api/controllers/web/audio.py +++ b/api/controllers/web/audio.py @@ -19,7 +19,7 @@ from controllers.web.error import ( from controllers.web.wraps import WebApiResource from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError -from models.model import App +from models.model import App, AppMode from services.audio_service import AudioService from services.errors.audio import ( AudioTooLargeServiceError, @@ -69,16 +69,35 @@ class AudioApi(WebApiResource): class TextApi(WebApiResource): def post(self, app_model: App, end_user): + from flask_restful import reqparse try: + parser = reqparse.RequestParser() + parser.add_argument('message_id', type=str, required=False, location='json') + parser.add_argument('voice', type=str, location='json') + parser.add_argument('streaming', type=bool, location='json') + args = parser.parse_args() + + message_id = args.get('message_id') + if (app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value] + and app_model.workflow + and app_model.workflow.features_dict): + text_to_speech = app_model.workflow.features_dict.get('text_to_speech') + voice = args.get('voice') if args.get('voice') else text_to_speech.get('voice') + else: + try: + voice = args.get('voice') if args.get( + 'voice') else app_model.app_model_config.text_to_speech_dict.get('voice') + except Exception: + voice = None + response = AudioService.transcript_tts( app_model=app_model, - text=request.form['text'], + message_id=message_id, end_user=end_user.external_user_id, - voice=request.form['voice'] if request.form.get('voice') else None, - streaming=False + voice=voice ) - return {'data': response.data.decode('latin1')} + return response except services.errors.app_model_config.AppModelConfigBrokenError: logging.exception("App model config broken.") raise AppUnavailableError() diff --git a/api/controllers/web/site.py b/api/controllers/web/site.py index c307959b20..99ec86e935 100644 --- a/api/controllers/web/site.py +++ b/api/controllers/web/site.py @@ -1,8 +1,8 @@ -from flask import current_app from flask_restful import fields, marshal_with from werkzeug.exceptions import Forbidden +from configs import dify_config from controllers.web import api from controllers.web.wraps import WebApiResource from extensions.ext_database import db @@ -84,7 +84,7 @@ class AppSiteInfo: self.can_replace_logo = can_replace_logo if can_replace_logo: - base_url = current_app.config.get('FILES_URL') + base_url = dify_config.FILES_URL remove_webapp_brand = tenant.custom_config_dict.get('remove_webapp_brand', False) replace_webapp_logo = f'{base_url}/files/workspaces/{tenant.id}/webapp-logo' if tenant.custom_config_dict.get('replace_webapp_logo') else None self.custom_config = { diff --git a/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py b/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py new file mode 100644 index 0000000000..8325994608 --- /dev/null +++ b/api/core/app/apps/advanced_chat/app_generator_tts_publisher.py @@ -0,0 +1,135 @@ +import base64 +import concurrent.futures +import logging +import queue +import re +import threading + +from core.app.entities.queue_entities import QueueAgentMessageEvent, QueueLLMChunkEvent, QueueTextChunkEvent +from core.model_manager import ModelManager +from core.model_runtime.entities.model_entities import ModelType + + +class AudioTrunk: + def __init__(self, status: str, audio): + self.audio = audio + self.status = status + + +def _invoiceTTS(text_content: str, model_instance, tenant_id: str, voice: str): + if not text_content or text_content.isspace(): + return + return model_instance.invoke_tts( + content_text=text_content.strip(), + user="responding_tts", + tenant_id=tenant_id, + voice=voice + ) + + +def _process_future(future_queue, audio_queue): + while True: + try: + future = future_queue.get() + if future is None: + break + for audio in future.result(): + audio_base64 = base64.b64encode(bytes(audio)) + audio_queue.put(AudioTrunk("responding", audio=audio_base64)) + except Exception as e: + logging.getLogger(__name__).warning(e) + break + audio_queue.put(AudioTrunk("finish", b'')) + + +class AppGeneratorTTSPublisher: + + def __init__(self, tenant_id: str, voice: str): + self.logger = logging.getLogger(__name__) + self.tenant_id = tenant_id + self.msg_text = '' + self._audio_queue = queue.Queue() + self._msg_queue = queue.Queue() + self.match = re.compile(r'[。.!?]') + self.model_manager = ModelManager() + self.model_instance = self.model_manager.get_default_model_instance( + tenant_id=self.tenant_id, + model_type=ModelType.TTS + ) + self.voices = self.model_instance.get_tts_voices() + values = [voice.get('value') for voice in self.voices] + self.voice = voice + if not voice or voice not in values: + self.voice = self.voices[0].get('value') + self.MAX_SENTENCE = 2 + self._last_audio_event = None + self._runtime_thread = threading.Thread(target=self._runtime).start() + self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) + + def publish(self, message): + try: + self._msg_queue.put(message) + except Exception as e: + self.logger.warning(e) + + def _runtime(self): + future_queue = queue.Queue() + threading.Thread(target=_process_future, args=(future_queue, self._audio_queue)).start() + while True: + try: + message = self._msg_queue.get() + if message is None: + if self.msg_text and len(self.msg_text.strip()) > 0: + futures_result = self.executor.submit(_invoiceTTS, self.msg_text, + self.model_instance, self.tenant_id, self.voice) + future_queue.put(futures_result) + break + elif isinstance(message.event, QueueAgentMessageEvent | QueueLLMChunkEvent): + self.msg_text += message.event.chunk.delta.message.content + elif isinstance(message.event, QueueTextChunkEvent): + self.msg_text += message.event.text + self.last_message = message + sentence_arr, text_tmp = self._extract_sentence(self.msg_text) + if len(sentence_arr) >= min(self.MAX_SENTENCE, 7): + self.MAX_SENTENCE += 1 + text_content = ''.join(sentence_arr) + futures_result = self.executor.submit(_invoiceTTS, text_content, + self.model_instance, + self.tenant_id, + self.voice) + future_queue.put(futures_result) + if text_tmp: + self.msg_text = text_tmp + else: + self.msg_text = '' + + except Exception as e: + self.logger.warning(e) + break + future_queue.put(None) + + def checkAndGetAudio(self) -> AudioTrunk | None: + try: + if self._last_audio_event and self._last_audio_event.status == "finish": + if self.executor: + self.executor.shutdown(wait=False) + return self.last_message + audio = self._audio_queue.get_nowait() + if audio and audio.status == "finish": + self.executor.shutdown(wait=False) + self._runtime_thread = None + if audio: + self._last_audio_event = audio + return audio + except queue.Empty: + return None + + def _extract_sentence(self, org_text): + tx = self.match.finditer(org_text) + start = 0 + result = [] + for i in tx: + end = i.regs[0][1] + result.append(org_text[start:end]) + start = end + return result, org_text[start:] diff --git a/api/core/app/apps/advanced_chat/app_runner.py b/api/core/app/apps/advanced_chat/app_runner.py index 96e9319dda..3856621700 100644 --- a/api/core/app/apps/advanced_chat/app_runner.py +++ b/api/core/app/apps/advanced_chat/app_runner.py @@ -255,6 +255,12 @@ class AdvancedChatAppRunner(AppRunner): ) index += 1 time.sleep(0.01) + else: + queue_manager.publish( + QueueTextChunkEvent( + text=text + ), PublishFrom.APPLICATION_MANAGER + ) queue_manager.publish( QueueStopEvent(stopped_by=stopped_by), diff --git a/api/core/app/apps/advanced_chat/generate_task_pipeline.py b/api/core/app/apps/advanced_chat/generate_task_pipeline.py index 5ca0fe2191..4b089f033f 100644 --- a/api/core/app/apps/advanced_chat/generate_task_pipeline.py +++ b/api/core/app/apps/advanced_chat/generate_task_pipeline.py @@ -4,6 +4,8 @@ import time from collections.abc import Generator from typing import Any, Optional, Union, cast +from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME +from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AdvancedChatAppGenerateEntity, @@ -33,6 +35,8 @@ from core.app.entities.task_entities import ( ChatbotAppStreamResponse, ChatflowStreamGenerateRoute, ErrorStreamResponse, + MessageAudioEndStreamResponse, + MessageAudioStreamResponse, MessageEndStreamResponse, StreamResponse, ) @@ -71,13 +75,13 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc _iteration_nested_relations: dict[str, list[str]] def __init__( - self, application_generate_entity: AdvancedChatAppGenerateEntity, - workflow: Workflow, - queue_manager: AppQueueManager, - conversation: Conversation, - message: Message, - user: Union[Account, EndUser], - stream: bool + self, application_generate_entity: AdvancedChatAppGenerateEntity, + workflow: Workflow, + queue_manager: AppQueueManager, + conversation: Conversation, + message: Message, + user: Union[Account, EndUser], + stream: bool ) -> None: """ Initialize AdvancedChatAppGenerateTaskPipeline. @@ -129,7 +133,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc self._application_generate_entity.query ) - generator = self._process_stream_response( + generator = self._wrapper_process_stream_response( trace_manager=self._application_generate_entity.trace_manager ) if self._stream: @@ -138,7 +142,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc return self._to_blocking_response(generator) def _to_blocking_response(self, generator: Generator[StreamResponse, None, None]) \ - -> ChatbotAppBlockingResponse: + -> ChatbotAppBlockingResponse: """ Process blocking response. :return: @@ -169,7 +173,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc raise Exception('Queue listening stopped unexpectedly.') def _to_stream_response(self, generator: Generator[StreamResponse, None, None]) \ - -> Generator[ChatbotAppStreamResponse, None, None]: + -> Generator[ChatbotAppStreamResponse, None, None]: """ To stream response. :return: @@ -182,14 +186,68 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc stream_response=stream_response ) + def _listenAudioMsg(self, publisher, task_id: str): + if not publisher: + return None + audio_msg: AudioTrunk = publisher.checkAndGetAudio() + if audio_msg and audio_msg.status != "finish": + return MessageAudioStreamResponse(audio=audio_msg.audio, task_id=task_id) + return None + + def _wrapper_process_stream_response(self, trace_manager: Optional[TraceQueueManager] = None) -> \ + Generator[StreamResponse, None, None]: + + publisher = None + task_id = self._application_generate_entity.task_id + tenant_id = self._application_generate_entity.app_config.tenant_id + features_dict = self._workflow.features_dict + + if features_dict.get('text_to_speech') and features_dict['text_to_speech'].get('enabled') and features_dict[ + 'text_to_speech'].get('autoPlay') == 'enabled': + publisher = AppGeneratorTTSPublisher(tenant_id, features_dict['text_to_speech'].get('voice')) + for response in self._process_stream_response(publisher=publisher, trace_manager=trace_manager): + while True: + audio_response = self._listenAudioMsg(publisher, task_id=task_id) + if audio_response: + yield audio_response + else: + break + yield response + + start_listener_time = time.time() + # timeout + while (time.time() - start_listener_time) < TTS_AUTO_PLAY_TIMEOUT: + try: + if not publisher: + break + audio_trunk = publisher.checkAndGetAudio() + if audio_trunk is None: + # release cpu + # sleep 20 ms ( 40ms => 1280 byte audio file,20ms => 640 byte audio file) + time.sleep(TTS_AUTO_PLAY_YIELD_CPU_TIME) + continue + if audio_trunk.status == "finish": + break + else: + start_listener_time = time.time() + yield MessageAudioStreamResponse(audio=audio_trunk.audio, task_id=task_id) + except Exception as e: + logger.error(e) + break + yield MessageAudioEndStreamResponse(audio='', task_id=task_id) + def _process_stream_response( - self, trace_manager: Optional[TraceQueueManager] = None + self, + publisher: AppGeneratorTTSPublisher, + trace_manager: Optional[TraceQueueManager] = None ) -> Generator[StreamResponse, None, None]: """ Process stream response. :return: """ for message in self._queue_manager.listen(): + if publisher: + publisher.publish(message=message) event = message.event if isinstance(event, QueueErrorEvent): @@ -301,7 +359,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc continue if not self._is_stream_out_support( - event=event + event=event ): continue @@ -318,7 +376,8 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc yield self._ping_stream_response() else: continue - + if publisher: + publisher.publish(None) if self._conversation_name_generate_thread: self._conversation_name_generate_thread.join() @@ -402,7 +461,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc return stream_generate_routes def _get_answer_start_at_node_ids(self, graph: dict, target_node_id: str) \ - -> list[str]: + -> list[str]: """ Get answer start at node id. :param graph: graph @@ -457,7 +516,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc start_node_id = target_node_id start_node_ids.append(start_node_id) elif node_type == NodeType.START.value or \ - node_iteration_id is not None and iteration_start_node_id == source_node.get('id'): + node_iteration_id is not None and iteration_start_node_id == source_node.get('id'): start_node_id = source_node_id start_node_ids.append(start_node_id) else: @@ -515,7 +574,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc # all route chunks are generated if self._task_state.current_stream_generate_state.current_route_position == len( - self._task_state.current_stream_generate_state.generate_route + self._task_state.current_stream_generate_state.generate_route ): self._task_state.current_stream_generate_state = None @@ -525,7 +584,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc :return: """ if not self._task_state.current_stream_generate_state: - return None + return route_chunks = self._task_state.current_stream_generate_state.generate_route[ self._task_state.current_stream_generate_state.current_route_position:] @@ -573,7 +632,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc # get route chunk node execution info route_chunk_node_execution_info = self._task_state.ran_node_execution_infos[route_chunk_node_id] if (route_chunk_node_execution_info.node_type == NodeType.LLM - and latest_node_execution_info.node_type == NodeType.LLM): + and latest_node_execution_info.node_type == NodeType.LLM): # only LLM support chunk stream output self._task_state.current_stream_generate_state.current_route_position += 1 continue @@ -643,7 +702,7 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc # all route chunks are generated if self._task_state.current_stream_generate_state.current_route_position == len( - self._task_state.current_stream_generate_state.generate_route + self._task_state.current_stream_generate_state.generate_route ): self._task_state.current_stream_generate_state = None diff --git a/api/core/app/apps/base_app_queue_manager.py b/api/core/app/apps/base_app_queue_manager.py index b5c49d65c2..dd2343d0b1 100644 --- a/api/core/app/apps/base_app_queue_manager.py +++ b/api/core/app/apps/base_app_queue_manager.py @@ -51,7 +51,6 @@ class AppQueueManager: listen_timeout = current_app.config.get("APP_MAX_EXECUTION_TIME") start_time = time.time() last_ping_time = 0 - while True: try: message = self._q.get(timeout=1) diff --git a/api/core/app/apps/workflow/generate_task_pipeline.py b/api/core/app/apps/workflow/generate_task_pipeline.py index f4bd396f46..2b4362150f 100644 --- a/api/core/app/apps/workflow/generate_task_pipeline.py +++ b/api/core/app/apps/workflow/generate_task_pipeline.py @@ -1,7 +1,10 @@ import logging +import time from collections.abc import Generator from typing import Any, Optional, Union +from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME +from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager from core.app.entities.app_invoke_entities import ( InvokeFrom, @@ -25,6 +28,8 @@ from core.app.entities.queue_entities import ( ) from core.app.entities.task_entities import ( ErrorStreamResponse, + MessageAudioEndStreamResponse, + MessageAudioStreamResponse, StreamResponse, TextChunkStreamResponse, TextReplaceStreamResponse, @@ -105,7 +110,7 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa db.session.refresh(self._user) db.session.close() - generator = self._process_stream_response( + generator = self._wrapper_process_stream_response( trace_manager=self._application_generate_entity.trace_manager ) if self._stream: @@ -161,8 +166,58 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa stream_response=stream_response ) + def _listenAudioMsg(self, publisher, task_id: str): + if not publisher: + return None + audio_msg: AudioTrunk = publisher.checkAndGetAudio() + if audio_msg and audio_msg.status != "finish": + return MessageAudioStreamResponse(audio=audio_msg.audio, task_id=task_id) + return None + + def _wrapper_process_stream_response(self, trace_manager: Optional[TraceQueueManager] = None) -> \ + Generator[StreamResponse, None, None]: + + publisher = None + task_id = self._application_generate_entity.task_id + tenant_id = self._application_generate_entity.app_config.tenant_id + features_dict = self._workflow.features_dict + + if features_dict.get('text_to_speech') and features_dict['text_to_speech'].get('enabled') and features_dict[ + 'text_to_speech'].get('autoPlay') == 'enabled': + publisher = AppGeneratorTTSPublisher(tenant_id, features_dict['text_to_speech'].get('voice')) + for response in self._process_stream_response(publisher=publisher, trace_manager=trace_manager): + while True: + audio_response = self._listenAudioMsg(publisher, task_id=task_id) + if audio_response: + yield audio_response + else: + break + yield response + + start_listener_time = time.time() + while (time.time() - start_listener_time) < TTS_AUTO_PLAY_TIMEOUT: + try: + if not publisher: + break + audio_trunk = publisher.checkAndGetAudio() + if audio_trunk is None: + # release cpu + # sleep 20 ms ( 40ms => 1280 byte audio file,20ms => 640 byte audio file) + time.sleep(TTS_AUTO_PLAY_YIELD_CPU_TIME) + continue + if audio_trunk.status == "finish": + break + else: + yield MessageAudioStreamResponse(audio=audio_trunk.audio, task_id=task_id) + except Exception as e: + logger.error(e) + break + yield MessageAudioEndStreamResponse(audio='', task_id=task_id) + + def _process_stream_response( self, + publisher: AppGeneratorTTSPublisher, trace_manager: Optional[TraceQueueManager] = None ) -> Generator[StreamResponse, None, None]: """ @@ -170,6 +225,8 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa :return: """ for message in self._queue_manager.listen(): + if publisher: + publisher.publish(message=message) event = message.event if isinstance(event, QueueErrorEvent): @@ -251,6 +308,10 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa else: continue + if publisher: + publisher.publish(None) + + def _save_workflow_app_log(self, workflow_run: WorkflowRun) -> None: """ Save workflow app log. diff --git a/api/core/app/entities/task_entities.py b/api/core/app/entities/task_entities.py index af93399a24..7bc5598984 100644 --- a/api/core/app/entities/task_entities.py +++ b/api/core/app/entities/task_entities.py @@ -69,6 +69,7 @@ class WorkflowTaskState(TaskState): iteration_nested_node_ids: list[str] = None + class AdvancedChatTaskState(WorkflowTaskState): """ AdvancedChatTaskState entity @@ -86,6 +87,8 @@ class StreamEvent(Enum): ERROR = "error" MESSAGE = "message" MESSAGE_END = "message_end" + TTS_MESSAGE = "tts_message" + TTS_MESSAGE_END = "tts_message_end" MESSAGE_FILE = "message_file" MESSAGE_REPLACE = "message_replace" AGENT_THOUGHT = "agent_thought" @@ -130,6 +133,22 @@ class MessageStreamResponse(StreamResponse): answer: str +class MessageAudioStreamResponse(StreamResponse): + """ + MessageStreamResponse entity + """ + event: StreamEvent = StreamEvent.TTS_MESSAGE + audio: str + + +class MessageAudioEndStreamResponse(StreamResponse): + """ + MessageStreamResponse entity + """ + event: StreamEvent = StreamEvent.TTS_MESSAGE_END + audio: str + + class MessageEndStreamResponse(StreamResponse): """ MessageEndStreamResponse entity @@ -186,6 +205,7 @@ class WorkflowStartStreamResponse(StreamResponse): """ WorkflowStartStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -205,6 +225,7 @@ class WorkflowFinishStreamResponse(StreamResponse): """ WorkflowFinishStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -232,6 +253,7 @@ class NodeStartStreamResponse(StreamResponse): """ NodeStartStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -273,6 +295,7 @@ class NodeFinishStreamResponse(StreamResponse): """ NodeFinishStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -323,10 +346,12 @@ class NodeFinishStreamResponse(StreamResponse): } } + class IterationNodeStartStreamResponse(StreamResponse): """ NodeStartStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -344,10 +369,12 @@ class IterationNodeStartStreamResponse(StreamResponse): workflow_run_id: str data: Data + class IterationNodeNextStreamResponse(StreamResponse): """ NodeStartStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -365,10 +392,12 @@ class IterationNodeNextStreamResponse(StreamResponse): workflow_run_id: str data: Data + class IterationNodeCompletedStreamResponse(StreamResponse): """ NodeCompletedStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -393,10 +422,12 @@ class IterationNodeCompletedStreamResponse(StreamResponse): workflow_run_id: str data: Data + class TextChunkStreamResponse(StreamResponse): """ TextChunkStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -411,6 +442,7 @@ class TextReplaceStreamResponse(StreamResponse): """ TextReplaceStreamResponse entity """ + class Data(BaseModel): """ Data entity @@ -473,6 +505,7 @@ class ChatbotAppBlockingResponse(AppBlockingResponse): """ ChatbotAppBlockingResponse entity """ + class Data(BaseModel): """ Data entity @@ -492,6 +525,7 @@ class CompletionAppBlockingResponse(AppBlockingResponse): """ CompletionAppBlockingResponse entity """ + class Data(BaseModel): """ Data entity @@ -510,6 +544,7 @@ class WorkflowAppBlockingResponse(AppBlockingResponse): """ WorkflowAppBlockingResponse entity """ + class Data(BaseModel): """ Data entity @@ -528,10 +563,12 @@ class WorkflowAppBlockingResponse(AppBlockingResponse): workflow_run_id: str data: Data + class WorkflowIterationState(BaseModel): """ WorkflowIterationState entity """ + class Data(BaseModel): """ Data entity diff --git a/api/core/app/features/rate_limiting/__init__.py b/api/core/app/features/rate_limiting/__init__.py new file mode 100644 index 0000000000..6624f6ad9d --- /dev/null +++ b/api/core/app/features/rate_limiting/__init__.py @@ -0,0 +1 @@ +from .rate_limit import RateLimit diff --git a/api/core/app/features/rate_limiting/rate_limit.py b/api/core/app/features/rate_limiting/rate_limit.py new file mode 100644 index 0000000000..f11e8021f0 --- /dev/null +++ b/api/core/app/features/rate_limiting/rate_limit.py @@ -0,0 +1,120 @@ +import logging +import time +import uuid +from collections.abc import Generator +from datetime import timedelta +from typing import Optional, Union + +from core.errors.error import AppInvokeQuotaExceededError +from extensions.ext_redis import redis_client + +logger = logging.getLogger(__name__) + + +class RateLimit: + _MAX_ACTIVE_REQUESTS_KEY = "dify:rate_limit:{}:max_active_requests" + _ACTIVE_REQUESTS_KEY = "dify:rate_limit:{}:active_requests" + _UNLIMITED_REQUEST_ID = "unlimited_request_id" + _REQUEST_MAX_ALIVE_TIME = 10 * 60 # 10 minutes + _ACTIVE_REQUESTS_COUNT_FLUSH_INTERVAL = 5 * 60 # recalculate request_count from request_detail every 5 minutes + _instance_dict = {} + + def __new__(cls: type['RateLimit'], client_id: str, max_active_requests: int): + if client_id not in cls._instance_dict: + instance = super().__new__(cls) + cls._instance_dict[client_id] = instance + return cls._instance_dict[client_id] + + def __init__(self, client_id: str, max_active_requests: int): + self.max_active_requests = max_active_requests + if hasattr(self, 'initialized'): + return + self.initialized = True + self.client_id = client_id + self.active_requests_key = self._ACTIVE_REQUESTS_KEY.format(client_id) + self.max_active_requests_key = self._MAX_ACTIVE_REQUESTS_KEY.format(client_id) + self.last_recalculate_time = float('-inf') + self.flush_cache(use_local_value=True) + + def flush_cache(self, use_local_value=False): + self.last_recalculate_time = time.time() + # flush max active requests + if use_local_value or not redis_client.exists(self.max_active_requests_key): + with redis_client.pipeline() as pipe: + pipe.set(self.max_active_requests_key, self.max_active_requests) + pipe.expire(self.max_active_requests_key, timedelta(days=1)) + pipe.execute() + else: + with redis_client.pipeline() as pipe: + self.max_active_requests = int(redis_client.get(self.max_active_requests_key).decode('utf-8')) + redis_client.expire(self.max_active_requests_key, timedelta(days=1)) + + # flush max active requests (in-transit request list) + if not redis_client.exists(self.active_requests_key): + return + request_details = redis_client.hgetall(self.active_requests_key) + redis_client.expire(self.active_requests_key, timedelta(days=1)) + timeout_requests = [k for k, v in request_details.items() if + time.time() - float(v.decode('utf-8')) > RateLimit._REQUEST_MAX_ALIVE_TIME] + if timeout_requests: + redis_client.hdel(self.active_requests_key, *timeout_requests) + + def enter(self, request_id: Optional[str] = None) -> str: + if time.time() - self.last_recalculate_time > RateLimit._ACTIVE_REQUESTS_COUNT_FLUSH_INTERVAL: + self.flush_cache() + if self.max_active_requests <= 0: + return RateLimit._UNLIMITED_REQUEST_ID + if not request_id: + request_id = RateLimit.gen_request_key() + + active_requests_count = redis_client.hlen(self.active_requests_key) + if active_requests_count >= self.max_active_requests: + raise AppInvokeQuotaExceededError("Too many requests. Please try again later. The current maximum " + "concurrent requests allowed is {}.".format(self.max_active_requests)) + redis_client.hset(self.active_requests_key, request_id, str(time.time())) + return request_id + + def exit(self, request_id: str): + if request_id == RateLimit._UNLIMITED_REQUEST_ID: + return + redis_client.hdel(self.active_requests_key, request_id) + + @staticmethod + def gen_request_key() -> str: + return str(uuid.uuid4()) + + def generate(self, generator: Union[Generator, callable, dict], request_id: str): + if isinstance(generator, dict): + return generator + else: + return RateLimitGenerator(self, generator, request_id) + + +class RateLimitGenerator: + def __init__(self, rate_limit: RateLimit, generator: Union[Generator, callable], request_id: str): + self.rate_limit = rate_limit + if callable(generator): + self.generator = generator() + else: + self.generator = generator + self.request_id = request_id + self.closed = False + + def __iter__(self): + return self + + def __next__(self): + if self.closed: + raise StopIteration + try: + return next(self.generator) + except StopIteration: + self.close() + raise + + def close(self): + if not self.closed: + self.closed = True + self.rate_limit.exit(self.request_id) + if self.generator is not None and hasattr(self.generator, 'close'): + self.generator.close() diff --git a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py index 7d16d015bf..c9644c7d4c 100644 --- a/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py +++ b/api/core/app/task_pipeline/easy_ui_based_generate_task_pipeline.py @@ -4,6 +4,8 @@ import time from collections.abc import Generator from typing import Optional, Union, cast +from constants.tts_auto_play_timeout import TTS_AUTO_PLAY_TIMEOUT, TTS_AUTO_PLAY_YIELD_CPU_TIME +from core.app.apps.advanced_chat.app_generator_tts_publisher import AppGeneratorTTSPublisher, AudioTrunk from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom from core.app.entities.app_invoke_entities import ( AgentChatAppGenerateEntity, @@ -32,6 +34,8 @@ from core.app.entities.task_entities import ( CompletionAppStreamResponse, EasyUITaskState, ErrorStreamResponse, + MessageAudioEndStreamResponse, + MessageAudioStreamResponse, MessageEndStreamResponse, StreamResponse, ) @@ -87,6 +91,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan """ super().__init__(application_generate_entity, queue_manager, user, stream) self._model_config = application_generate_entity.model_conf + self._app_config = application_generate_entity.app_config self._conversation = conversation self._message = message @@ -102,7 +107,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan self._conversation_name_generate_thread = None def process( - self, + self, ) -> Union[ ChatbotAppBlockingResponse, CompletionAppBlockingResponse, @@ -123,7 +128,7 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan self._application_generate_entity.query ) - generator = self._process_stream_response( + generator = self._wrapper_process_stream_response( trace_manager=self._application_generate_entity.trace_manager ) if self._stream: @@ -202,14 +207,64 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan stream_response=stream_response ) + def _listenAudioMsg(self, publisher, task_id: str): + if publisher is None: + return None + audio_msg: AudioTrunk = publisher.checkAndGetAudio() + if audio_msg and audio_msg.status != "finish": + # audio_str = audio_msg.audio.decode('utf-8', errors='ignore') + return MessageAudioStreamResponse(audio=audio_msg.audio, task_id=task_id) + return None + + def _wrapper_process_stream_response(self, trace_manager: Optional[TraceQueueManager] = None) -> \ + Generator[StreamResponse, None, None]: + + tenant_id = self._application_generate_entity.app_config.tenant_id + task_id = self._application_generate_entity.task_id + publisher = None + text_to_speech_dict = self._app_config.app_model_config_dict.get('text_to_speech') + if text_to_speech_dict and text_to_speech_dict.get('autoPlay') == 'enabled' and text_to_speech_dict.get('enabled'): + publisher = AppGeneratorTTSPublisher(tenant_id, text_to_speech_dict.get('voice', None)) + for response in self._process_stream_response(publisher=publisher, trace_manager=trace_manager): + while True: + audio_response = self._listenAudioMsg(publisher, task_id) + if audio_response: + yield audio_response + else: + break + yield response + + start_listener_time = time.time() + # timeout + while (time.time() - start_listener_time) < TTS_AUTO_PLAY_TIMEOUT: + if publisher is None: + break + audio = publisher.checkAndGetAudio() + if audio is None: + # release cpu + # sleep 20 ms ( 40ms => 1280 byte audio file,20ms => 640 byte audio file) + time.sleep(TTS_AUTO_PLAY_YIELD_CPU_TIME) + continue + if audio.status == "finish": + break + else: + start_listener_time = time.time() + yield MessageAudioStreamResponse(audio=audio.audio, + task_id=task_id) + yield MessageAudioEndStreamResponse(audio='', task_id=task_id) + def _process_stream_response( - self, trace_manager: Optional[TraceQueueManager] = None + self, + publisher: AppGeneratorTTSPublisher, + trace_manager: Optional[TraceQueueManager] = None ) -> Generator[StreamResponse, None, None]: """ Process stream response. :return: """ for message in self._queue_manager.listen(): + if publisher: + publisher.publish(message) event = message.event if isinstance(event, QueueErrorEvent): @@ -272,12 +327,13 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan yield self._ping_stream_response() else: continue - + if publisher: + publisher.publish(None) if self._conversation_name_generate_thread: self._conversation_name_generate_thread.join() def _save_message( - self, trace_manager: Optional[TraceQueueManager] = None + self, trace_manager: Optional[TraceQueueManager] = None ) -> None: """ Save message. diff --git a/api/core/errors/error.py b/api/core/errors/error.py index fddfb345fd..859a747c12 100644 --- a/api/core/errors/error.py +++ b/api/core/errors/error.py @@ -31,6 +31,13 @@ class QuotaExceededError(Exception): description = "Quota Exceeded" +class AppInvokeQuotaExceededError(Exception): + """ + Custom exception raised when the quota for an app has been exceeded. + """ + description = "App Invoke Quota Exceeded" + + class ModelCurrentlyNotSupportError(Exception): """ Custom exception raised when the model not support diff --git a/api/core/indexing_runner.py b/api/core/indexing_runner.py index 826edff608..83dbacbfcc 100644 --- a/api/core/indexing_runner.py +++ b/api/core/indexing_runner.py @@ -730,7 +730,7 @@ class IndexingRunner: self._check_document_paused_status(dataset_document.id) tokens = 0 - if dataset.indexing_technique == 'high_quality' or embedding_model_type_instance: + if embedding_model_instance: tokens += sum( embedding_model_instance.get_text_embedding_num_tokens( [document.page_content] diff --git a/api/core/model_manager.py b/api/core/model_manager.py index e0b6960c23..d64db890f9 100644 --- a/api/core/model_manager.py +++ b/api/core/model_manager.py @@ -264,7 +264,7 @@ class ModelInstance: user=user ) - def invoke_tts(self, content_text: str, tenant_id: str, voice: str, streaming: bool, user: Optional[str] = None) \ + def invoke_tts(self, content_text: str, tenant_id: str, voice: str, user: Optional[str] = None) \ -> str: """ Invoke large language tts model @@ -287,8 +287,7 @@ class ModelInstance: content_text=content_text, user=user, tenant_id=tenant_id, - voice=voice, - streaming=streaming + voice=voice ) def _round_robin_invoke(self, function: Callable, *args, **kwargs): diff --git a/api/core/model_runtime/model_providers/__base/tts_model.py b/api/core/model_runtime/model_providers/__base/tts_model.py index bc6a475f36..086a189246 100644 --- a/api/core/model_runtime/model_providers/__base/tts_model.py +++ b/api/core/model_runtime/model_providers/__base/tts_model.py @@ -1,4 +1,6 @@ import hashlib +import logging +import re import subprocess import uuid from abc import abstractmethod @@ -10,7 +12,7 @@ from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelTy from core.model_runtime.errors.invoke import InvokeBadRequestError from core.model_runtime.model_providers.__base.ai_model import AIModel - +logger = logging.getLogger(__name__) class TTSModel(AIModel): """ Model class for ttstext model. @@ -20,7 +22,7 @@ class TTSModel(AIModel): # pydantic configs model_config = ConfigDict(protected_namespaces=()) - def invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, streaming: bool, + def invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None): """ Invoke large language model @@ -35,14 +37,15 @@ class TTSModel(AIModel): :return: translated audio file """ try: + logger.info(f"Invoke TTS model: {model} , invoke content : {content_text}") self._is_ffmpeg_installed() - return self._invoke(model=model, credentials=credentials, user=user, streaming=streaming, + return self._invoke(model=model, credentials=credentials, user=user, content_text=content_text, voice=voice, tenant_id=tenant_id) except Exception as e: raise self._transform_invoke_error(e) @abstractmethod - def _invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, streaming: bool, + def _invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None): """ Invoke large language model @@ -123,26 +126,26 @@ class TTSModel(AIModel): return model_schema.model_properties[ModelPropertyKey.MAX_WORKERS] @staticmethod - def _split_text_into_sentences(text: str, limit: int, delimiters=None): - if delimiters is None: - delimiters = set('。!?;\n') - - buf = [] - word_count = 0 - for char in text: - buf.append(char) - if char in delimiters: - if word_count >= limit: - yield ''.join(buf) - buf = [] - word_count = 0 - else: - word_count += 1 - else: - word_count += 1 - - if buf: - yield ''.join(buf) + def _split_text_into_sentences(org_text, max_length=2000, pattern=r'[。.!?]'): + match = re.compile(pattern) + tx = match.finditer(org_text) + start = 0 + result = [] + one_sentence = '' + for i in tx: + end = i.regs[0][1] + tmp = org_text[start:end] + if len(one_sentence + tmp) > max_length: + result.append(one_sentence) + one_sentence = '' + one_sentence += tmp + start = end + last_sens = org_text[start:] + if last_sens: + one_sentence += last_sens + if one_sentence != '': + result.append(one_sentence) + return result @staticmethod def _is_ffmpeg_installed(): diff --git a/api/core/model_runtime/model_providers/_position.yaml b/api/core/model_runtime/model_providers/_position.yaml index da654d2174..cf4ac10828 100644 --- a/api/core/model_runtime/model_providers/_position.yaml +++ b/api/core/model_runtime/model_providers/_position.yaml @@ -33,3 +33,4 @@ - deepseek - hunyuan - siliconflow +- perfxcloud diff --git a/api/core/model_runtime/model_providers/azure_openai/tts/tts.py b/api/core/model_runtime/model_providers/azure_openai/tts/tts.py index dcd154cff0..50c125b873 100644 --- a/api/core/model_runtime/model_providers/azure_openai/tts/tts.py +++ b/api/core/model_runtime/model_providers/azure_openai/tts/tts.py @@ -4,7 +4,7 @@ from functools import reduce from io import BytesIO from typing import Optional -from flask import Response, stream_with_context +from flask import Response from openai import AzureOpenAI from pydub import AudioSegment @@ -14,7 +14,6 @@ from core.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.model_providers.__base.tts_model import TTSModel from core.model_runtime.model_providers.azure_openai._common import _CommonAzureOpenAI from core.model_runtime.model_providers.azure_openai._constant import TTS_BASE_MODELS, AzureBaseModel -from extensions.ext_storage import storage class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): @@ -23,7 +22,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): """ def _invoke(self, model: str, tenant_id: str, credentials: dict, - content_text: str, voice: str, streaming: bool, user: Optional[str] = None) -> any: + content_text: str, voice: str, user: Optional[str] = None) -> any: """ _invoke text2speech model @@ -32,30 +31,23 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): :param credentials: model credentials :param content_text: text content to be translated :param voice: model timbre - :param streaming: output is streaming :param user: unique user id :return: text translated to audio file """ - audio_type = self._get_model_audio_type(model, credentials) if not voice or voice not in [d['value'] for d in self.get_tts_model_voices(model=model, credentials=credentials)]: voice = self._get_model_default_voice(model, credentials) - if streaming: - return Response(stream_with_context(self._tts_invoke_streaming(model=model, - credentials=credentials, - content_text=content_text, - tenant_id=tenant_id, - voice=voice)), - status=200, mimetype=f'audio/{audio_type}') - else: - return self._tts_invoke(model=model, credentials=credentials, content_text=content_text, voice=voice) - def validate_credentials(self, model: str, credentials: dict, user: Optional[str] = None) -> None: + return self._tts_invoke_streaming(model=model, + credentials=credentials, + content_text=content_text, + voice=voice) + + def validate_credentials(self, model: str, credentials: dict) -> None: """ validate credentials text2speech model :param model: model name :param credentials: model credentials - :param user: unique user id :return: text translated to audio file """ try: @@ -82,7 +74,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): word_limit = self._get_model_word_limit(model, credentials) max_workers = self._get_model_workers_limit(model, credentials) try: - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) + sentences = list(self._split_text_into_sentences(org_text=content_text, max_length=word_limit)) audio_bytes_list = [] # Create a thread pool and map the function to the list of sentences @@ -107,34 +99,37 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): except Exception as ex: raise InvokeBadRequestError(str(ex)) - # Todo: To improve the streaming function - def _tts_invoke_streaming(self, model: str, tenant_id: str, credentials: dict, content_text: str, + def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> any: """ _tts_invoke_streaming text2speech model - :param model: model name - :param tenant_id: user tenant id :param credentials: model credentials :param content_text: text content to be translated :param voice: model timbre :return: text translated to audio file """ - # transform credentials to kwargs for model instance - credentials_kwargs = self._to_credential_kwargs(credentials) - if not voice or voice not in self.get_tts_model_voices(model=model, credentials=credentials): - voice = self._get_model_default_voice(model, credentials) - word_limit = self._get_model_word_limit(model, credentials) - audio_type = self._get_model_audio_type(model, credentials) - tts_file_id = self._get_file_name(content_text) - file_path = f'generate_files/audio/{tenant_id}/{tts_file_id}.{audio_type}' try: + # doc: https://platform.openai.com/docs/guides/text-to-speech + credentials_kwargs = self._to_credential_kwargs(credentials) client = AzureOpenAI(**credentials_kwargs) - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) - for sentence in sentences: - response = client.audio.speech.create(model=model, voice=voice, input=sentence.strip()) - # response.stream_to_file(file_path) - storage.save(file_path, response.read()) + # max font is 4096,there is 3500 limit for each request + max_length = 3500 + if len(content_text) > max_length: + sentences = self._split_text_into_sentences(content_text, max_length=max_length) + executor = concurrent.futures.ThreadPoolExecutor(max_workers=min(3, len(sentences))) + futures = [executor.submit(client.audio.speech.with_streaming_response.create, model=model, + response_format="mp3", + input=sentences[i], voice=voice) for i in range(len(sentences))] + for index, future in enumerate(futures): + yield from future.result().__enter__().iter_bytes(1024) + + else: + response = client.audio.speech.with_streaming_response.create(model=model, voice=voice, + response_format="mp3", + input=content_text.strip()) + + yield from response.__enter__().iter_bytes(1024) except Exception as ex: raise InvokeBadRequestError(str(ex)) @@ -162,7 +157,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): @staticmethod - def _get_ai_model_entity(base_model_name: str, model: str) -> AzureBaseModel: + def _get_ai_model_entity(base_model_name: str, model: str) -> AzureBaseModel | None: for ai_model_entity in TTS_BASE_MODELS: if ai_model_entity.base_model_name == base_model_name: ai_model_entity_copy = copy.deepcopy(ai_model_entity) @@ -170,5 +165,4 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel): ai_model_entity_copy.entity.label.en_US = model ai_model_entity_copy.entity.label.zh_Hans = model return ai_model_entity_copy - return None diff --git a/api/core/model_runtime/model_providers/deepseek/llm/deepseek-chat.yaml b/api/core/model_runtime/model_providers/deepseek/llm/deepseek-chat.yaml index 80607ca9e5..d50529926b 100644 --- a/api/core/model_runtime/model_providers/deepseek/llm/deepseek-chat.yaml +++ b/api/core/model_runtime/model_providers/deepseek/llm/deepseek-chat.yaml @@ -7,7 +7,7 @@ features: - agent-thought model_properties: mode: chat - context_size: 32000 + context_size: 128000 parameter_rules: - name: temperature use_template: temperature diff --git a/api/core/model_runtime/model_providers/deepseek/llm/deepseek-coder.yaml b/api/core/model_runtime/model_providers/deepseek/llm/deepseek-coder.yaml index f6ce775d76..4da75b9aa3 100644 --- a/api/core/model_runtime/model_providers/deepseek/llm/deepseek-coder.yaml +++ b/api/core/model_runtime/model_providers/deepseek/llm/deepseek-coder.yaml @@ -7,7 +7,7 @@ features: - agent-thought model_properties: mode: chat - context_size: 32000 + context_size: 128000 parameter_rules: - name: temperature use_template: temperature diff --git a/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml b/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml index 72f15134ea..449c131f9d 100644 --- a/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml +++ b/api/core/model_runtime/model_providers/openai/tts/tts-1-hd.yaml @@ -21,7 +21,7 @@ model_properties: - mode: 'shimmer' name: 'Shimmer' language: [ 'zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID' ] - word_limit: 120 + word_limit: 3500 audio_type: 'mp3' max_workers: 5 pricing: diff --git a/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml b/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml index 8d222fed64..83969fb2f7 100644 --- a/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml +++ b/api/core/model_runtime/model_providers/openai/tts/tts-1.yaml @@ -21,7 +21,7 @@ model_properties: - mode: 'shimmer' name: 'Shimmer' language: ['zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID'] - word_limit: 120 + word_limit: 3500 audio_type: 'mp3' max_workers: 5 pricing: diff --git a/api/core/model_runtime/model_providers/openai/tts/tts.py b/api/core/model_runtime/model_providers/openai/tts/tts.py index f83c57078a..608ed897e0 100644 --- a/api/core/model_runtime/model_providers/openai/tts/tts.py +++ b/api/core/model_runtime/model_providers/openai/tts/tts.py @@ -3,7 +3,7 @@ from functools import reduce from io import BytesIO from typing import Optional -from flask import Response, stream_with_context +from flask import Response from openai import OpenAI from pydub import AudioSegment @@ -11,7 +11,6 @@ from core.model_runtime.errors.invoke import InvokeBadRequestError from core.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.model_providers.__base.tts_model import TTSModel from core.model_runtime.model_providers.openai._common import _CommonOpenAI -from extensions.ext_storage import storage class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): @@ -20,7 +19,7 @@ class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): """ def _invoke(self, model: str, tenant_id: str, credentials: dict, - content_text: str, voice: str, streaming: bool, user: Optional[str] = None) -> any: + content_text: str, voice: str, user: Optional[str] = None) -> any: """ _invoke text2speech model @@ -29,22 +28,17 @@ class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): :param credentials: model credentials :param content_text: text content to be translated :param voice: model timbre - :param streaming: output is streaming :param user: unique user id :return: text translated to audio file """ - audio_type = self._get_model_audio_type(model, credentials) + if not voice or voice not in [d['value'] for d in self.get_tts_model_voices(model=model, credentials=credentials)]: voice = self._get_model_default_voice(model, credentials) - if streaming: - return Response(stream_with_context(self._tts_invoke_streaming(model=model, - credentials=credentials, - content_text=content_text, - tenant_id=tenant_id, - voice=voice)), - status=200, mimetype=f'audio/{audio_type}') - else: - return self._tts_invoke(model=model, credentials=credentials, content_text=content_text, voice=voice) + # if streaming: + return self._tts_invoke_streaming(model=model, + credentials=credentials, + content_text=content_text, + voice=voice) def validate_credentials(self, model: str, credentials: dict, user: Optional[str] = None) -> None: """ @@ -79,7 +73,7 @@ class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): word_limit = self._get_model_word_limit(model, credentials) max_workers = self._get_model_workers_limit(model, credentials) try: - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) + sentences = list(self._split_text_into_sentences(org_text=content_text, max_length=word_limit)) audio_bytes_list = [] # Create a thread pool and map the function to the list of sentences @@ -104,34 +98,40 @@ class OpenAIText2SpeechModel(_CommonOpenAI, TTSModel): except Exception as ex: raise InvokeBadRequestError(str(ex)) - # Todo: To improve the streaming function - def _tts_invoke_streaming(self, model: str, tenant_id: str, credentials: dict, content_text: str, + + def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> any: """ _tts_invoke_streaming text2speech model :param model: model name - :param tenant_id: user tenant id :param credentials: model credentials :param content_text: text content to be translated :param voice: model timbre :return: text translated to audio file """ - # transform credentials to kwargs for model instance - credentials_kwargs = self._to_credential_kwargs(credentials) - if not voice or voice not in self.get_tts_model_voices(model=model, credentials=credentials): - voice = self._get_model_default_voice(model, credentials) - word_limit = self._get_model_word_limit(model, credentials) - audio_type = self._get_model_audio_type(model, credentials) - tts_file_id = self._get_file_name(content_text) - file_path = f'generate_files/audio/{tenant_id}/{tts_file_id}.{audio_type}' try: + # doc: https://platform.openai.com/docs/guides/text-to-speech + credentials_kwargs = self._to_credential_kwargs(credentials) client = OpenAI(**credentials_kwargs) - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) - for sentence in sentences: - response = client.audio.speech.create(model=model, voice=voice, input=sentence.strip()) - # response.stream_to_file(file_path) - storage.save(file_path, response.read()) + if not voice or voice not in self.get_tts_model_voices(model=model, credentials=credentials): + voice = self._get_model_default_voice(model, credentials) + word_limit = self._get_model_word_limit(model, credentials) + if len(content_text) > word_limit: + sentences = self._split_text_into_sentences(content_text, max_length=word_limit) + executor = concurrent.futures.ThreadPoolExecutor(max_workers=min(3, len(sentences))) + futures = [executor.submit(client.audio.speech.with_streaming_response.create, model=model, + response_format="mp3", + input=sentences[i], voice=voice) for i in range(len(sentences))] + for index, future in enumerate(futures): + yield from future.result().__enter__().iter_bytes(1024) + + else: + response = client.audio.speech.with_streaming_response.create(model=model, voice=voice, + response_format="mp3", + input=content_text.strip()) + + yield from response.__enter__().iter_bytes(1024) except Exception as ex: raise InvokeBadRequestError(str(ex)) diff --git a/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py b/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py index b76f460737..e5cc884b6d 100644 --- a/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py +++ b/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py @@ -616,30 +616,34 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel): message = cast(AssistantPromptMessage, message) message_dict = {"role": "assistant", "content": message.content} if message.tool_calls: - # message_dict["tool_calls"] = [helper.dump_model(PromptMessageFunction(function=tool_call)) for tool_call - # in - # message.tool_calls] - - function_call = message.tool_calls[0] - message_dict["function_call"] = { - "name": function_call.function.name, - "arguments": function_call.function.arguments, - } + function_calling_type = credentials.get('function_calling_type', 'no_call') + if function_calling_type == 'tool_call': + message_dict["tool_calls"] = [tool_call.dict() for tool_call in + message.tool_calls] + elif function_calling_type == 'function_call': + function_call = message.tool_calls[0] + message_dict["function_call"] = { + "name": function_call.function.name, + "arguments": function_call.function.arguments, + } elif isinstance(message, SystemPromptMessage): message = cast(SystemPromptMessage, message) message_dict = {"role": "system", "content": message.content} elif isinstance(message, ToolPromptMessage): message = cast(ToolPromptMessage, message) - # message_dict = { - # "role": "tool", - # "content": message.content, - # "tool_call_id": message.tool_call_id - # } - message_dict = { - "role": "tool" if credentials and credentials.get('function_calling_type', 'no_call') == 'tool_call' else "function", - "content": message.content, - "name": message.tool_call_id - } + function_calling_type = credentials.get('function_calling_type', 'no_call') + if function_calling_type == 'tool_call': + message_dict = { + "role": "tool", + "content": message.content, + "tool_call_id": message.tool_call_id + } + elif function_calling_type == 'function_call': + message_dict = { + "role": "function", + "content": message.content, + "name": message.tool_call_id + } else: raise ValueError(f"Got unknown type {message}") diff --git a/api/core/model_runtime/model_providers/perfxcloud/__init__.py b/api/core/model_runtime/model_providers/perfxcloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_l_en.svg b/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_l_en.svg new file mode 100644 index 0000000000..060d9de3a9 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_l_en.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_s_en.svg b/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_s_en.svg new file mode 100644 index 0000000000..be0c2eeb1c --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/_assets/icon_s_en.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen-14B-Chat-Int4.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen-14B-Chat-Int4.yaml new file mode 100644 index 0000000000..af6fb91cd9 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen-14B-Chat-Int4.yaml @@ -0,0 +1,61 @@ +model: Qwen-14B-Chat-Int4 +label: + en_US: Qwen-14B-Chat-Int4 +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 4096 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 600 + min: 1 + max: 1248 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-110B-Chat-GPTQ-Int4.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-110B-Chat-GPTQ-Int4.yaml new file mode 100644 index 0000000000..4ab9a80055 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-110B-Chat-GPTQ-Int4.yaml @@ -0,0 +1,61 @@ +model: Qwen1.5-110B-Chat-GPTQ-Int4 +label: + en_US: Qwen1.5-110B-Chat-GPTQ-Int4 +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 128 + min: 1 + max: 256 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-72B-Chat-GPTQ-Int4.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-72B-Chat-GPTQ-Int4.yaml new file mode 100644 index 0000000000..4a8b1cf479 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-72B-Chat-GPTQ-Int4.yaml @@ -0,0 +1,61 @@ +model: Qwen1.5-72B-Chat-GPTQ-Int4 +label: + en_US: Qwen1.5-72B-Chat-GPTQ-Int4 +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 600 + min: 1 + max: 2000 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-7B.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-7B.yaml new file mode 100644 index 0000000000..b076504493 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen1.5-7B.yaml @@ -0,0 +1,61 @@ +model: Qwen1.5-7B +label: + en_US: Qwen1.5-7B +model_type: llm +features: + - agent-thought +model_properties: + mode: completion + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 600 + min: 1 + max: 2000 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-72B-Instruct-GPTQ-Int4.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-72B-Instruct-GPTQ-Int4.yaml new file mode 100644 index 0000000000..e24a69fe63 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-72B-Instruct-GPTQ-Int4.yaml @@ -0,0 +1,63 @@ +model: Qwen2-72B-Instruct-GPTQ-Int4 +label: + en_US: Qwen2-72B-Instruct-GPTQ-Int4 +model_type: llm +features: + - multi-tool-call + - agent-thought + - stream-tool-call +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 600 + min: 1 + max: 2000 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-7B.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-7B.yaml new file mode 100644 index 0000000000..e3d804729d --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/Qwen2-7B.yaml @@ -0,0 +1,63 @@ +model: Qwen2-7B +label: + en_US: Qwen2-7B +model_type: llm +features: + - multi-tool-call + - agent-thought + - stream-tool-call +model_properties: + mode: completion + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + type: float + default: 0.3 + min: 0.0 + max: 2.0 + help: + zh_Hans: 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。 + en_US: Used to control the degree of randomness and diversity. Specifically, the temperature value controls the degree to which the probability distribution of each candidate word is smoothed when generating text. A higher temperature value will reduce the peak value of the probability distribution, allowing more low-probability words to be selected, and the generated results will be more diverse; while a lower temperature value will enhance the peak value of the probability distribution, making it easier for high-probability words to be selected. , the generated results are more certain. + - name: max_tokens + use_template: max_tokens + type: int + default: 600 + min: 1 + max: 2000 + help: + zh_Hans: 用于指定模型在生成内容时token的最大数量,它定义了生成的上限,但不保证每次都会生成到这个数量。 + en_US: It is used to specify the maximum number of tokens when the model generates content. It defines the upper limit of generation, but does not guarantee that this number will be generated every time. + - name: top_p + use_template: top_p + type: float + default: 0.8 + min: 0.1 + max: 0.9 + help: + zh_Hans: 生成过程中核采样方法概率阈值,例如,取值为0.8时,仅保留概率加起来大于等于0.8的最可能token的最小集合作为候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的确定性越高。 + en_US: The probability threshold of the kernel sampling method during the generation process. For example, when the value is 0.8, only the smallest set of the most likely tokens with a sum of probabilities greater than or equal to 0.8 is retained as the candidate set. The value range is (0,1.0). The larger the value, the higher the randomness generated; the lower the value, the higher the certainty generated. + - name: top_k + type: int + min: 0 + max: 99 + label: + zh_Hans: 取样数量 + en_US: Top k + help: + zh_Hans: 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。 + en_US: The size of the sample candidate set when generated. For example, when the value is 50, only the 50 highest-scoring tokens in a single generation form a randomly sampled candidate set. The larger the value, the higher the randomness generated; the smaller the value, the higher the certainty generated. + - name: repetition_penalty + required: false + type: float + default: 1.1 + label: + en_US: Repetition penalty + help: + zh_Hans: 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚。 + en_US: Used to control the repeatability when generating models. Increasing repetition_penalty can reduce the duplication of model generation. 1.0 means no punishment. +pricing: + input: '0.000' + output: '0.000' + unit: '0.000' + currency: RMB diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/__init__.py b/api/core/model_runtime/model_providers/perfxcloud/llm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/_position.yaml b/api/core/model_runtime/model_providers/perfxcloud/llm/_position.yaml new file mode 100644 index 0000000000..b95f6bdc1b --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/_position.yaml @@ -0,0 +1,6 @@ +- Qwen2-72B-Instruct-GPTQ-Int4 +- Qwen2-7B +- Qwen1.5-110B-Chat-GPTQ-Int4 +- Qwen1.5-72B-Chat-GPTQ-Int4 +- Qwen1.5-7B +- Qwen-14B-Chat-Int4 diff --git a/api/core/model_runtime/model_providers/perfxcloud/llm/llm.py b/api/core/model_runtime/model_providers/perfxcloud/llm/llm.py new file mode 100644 index 0000000000..c9116bf685 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/llm/llm.py @@ -0,0 +1,110 @@ +from collections.abc import Generator +from typing import Optional, Union +from urllib.parse import urlparse + +import tiktoken + +from core.model_runtime.entities.llm_entities import LLMResult +from core.model_runtime.entities.message_entities import ( + PromptMessage, + PromptMessageTool, +) +from core.model_runtime.model_providers.openai.llm.llm import OpenAILargeLanguageModel + + +class PerfXCloudLargeLanguageModel(OpenAILargeLanguageModel): + def _invoke(self, model: str, credentials: dict, + prompt_messages: list[PromptMessage], model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None, + stream: bool = True, user: Optional[str] = None) \ + -> Union[LLMResult, Generator]: + self._add_custom_parameters(credentials) + + return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream) + + def validate_credentials(self, model: str, credentials: dict) -> None: + self._add_custom_parameters(credentials) + super().validate_credentials(model, credentials) + + # refactored from openai model runtime, use cl100k_base for calculate token number + def _num_tokens_from_string(self, model: str, text: str, + tools: Optional[list[PromptMessageTool]] = None) -> int: + """ + Calculate num tokens for text completion model with tiktoken package. + + :param model: model name + :param text: prompt text + :param tools: tools for tool calling + :return: number of tokens + """ + encoding = tiktoken.get_encoding("cl100k_base") + num_tokens = len(encoding.encode(text)) + + if tools: + num_tokens += self._num_tokens_for_tools(encoding, tools) + + return num_tokens + + # refactored from openai model runtime, use cl100k_base for calculate token number + def _num_tokens_from_messages(self, model: str, messages: list[PromptMessage], + tools: Optional[list[PromptMessageTool]] = None) -> int: + """Calculate num tokens for gpt-3.5-turbo and gpt-4 with tiktoken package. + + Official documentation: https://github.com/openai/openai-cookbook/blob/ + main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb""" + encoding = tiktoken.get_encoding("cl100k_base") + tokens_per_message = 3 + tokens_per_name = 1 + + num_tokens = 0 + messages_dict = [self._convert_prompt_message_to_dict(m) for m in messages] + for message in messages_dict: + num_tokens += tokens_per_message + for key, value in message.items(): + # Cast str(value) in case the message value is not a string + # This occurs with function messages + # TODO: The current token calculation method for the image type is not implemented, + # which need to download the image and then get the resolution for calculation, + # and will increase the request delay + if isinstance(value, list): + text = '' + for item in value: + if isinstance(item, dict) and item['type'] == 'text': + text += item['text'] + + value = text + + if key == "tool_calls": + for tool_call in value: + for t_key, t_value in tool_call.items(): + num_tokens += len(encoding.encode(t_key)) + if t_key == "function": + for f_key, f_value in t_value.items(): + num_tokens += len(encoding.encode(f_key)) + num_tokens += len(encoding.encode(f_value)) + else: + num_tokens += len(encoding.encode(t_key)) + num_tokens += len(encoding.encode(t_value)) + else: + num_tokens += len(encoding.encode(str(value))) + + if key == "name": + num_tokens += tokens_per_name + + # every reply is primed with assistant + num_tokens += 3 + + if tools: + num_tokens += self._num_tokens_for_tools(encoding, tools) + + return num_tokens + + @staticmethod + def _add_custom_parameters(credentials: dict) -> None: + credentials['mode'] = 'chat' + credentials['openai_api_key']=credentials['api_key'] + if 'endpoint_url' not in credentials or credentials['endpoint_url'] == "": + credentials['openai_api_base']='https://cloud.perfxlab.cn' + else: + parsed_url = urlparse(credentials['endpoint_url']) + credentials['openai_api_base']=f"{parsed_url.scheme}://{parsed_url.netloc}" diff --git a/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.py b/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.py new file mode 100644 index 0000000000..0854ef5185 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.py @@ -0,0 +1,32 @@ +import logging + +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.model_provider import ModelProvider + +logger = logging.getLogger(__name__) + + +class PerfXCloudProvider(ModelProvider): + + def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ + try: + model_instance = self.get_model_instance(ModelType.LLM) + + # Use `Qwen2_72B_Chat_GPTQ_Int4` model for validate, + # no matter what model you pass in, text completion model or chat model + model_instance.validate_credentials( + model='Qwen2-72B-Instruct-GPTQ-Int4', + credentials=credentials + ) + except CredentialsValidateFailedError as ex: + raise ex + except Exception as ex: + logger.exception(f'{self.get_provider_schema().provider} credentials validate failed') + raise ex diff --git a/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.yaml b/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.yaml new file mode 100644 index 0000000000..10ee691ebd --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/perfxcloud.yaml @@ -0,0 +1,42 @@ +provider: perfxcloud +label: + en_US: PerfXCloud + zh_Hans: PerfXCloud +description: + en_US: PerfXCloud (Pengfeng Technology) is an AI development and deployment platform tailored for developers and enterprises, providing reasoning capabilities for multiple models. + zh_Hans: PerfXCloud(澎峰科技)为开发者和企业量身打造的AI开发和部署平台,提供多种模型的的推理能力。 +icon_small: + en_US: icon_s_en.svg +icon_large: + en_US: icon_l_en.svg +background: "#e3f0ff" +help: + title: + en_US: Get your API Key from PerfXCloud + zh_Hans: 从 PerfXCloud 获取 API Key + url: + en_US: https://cloud.perfxlab.cn/panel/token +supported_model_types: + - llm + - text-embedding +configurate_methods: + - predefined-model +provider_credential_schema: + credential_form_schemas: + - variable: api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的 API Key + en_US: Enter your API Key + - variable: endpoint_url + label: + zh_Hans: 自定义 API endpoint 地址 + en_US: Custom API endpoint URL + type: text-input + required: false + placeholder: + zh_Hans: Base URL, e.g. https://cloud.perfxlab.cn/v1 + en_US: Base URL, e.g. https://cloud.perfxlab.cn/v1 diff --git a/api/core/model_runtime/model_providers/perfxcloud/text_embedding/BAAI-bge-m3.yaml b/api/core/model_runtime/model_providers/perfxcloud/text_embedding/BAAI-bge-m3.yaml new file mode 100644 index 0000000000..55488e5688 --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/text_embedding/BAAI-bge-m3.yaml @@ -0,0 +1,4 @@ +model: BAAI/bge-m3 +model_type: text-embedding +model_properties: + context_size: 32768 diff --git a/api/core/model_runtime/model_providers/perfxcloud/text_embedding/__init__.py b/api/core/model_runtime/model_providers/perfxcloud/text_embedding/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/perfxcloud/text_embedding/text_embedding.py b/api/core/model_runtime/model_providers/perfxcloud/text_embedding/text_embedding.py new file mode 100644 index 0000000000..5a99ad301f --- /dev/null +++ b/api/core/model_runtime/model_providers/perfxcloud/text_embedding/text_embedding.py @@ -0,0 +1,250 @@ +import json +import time +from decimal import Decimal +from typing import Optional +from urllib.parse import urljoin + +import numpy as np +import requests + +from core.model_runtime.entities.common_entities import I18nObject +from core.model_runtime.entities.model_entities import ( + AIModelEntity, + FetchFrom, + ModelPropertyKey, + ModelType, + PriceConfig, + PriceType, +) +from core.model_runtime.entities.text_embedding_entities import EmbeddingUsage, TextEmbeddingResult +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel +from core.model_runtime.model_providers.openai_api_compatible._common import _CommonOAI_API_Compat + + +class OAICompatEmbeddingModel(_CommonOAI_API_Compat, TextEmbeddingModel): + """ + Model class for an OpenAI API-compatible text embedding model. + """ + + def _invoke(self, model: str, credentials: dict, + texts: list[str], user: Optional[str] = None) \ + -> TextEmbeddingResult: + """ + Invoke text embedding model + + :param model: model name + :param credentials: model credentials + :param texts: texts to embed + :param user: unique user id + :return: embeddings result + """ + + # Prepare headers and payload for the request + headers = { + 'Content-Type': 'application/json' + } + + api_key = credentials.get('api_key') + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + if 'endpoint_url' not in credentials or credentials['endpoint_url'] == "": + endpoint_url='https://cloud.perfxlab.cn/v1/' + else: + endpoint_url = credentials.get('endpoint_url') + if not endpoint_url.endswith('/'): + endpoint_url += '/' + + endpoint_url = urljoin(endpoint_url, 'embeddings') + + extra_model_kwargs = {} + if user: + extra_model_kwargs['user'] = user + + extra_model_kwargs['encoding_format'] = 'float' + + # get model properties + context_size = self._get_context_size(model, credentials) + max_chunks = self._get_max_chunks(model, credentials) + + inputs = [] + indices = [] + used_tokens = 0 + + for i, text in enumerate(texts): + + # Here token count is only an approximation based on the GPT2 tokenizer + # TODO: Optimize for better token estimation and chunking + num_tokens = self._get_num_tokens_by_gpt2(text) + + if num_tokens >= context_size: + cutoff = int(len(text) * (np.floor(context_size / num_tokens))) + # if num tokens is larger than context length, only use the start + inputs.append(text[0: cutoff]) + else: + inputs.append(text) + indices += [i] + + batched_embeddings = [] + _iter = range(0, len(inputs), max_chunks) + + for i in _iter: + # Prepare the payload for the request + payload = { + 'input': inputs[i: i + max_chunks], + 'model': model, + **extra_model_kwargs + } + + # Make the request to the OpenAI API + response = requests.post( + endpoint_url, + headers=headers, + data=json.dumps(payload), + timeout=(10, 300) + ) + + response.raise_for_status() # Raise an exception for HTTP errors + response_data = response.json() + + # Extract embeddings and used tokens from the response + embeddings_batch = [data['embedding'] for data in response_data['data']] + embedding_used_tokens = response_data['usage']['total_tokens'] + + used_tokens += embedding_used_tokens + batched_embeddings += embeddings_batch + + # calc usage + usage = self._calc_response_usage( + model=model, + credentials=credentials, + tokens=used_tokens + ) + + return TextEmbeddingResult( + embeddings=batched_embeddings, + usage=usage, + model=model + ) + + def get_num_tokens(self, model: str, credentials: dict, texts: list[str]) -> int: + """ + Approximate number of tokens for given messages using GPT2 tokenizer + + :param model: model name + :param credentials: model credentials + :param texts: texts to embed + :return: + """ + return sum(self._get_num_tokens_by_gpt2(text) for text in texts) + + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + try: + headers = { + 'Content-Type': 'application/json' + } + + api_key = credentials.get('api_key') + + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + if 'endpoint_url' not in credentials or credentials['endpoint_url'] == "": + endpoint_url='https://cloud.perfxlab.cn/v1/' + else: + endpoint_url = credentials.get('endpoint_url') + if not endpoint_url.endswith('/'): + endpoint_url += '/' + + endpoint_url = urljoin(endpoint_url, 'embeddings') + + payload = { + 'input': 'ping', + 'model': model + } + + response = requests.post( + url=endpoint_url, + headers=headers, + data=json.dumps(payload), + timeout=(10, 300) + ) + + if response.status_code != 200: + raise CredentialsValidateFailedError( + f'Credentials validation failed with status code {response.status_code}') + + try: + json_result = response.json() + except json.JSONDecodeError as e: + raise CredentialsValidateFailedError('Credentials validation failed: JSON decode error') + + if 'model' not in json_result: + raise CredentialsValidateFailedError( + 'Credentials validation failed: invalid response') + except CredentialsValidateFailedError: + raise + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) + + def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity: + """ + generate custom model entities from credentials + """ + entity = AIModelEntity( + model=model, + label=I18nObject(en_US=model), + model_type=ModelType.TEXT_EMBEDDING, + fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, + model_properties={ + ModelPropertyKey.CONTEXT_SIZE: int(credentials.get('context_size')), + ModelPropertyKey.MAX_CHUNKS: 1, + }, + parameter_rules=[], + pricing=PriceConfig( + input=Decimal(credentials.get('input_price', 0)), + unit=Decimal(credentials.get('unit', 0)), + currency=credentials.get('currency', "USD") + ) + ) + + return entity + + + def _calc_response_usage(self, model: str, credentials: dict, tokens: int) -> EmbeddingUsage: + """ + Calculate response usage + + :param model: model name + :param credentials: model credentials + :param tokens: input tokens + :return: usage + """ + # get input price info + input_price_info = self.get_price( + model=model, + credentials=credentials, + price_type=PriceType.INPUT, + tokens=tokens + ) + + # transform usage + usage = EmbeddingUsage( + tokens=tokens, + total_tokens=tokens, + unit_price=input_price_info.unit_price, + price_unit=input_price_info.unit, + total_price=input_price_info.total_amount, + currency=input_price_info.currency, + latency=time.perf_counter() - self.started_at + ) + + return usage diff --git a/api/core/model_runtime/model_providers/tongyi/tts/tts-1.yaml b/api/core/model_runtime/model_providers/tongyi/tts/tts-1.yaml index e533d5812d..4eaa0ff361 100644 --- a/api/core/model_runtime/model_providers/tongyi/tts/tts-1.yaml +++ b/api/core/model_runtime/model_providers/tongyi/tts/tts-1.yaml @@ -129,7 +129,7 @@ model_properties: - mode: "sambert-waan-v1" name: "Waan(泰语女声)" language: [ "th-TH" ] - word_limit: 120 + word_limit: 7000 audio_type: 'mp3' max_workers: 5 pricing: diff --git a/api/core/model_runtime/model_providers/tongyi/tts/tts.py b/api/core/model_runtime/model_providers/tongyi/tts/tts.py index 7ef053479b..655ed2d1d0 100644 --- a/api/core/model_runtime/model_providers/tongyi/tts/tts.py +++ b/api/core/model_runtime/model_providers/tongyi/tts/tts.py @@ -1,17 +1,21 @@ import concurrent.futures +import threading from functools import reduce from io import BytesIO +from queue import Queue from typing import Optional import dashscope -from flask import Response, stream_with_context +from dashscope import SpeechSynthesizer +from dashscope.api_entities.dashscope_response import SpeechSynthesisResponse +from dashscope.audio.tts import ResultCallback, SpeechSynthesisResult +from flask import Response from pydub import AudioSegment from core.model_runtime.errors.invoke import InvokeBadRequestError from core.model_runtime.errors.validate import CredentialsValidateFailedError from core.model_runtime.model_providers.__base.tts_model import TTSModel from core.model_runtime.model_providers.tongyi._common import _CommonTongyi -from extensions.ext_storage import storage class TongyiText2SpeechModel(_CommonTongyi, TTSModel): @@ -19,7 +23,7 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): Model class for Tongyi Speech to text model. """ - def _invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, streaming: bool, + def _invoke(self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None) -> any: """ _invoke text2speech model @@ -29,22 +33,17 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): :param credentials: model credentials :param voice: model timbre :param content_text: text content to be translated - :param streaming: output is streaming :param user: unique user id :return: text translated to audio file """ - audio_type = self._get_model_audio_type(model, credentials) - if not voice or voice not in [d['value'] for d in self.get_tts_model_voices(model=model, credentials=credentials)]: + if not voice or voice not in [d['value'] for d in + self.get_tts_model_voices(model=model, credentials=credentials)]: voice = self._get_model_default_voice(model, credentials) - if streaming: - return Response(stream_with_context(self._tts_invoke_streaming(model=model, - credentials=credentials, - content_text=content_text, - voice=voice, - tenant_id=tenant_id)), - status=200, mimetype=f'audio/{audio_type}') - else: - return self._tts_invoke(model=model, credentials=credentials, content_text=content_text, voice=voice) + + return self._tts_invoke_streaming(model=model, + credentials=credentials, + content_text=content_text, + voice=voice) def validate_credentials(self, model: str, credentials: dict, user: Optional[str] = None) -> None: """ @@ -79,7 +78,7 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): word_limit = self._get_model_word_limit(model, credentials) max_workers = self._get_model_workers_limit(model, credentials) try: - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) + sentences = list(self._split_text_into_sentences(org_text=content_text, max_length=word_limit)) audio_bytes_list = [] # Create a thread pool and map the function to the list of sentences @@ -105,14 +104,12 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): except Exception as ex: raise InvokeBadRequestError(str(ex)) - # Todo: To improve the streaming function - def _tts_invoke_streaming(self, model: str, tenant_id: str, credentials: dict, content_text: str, + def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> any: """ _tts_invoke_streaming text2speech model :param model: model name - :param tenant_id: user tenant id :param credentials: model credentials :param voice: model timbre :param content_text: text content to be translated @@ -120,18 +117,32 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): """ word_limit = self._get_model_word_limit(model, credentials) audio_type = self._get_model_audio_type(model, credentials) - tts_file_id = self._get_file_name(content_text) - file_path = f'generate_files/audio/{tenant_id}/{tts_file_id}.{audio_type}' try: - sentences = list(self._split_text_into_sentences(text=content_text, limit=word_limit)) - for sentence in sentences: - response = dashscope.audio.tts.SpeechSynthesizer.call(model=voice, sample_rate=48000, - api_key=credentials.get('dashscope_api_key'), - text=sentence.strip(), - format=audio_type, word_timestamp_enabled=True, - phoneme_timestamp_enabled=True) - if isinstance(response.get_audio_data(), bytes): - storage.save(file_path, response.get_audio_data()) + audio_queue: Queue = Queue() + callback = Callback(queue=audio_queue) + + def invoke_remote(content, v, api_key, cb, at, wl): + if len(content) < word_limit: + sentences = [content] + else: + sentences = list(self._split_text_into_sentences(org_text=content, max_length=wl)) + for sentence in sentences: + SpeechSynthesizer.call(model=v, sample_rate=16000, + api_key=api_key, + text=sentence.strip(), + callback=cb, + format=at, word_timestamp_enabled=True, + phoneme_timestamp_enabled=True) + + threading.Thread(target=invoke_remote, args=( + content_text, voice, credentials.get('dashscope_api_key'), callback, audio_type, word_limit)).start() + + while True: + audio = audio_queue.get() + if audio is None: + break + yield audio + except Exception as ex: raise InvokeBadRequestError(str(ex)) @@ -152,3 +163,29 @@ class TongyiText2SpeechModel(_CommonTongyi, TTSModel): format=audio_type) if isinstance(response.get_audio_data(), bytes): return response.get_audio_data() + + +class Callback(ResultCallback): + + def __init__(self, queue: Queue): + self._queue = queue + + def on_open(self): + pass + + def on_complete(self): + self._queue.put(None) + self._queue.task_done() + + def on_error(self, response: SpeechSynthesisResponse): + self._queue.put(None) + self._queue.task_done() + + def on_close(self): + self._queue.put(None) + self._queue.task_done() + + def on_event(self, result: SpeechSynthesisResult): + ad = result.get_audio_frame() + if ad: + self._queue.put(ad) diff --git a/api/core/model_runtime/model_providers/triton_inference_server/triton_inference_server.yaml b/api/core/model_runtime/model_providers/triton_inference_server/triton_inference_server.yaml index ca2fad33ad..218678b883 100644 --- a/api/core/model_runtime/model_providers/triton_inference_server/triton_inference_server.yaml +++ b/api/core/model_runtime/model_providers/triton_inference_server/triton_inference_server.yaml @@ -29,7 +29,7 @@ model_credential_schema: label: zh_Hans: 服务器URL en_US: Server url - type: secret-input + type: text-input required: true placeholder: zh_Hans: 在此输入 Triton Inference Server 的服务器地址,如 http://192.168.1.100:8000 diff --git a/api/core/model_runtime/model_providers/wenxin/llm/ernie-4.0-turbo-8k-preview b/api/core/model_runtime/model_providers/wenxin/llm/ernie-4.0-turbo-8k-preview new file mode 100644 index 0000000000..16df540220 --- /dev/null +++ b/api/core/model_runtime/model_providers/wenxin/llm/ernie-4.0-turbo-8k-preview @@ -0,0 +1,40 @@ +model: ernie-4.0-turbo-8k-preview +label: + en_US: Ernie-4.0-turbo-8k-preview +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 8192 +parameter_rules: + - name: temperature + use_template: temperature + min: 0.1 + max: 1.0 + default: 0.8 + - name: top_p + use_template: top_p + - name: max_tokens + use_template: max_tokens + default: 1024 + min: 2 + max: 2048 + - name: presence_penalty + use_template: presence_penalty + default: 1.0 + min: 1.0 + max: 2.0 + - name: frequency_penalty + use_template: frequency_penalty + - name: response_format + use_template: response_format + - name: disable_search + label: + zh_Hans: 禁用搜索 + en_US: Disable Search + type: boolean + help: + zh_Hans: 禁用模型自行进行外部搜索。 + en_US: Disable the model to perform external search. + required: false diff --git a/api/core/model_runtime/model_providers/wenxin/llm/ernie_bot.py b/api/core/model_runtime/model_providers/wenxin/llm/ernie_bot.py index 305769c1c1..9aeab04cd2 100644 --- a/api/core/model_runtime/model_providers/wenxin/llm/ernie_bot.py +++ b/api/core/model_runtime/model_providers/wenxin/llm/ernie_bot.py @@ -138,6 +138,7 @@ class ErnieBotModel: 'ernie-lite-8k-0922': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant', 'ernie-lite-8k-0308': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-lite-8k', 'ernie-character-8k-0321': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-char-8k', + 'ernie-4.0-tutbo-8k-preview': 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-4.0-turbo-8k-preview', } function_calling_supports = [ @@ -149,6 +150,7 @@ class ErnieBotModel: 'ernie-3.5-4k-0205', 'ernie-3.5-128k', 'ernie-4.0-8k' + 'ernie-4.0-turbo-8k-preview' ] api_key: str = '' diff --git a/api/core/model_runtime/model_providers/xinference/xinference.yaml b/api/core/model_runtime/model_providers/xinference/xinference.yaml index 6744c34268..28ffc0389e 100644 --- a/api/core/model_runtime/model_providers/xinference/xinference.yaml +++ b/api/core/model_runtime/model_providers/xinference/xinference.yaml @@ -32,7 +32,7 @@ model_credential_schema: label: zh_Hans: 服务器URL en_US: Server url - type: secret-input + type: text-input required: true placeholder: zh_Hans: 在此输入Xinference的服务器地址,如 http://192.168.1.100:9997 diff --git a/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/api_resource/images.py b/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/api_resource/images.py index 3201426dfa..8eae1216d0 100644 --- a/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/api_resource/images.py +++ b/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/api_resource/images.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional import httpx from ..core._base_api import BaseAPI -from ..core._base_type import NOT_GIVEN, Headers, NotGiven +from ..core._base_type import NOT_GIVEN, Body, Headers, NotGiven from ..core._http_client import make_user_request_input from ..types.image import ImagesResponded @@ -28,7 +28,9 @@ class Images(BaseAPI): size: Optional[str] | NotGiven = NOT_GIVEN, style: Optional[str] | NotGiven = NOT_GIVEN, user: str | NotGiven = NOT_GIVEN, + request_id: Optional[str] | NotGiven = NOT_GIVEN, extra_headers: Headers | None = None, + extra_body: Body | None = None, disable_strict_validation: Optional[bool] | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ImagesResponded: @@ -46,9 +48,12 @@ class Images(BaseAPI): "size": size, "style": style, "user": user, + "request_id": request_id, }, options=make_user_request_input( - extra_headers=extra_headers, timeout=timeout + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout ), cast_type=_cast_type, enable_stream=False, diff --git a/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/core/_http_client.py b/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/core/_http_client.py index 924d009123..263fe82990 100644 --- a/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/core/_http_client.py +++ b/api/core/model_runtime/model_providers/zhipuai/zhipuai_sdk/core/_http_client.py @@ -11,7 +11,7 @@ from tenacity import retry from tenacity.stop import stop_after_attempt from . import _errors -from ._base_type import NOT_GIVEN, Body, Data, Headers, NotGiven, Query, RequestFiles, ResponseT +from ._base_type import NOT_GIVEN, AnyMapping, Body, Data, Headers, NotGiven, Query, RequestFiles, ResponseT from ._errors import APIResponseValidationError, APIStatusError, APITimeoutError from ._files import make_httpx_files from ._request_opt import ClientRequestParam, UserRequestInput @@ -358,6 +358,7 @@ def make_user_request_input( max_retries: int | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, extra_headers: Headers = None, + extra_body: Body | None = None, query: Query | None = None, ) -> UserRequestInput: options: UserRequestInput = {} @@ -370,5 +371,7 @@ def make_user_request_input( options['timeout'] = timeout if query is not None: options["params"] = query + if extra_body is not None: + options["extra_json"] = cast(AnyMapping, extra_body) return options diff --git a/api/core/rag/datasource/keyword/keyword_factory.py b/api/core/rag/datasource/keyword/keyword_factory.py index beb3322aa6..6ac610f82b 100644 --- a/api/core/rag/datasource/keyword/keyword_factory.py +++ b/api/core/rag/datasource/keyword/keyword_factory.py @@ -1,7 +1,6 @@ from typing import Any -from flask import current_app - +from configs import dify_config from core.rag.datasource.keyword.jieba.jieba import Jieba from core.rag.datasource.keyword.keyword_base import BaseKeyword from core.rag.models.document import Document @@ -14,8 +13,8 @@ class Keyword: self._keyword_processor = self._init_keyword() def _init_keyword(self) -> BaseKeyword: - config = current_app.config - keyword_type = config.get('KEYWORD_STORE') + config = dify_config + keyword_type = config.KEYWORD_STORE if not keyword_type: raise ValueError("Keyword store must be specified.") diff --git a/api/core/rag/datasource/vdb/analyticdb/__init__.py b/api/core/rag/datasource/vdb/analyticdb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector.py b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector.py new file mode 100644 index 0000000000..d7a5dd5dcc --- /dev/null +++ b/api/core/rag/datasource/vdb/analyticdb/analyticdb_vector.py @@ -0,0 +1,332 @@ +import json +from typing import Any + +from pydantic import BaseModel + +_import_err_msg = ( + "`alibabacloud_gpdb20160503` and `alibabacloud_tea_openapi` packages not found, " + "please run `pip install alibabacloud_gpdb20160503 alibabacloud_tea_openapi`" +) +from flask import current_app + +from core.rag.datasource.entity.embedding import Embeddings +from core.rag.datasource.vdb.vector_base import BaseVector +from core.rag.datasource.vdb.vector_factory import AbstractVectorFactory +from core.rag.datasource.vdb.vector_type import VectorType +from core.rag.models.document import Document +from extensions.ext_redis import redis_client +from models.dataset import Dataset + + +class AnalyticdbConfig(BaseModel): + access_key_id: str + access_key_secret: str + region_id: str + instance_id: str + account: str + account_password: str + namespace: str = ("dify",) + namespace_password: str = (None,) + metrics: str = ("cosine",) + read_timeout: int = 60000 + def to_analyticdb_client_params(self): + return { + "access_key_id": self.access_key_id, + "access_key_secret": self.access_key_secret, + "region_id": self.region_id, + "read_timeout": self.read_timeout, + } + +class AnalyticdbVector(BaseVector): + _instance = None + _init = False + + def __new__(cls, *args, **kwargs): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, collection_name: str, config: AnalyticdbConfig): + # collection_name must be updated every time + self._collection_name = collection_name.lower() + if AnalyticdbVector._init: + return + try: + from alibabacloud_gpdb20160503.client import Client + from alibabacloud_tea_openapi import models as open_api_models + except: + raise ImportError(_import_err_msg) + self.config = config + self._client_config = open_api_models.Config( + user_agent="dify", **config.to_analyticdb_client_params() + ) + self._client = Client(self._client_config) + self._initialize() + AnalyticdbVector._init = True + + def _initialize(self) -> None: + self._initialize_vector_database() + self._create_namespace_if_not_exists() + + def _initialize_vector_database(self) -> None: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + request = gpdb_20160503_models.InitVectorDatabaseRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + manager_account=self.config.account, + manager_account_password=self.config.account_password, + ) + self._client.init_vector_database(request) + + def _create_namespace_if_not_exists(self) -> None: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + from Tea.exceptions import TeaException + try: + request = gpdb_20160503_models.DescribeNamespaceRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + manager_account=self.config.account, + manager_account_password=self.config.account_password, + ) + self._client.describe_namespace(request) + except TeaException as e: + if e.statusCode == 404: + request = gpdb_20160503_models.CreateNamespaceRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + manager_account=self.config.account, + manager_account_password=self.config.account_password, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + ) + self._client.create_namespace(request) + else: + raise ValueError( + f"failed to create namespace {self.config.namespace}: {e}" + ) + + def _create_collection_if_not_exists(self, embedding_dimension: int): + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + from Tea.exceptions import TeaException + cache_key = f"vector_indexing_{self._collection_name}" + lock_name = f"{cache_key}_lock" + with redis_client.lock(lock_name, timeout=20): + collection_exist_cache_key = f"vector_indexing_{self._collection_name}" + if redis_client.get(collection_exist_cache_key): + return + try: + request = gpdb_20160503_models.DescribeCollectionRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + ) + self._client.describe_collection(request) + except TeaException as e: + if e.statusCode == 404: + metadata = '{"ref_doc_id":"text","page_content":"text","metadata_":"jsonb"}' + full_text_retrieval_fields = "page_content" + request = gpdb_20160503_models.CreateCollectionRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + manager_account=self.config.account, + manager_account_password=self.config.account_password, + namespace=self.config.namespace, + collection=self._collection_name, + dimension=embedding_dimension, + metrics=self.config.metrics, + metadata=metadata, + full_text_retrieval_fields=full_text_retrieval_fields, + ) + self._client.create_collection(request) + else: + raise ValueError( + f"failed to create collection {self._collection_name}: {e}" + ) + redis_client.set(collection_exist_cache_key, 1, ex=3600) + + def get_type(self) -> str: + return VectorType.ANALYTICDB + + def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs): + dimension = len(embeddings[0]) + self._create_collection_if_not_exists(dimension) + self.add_texts(texts, embeddings) + + def add_texts( + self, documents: list[Document], embeddings: list[list[float]], **kwargs + ): + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + rows: list[gpdb_20160503_models.UpsertCollectionDataRequestRows] = [] + for doc, embedding in zip(documents, embeddings, strict=True): + metadata = { + "ref_doc_id": doc.metadata["doc_id"], + "page_content": doc.page_content, + "metadata_": json.dumps(doc.metadata), + } + rows.append( + gpdb_20160503_models.UpsertCollectionDataRequestRows( + vector=embedding, + metadata=metadata, + ) + ) + request = gpdb_20160503_models.UpsertCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + rows=rows, + ) + self._client.upsert_collection_data(request) + + def text_exists(self, id: str) -> bool: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + request = gpdb_20160503_models.QueryCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + metrics=self.config.metrics, + include_values=True, + vector=None, + content=None, + top_k=1, + filter=f"ref_doc_id='{id}'" + ) + response = self._client.query_collection_data(request) + return len(response.body.matches.match) > 0 + + def delete_by_ids(self, ids: list[str]) -> None: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + ids_str = ",".join(f"'{id}'" for id in ids) + ids_str = f"({ids_str})" + request = gpdb_20160503_models.DeleteCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + collection_data=None, + collection_data_filter=f"ref_doc_id IN {ids_str}", + ) + self._client.delete_collection_data(request) + + def delete_by_metadata_field(self, key: str, value: str) -> None: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + request = gpdb_20160503_models.DeleteCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + collection_data=None, + collection_data_filter=f"metadata_ ->> '{key}' = '{value}'", + ) + self._client.delete_collection_data(request) + + def search_by_vector( + self, query_vector: list[float], **kwargs: Any + ) -> list[Document]: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + score_threshold = ( + kwargs.get("score_threshold", 0.0) + if kwargs.get("score_threshold", 0.0) + else 0.0 + ) + request = gpdb_20160503_models.QueryCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + include_values=kwargs.pop("include_values", True), + metrics=self.config.metrics, + vector=query_vector, + content=None, + top_k=kwargs.get("top_k", 4), + filter=None, + ) + response = self._client.query_collection_data(request) + documents = [] + for match in response.body.matches.match: + if match.score > score_threshold: + doc = Document( + page_content=match.metadata.get("page_content"), + metadata=json.loads(match.metadata.get("metadata_")), + ) + documents.append(doc) + return documents + + def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + score_threshold = ( + kwargs.get("score_threshold", 0.0) + if kwargs.get("score_threshold", 0.0) + else 0.0 + ) + request = gpdb_20160503_models.QueryCollectionDataRequest( + dbinstance_id=self.config.instance_id, + region_id=self.config.region_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + collection=self._collection_name, + include_values=kwargs.pop("include_values", True), + metrics=self.config.metrics, + vector=None, + content=query, + top_k=kwargs.get("top_k", 4), + filter=None, + ) + response = self._client.query_collection_data(request) + documents = [] + for match in response.body.matches.match: + if match.score > score_threshold: + doc = Document( + page_content=match.metadata.get("page_content"), + metadata=json.loads(match.metadata.get("metadata_")), + ) + documents.append(doc) + return documents + + def delete(self) -> None: + from alibabacloud_gpdb20160503 import models as gpdb_20160503_models + request = gpdb_20160503_models.DeleteCollectionRequest( + collection=self._collection_name, + dbinstance_id=self.config.instance_id, + namespace=self.config.namespace, + namespace_password=self.config.namespace_password, + region_id=self.config.region_id, + ) + self._client.delete_collection(request) + +class AnalyticdbVectorFactory(AbstractVectorFactory): + def init_vector(self, dataset: Dataset, attributes: list, embeddings: Embeddings): + if dataset.index_struct_dict: + class_prefix: str = dataset.index_struct_dict["vector_store"][ + "class_prefix" + ] + collection_name = class_prefix.lower() + else: + dataset_id = dataset.id + collection_name = Dataset.gen_collection_name_by_id(dataset_id).lower() + dataset.index_struct = json.dumps( + self.gen_index_struct_dict(VectorType.ANALYTICDB, collection_name) + ) + config = current_app.config + return AnalyticdbVector( + collection_name, + AnalyticdbConfig( + access_key_id=config.get("ANALYTICDB_KEY_ID"), + access_key_secret=config.get("ANALYTICDB_KEY_SECRET"), + region_id=config.get("ANALYTICDB_REGION_ID"), + instance_id=config.get("ANALYTICDB_INSTANCE_ID"), + account=config.get("ANALYTICDB_ACCOUNT"), + account_password=config.get("ANALYTICDB_PASSWORD"), + namespace=config.get("ANALYTICDB_NAMESPACE"), + namespace_password=config.get("ANALYTICDB_NAMESPACE_PASSWORD"), + ), + ) \ No newline at end of file diff --git a/api/core/rag/datasource/vdb/myscale/__init__.py b/api/core/rag/datasource/vdb/myscale/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/rag/datasource/vdb/myscale/myscale_vector.py b/api/core/rag/datasource/vdb/myscale/myscale_vector.py new file mode 100644 index 0000000000..811b08818c --- /dev/null +++ b/api/core/rag/datasource/vdb/myscale/myscale_vector.py @@ -0,0 +1,170 @@ +import json +import logging +import uuid +from enum import Enum +from typing import Any + +from clickhouse_connect import get_client +from flask import current_app +from pydantic import BaseModel + +from core.rag.datasource.entity.embedding import Embeddings +from core.rag.datasource.vdb.vector_base import BaseVector +from core.rag.datasource.vdb.vector_factory import AbstractVectorFactory +from core.rag.datasource.vdb.vector_type import VectorType +from core.rag.models.document import Document +from models.dataset import Dataset + + +class MyScaleConfig(BaseModel): + host: str + port: int + user: str + password: str + database: str + fts_params: str + + +class SortOrder(Enum): + ASC = "ASC" + DESC = "DESC" + + +class MyScaleVector(BaseVector): + + def __init__(self, collection_name: str, config: MyScaleConfig, metric: str = "Cosine"): + super().__init__(collection_name) + self._config = config + self._metric = metric + self._vec_order = SortOrder.ASC if metric.upper() in ["COSINE", "L2"] else SortOrder.DESC + self._client = get_client( + host=config.host, + port=config.port, + username=config.user, + password=config.password, + ) + self._client.command("SET allow_experimental_object_type=1") + + def get_type(self) -> str: + return VectorType.MYSCALE + + def create(self, texts: list[Document], embeddings: list[list[float]], **kwargs): + dimension = len(embeddings[0]) + self._create_collection(dimension) + return self.add_texts(documents=texts, embeddings=embeddings, **kwargs) + + def _create_collection(self, dimension: int): + logging.info(f"create MyScale collection {self._collection_name} with dimension {dimension}") + self._client.command(f"CREATE DATABASE IF NOT EXISTS {self._config.database}") + fts_params = f"('{self._config.fts_params}')" if self._config.fts_params else "" + sql = f""" + CREATE TABLE IF NOT EXISTS {self._config.database}.{self._collection_name}( + id String, + text String, + vector Array(Float32), + metadata JSON, + CONSTRAINT cons_vec_len CHECK length(vector) = {dimension}, + VECTOR INDEX vidx vector TYPE DEFAULT('metric_type = {self._metric}'), + INDEX text_idx text TYPE fts{fts_params} + ) ENGINE = MergeTree ORDER BY id + """ + self._client.command(sql) + + def add_texts(self, documents: list[Document], embeddings: list[list[float]], **kwargs): + ids = [] + columns = ["id", "text", "vector", "metadata"] + values = [] + for i, doc in enumerate(documents): + doc_id = doc.metadata.get("doc_id", str(uuid.uuid4())) + row = ( + doc_id, + self.escape_str(doc.page_content), + embeddings[i], + json.dumps(doc.metadata) if doc.metadata else {} + ) + values.append(str(row)) + ids.append(doc_id) + sql = f""" + INSERT INTO {self._config.database}.{self._collection_name} + ({",".join(columns)}) VALUES {",".join(values)} + """ + self._client.command(sql) + return ids + + @staticmethod + def escape_str(value: Any) -> str: + return "".join(f"\\{c}" if c in ("\\", "'") else c for c in str(value)) + + def text_exists(self, id: str) -> bool: + results = self._client.query(f"SELECT id FROM {self._config.database}.{self._collection_name} WHERE id='{id}'") + return results.row_count > 0 + + def delete_by_ids(self, ids: list[str]) -> None: + self._client.command( + f"DELETE FROM {self._config.database}.{self._collection_name} WHERE id IN {str(tuple(ids))}") + + def get_ids_by_metadata_field(self, key: str, value: str): + rows = self._client.query( + f"SELECT DISTINCT id FROM {self._config.database}.{self._collection_name} WHERE metadata.{key}='{value}'" + ).result_rows + return [row[0] for row in rows] + + def delete_by_metadata_field(self, key: str, value: str) -> None: + self._client.command( + f"DELETE FROM {self._config.database}.{self._collection_name} WHERE metadata.{key}='{value}'" + ) + + def search_by_vector(self, query_vector: list[float], **kwargs: Any) -> list[Document]: + return self._search(f"distance(vector, {str(query_vector)})", self._vec_order, **kwargs) + + def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: + return self._search(f"TextSearch(text, '{query}')", SortOrder.DESC, **kwargs) + + def _search(self, dist: str, order: SortOrder, **kwargs: Any) -> list[Document]: + top_k = kwargs.get("top_k", 5) + score_threshold = kwargs.get("score_threshold", 0.0) + where_str = f"WHERE dist < {1 - score_threshold}" if \ + self._metric.upper() == "COSINE" and order == SortOrder.ASC and score_threshold > 0.0 else "" + sql = f""" + SELECT text, metadata, {dist} as dist FROM {self._config.database}.{self._collection_name} + {where_str} ORDER BY dist {order.value} LIMIT {top_k} + """ + try: + return [ + Document( + page_content=r["text"], + metadata=r["metadata"], + ) + for r in self._client.query(sql).named_results() + ] + except Exception as e: + logging.error(f"\033[91m\033[1m{type(e)}\033[0m \033[95m{str(e)}\033[0m") + return [] + + def delete(self) -> None: + self._client.command(f"DROP TABLE IF EXISTS {self._config.database}.{self._collection_name}") + + +class MyScaleVectorFactory(AbstractVectorFactory): + def init_vector(self, dataset: Dataset, attributes: list, embeddings: Embeddings) -> MyScaleVector: + if dataset.index_struct_dict: + class_prefix: str = dataset.index_struct_dict['vector_store']['class_prefix'] + collection_name = class_prefix.lower() + else: + dataset_id = dataset.id + collection_name = Dataset.gen_collection_name_by_id(dataset_id).lower() + dataset.index_struct = json.dumps( + self.gen_index_struct_dict(VectorType.MYSCALE, collection_name)) + + config = current_app.config + return MyScaleVector( + collection_name=collection_name, + config=MyScaleConfig( + host=config.get("MYSCALE_HOST", "localhost"), + port=int(config.get("MYSCALE_PORT", 8123)), + user=config.get("MYSCALE_USER", "default"), + password=config.get("MYSCALE_PASSWORD", ""), + database=config.get("MYSCALE_DATABASE", "default"), + fts_params=config.get("MYSCALE_FTS_PARAMS", ""), + ), + ) diff --git a/api/core/rag/datasource/vdb/vector_factory.py b/api/core/rag/datasource/vdb/vector_factory.py index 719e2b9a23..f8b58e1b9a 100644 --- a/api/core/rag/datasource/vdb/vector_factory.py +++ b/api/core/rag/datasource/vdb/vector_factory.py @@ -57,6 +57,9 @@ class Vector: case VectorType.MILVUS: from core.rag.datasource.vdb.milvus.milvus_vector import MilvusVectorFactory return MilvusVectorFactory + case VectorType.MYSCALE: + from core.rag.datasource.vdb.myscale.myscale_vector import MyScaleVectorFactory + return MyScaleVectorFactory case VectorType.PGVECTOR: from core.rag.datasource.vdb.pgvector.pgvector import PGVectorFactory return PGVectorFactory @@ -84,6 +87,9 @@ class Vector: case VectorType.OPENSEARCH: from core.rag.datasource.vdb.opensearch.opensearch_vector import OpenSearchVectorFactory return OpenSearchVectorFactory + case VectorType.ANALYTICDB: + from core.rag.datasource.vdb.analyticdb.analyticdb_vector import AnalyticdbVectorFactory + return AnalyticdbVectorFactory case _: raise ValueError(f"Vector store {vector_type} is not supported.") diff --git a/api/core/rag/datasource/vdb/vector_type.py b/api/core/rag/datasource/vdb/vector_type.py index dbd5afcb3e..77495044df 100644 --- a/api/core/rag/datasource/vdb/vector_type.py +++ b/api/core/rag/datasource/vdb/vector_type.py @@ -2,8 +2,10 @@ from enum import Enum class VectorType(str, Enum): + ANALYTICDB = 'analyticdb' CHROMA = 'chroma' MILVUS = 'milvus' + MYSCALE = 'myscale' PGVECTOR = 'pgvector' PGVECTO_RS = 'pgvecto-rs' QDRANT = 'qdrant' diff --git a/api/core/rag/extractor/firecrawl/firecrawl_app.py b/api/core/rag/extractor/firecrawl/firecrawl_app.py index af6b568936..2b85ad9739 100644 --- a/api/core/rag/extractor/firecrawl/firecrawl_app.py +++ b/api/core/rag/extractor/firecrawl/firecrawl_app.py @@ -46,7 +46,6 @@ class FirecrawlApp: raise Exception(f'Failed to scrape URL. Status code: {response.status_code}') def crawl_url(self, url, params=None) -> str: - start_time = time.time() headers = self._prepare_headers() json_data = {'url': url} if params: diff --git a/api/core/rag/extractor/markdown_extractor.py b/api/core/rag/extractor/markdown_extractor.py index 91c687bac9..faa1e64057 100644 --- a/api/core/rag/extractor/markdown_extractor.py +++ b/api/core/rag/extractor/markdown_extractor.py @@ -18,8 +18,8 @@ class MarkdownExtractor(BaseExtractor): def __init__( self, file_path: str, - remove_hyperlinks: bool = True, - remove_images: bool = True, + remove_hyperlinks: bool = False, + remove_images: bool = False, encoding: Optional[str] = None, autodetect_encoding: bool = True, ): diff --git a/api/core/tools/docs/en_US/advanced_scale_out.md b/api/core/tools/docs/en_US/advanced_scale_out.md index 56c8509785..644ad29129 100644 --- a/api/core/tools/docs/en_US/advanced_scale_out.md +++ b/api/core/tools/docs/en_US/advanced_scale_out.md @@ -8,7 +8,7 @@ We have defined a series of helper methods in the `Tool` class to help developer ### Message Return -Dify supports various message types such as `text`, `link`, `image`, and `file BLOB`. You can return different types of messages to the LLM and users through the following interfaces. +Dify supports various message types such as `text`, `link`, `json`, `image`, and `file BLOB`. You can return different types of messages to the LLM and users through the following interfaces. Please note, some parameters in the following interfaces will be introduced in later sections. @@ -67,6 +67,18 @@ If you need to return the raw data of a file, such as images, audio, video, PPT, """ ``` +#### JSON +If you need to return a formatted JSON, you can use the following interface. This is commonly used for data transmission between nodes in a workflow, of course, in agent mode, most LLM are also able to read and understand JSON. + +- `object` A Python dictionary object will be automatically serialized into JSON + +```python + def create_json_message(self, object: dict) -> ToolInvokeMessage: + """ + create a json message + """ +``` + ### Shortcut Tools In large model applications, we have two common needs: diff --git a/api/core/tools/docs/en_US/tool_scale_out.md b/api/core/tools/docs/en_US/tool_scale_out.md index f75c91cad6..121b7a5a76 100644 --- a/api/core/tools/docs/en_US/tool_scale_out.md +++ b/api/core/tools/docs/en_US/tool_scale_out.md @@ -145,19 +145,25 @@ parameters: # Parameter list - The `identity` field is mandatory, it contains the basic information of the tool, including name, author, label, description, etc. - `parameters` Parameter list - - `name` Parameter name, unique, no duplication with other parameters - - `type` Parameter type, currently supports `string`, `number`, `boolean`, `select`, `secret-input` four types, corresponding to string, number, boolean, drop-down box, and encrypted input box, respectively. For sensitive information, we recommend using `secret-input` type - - `required` Required or not + - `name` (Mandatory) Parameter name, must be unique and not duplicate with other parameters. + - `type` (Mandatory) Parameter type, currently supports `string`, `number`, `boolean`, `select`, `secret-input` five types, corresponding to string, number, boolean, drop-down box, and encrypted input box, respectively. For sensitive information, we recommend using the `secret-input` type + - `label` (Mandatory) Parameter label, for frontend display + - `form` (Mandatory) Form type, currently supports `llm`, `form` two types. + - In an agent app, `llm` indicates that the parameter is inferred by the LLM itself, while `form` indicates that the parameter can be pre-set for the tool. + - In a workflow app, both `llm` and `form` need to be filled out by the front end, but the parameters of `llm` will be used as input variables for the tool node. + - `required` Indicates whether the parameter is required or not - In `llm` mode, if the parameter is required, the Agent is required to infer this parameter - In `form` mode, if the parameter is required, the user is required to fill in this parameter on the frontend before the conversation starts - `options` Parameter options - In `llm` mode, Dify will pass all options to LLM, LLM can infer based on these options - In `form` mode, when `type` is `select`, the frontend will display these options - `default` Default value - - `label` Parameter label, for frontend display + - `min` Minimum value, can be set when the parameter type is `number`. + - `max` Maximum value, can be set when the parameter type is `number`. + - `placeholder` The prompt text for input boxes. It can be set when the form type is `form`, and the parameter type is `string`, `number`, or `secret-input`. It supports multiple languages. - `human_description` Introduction for frontend display, supports multiple languages - `llm_description` Introduction passed to LLM, in order to make LLM better understand this parameter, we suggest to write as detailed information about this parameter as possible here, so that LLM can understand this parameter - - `form` Form type, currently supports `llm`, `form` two types, corresponding to Agent self-inference and frontend filling + ## 4. Add Tool Logic @@ -196,7 +202,7 @@ The overall logic of the tool is in the `_invoke` method, this method accepts tw ### Return Data -When the tool returns, you can choose to return one message or multiple messages, here we return one message, using `create_text_message` and `create_link_message` can create a text message or a link message. +When the tool returns, you can choose to return one message or multiple messages, here we return one message, using `create_text_message` and `create_link_message` can create a text message or a link message. If you want to return multiple messages, you can use `[self.create_text_message('msg1'), self.create_text_message('msg2')]` to create a list of messages. ## 5. Add Provider Code @@ -205,8 +211,6 @@ Finally, we need to create a provider class under the provider module to impleme Create `google.py` under the `google` module, the content is as follows. ```python -from core.tools.entities.tool_entities import ToolInvokeMessage, ToolProviderType -from core.tools.tool.tool import Tool from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController from core.tools.errors import ToolProviderCredentialValidationError diff --git a/api/core/tools/docs/zh_Hans/advanced_scale_out.md b/api/core/tools/docs/zh_Hans/advanced_scale_out.md index 3a760e7a72..93f81b033d 100644 --- a/api/core/tools/docs/zh_Hans/advanced_scale_out.md +++ b/api/core/tools/docs/zh_Hans/advanced_scale_out.md @@ -8,7 +8,7 @@ ### 消息返回 -Dify支持`文本` `链接` `图片` `文件BLOB` 等多种消息类型,你可以通过以下几个接口返回不同类型的消息给LLM和用户。 +Dify支持`文本` `链接` `图片` `文件BLOB` `JSON` 等多种消息类型,你可以通过以下几个接口返回不同类型的消息给LLM和用户。 注意,在下面的接口中的部分参数将在后面的章节中介绍。 @@ -67,6 +67,18 @@ Dify支持`文本` `链接` `图片` `文件BLOB` 等多种消息类型,你可 """ ``` +#### JSON +如果你需要返回一个格式化的JSON,可以使用以下接口。这通常用于workflow中的节点间的数据传递,当然agent模式中,大部分大模型也都能够阅读和理解JSON。 + +- `object` 一个Python的字典对象,会被自动序列化为JSON + +```python + def create_json_message(self, object: dict) -> ToolInvokeMessage: + """ + create a json message + """ +``` + ### 快捷工具 在大模型应用中,我们有两种常见的需求: @@ -97,8 +109,8 @@ Dify支持`文本` `链接` `图片` `文件BLOB` 等多种消息类型,你可 ```python def get_url(self, url: str, user_agent: str = None) -> str: """ - get url - """ the crawled result + get url from the crawled result + """ ``` ### 变量池 diff --git a/api/core/tools/docs/zh_Hans/tool_scale_out.md b/api/core/tools/docs/zh_Hans/tool_scale_out.md index 20f0f935e8..06a8d9a4f9 100644 --- a/api/core/tools/docs/zh_Hans/tool_scale_out.md +++ b/api/core/tools/docs/zh_Hans/tool_scale_out.md @@ -140,8 +140,12 @@ parameters: # 参数列表 - `identity` 字段是必须的,它包含了工具的基本信息,包括名称、作者、标签、描述等 - `parameters` 参数列表 - - `name` 参数名称,唯一,不允许和其他参数重名 - - `type` 参数类型,目前支持`string`、`number`、`boolean`、`select`、`secret-input` 五种类型,分别对应字符串、数字、布尔值、下拉框、加密输入框,对于敏感信息,我们建议使用`secret-input`类型 + - `name` (必填)参数名称,唯一,不允许和其他参数重名 + - `type` (必填)参数类型,目前支持`string`、`number`、`boolean`、`select`、`secret-input` 五种类型,分别对应字符串、数字、布尔值、下拉框、加密输入框,对于敏感信息,我们建议使用`secret-input`类型 + - `label`(必填)参数标签,用于前端展示 + - `form` (必填)表单类型,目前支持`llm`、`form`两种类型 + - 在Agent应用中,`llm`表示该参数LLM自行推理,`form`表示要使用该工具可提前设定的参数 + - 在workflow应用中,`llm`和`form`均需要前端填写,但`llm`的参数会做为工具节点的输入变量 - `required` 是否必填 - 在`llm`模式下,如果参数为必填,则会要求Agent必须要推理出这个参数 - 在`form`模式下,如果参数为必填,则会要求用户在对话开始前在前端填写这个参数 @@ -149,10 +153,12 @@ parameters: # 参数列表 - 在`llm`模式下,Dify会将所有选项传递给LLM,LLM可以根据这些选项进行推理 - 在`form`模式下,`type`为`select`时,前端会展示这些选项 - `default` 默认值 - - `label` 参数标签,用于前端展示 + - `min` 最小值,当参数类型为`number`时可以设定 + - `max` 最大值,当参数类型为`number`时可以设定 - `human_description` 用于前端展示的介绍,支持多语言 + - `placeholder` 字段输入框的提示文字,在表单类型为`form`,参数类型为`string`、`number`、`secret-input`时,可以设定,支持多语言 - `llm_description` 传递给LLM的介绍,为了使得LLM更好理解这个参数,我们建议在这里写上关于这个参数尽可能详细的信息,让LLM能够理解这个参数 - - `form` 表单类型,目前支持`llm`、`form`两种类型,分别对应Agent自行推理和前端填写 + ## 4. 准备工具代码 当完成工具的配置以后,我们就可以开始编写工具代码了,主要用于实现工具的逻辑。 @@ -176,7 +182,6 @@ class GoogleSearchTool(BuiltinTool): query = tool_parameters['query'] result_type = tool_parameters['result_type'] api_key = self.runtime.credentials['serpapi_api_key'] - # TODO: search with serpapi result = SerpAPI(api_key).run(query, result_type=result_type) if result_type == 'text': @@ -188,7 +193,7 @@ class GoogleSearchTool(BuiltinTool): 工具的整体逻辑都在`_invoke`方法中,这个方法接收两个参数:`user_id`和`tool_parameters`,分别表示用户ID和工具参数 ### 返回数据 -在工具返回时,你可以选择返回一个消息或者多个消息,这里我们返回一个消息,使用`create_text_message`和`create_link_message`可以创建一个文本消息或者一个链接消息。 +在工具返回时,你可以选择返回一条消息或者多个消息,这里我们返回一条消息,使用`create_text_message`和`create_link_message`可以创建一条文本消息或者一条链接消息。如需返回多条消息,可以使用列表构建,例如`[self.create_text_message('msg1'), self.create_text_message('msg2')]` ## 5. 准备供应商代码 最后,我们需要在供应商模块下创建一个供应商类,用于实现供应商的凭据验证逻辑,如果凭据验证失败,将会抛出`ToolProviderCredentialValidationError`异常。 @@ -196,8 +201,6 @@ class GoogleSearchTool(BuiltinTool): 在`google`模块下创建`google.py`,内容如下。 ```python -from core.tools.entities.tool_entities import ToolInvokeMessage, ToolProviderType -from core.tools.tool.tool import Tool from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController from core.tools.errors import ToolProviderCredentialValidationError diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index d00e89d5cd..569a1d3238 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -142,7 +142,8 @@ class ToolParameter(BaseModel): name: str = Field(..., description="The name of the parameter") label: I18nObject = Field(..., description="The label presented to the user") - human_description: I18nObject = Field(..., description="The description presented to the user") + human_description: Optional[I18nObject] = Field(None, description="The description presented to the user") + placeholder: Optional[I18nObject] = Field(None, description="The placeholder presented to the user") type: ToolParameterType = Field(..., description="The type of the parameter") form: ToolParameterForm = Field(..., description="The form of the parameter, schema/form/llm") llm_description: Optional[str] = None diff --git a/api/core/tools/provider/builtin/cogview/__init__.py b/api/core/tools/provider/builtin/cogview/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/tools/provider/builtin/cogview/_assets/icon.png b/api/core/tools/provider/builtin/cogview/_assets/icon.png new file mode 100644 index 0000000000..f0c1c24a02 Binary files /dev/null and b/api/core/tools/provider/builtin/cogview/_assets/icon.png differ diff --git a/api/core/tools/provider/builtin/cogview/cogview.py b/api/core/tools/provider/builtin/cogview/cogview.py new file mode 100644 index 0000000000..801817ec06 --- /dev/null +++ b/api/core/tools/provider/builtin/cogview/cogview.py @@ -0,0 +1,27 @@ +""" Provide the input parameters type for the cogview provider class """ +from typing import Any + +from core.tools.errors import ToolProviderCredentialValidationError +from core.tools.provider.builtin.cogview.tools.cogview3 import CogView3Tool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class COGVIEWProvider(BuiltinToolProviderController): + """ cogview provider """ + def _validate_credentials(self, credentials: dict[str, Any]) -> None: + try: + CogView3Tool().fork_tool_runtime( + runtime={ + "credentials": credentials, + } + ).invoke( + user_id='', + tool_parameters={ + "prompt": "一个城市在水晶瓶中欢快生活的场景,水彩画风格,展现出微观与珠宝般的美丽。", + "size": "square", + "n": 1 + }, + ) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) from e + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/cogview/cogview.yaml b/api/core/tools/provider/builtin/cogview/cogview.yaml new file mode 100644 index 0000000000..374b0e98d9 --- /dev/null +++ b/api/core/tools/provider/builtin/cogview/cogview.yaml @@ -0,0 +1,61 @@ +identity: + author: Waffle + name: cogview + label: + en_US: CogView + zh_Hans: CogView 绘画 + pt_BR: CogView + description: + en_US: CogView art + zh_Hans: CogView 绘画 + pt_BR: CogView art + icon: icon.png + tags: + - image + - productivity +credentials_for_provider: + zhipuai_api_key: + type: secret-input + required: true + label: + en_US: ZhipuAI API key + zh_Hans: ZhipuAI API key + pt_BR: ZhipuAI API key + help: + en_US: Please input your ZhipuAI API key + zh_Hans: 请输入你的 ZhipuAI API key + pt_BR: Please input your ZhipuAI API key + placeholder: + en_US: Please input your ZhipuAI API key + zh_Hans: 请输入你的 ZhipuAI API key + pt_BR: Please input your ZhipuAI API key + zhipuai_organizaion_id: + type: text-input + required: false + label: + en_US: ZhipuAI organization ID + zh_Hans: ZhipuAI organization ID + pt_BR: ZhipuAI organization ID + help: + en_US: Please input your ZhipuAI organization ID + zh_Hans: 请输入你的 ZhipuAI organization ID + pt_BR: Please input your ZhipuAI organization ID + placeholder: + en_US: Please input your ZhipuAI organization ID + zh_Hans: 请输入你的 ZhipuAI organization ID + pt_BR: Please input your ZhipuAI organization ID + zhipuai_base_url: + type: text-input + required: false + label: + en_US: ZhipuAI base URL + zh_Hans: ZhipuAI base URL + pt_BR: ZhipuAI base URL + help: + en_US: Please input your ZhipuAI base URL + zh_Hans: 请输入你的 ZhipuAI base URL + pt_BR: Please input your ZhipuAI base URL + placeholder: + en_US: Please input your ZhipuAI base URL + zh_Hans: 请输入你的 ZhipuAI base URL + pt_BR: Please input your ZhipuAI base URL diff --git a/api/core/tools/provider/builtin/cogview/tools/cogview3.py b/api/core/tools/provider/builtin/cogview/tools/cogview3.py new file mode 100644 index 0000000000..bb2720196f --- /dev/null +++ b/api/core/tools/provider/builtin/cogview/tools/cogview3.py @@ -0,0 +1,69 @@ +import random +from typing import Any, Union + +from core.model_runtime.model_providers.zhipuai.zhipuai_sdk._client import ZhipuAI +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class CogView3Tool(BuiltinTool): + """ CogView3 Tool """ + + def _invoke(self, + user_id: str, + tool_parameters: dict[str, Any] + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + """ + Invoke CogView3 tool + """ + client = ZhipuAI( + base_url=self.runtime.credentials['zhipuai_base_url'], + api_key=self.runtime.credentials['zhipuai_api_key'], + ) + size_mapping = { + 'square': '1024x1024', + 'vertical': '1024x1792', + 'horizontal': '1792x1024', + } + # prompt + prompt = tool_parameters.get('prompt', '') + if not prompt: + return self.create_text_message('Please input prompt') + # get size + print(tool_parameters.get('prompt', 'square')) + size = size_mapping[tool_parameters.get('size', 'square')] + # get n + n = tool_parameters.get('n', 1) + # get quality + quality = tool_parameters.get('quality', 'standard') + if quality not in ['standard', 'hd']: + return self.create_text_message('Invalid quality') + # get style + style = tool_parameters.get('style', 'vivid') + if style not in ['natural', 'vivid']: + return self.create_text_message('Invalid style') + # set extra body + seed_id = tool_parameters.get('seed_id', self._generate_random_id(8)) + extra_body = {'seed': seed_id} + response = client.images.generations( + prompt=prompt, + model="cogview-3", + size=size, + n=n, + extra_body=extra_body, + style=style, + quality=quality, + response_format='b64_json' + ) + result = [] + for image in response.data: + result.append(self.create_image_message(image=image.url)) + result.append(self.create_text_message( + f'\nGenerate image source to Seed ID: {seed_id}')) + return result + + @staticmethod + def _generate_random_id(length=8): + characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + random_id = ''.join(random.choices(characters, k=length)) + return random_id diff --git a/api/core/tools/provider/builtin/cogview/tools/cogview3.yaml b/api/core/tools/provider/builtin/cogview/tools/cogview3.yaml new file mode 100644 index 0000000000..ba0b271a1c --- /dev/null +++ b/api/core/tools/provider/builtin/cogview/tools/cogview3.yaml @@ -0,0 +1,123 @@ +identity: + name: cogview3 + author: Waffle + label: + en_US: CogView 3 + zh_Hans: CogView 3 绘画 + pt_BR: CogView 3 + description: + en_US: CogView 3 is a powerful drawing tool that can draw the image you want based on your prompt + zh_Hans: CogView 3 是一个强大的绘画工具,它可以根据您的提示词绘制出您想要的图像 + pt_BR: CogView 3 is a powerful drawing tool that can draw the image you want based on your prompt +description: + human: + en_US: CogView 3 is a text to image tool + zh_Hans: CogView 3 是一个文本到图像的工具 + pt_BR: CogView 3 is a text to image tool + llm: CogView 3 is a tool used to generate images from text +parameters: + - name: prompt + type: string + required: true + label: + en_US: Prompt + zh_Hans: 提示词 + pt_BR: Prompt + human_description: + en_US: Image prompt, you can check the official documentation of CogView 3 + zh_Hans: 图像提示词,您可以查看CogView 3 的官方文档 + pt_BR: Image prompt, you can check the official documentation of CogView 3 + llm_description: Image prompt of CogView 3, you should describe the image you want to generate as a list of words as possible as detailed + form: llm + - name: size + type: select + required: true + human_description: + en_US: selecting the image size + zh_Hans: 选择图像大小 + pt_BR: selecting the image size + label: + en_US: Image size + zh_Hans: 图像大小 + pt_BR: Image size + form: form + options: + - value: square + label: + en_US: Squre(1024x1024) + zh_Hans: 方(1024x1024) + pt_BR: Squre(1024x1024) + - value: vertical + label: + en_US: Vertical(1024x1792) + zh_Hans: 竖屏(1024x1792) + pt_BR: Vertical(1024x1792) + - value: horizontal + label: + en_US: Horizontal(1792x1024) + zh_Hans: 横屏(1792x1024) + pt_BR: Horizontal(1792x1024) + default: square + - name: n + type: number + required: true + human_description: + en_US: selecting the number of images + zh_Hans: 选择图像数量 + pt_BR: selecting the number of images + label: + en_US: Number of images + zh_Hans: 图像数量 + pt_BR: Number of images + form: form + min: 1 + max: 1 + default: 1 + - name: quality + type: select + required: true + human_description: + en_US: selecting the image quality + zh_Hans: 选择图像质量 + pt_BR: selecting the image quality + label: + en_US: Image quality + zh_Hans: 图像质量 + pt_BR: Image quality + form: form + options: + - value: standard + label: + en_US: Standard + zh_Hans: 标准 + pt_BR: Standard + - value: hd + label: + en_US: HD + zh_Hans: 高清 + pt_BR: HD + default: standard + - name: style + type: select + required: true + human_description: + en_US: selecting the image style + zh_Hans: 选择图像风格 + pt_BR: selecting the image style + label: + en_US: Image style + zh_Hans: 图像风格 + pt_BR: Image style + form: form + options: + - value: vivid + label: + en_US: Vivid + zh_Hans: 生动 + pt_BR: Vivid + - value: natural + label: + en_US: Natural + zh_Hans: 自然 + pt_BR: Natural + default: vivid diff --git a/api/core/tools/provider/builtin/firecrawl/firecrawl_appx.py b/api/core/tools/provider/builtin/firecrawl/firecrawl_appx.py index 23cb659652..bfe3e7999d 100644 --- a/api/core/tools/provider/builtin/firecrawl/firecrawl_appx.py +++ b/api/core/tools/provider/builtin/firecrawl/firecrawl_appx.py @@ -1,3 +1,4 @@ +import logging import time from collections.abc import Mapping from typing import Any @@ -5,6 +6,7 @@ from typing import Any import requests from requests.exceptions import HTTPError +logger = logging.getLogger(__name__) class FirecrawlApp: def __init__(self, api_key: str | None = None, base_url: str | None = None): @@ -48,6 +50,7 @@ class FirecrawlApp: headers = self._prepare_headers() data = {'url': url, **kwargs} response = self._request('POST', endpoint, data, headers) + logger.debug(f"Sent request to {endpoint=} body={data}") if response is None: raise HTTPError("Failed to scrape URL after multiple retries") return response @@ -57,6 +60,7 @@ class FirecrawlApp: headers = self._prepare_headers() data = {'query': query, **kwargs} response = self._request('POST', endpoint, data, headers) + logger.debug(f"Sent request to {endpoint=} body={data}") if response is None: raise HTTPError("Failed to perform search after multiple retries") return response @@ -66,8 +70,9 @@ class FirecrawlApp: ): endpoint = f'{self.base_url}/v0/crawl' headers = self._prepare_headers(idempotency_key) - data = {'url': url, **kwargs} + data = {'url': url, **kwargs['params']} response = self._request('POST', endpoint, data, headers) + logger.debug(f"Sent request to {endpoint=} body={data}") if response is None: raise HTTPError("Failed to initiate crawl after multiple retries") job_id: str = response['jobId'] diff --git a/api/core/tools/provider/builtin/jina/tools/jina_reader.py b/api/core/tools/provider/builtin/jina/tools/jina_reader.py index ac06688c18..8409129833 100644 --- a/api/core/tools/provider/builtin/jina/tools/jina_reader.py +++ b/api/core/tools/provider/builtin/jina/tools/jina_reader.py @@ -1,3 +1,4 @@ +import json from typing import Any, Union from yarl import URL @@ -26,6 +27,15 @@ class JinaReaderTool(BuiltinTool): if 'api_key' in self.runtime.credentials and self.runtime.credentials.get('api_key'): headers['Authorization'] = "Bearer " + self.runtime.credentials.get('api_key') + request_params = tool_parameters.get('request_params') + if request_params is not None and request_params != '': + try: + request_params = json.loads(request_params) + if not isinstance(request_params, dict): + raise ValueError("request_params must be a JSON object") + except (json.JSONDecodeError, ValueError) as e: + raise ValueError(f"Invalid request_params: {e}") + target_selector = tool_parameters.get('target_selector') if target_selector is not None and target_selector != '': headers['X-Target-Selector'] = target_selector @@ -53,7 +63,8 @@ class JinaReaderTool(BuiltinTool): response = ssrf_proxy.get( str(URL(self._jina_reader_endpoint + url)), headers=headers, - timeout=(10, 60) + params=request_params, + timeout=(10, 60), ) if tool_parameters.get('summary', False): diff --git a/api/core/tools/provider/builtin/jina/tools/jina_reader.yaml b/api/core/tools/provider/builtin/jina/tools/jina_reader.yaml index 5eb2692ea5..072e7f0528 100644 --- a/api/core/tools/provider/builtin/jina/tools/jina_reader.yaml +++ b/api/core/tools/provider/builtin/jina/tools/jina_reader.yaml @@ -25,6 +25,22 @@ parameters: pt_BR: used for linking to webpages llm_description: url for scraping form: llm + - name: request_params + type: string + required: false + label: + en_US: Request params + zh_Hans: 请求参数 + pt_BR: Request params + human_description: + en_US: | + request parameters, format: {"key1": "value1", "key2": "value2"} + zh_Hans: | + 请求参数,格式:{"key1": "value1", "key2": "value2"} + pt_BR: | + request parameters, format: {"key1": "value1", "key2": "value2"} + llm_description: request parameters + form: llm - name: target_selector type: string required: false diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index 1d600d5efc..3b3ccdad2b 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -239,7 +239,7 @@ class ApiTool(Tool): return int(value) elif property['type'] == 'number': # check if it is a float - if '.' in value: + if '.' in str(value): return float(value) else: return int(value) diff --git a/api/core/tools/tool/dataset_retriever_tool.py b/api/core/tools/tool/dataset_retriever_tool.py index 3e81d84c92..60ea084bcc 100644 --- a/api/core/tools/tool/dataset_retriever_tool.py +++ b/api/core/tools/tool/dataset_retriever_tool.py @@ -93,12 +93,12 @@ class DatasetRetrieverTool(Tool): """ query = tool_parameters.get('query') if not query: - return self.create_text_message(text='please input query') + yield self.create_text_message(text='please input query') + else: + # invoke dataset retriever tool + result = self.retrival_tool._run(query=query) - # invoke dataset retriever tool - result = self.retrival_tool._run(query=query) - - yield self.create_text_message(text=result) + yield self.create_text_message(text=result) def validate_credentials(self, credentials: dict[str, Any], parameters: dict[str, Any]) -> None: """ diff --git a/api/core/workflow/nodes/if_else/entities.py b/api/core/workflow/nodes/if_else/entities.py index 68d51c93be..bc6dce0d3b 100644 --- a/api/core/workflow/nodes/if_else/entities.py +++ b/api/core/workflow/nodes/if_else/entities.py @@ -5,22 +5,34 @@ from pydantic import BaseModel from core.workflow.entities.base_node_data_entities import BaseNodeData +class Condition(BaseModel): + """ + Condition entity + """ + variable_selector: list[str] + comparison_operator: Literal[ + # for string or array + "contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", + # for number + "=", "≠", ">", "<", "≥", "≤", "null", "not null" + ] + value: Optional[str] = None + + class IfElseNodeData(BaseNodeData): """ Answer Node Data. """ - class Condition(BaseModel): - """ - Condition entity - """ - variable_selector: list[str] - comparison_operator: Literal[ - # for string or array - "contains", "not contains", "start with", "end with", "is", "is not", "empty", "not empty", - # for number - "=", "≠", ">", "<", "≥", "≤", "null", "not null" - ] - value: Optional[str] = None - logical_operator: Literal["and", "or"] = "and" - conditions: list[Condition] + class Case(BaseModel): + """ + Case entity representing a single logical condition group + """ + case_id: str + logical_operator: Literal["and", "or"] + conditions: list[Condition] + + logical_operator: Optional[Literal["and", "or"]] = "and" + conditions: Optional[list[Condition]] = None + + cases: Optional[list[Case]] = None diff --git a/api/core/workflow/nodes/if_else/if_else_node.py b/api/core/workflow/nodes/if_else/if_else_node.py index 44a4091a2e..6176a75201 100644 --- a/api/core/workflow/nodes/if_else/if_else_node.py +++ b/api/core/workflow/nodes/if_else/if_else_node.py @@ -4,7 +4,8 @@ from core.workflow.entities.base_node_data_entities import BaseNodeData from core.workflow.entities.node_entities import NodeRunResult, NodeType from core.workflow.entities.variable_pool import VariablePool from core.workflow.nodes.base_node import BaseNode -from core.workflow.nodes.if_else.entities import IfElseNodeData +from core.workflow.nodes.if_else.entities import Condition, IfElseNodeData +from core.workflow.utils.variable_template_parser import VariableTemplateParser from models.workflow import WorkflowNodeExecutionStatus @@ -29,68 +30,48 @@ class IfElseNode(BaseNode): "condition_results": [] } + input_conditions = [] + final_result = False + selected_case_id = None try: - logical_operator = node_data.logical_operator - input_conditions = [] - for condition in node_data.conditions: - actual_value = variable_pool.get_variable_value( - variable_selector=condition.variable_selector + # Check if the new cases structure is used + if node_data.cases: + for case in node_data.cases: + input_conditions, group_result = self.process_conditions(variable_pool, case.conditions) + # Apply the logical operator for the current case + final_result = all(group_result) if case.logical_operator == "and" else any(group_result) + + process_datas["condition_results"].append( + { + "group": case.model_dump(), + "results": group_result, + "final_result": final_result, + } + ) + + # Break if a case passes (logical short-circuit) + if final_result: + selected_case_id = case.case_id # Capture the ID of the passing case + break + + else: + # Fallback to old structure if cases are not defined + input_conditions, group_result = self.process_conditions(variable_pool, node_data.conditions) + + final_result = all(group_result) if node_data.logical_operator == "and" else any(group_result) + + selected_case_id = "true" if final_result else "false" + + process_datas["condition_results"].append( + { + "group": "default", + "results": group_result, + "final_result": final_result + } ) - expected_value = condition.value - - input_conditions.append({ - "actual_value": actual_value, - "expected_value": expected_value, - "comparison_operator": condition.comparison_operator - }) - node_inputs["conditions"] = input_conditions - for input_condition in input_conditions: - actual_value = input_condition["actual_value"] - expected_value = input_condition["expected_value"] - comparison_operator = input_condition["comparison_operator"] - - if comparison_operator == "contains": - compare_result = self._assert_contains(actual_value, expected_value) - elif comparison_operator == "not contains": - compare_result = self._assert_not_contains(actual_value, expected_value) - elif comparison_operator == "start with": - compare_result = self._assert_start_with(actual_value, expected_value) - elif comparison_operator == "end with": - compare_result = self._assert_end_with(actual_value, expected_value) - elif comparison_operator == "is": - compare_result = self._assert_is(actual_value, expected_value) - elif comparison_operator == "is not": - compare_result = self._assert_is_not(actual_value, expected_value) - elif comparison_operator == "empty": - compare_result = self._assert_empty(actual_value) - elif comparison_operator == "not empty": - compare_result = self._assert_not_empty(actual_value) - elif comparison_operator == "=": - compare_result = self._assert_equal(actual_value, expected_value) - elif comparison_operator == "≠": - compare_result = self._assert_not_equal(actual_value, expected_value) - elif comparison_operator == ">": - compare_result = self._assert_greater_than(actual_value, expected_value) - elif comparison_operator == "<": - compare_result = self._assert_less_than(actual_value, expected_value) - elif comparison_operator == "≥": - compare_result = self._assert_greater_than_or_equal(actual_value, expected_value) - elif comparison_operator == "≤": - compare_result = self._assert_less_than_or_equal(actual_value, expected_value) - elif comparison_operator == "null": - compare_result = self._assert_null(actual_value) - elif comparison_operator == "not null": - compare_result = self._assert_not_null(actual_value) - else: - continue - - process_datas["condition_results"].append({ - **input_condition, - "result": compare_result - }) except Exception as e: return NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, @@ -99,21 +80,102 @@ class IfElseNode(BaseNode): error=str(e) ) - if logical_operator == "and": - compare_result = False not in [condition["result"] for condition in process_datas["condition_results"]] - else: - compare_result = True in [condition["result"] for condition in process_datas["condition_results"]] + outputs = {"result": final_result, "selected_case_id": selected_case_id} - return NodeRunResult( + data = NodeRunResult( status=WorkflowNodeExecutionStatus.SUCCEEDED, inputs=node_inputs, process_data=process_datas, - edge_source_handle="false" if not compare_result else "true", - outputs={ - "result": compare_result - } + edge_source_handle=selected_case_id if selected_case_id else "false", # Use case ID or 'default' + outputs=outputs ) + return data + + def evaluate_condition( + self, actual_value: Optional[str | list], expected_value: str, comparison_operator: str + ) -> bool: + """ + Evaluate condition + :param actual_value: actual value + :param expected_value: expected value + :param comparison_operator: comparison operator + + :return: bool + """ + if comparison_operator == "contains": + return self._assert_contains(actual_value, expected_value) + elif comparison_operator == "not contains": + return self._assert_not_contains(actual_value, expected_value) + elif comparison_operator == "start with": + return self._assert_start_with(actual_value, expected_value) + elif comparison_operator == "end with": + return self._assert_end_with(actual_value, expected_value) + elif comparison_operator == "is": + return self._assert_is(actual_value, expected_value) + elif comparison_operator == "is not": + return self._assert_is_not(actual_value, expected_value) + elif comparison_operator == "empty": + return self._assert_empty(actual_value) + elif comparison_operator == "not empty": + return self._assert_not_empty(actual_value) + elif comparison_operator == "=": + return self._assert_equal(actual_value, expected_value) + elif comparison_operator == "≠": + return self._assert_not_equal(actual_value, expected_value) + elif comparison_operator == ">": + return self._assert_greater_than(actual_value, expected_value) + elif comparison_operator == "<": + return self._assert_less_than(actual_value, expected_value) + elif comparison_operator == "≥": + return self._assert_greater_than_or_equal(actual_value, expected_value) + elif comparison_operator == "≤": + return self._assert_less_than_or_equal(actual_value, expected_value) + elif comparison_operator == "null": + return self._assert_null(actual_value) + elif comparison_operator == "not null": + return self._assert_not_null(actual_value) + else: + raise ValueError(f"Invalid comparison operator: {comparison_operator}") + + def process_conditions(self, variable_pool: VariablePool, conditions: list[Condition]): + input_conditions = [] + group_result = [] + + for condition in conditions: + actual_value = variable_pool.get_variable_value( + variable_selector=condition.variable_selector + ) + + if condition.value is not None: + variable_template_parser = VariableTemplateParser(template=condition.value) + expected_value = variable_template_parser.extract_variable_selectors() + variable_selectors = variable_template_parser.extract_variable_selectors() + if variable_selectors: + for variable_selector in variable_selectors: + value = variable_pool.get_variable_value( + variable_selector=variable_selector.value_selector + ) + expected_value = variable_template_parser.format({variable_selector.variable: value}) + else: + expected_value = condition.value + else: + expected_value = None + + comparison_operator = condition.comparison_operator + input_conditions.append( + { + "actual_value": actual_value, + "expected_value": expected_value, + "comparison_operator": comparison_operator + } + ) + + result = self.evaluate_condition(actual_value, expected_value, comparison_operator) + group_result.append(result) + + return input_conditions, group_result + def _assert_contains(self, actual_value: Optional[str | list], expected_value: str) -> bool: """ Assert contains diff --git a/api/extensions/storage/azure_storage.py b/api/extensions/storage/azure_storage.py index a712a8bbdb..af3e7ef849 100644 --- a/api/extensions/storage/azure_storage.py +++ b/api/extensions/storage/azure_storage.py @@ -1,5 +1,4 @@ from collections.abc import Generator -from contextlib import closing from datetime import datetime, timedelta, timezone from azure.storage.blob import AccountSasPermissions, BlobServiceClient, ResourceTypes, generate_account_sas @@ -38,11 +37,9 @@ class AzureStorage(BaseStorage): def generate(filename: str = filename) -> Generator: blob = client.get_blob_client(container=self.bucket_name, blob=filename) - with closing(blob.download_blob()) as blob_stream: - while chunk := blob_stream.readall(): - yield chunk - - return generate() + blob_data = blob.download_blob() + yield from blob_data.chunks() + return generate(filename) def download(self, filename, target_filepath): client = self._sync_client() diff --git a/api/fields/app_fields.py b/api/fields/app_fields.py index 83045f5c64..94d804a919 100644 --- a/api/fields/app_fields.py +++ b/api/fields/app_fields.py @@ -72,6 +72,7 @@ tag_fields = { app_partial_fields = { 'id': fields.String, 'name': fields.String, + 'max_active_requests': fields.Raw(), 'description': fields.String(attribute='desc_or_prompt'), 'mode': fields.String(attribute='mode_compatible_with_agent'), 'icon': fields.String, diff --git a/api/libs/external_api.py b/api/libs/external_api.py index b134fd86a0..677ff0fc5b 100644 --- a/api/libs/external_api.py +++ b/api/libs/external_api.py @@ -6,6 +6,8 @@ from flask_restful import Api, http_status_message from werkzeug.datastructures import Headers from werkzeug.exceptions import HTTPException +from core.errors.error import AppInvokeQuotaExceededError + class ExternalApi(Api): @@ -43,6 +45,13 @@ class ExternalApi(Api): 'message': str(e), 'status': status_code } + elif isinstance(e, AppInvokeQuotaExceededError): + status_code = 429 + default_data = { + 'code': 'too_many_requests', + 'message': str(e), + 'status': status_code + } else: status_code = 500 default_data = { diff --git a/api/migrations/versions/161cadc1af8d_add_dataset_permission_tenant_id.py b/api/migrations/versions/161cadc1af8d_add_dataset_permission_tenant_id.py new file mode 100644 index 0000000000..8907f78117 --- /dev/null +++ b/api/migrations/versions/161cadc1af8d_add_dataset_permission_tenant_id.py @@ -0,0 +1,34 @@ +"""add dataset permission tenant id + +Revision ID: 161cadc1af8d +Revises: 7e6a8693e07a +Create Date: 2024-07-05 14:30:59.472593 + +""" +import sqlalchemy as sa +from alembic import op + +import models as models + +# revision identifiers, used by Alembic. +revision = '161cadc1af8d' +down_revision = '7e6a8693e07a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: + # Step 1: Add column without NOT NULL constraint + op.add_column('dataset_permissions', sa.Column('tenant_id', sa.UUID(), nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: + batch_op.drop_column('tenant_id') + + # ### end Alembic commands ### diff --git a/api/migrations/versions/408176b91ad3_add_max_active_requests.py b/api/migrations/versions/408176b91ad3_add_max_active_requests.py new file mode 100644 index 0000000000..c19a68586f --- /dev/null +++ b/api/migrations/versions/408176b91ad3_add_max_active_requests.py @@ -0,0 +1,33 @@ +"""'add_max_active_requests' + +Revision ID: 408176b91ad3 +Revises: 7e6a8693e07a +Create Date: 2024-07-04 09:25:14.029023 + +""" +import sqlalchemy as sa +from alembic import op + +import models as models + +# revision identifiers, used by Alembic. +revision = '408176b91ad3' +down_revision = '161cadc1af8d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('apps', schema=None) as batch_op: + batch_op.add_column(sa.Column('max_active_requests', sa.Integer(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('apps', schema=None) as batch_op: + batch_op.drop_column('max_active_requests') + + # ### end Alembic commands ### diff --git a/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py b/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py new file mode 100644 index 0000000000..ff53eb65a6 --- /dev/null +++ b/api/migrations/versions/7e6a8693e07a_add_table_dataset_permissions.py @@ -0,0 +1,42 @@ +"""add table dataset_permissions + +Revision ID: 7e6a8693e07a +Revises: 4ff534e1eb11 +Create Date: 2024-06-25 03:20:46.012193 + +""" +import sqlalchemy as sa +from alembic import op + +import models as models + +# revision identifiers, used by Alembic. +revision = '7e6a8693e07a' +down_revision = 'b2602e131636' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('dataset_permissions', + sa.Column('id', models.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False), + sa.Column('dataset_id', models.StringUUID(), nullable=False), + sa.Column('account_id', models.StringUUID(), nullable=False), + sa.Column('has_permission', sa.Boolean(), server_default=sa.text('true'), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP(0)'), nullable=False), + sa.PrimaryKeyConstraint('id', name='dataset_permission_pkey') + ) + with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: + batch_op.create_index('idx_dataset_permissions_account_id', ['account_id'], unique=False) + batch_op.create_index('idx_dataset_permissions_dataset_id', ['dataset_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('dataset_permissions', schema=None) as batch_op: + batch_op.drop_index('idx_dataset_permissions_dataset_id') + batch_op.drop_index('idx_dataset_permissions_account_id') + op.drop_table('dataset_permissions') + # ### end Alembic commands ### diff --git a/api/models/account.py b/api/models/account.py index 3b258c4c82..23e7528d22 100644 --- a/api/models/account.py +++ b/api/models/account.py @@ -80,6 +80,10 @@ class Account(UserMixin, db.Model): self._current_tenant = tenant + @property + def current_role(self): + return self._current_tenant.current_role + def get_status(self) -> AccountStatus: status_str = self.status return AccountStatus(status_str) @@ -110,6 +114,14 @@ class Account(UserMixin, db.Model): def is_editor(self): return TenantAccountRole.is_editing_role(self._current_tenant.current_role) + @property + def is_dataset_editor(self): + return TenantAccountRole.is_dataset_edit_role(self._current_tenant.current_role) + + @property + def is_dataset_operator(self): + return self._current_tenant.current_role == TenantAccountRole.DATASET_OPERATOR + class TenantStatus(str, enum.Enum): NORMAL = 'normal' ARCHIVE = 'archive' @@ -120,10 +132,12 @@ class TenantAccountRole(str, enum.Enum): ADMIN = 'admin' EDITOR = 'editor' NORMAL = 'normal' + DATASET_OPERATOR = 'dataset_operator' @staticmethod def is_valid_role(role: str) -> bool: - return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} + return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, + TenantAccountRole.NORMAL, TenantAccountRole.DATASET_OPERATOR} @staticmethod def is_privileged_role(role: str) -> bool: @@ -131,12 +145,17 @@ class TenantAccountRole(str, enum.Enum): @staticmethod def is_non_owner_role(role: str) -> bool: - return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL} + return role and role in {TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.NORMAL, + TenantAccountRole.DATASET_OPERATOR} @staticmethod def is_editing_role(role: str) -> bool: return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR} + @staticmethod + def is_dataset_edit_role(role: str) -> bool: + return role and role in {TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, + TenantAccountRole.DATASET_OPERATOR} class Tenant(db.Model): __tablename__ = 'tenants' @@ -172,6 +191,7 @@ class TenantAccountJoinRole(enum.Enum): OWNER = 'owner' ADMIN = 'admin' NORMAL = 'normal' + DATASET_OPERATOR = 'dataset_operator' class TenantAccountJoin(db.Model): diff --git a/api/models/dataset.py b/api/models/dataset.py index 672c2be8fa..02d49380bd 100644 --- a/api/models/dataset.py +++ b/api/models/dataset.py @@ -663,3 +663,20 @@ class DatasetCollectionBinding(db.Model): type = db.Column(db.String(40), server_default=db.text("'dataset'::character varying"), nullable=False) collection_name = db.Column(db.String(64), nullable=False) created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) + + +class DatasetPermission(db.Model): + __tablename__ = 'dataset_permissions' + __table_args__ = ( + db.PrimaryKeyConstraint('id', name='dataset_permission_pkey'), + db.Index('idx_dataset_permissions_dataset_id', 'dataset_id'), + db.Index('idx_dataset_permissions_account_id', 'account_id'), + db.Index('idx_dataset_permissions_tenant_id', 'tenant_id') + ) + + id = db.Column(StringUUID, server_default=db.text('uuid_generate_v4()'), primary_key=True) + dataset_id = db.Column(StringUUID, nullable=False) + account_id = db.Column(StringUUID, nullable=False) + tenant_id = db.Column(StringUUID, nullable=False) + has_permission = db.Column(db.Boolean, nullable=False, server_default=db.text('true')) + created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) diff --git a/api/models/model.py b/api/models/model.py index f59e8ebb7c..4d67272c1a 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -74,6 +74,7 @@ class App(db.Model): is_public = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) is_universal = db.Column(db.Boolean, nullable=False, server_default=db.text('false')) tracing = db.Column(db.Text, nullable=True) + max_active_requests = db.Column(db.Integer, nullable=True) created_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text('CURRENT_TIMESTAMP(0)')) diff --git a/api/poetry.lock b/api/poetry.lock index ea99ae09d5..ae7cdbb32d 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -143,6 +143,198 @@ typing-extensions = ">=4" [package.extras] tz = ["backports.zoneinfo"] +[[package]] +name = "alibabacloud-credentials" +version = "0.3.4" +description = "The alibabacloud credentials module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_credentials-0.3.4.tar.gz", hash = "sha256:c15a34fe782c318d4cf24cb041a0385ac4ccd2548e524e5d7fe1cff56a9a6acc"}, +] + +[package.dependencies] +alibabacloud-tea = "*" + +[[package]] +name = "alibabacloud-endpoint-util" +version = "0.0.3" +description = "The endpoint-util module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_endpoint_util-0.0.3.tar.gz", hash = "sha256:8c0efb76fdcc3af4ca716ef24bbce770201a3f83f98c0afcf81655f684b9c7d2"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.0.1" + +[[package]] +name = "alibabacloud-gateway-spi" +version = "0.0.1" +description = "Alibaba Cloud Gateway SPI SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_gateway_spi-0.0.1.tar.gz", hash = "sha256:1b259855708afc3c04d8711d8530c63f7645e1edc0cf97e2fd15461b08e11c30"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.2.0,<1.0.0" + +[[package]] +name = "alibabacloud-gpdb20160503" +version = "3.8.2" +description = "Alibaba Cloud AnalyticDB for PostgreSQL (20160503) SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_gpdb20160503-3.8.2-py3-none-any.whl", hash = "sha256:081977cdd4174c786b303f3c5651026297d84baa0256386be8215ee997cd5c75"}, + {file = "alibabacloud_gpdb20160503-3.8.2.tar.gz", hash = "sha256:c964ca721a05e440a1065e33aa74d456eafe2c8b17f6e0d960d5bb44dfe4bd9c"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.3,<1.0.0" +alibabacloud-openapi-util = ">=0.2.1,<1.0.0" +alibabacloud-openplatform20191219 = ">=2.0.0,<3.0.0" +alibabacloud-oss-sdk = ">=0.1.0,<1.0.0" +alibabacloud-oss-util = ">=0.0.5,<1.0.0" +alibabacloud-tea-fileform = ">=0.0.3,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.10,<1.0.0" +alibabacloud-tea-util = ">=0.3.12,<1.0.0" + +[[package]] +name = "alibabacloud-openapi-util" +version = "0.2.2" +description = "Aliyun Tea OpenApi Library for Python" +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_openapi_util-0.2.2.tar.gz", hash = "sha256:ebbc3906f554cb4bf8f513e43e8a33e8b6a3d4a0ef13617a0e14c3dda8ef52a8"}, +] + +[package.dependencies] +alibabacloud_tea_util = ">=0.0.2" +cryptography = ">=3.0.0" + +[[package]] +name = "alibabacloud-openplatform20191219" +version = "2.0.0" +description = "Alibaba Cloud OpenPlatform (20191219) SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_openplatform20191219-2.0.0-py3-none-any.whl", hash = "sha256:873821c45bca72a6c6ec7a906c9cb21554c122e88893bbac3986934dab30dd36"}, + {file = "alibabacloud_openplatform20191219-2.0.0.tar.gz", hash = "sha256:e67f4c337b7542538746592c6a474bd4ae3a9edccdf62e11a32ca61fad3c9020"}, +] + +[package.dependencies] +alibabacloud-endpoint-util = ">=0.0.3,<1.0.0" +alibabacloud-openapi-util = ">=0.1.6,<1.0.0" +alibabacloud-tea-openapi = ">=0.3.3,<1.0.0" +alibabacloud-tea-util = ">=0.3.6,<1.0.0" + +[[package]] +name = "alibabacloud-oss-sdk" +version = "0.1.0" +description = "Aliyun Tea OSS SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_oss_sdk-0.1.0.tar.gz", hash = "sha256:cc5ce36044bae758047fccb56c0cb6204cbc362d18cc3dd4ceac54c8c0897b8b"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.1.2,<1.0.0" +alibabacloud_oss_util = ">=0.0.5,<1.0.0" +alibabacloud_tea_fileform = ">=0.0.3,<1.0.0" +alibabacloud_tea_util = ">=0.3.1,<1.0.0" +alibabacloud_tea_xml = ">=0.0.2,<1.0.0" + +[[package]] +name = "alibabacloud-oss-util" +version = "0.0.6" +description = "The oss util module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_oss_util-0.0.6.tar.gz", hash = "sha256:d3ecec36632434bd509a113e8cf327dc23e830ac8d9dd6949926f4e334c8b5d6"}, +] + +[package.dependencies] +alibabacloud-tea = "*" + +[[package]] +name = "alibabacloud-tea" +version = "0.3.9" +description = "The tea module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud-tea-0.3.9.tar.gz", hash = "sha256:a9689770003fa9313d1995812f9fe36a2be315e5cdfc8d58de0d96808219ced9"}, + {file = "alibabacloud_tea-0.3.9-py3-none-any.whl", hash = "sha256:402fd2a92e6729f228d8c0300b182f80019edce19d83afa497aeb15fd7947f9a"}, +] + +[package.dependencies] +aiohttp = ">=3.7.0,<4.0.0" +requests = ">=2.21.0,<3.0.0" + +[[package]] +name = "alibabacloud-tea-fileform" +version = "0.0.5" +description = "The tea-fileform module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_tea_fileform-0.0.5.tar.gz", hash = "sha256:fd00a8c9d85e785a7655059e9651f9e91784678881831f60589172387b968ee8"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.0.1" + +[[package]] +name = "alibabacloud-tea-openapi" +version = "0.3.10" +description = "Alibaba Cloud openapi SDK Library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_tea_openapi-0.3.10.tar.gz", hash = "sha256:46e9c54ea857346306cd5c628dc33479349b559179ed2fdb2251dbe6ec9a1cf1"}, +] + +[package.dependencies] +alibabacloud_credentials = ">=0.3.1,<1.0.0" +alibabacloud_gateway_spi = ">=0.0.1,<1.0.0" +alibabacloud_openapi_util = ">=0.2.1,<1.0.0" +alibabacloud_tea_util = ">=0.3.12,<1.0.0" +alibabacloud_tea_xml = ">=0.0.2,<1.0.0" + +[[package]] +name = "alibabacloud-tea-util" +version = "0.3.12" +description = "The tea-util module of alibabaCloud Python SDK." +optional = false +python-versions = ">=3.6" +files = [ + {file = "alibabacloud_tea_util-0.3.12.tar.gz", hash = "sha256:72a2f5a046e5b977ade4202eb4f65b3d70ad707a548e29aacd4a572c2d18d06b"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.3.3" + +[[package]] +name = "alibabacloud-tea-xml" +version = "0.0.2" +description = "The tea-xml module of alibabaCloud Python SDK." +optional = false +python-versions = "*" +files = [ + {file = "alibabacloud_tea_xml-0.0.2.tar.gz", hash = "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8"}, +] + +[package.dependencies] +alibabacloud-tea = ">=0.0.1" + [[package]] name = "aliyun-python-sdk-core" version = "2.15.1" @@ -1206,6 +1398,96 @@ prompt-toolkit = ">=3.0.36" [package.extras] testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"] +[[package]] +name = "clickhouse-connect" +version = "0.7.16" +description = "ClickHouse Database Core Driver for Python, Pandas, and Superset" +optional = false +python-versions = "~=3.8" +files = [ + {file = "clickhouse-connect-0.7.16.tar.gz", hash = "sha256:253a2089efad5729903d00382f73fa8da2cbbfdb118db498cf708ee9f4a2134f"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00413deb9e086aabf661d18ac3a3539f25eb773c3675f49353e0d7e6ef1205fc"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:faadaf206ea7753782db017daedbf592e4edc7c71cb985aad787eb9dc516bf21"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1db8f1168f33fda78adddb733913b211ddf648984d8fef8d934e30df876e5f23"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fa630bf50fb064cc53b7ea5d862066476d3c6074003f6d39d2594fb1a7abf67"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cba9547dad41b2d333458615208a3c7db6f56a63473ffea2c05c44225ffa020"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:480f7856fcf42a21f17886e0b42d70499067c865fc2a0ea7c0eb5c0bdca281a8"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b65f3eb570cbcf9fa383b4e0925d1ceb3efd3deba42a435625cad75b3a9ff7f3"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b78d3cc0fe42374bb9d5a05ba71578dc69f7e4b4c771e86dcf292ae0412265cc"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-win32.whl", hash = "sha256:1cb76b26fcde1ba6a8ae68e1db1f9e42d458879a0d4d2c9843cc998f42f445ac"}, + {file = "clickhouse_connect-0.7.16-cp310-cp310-win_amd64.whl", hash = "sha256:9298b344168271e952ea41021963ca1b81b9b3c38be8b036cb64a2556edbb4b7"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ae39a765735cc6e786e5f9a0dba799e7f8ee0bbd5dfc5d5ff755dfa9dd13855"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f32546f65dd234a49310cda454713a5f7fbc8ba978744e070355c7ea8819a5a"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20865c81a5b378625a528ac8960e08cdca316147f87fad6deb9f16c0d5e5f62f"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609c076261d779703bf29e7a27dafc8283153403ceab1ec23d50eb2acabc4b9d"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e07862e75ac7419c5671384055f11ca5e76dc2c0be4a6f3aed7bf419997184bc"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d5db7da6f20b9a49b288063de9b3224a56634f8cb94d19d435af518ed81872c3"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:955c567ede68a10325045bb2adf1314ff569dfb7e52f6074c18182f3803279f6"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df517bfe23d85f5aeeb17b262c06d0a5c24e0baea09688a96d02dc8589ef8b07"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-win32.whl", hash = "sha256:7f2c6132fc90df6a8318abb9f257c2b777404908b7d168ac08235d516f65a663"}, + {file = "clickhouse_connect-0.7.16-cp311-cp311-win_amd64.whl", hash = "sha256:ca1dba53da86691a11671d846988dc4f6ad02a66f5a0df9a87a46dc4ec9bb0a1"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8f7260073b6ee63e19d442ebb6954bc7741a5ce4ed563eb8074c8c6a0158eca"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b3dd93ada1099cb6df244d79973c811e90a4590685e78e60e8846914b3c261e"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3c3458bce25fe9c10e1dbf82dbeeeb2f04e382130f9811cc3bedf44c2028ca"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcc302390b4ea975efd8d2ca53d295d40dc766179dd5e9fc158e808f01d9280d"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94f6d095d7174c55825e0b5c04b77897a1b2a8a8bbb38f3f773fd3113a7be27"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6b7e2572993ef2e1dee5012875a7a2d08cede319e32ccdd2db90ed26a0d0c037"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e9c35ee425309ed8ef63bae31e1d3c5f35706fa27ae2836e61e7cb9bbe7f00cb"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eb0471d5a32d07eaa37772871ee9e6b5eb37ab907c3c154833824ed68ee4795b"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-win32.whl", hash = "sha256:b531ee18b4ce16f1d2b8f6249859cbd600f7e0f312f80dda8deb969791a90f17"}, + {file = "clickhouse_connect-0.7.16-cp312-cp312-win_amd64.whl", hash = "sha256:38392308344770864843f7f8b914799684c13ce4b272d5a3a55e5512ff8a3ae0"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:052ca80d66e49c94d103c9842d2a5b0ebf4610981b79164660ef6b1bdc4b5e85"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b496059d145c68e956aa10cd04e5c7cb4e97312eb3f7829cec8f4f7024f8ced6"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de1e423fc9c415b9fdcbb6f23eccae981e3f0f0cf142e518efec709bda7c1394"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:555c64719cbc72675d58ea6dfc144fa8064ea1d673a54afd2d54e34c58f17c6b"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0c3c063ab23df8f71a36505880bf5de6c18aee246938d787447e52b4d9d5531"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5ed62e08cfe445d0430b91c26fb276e2a5175e456e9786594fb6e67c9ebd8c6c"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9eb056bd14ca3c1d7e3edd7ca79ea970d45e5e536930dbb6179aeb965d5bc3d"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:54e0a03b685ee6c138954846dafb6ec0e0baf8257f2587c61e34c017f3dc9d63"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-win32.whl", hash = "sha256:d8402c3145387726bd19f916ca2890576be70c4493f030c068f6f03a75addff7"}, + {file = "clickhouse_connect-0.7.16-cp38-cp38-win_amd64.whl", hash = "sha256:70e376d2ebc0f092fae35f7b50ff7296ee8ffd2dda3536238f6c39a5c949d115"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cee4f91ad22401c3b96f5df3f3149ef2894e7c2d00b5abd9da80119e7b6592f7"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3009145f35e9ac2535dbd8fdbdc218abfe0971c9bc9b730eb5c3f6c40faeb5f"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d0ef9f877ffbcb0f526ce9c35c657fc54930d043e45c077d9d886c0f1add727"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc437b3ff2f7991b209b861a89c003ac1971c890775190178438780e967a9d3"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ed836dcee4ac097bd83714abe0af987b1ef767675a555e7643d793164c3f1cc"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4c4e0d173239c0b4594c8703fae5c8ba3241c4e0763a8cf436b94564692671f9"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a17a348dd8c00df343a01128497e8c3a6ae431f13c7a88e363ac12c035316ce0"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:805ae7ad39c043af13e2b5af45abb70330f0907749dc87ad4a2481a4ac209cc6"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-win32.whl", hash = "sha256:38fc6ca1bd73cf4dcebd22fbb8dceda267908ff674fc57fbc23c3b5df9c21ac1"}, + {file = "clickhouse_connect-0.7.16-cp39-cp39-win_amd64.whl", hash = "sha256:3dc67e99e40b5a8bc493a21016830b0f3800006a6038c1fd881f7cae6246cc44"}, + {file = "clickhouse_connect-0.7.16-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7f526fef71bd5265f47915340a6369a5b5685278b72b5aff281cc521a8ec376"}, + {file = "clickhouse_connect-0.7.16-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e00f87ba68bbc63dd32d7a304fd629b759f24b09f88fbc2bac0a9ed1fe7b2938"}, + {file = "clickhouse_connect-0.7.16-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c84f3b64d6bebedcfbbd19e8369b3df2cb7d313afb2a0d64a3e151d344c1c1"}, + {file = "clickhouse_connect-0.7.16-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d104ab78edee26e8cef056e2db83f03e1da918df0946e1ef1ad9a27a024dd0"}, + {file = "clickhouse_connect-0.7.16-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cc1ad53e282ff5b4288fdfcf6df72cda542d9d997de5889d66a1f8e2b9f477f0"}, + {file = "clickhouse_connect-0.7.16-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fddc99322054f5d3df8715ab3724bd36ac636f8ceaed4f5f3f60d377abd22d22"}, + {file = "clickhouse_connect-0.7.16-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:765a2de98197d1b4f6424611ceaca2ae896a1d7093b943403973888cb7c144e6"}, + {file = "clickhouse_connect-0.7.16-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1540e0a93e5f2147400f644606a399c91705066f05d5a91429616ee9812f4521"}, + {file = "clickhouse_connect-0.7.16-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba928c4178b0d4a513e1b0ad32a464ab56cb1bc27736a7f41b32e4eb70eb08d6"}, + {file = "clickhouse_connect-0.7.16-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a17ffc22e905081f002173b30959089de6987fd40c87e7794da9d978d723e610"}, + {file = "clickhouse_connect-0.7.16-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:26df09787232b495285d8358db145b9770f472e2e30147912634c5b56392e73f"}, + {file = "clickhouse_connect-0.7.16-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2a3ce33241441dc7c718c19e31645323e6c5da793d46bbb670fd4e8557b8605"}, + {file = "clickhouse_connect-0.7.16-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29f9dc9cc1f4ec4a333bf119abb5cee13563e89bc990d4d77b8f43cf630e9fb1"}, + {file = "clickhouse_connect-0.7.16-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16a7ada11996a6fa0959c83e2e46ff32773e57eca40eff86176fd62a30054ca"}, + {file = "clickhouse_connect-0.7.16-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ead20e1d4f3c5493dd075b7dc81b5d21be4b876aca6952e1c155824876c621f3"}, +] + +[package.dependencies] +certifi = "*" +lz4 = "*" +pytz = "*" +urllib3 = ">=1.26" +zstandard = "*" + +[package.extras] +arrow = ["pyarrow"] +numpy = ["numpy"] +orjson = ["orjson"] +pandas = ["pandas"] +sqlalchemy = ["sqlalchemy (>1.3.21,<2.0)"] +tzlocal = ["tzlocal (>=4.0)"] + [[package]] name = "clickhouse-driver" version = "0.2.8" @@ -4067,6 +4349,56 @@ html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] source = ["Cython (>=3.0.7)"] +[[package]] +name = "lz4" +version = "4.3.3" +description = "LZ4 Bindings for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "lz4-4.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b891880c187e96339474af2a3b2bfb11a8e4732ff5034be919aa9029484cd201"}, + {file = "lz4-4.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:222a7e35137d7539c9c33bb53fcbb26510c5748779364014235afc62b0ec797f"}, + {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76176492ff082657ada0d0f10c794b6da5800249ef1692b35cf49b1e93e8ef7"}, + {file = "lz4-4.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d18718f9d78182c6b60f568c9a9cec8a7204d7cb6fad4e511a2ef279e4cb05"}, + {file = "lz4-4.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cdc60e21ec70266947a48839b437d46025076eb4b12c76bd47f8e5eb8a75dcc"}, + {file = "lz4-4.3.3-cp310-cp310-win32.whl", hash = "sha256:c81703b12475da73a5d66618856d04b1307e43428a7e59d98cfe5a5d608a74c6"}, + {file = "lz4-4.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:43cf03059c0f941b772c8aeb42a0813d68d7081c009542301637e5782f8a33e2"}, + {file = "lz4-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30e8c20b8857adef7be045c65f47ab1e2c4fabba86a9fa9a997d7674a31ea6b6"}, + {file = "lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7b1839f795315e480fb87d9bc60b186a98e3e5d17203c6e757611ef7dcef61"}, + {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edfd858985c23523f4e5a7526ca6ee65ff930207a7ec8a8f57a01eae506aaee7"}, + {file = "lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e9c410b11a31dbdc94c05ac3c480cb4b222460faf9231f12538d0074e56c563"}, + {file = "lz4-4.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2507ee9c99dbddd191c86f0e0c8b724c76d26b0602db9ea23232304382e1f21"}, + {file = "lz4-4.3.3-cp311-cp311-win32.whl", hash = "sha256:f180904f33bdd1e92967923a43c22899e303906d19b2cf8bb547db6653ea6e7d"}, + {file = "lz4-4.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:b14d948e6dce389f9a7afc666d60dd1e35fa2138a8ec5306d30cd2e30d36b40c"}, + {file = "lz4-4.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e36cd7b9d4d920d3bfc2369840da506fa68258f7bb176b8743189793c055e43d"}, + {file = "lz4-4.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31ea4be9d0059c00b2572d700bf2c1bc82f241f2c3282034a759c9a4d6ca4dc2"}, + {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33c9a6fd20767ccaf70649982f8f3eeb0884035c150c0b818ea660152cf3c809"}, + {file = "lz4-4.3.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca8fccc15e3add173da91be8f34121578dc777711ffd98d399be35487c934bf"}, + {file = "lz4-4.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d84b479ddf39fe3ea05387f10b779155fc0990125f4fb35d636114e1c63a2e"}, + {file = "lz4-4.3.3-cp312-cp312-win32.whl", hash = "sha256:337cb94488a1b060ef1685187d6ad4ba8bc61d26d631d7ba909ee984ea736be1"}, + {file = "lz4-4.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:5d35533bf2cee56f38ced91f766cd0038b6abf46f438a80d50c52750088be93f"}, + {file = "lz4-4.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:363ab65bf31338eb364062a15f302fc0fab0a49426051429866d71c793c23394"}, + {file = "lz4-4.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a136e44a16fc98b1abc404fbabf7f1fada2bdab6a7e970974fb81cf55b636d0"}, + {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abc197e4aca8b63f5ae200af03eb95fb4b5055a8f990079b5bdf042f568469dd"}, + {file = "lz4-4.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56f4fe9c6327adb97406f27a66420b22ce02d71a5c365c48d6b656b4aaeb7775"}, + {file = "lz4-4.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0e822cd7644995d9ba248cb4b67859701748a93e2ab7fc9bc18c599a52e4604"}, + {file = "lz4-4.3.3-cp38-cp38-win32.whl", hash = "sha256:24b3206de56b7a537eda3a8123c644a2b7bf111f0af53bc14bed90ce5562d1aa"}, + {file = "lz4-4.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:b47839b53956e2737229d70714f1d75f33e8ac26e52c267f0197b3189ca6de24"}, + {file = "lz4-4.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6756212507405f270b66b3ff7f564618de0606395c0fe10a7ae2ffcbbe0b1fba"}, + {file = "lz4-4.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee9ff50557a942d187ec85462bb0960207e7ec5b19b3b48949263993771c6205"}, + {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b901c7784caac9a1ded4555258207d9e9697e746cc8532129f150ffe1f6ba0d"}, + {file = "lz4-4.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d9ec061b9eca86e4dcc003d93334b95d53909afd5a32c6e4f222157b50c071"}, + {file = "lz4-4.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4c7bf687303ca47d69f9f0133274958fd672efaa33fb5bcde467862d6c621f0"}, + {file = "lz4-4.3.3-cp39-cp39-win32.whl", hash = "sha256:054b4631a355606e99a42396f5db4d22046a3397ffc3269a348ec41eaebd69d2"}, + {file = "lz4-4.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:eac9af361e0d98335a02ff12fb56caeb7ea1196cf1a49dbf6f17828a131da807"}, + {file = "lz4-4.3.3.tar.gz", hash = "sha256:01fe674ef2889dbb9899d8a67361e0c4a2c833af5aeb37dd505727cf5d2a131e"}, +] + +[package.extras] +docs = ["sphinx (>=1.6.0)", "sphinx-bootstrap-theme"] +flake8 = ["flake8"] +tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"] + [[package]] name = "mailchimp-transactional" version = "1.0.56" @@ -7006,28 +7338,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.4.10" +version = "0.5.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, - {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, - {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, - {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, - {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, - {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, - {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, - {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, + {file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"}, + {file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"}, + {file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"}, + {file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"}, + {file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"}, + {file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"}, + {file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"}, + {file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"}, + {file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"}, ] [[package]] @@ -7169,90 +7502,6 @@ tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] torch = ["safetensors[numpy]", "torch (>=1.10)"] -[[package]] -name = "scikit-learn" -version = "1.2.2" -description = "A set of python modules for machine learning and data mining" -optional = false -python-versions = ">=3.8" -files = [ - {file = "scikit-learn-1.2.2.tar.gz", hash = "sha256:8429aea30ec24e7a8c7ed8a3fa6213adf3814a6efbea09e16e0a0c71e1a1a3d7"}, - {file = "scikit_learn-1.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99cc01184e347de485bf253d19fcb3b1a3fb0ee4cea5ee3c43ec0cc429b6d29f"}, - {file = "scikit_learn-1.2.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e6e574db9914afcb4e11ade84fab084536a895ca60aadea3041e85b8ac963edb"}, - {file = "scikit_learn-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe83b676f407f00afa388dd1fdd49e5c6612e551ed84f3b1b182858f09e987d"}, - {file = "scikit_learn-1.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2642baa0ad1e8f8188917423dd73994bf25429f8893ddbe115be3ca3183584"}, - {file = "scikit_learn-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ad66c3848c0a1ec13464b2a95d0a484fd5b02ce74268eaa7e0c697b904f31d6c"}, - {file = "scikit_learn-1.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfeaf8be72117eb61a164ea6fc8afb6dfe08c6f90365bde2dc16456e4bc8e45f"}, - {file = "scikit_learn-1.2.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:fe0aa1a7029ed3e1dcbf4a5bc675aa3b1bc468d9012ecf6c6f081251ca47f590"}, - {file = "scikit_learn-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:065e9673e24e0dc5113e2dd2b4ca30c9d8aa2fa90f4c0597241c93b63130d233"}, - {file = "scikit_learn-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf036ea7ef66115e0d49655f16febfa547886deba20149555a41d28f56fd6d3c"}, - {file = "scikit_learn-1.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8b0670d4224a3c2d596fd572fb4fa673b2a0ccfb07152688ebd2ea0b8c61025c"}, - {file = "scikit_learn-1.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c710ff9f9936ba8a3b74a455ccf0dcf59b230caa1e9ba0223773c490cab1e51"}, - {file = "scikit_learn-1.2.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:2dd3ffd3950e3d6c0c0ef9033a9b9b32d910c61bd06cb8206303fb4514b88a49"}, - {file = "scikit_learn-1.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44b47a305190c28dd8dd73fc9445f802b6ea716669cfc22ab1eb97b335d238b1"}, - {file = "scikit_learn-1.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:953236889928d104c2ef14027539f5f2609a47ebf716b8cbe4437e85dce42744"}, - {file = "scikit_learn-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:7f69313884e8eb311460cc2f28676d5e400bd929841a2c8eb8742ae78ebf7c20"}, - {file = "scikit_learn-1.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8156db41e1c39c69aa2d8599ab7577af53e9e5e7a57b0504e116cc73c39138dd"}, - {file = "scikit_learn-1.2.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fe175ee1dab589d2e1033657c5b6bec92a8a3b69103e3dd361b58014729975c3"}, - {file = "scikit_learn-1.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d5312d9674bed14f73773d2acf15a3272639b981e60b72c9b190a0cffed5bad"}, - {file = "scikit_learn-1.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea061bf0283bf9a9f36ea3c5d3231ba2176221bbd430abd2603b1c3b2ed85c89"}, - {file = "scikit_learn-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6477eed40dbce190f9f9e9d0d37e020815825b300121307942ec2110302b66a3"}, -] - -[package.dependencies] -joblib = ">=1.1.1" -numpy = ">=1.17.3" -scipy = ">=1.3.2" -threadpoolctl = ">=2.0.0" - -[package.extras] -benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.10.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=4.0.1)", "sphinx-gallery (>=0.7.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] -examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.10.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] -tests = ["black (>=22.3.0)", "flake8 (>=3.8.2)", "matplotlib (>=3.1.3)", "mypy (>=0.961)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=5.3.1)", "pytest-cov (>=2.9.0)", "scikit-image (>=0.16.2)"] - -[[package]] -name = "scipy" -version = "1.14.0" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, - {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, - {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, - {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, - {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, - {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, - {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, - {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - [[package]] name = "sentry-sdk" version = "1.39.2" @@ -7660,17 +7909,6 @@ files = [ [package.dependencies] tencentcloud-sdk-python-common = "3.0.1183" -[[package]] -name = "threadpoolctl" -version = "3.5.0" -description = "threadpoolctl" -optional = false -python-versions = ">=3.8" -files = [ - {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, - {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, -] - [[package]] name = "tidb-vector" version = "0.0.9" @@ -9092,7 +9330,68 @@ docs = ["Sphinx", "repoze.sphinx.autointerface", "sphinx-rtd-theme"] test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +[[package]] +name = "zstandard" +version = "0.22.0" +description = "Zstandard bindings for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zstandard-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275df437ab03f8c033b8a2c181e51716c32d831082d93ce48002a5227ec93019"}, + {file = "zstandard-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ac9957bc6d2403c4772c890916bf181b2653640da98f32e04b96e4d6fb3252a"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe3390c538f12437b859d815040763abc728955a52ca6ff9c5d4ac707c4ad98e"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2"}, + {file = "zstandard-0.22.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e1856c8313bc688d5df069e106a4bc962eef3d13372020cc6e3ebf5e045202"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1a90ba9a4c9c884bb876a14be2b1d216609385efb180393df40e5172e7ecf356"}, + {file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3db41c5e49ef73641d5111554e1d1d3af106410a6c1fb52cf68912ba7a343a0d"}, + {file = "zstandard-0.22.0-cp310-cp310-win32.whl", hash = "sha256:d8593f8464fb64d58e8cb0b905b272d40184eac9a18d83cf8c10749c3eafcd7e"}, + {file = "zstandard-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a4b358947a65b94e2501ce3e078bbc929b039ede4679ddb0460829b12f7375"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08"}, + {file = "zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775"}, + {file = "zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94"}, + {file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88"}, + {file = "zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440"}, + {file = "zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a"}, + {file = "zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c"}, + {file = "zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc"}, + {file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45"}, + {file = "zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2"}, + {file = "zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d75f693bb4e92c335e0645e8845e553cd09dc91616412d1d4650da835b5449df"}, + {file = "zstandard-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36a47636c3de227cd765e25a21dc5dace00539b82ddd99ee36abae38178eff9e"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68953dc84b244b053c0d5f137a21ae8287ecf51b20872eccf8eaac0302d3e3b0"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2612e9bb4977381184bb2463150336d0f7e014d6bb5d4a370f9a372d21916f69"}, + {file = "zstandard-0.22.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23d2b3c2b8e7e5a6cb7922f7c27d73a9a615f0a5ab5d0e03dd533c477de23004"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d43501f5f31e22baf822720d82b5547f8a08f5386a883b32584a185675c8fbf"}, + {file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a493d470183ee620a3df1e6e55b3e4de8143c0ba1b16f3ded83208ea8ddfd91d"}, + {file = "zstandard-0.22.0-cp38-cp38-win32.whl", hash = "sha256:7034d381789f45576ec3f1fa0e15d741828146439228dc3f7c59856c5bcd3292"}, + {file = "zstandard-0.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:d8fff0f0c1d8bc5d866762ae95bd99d53282337af1be9dc0d88506b340e74b73"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fdd53b806786bd6112d97c1f1e7841e5e4daa06810ab4b284026a1a0e484c0b"}, + {file = "zstandard-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73a1d6bd01961e9fd447162e137ed949c01bdb830dfca487c4a14e9742dccc93"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9501f36fac6b875c124243a379267d879262480bf85b1dbda61f5ad4d01b75a3"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f260e4c7294ef275744210a4010f116048e0c95857befb7462e033f09442fe"}, + {file = "zstandard-0.22.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959665072bd60f45c5b6b5d711f15bdefc9849dd5da9fb6c873e35f5d34d8cfb"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d22fdef58976457c65e2796e6730a3ea4a254f3ba83777ecfc8592ff8d77d303"}, + {file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7ccf5825fd71d4542c8ab28d4d482aace885f5ebe4b40faaa290eed8e095a4c"}, + {file = "zstandard-0.22.0-cp39-cp39-win32.whl", hash = "sha256:f058a77ef0ece4e210bb0450e68408d4223f728b109764676e1a13537d056bb0"}, + {file = "zstandard-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:e9e9d4e2e336c529d4c435baad846a181e39a982f823f7e4495ec0b0ec8538d2"}, + {file = "zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70"}, +] + +[package.dependencies] +cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""} + +[package.extras] +cffi = ["cffi (>=1.11)"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a31e1524d35da47f63f5e8d24236cbe14585e6a5d9edf9b734d517d24f83e287" +content-hash = "76374a3483905c3219821ec31cefd75c64e0ccb3f0c5424acf1b8a1322587411" diff --git a/api/pyproject.toml b/api/pyproject.toml index 3c633675e2..88721b5a35 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -49,7 +49,7 @@ ignore = [ "B006", # mutable-argument-default "B007", # unused-loop-control-variable "B026", # star-arg-unpacking-after-keyword-arg - "B901", # return-in-generator +# "B901", # return-in-generator "B904", # raise-without-from-inside-except "B905", # zip-without-explicit-strict ] @@ -163,7 +163,6 @@ redis = { version = "~5.0.3", extras = ["hiredis"] } replicate = "~0.22.0" resend = "~0.7.0" safetensors = "~0.4.3" -scikit-learn = "1.2.2" sentry-sdk = { version = "~1.39.2", extras = ["flask"] } sqlalchemy = "~2.0.29" tencentcloud-sdk-python-hunyuan = "~3.0.1158" @@ -210,6 +209,9 @@ tcvectordb = "1.3.2" tidb-vector = "0.0.9" qdrant-client = "1.7.3" weaviate-client = "~3.21.0" +alibabacloud_gpdb20160503 = "~3.8.0" +alibabacloud_tea_openapi = "~0.3.9" +clickhouse-connect = "~0.7.16" ############################################################ # Transparent dependencies required by main dependencies @@ -245,5 +247,5 @@ pytest-mock = "~3.14.0" optional = true [tool.poetry.group.lint.dependencies] -ruff = "~0.4.8" -dotenv-linter = "~0.5.0" \ No newline at end of file +ruff = "~0.5.1" +dotenv-linter = "~0.5.0" diff --git a/api/schedule/clean_embedding_cache_task.py b/api/schedule/clean_embedding_cache_task.py index 0daf651d2f..3d49b487c6 100644 --- a/api/schedule/clean_embedding_cache_task.py +++ b/api/schedule/clean_embedding_cache_task.py @@ -2,10 +2,10 @@ import datetime import time import click -from flask import current_app from werkzeug.exceptions import NotFound import app +from configs import dify_config from extensions.ext_database import db from models.dataset import Embedding @@ -13,7 +13,7 @@ from models.dataset import Embedding @app.celery.task(queue='dataset') def clean_embedding_cache_task(): click.echo(click.style('Start clean embedding cache.', fg='green')) - clean_days = int(current_app.config.get('CLEAN_DAY_SETTING')) + clean_days = int(dify_config.CLEAN_DAY_SETTING) start_at = time.perf_counter() thirty_days_ago = datetime.datetime.now() - datetime.timedelta(days=clean_days) page = 1 diff --git a/api/schedule/clean_unused_datasets_task.py b/api/schedule/clean_unused_datasets_task.py index cdcb3121b9..2033791ace 100644 --- a/api/schedule/clean_unused_datasets_task.py +++ b/api/schedule/clean_unused_datasets_task.py @@ -2,10 +2,10 @@ import datetime import time import click -from flask import current_app from werkzeug.exceptions import NotFound import app +from configs import dify_config from core.rag.index_processor.index_processor_factory import IndexProcessorFactory from extensions.ext_database import db from models.dataset import Dataset, DatasetQuery, Document @@ -14,7 +14,7 @@ from models.dataset import Dataset, DatasetQuery, Document @app.celery.task(queue='dataset') def clean_unused_datasets_task(): click.echo(click.style('Start clean unused datasets indexes.', fg='green')) - clean_days = int(current_app.config.get('CLEAN_DAY_SETTING')) + clean_days = int(dify_config.CLEAN_DAY_SETTING) start_at = time.perf_counter() thirty_days_ago = datetime.datetime.now() - datetime.timedelta(days=clean_days) page = 1 diff --git a/api/services/account_service.py b/api/services/account_service.py index 36c24ef7bf..0bcbe8b2c0 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -6,10 +6,10 @@ from datetime import datetime, timedelta, timezone from hashlib import sha256 from typing import Any, Optional -from flask import current_app from sqlalchemy import func from werkzeug.exceptions import Unauthorized +from configs import dify_config from constants.languages import language_timezone_mapping, languages from events.tenant_event import tenant_was_created from extensions.ext_redis import redis_client @@ -80,7 +80,7 @@ class AccountService: payload = { "user_id": account.id, "exp": datetime.now(timezone.utc).replace(tzinfo=None) + exp, - "iss": current_app.config['EDITION'], + "iss": dify_config.EDITION, "sub": 'Console API Passport', } @@ -369,6 +369,28 @@ class TenantService: return updated_accounts + @staticmethod + def get_dataset_operator_members(tenant: Tenant) -> list[Account]: + """Get dataset admin members""" + query = ( + db.session.query(Account, TenantAccountJoin.role) + .select_from(Account) + .join( + TenantAccountJoin, Account.id == TenantAccountJoin.account_id + ) + .filter(TenantAccountJoin.tenant_id == tenant.id) + .filter(TenantAccountJoin.role == 'dataset_operator') + ) + + # Initialize an empty list to store the updated accounts + updated_accounts = [] + + for account, role in query: + account.role = role + updated_accounts.append(account) + + return updated_accounts + @staticmethod def has_roles(tenant: Tenant, roles: list[TenantAccountJoinRole]) -> bool: """Check if user has any of the given roles for a tenant""" @@ -502,7 +524,7 @@ class RegisterService: TenantService.create_owner_tenant_if_not_exist(account) dify_setup = DifySetup( - version=current_app.config['CURRENT_VERSION'] + version=dify_config.CURRENT_VERSION ) db.session.add(dify_setup) db.session.commit() @@ -537,7 +559,7 @@ class RegisterService: if open_id is not None or provider is not None: AccountService.link_account_integrate(provider, open_id, account) - if current_app.config['EDITION'] != 'SELF_HOSTED': + if dify_config.EDITION != 'SELF_HOSTED': tenant = TenantService.create_tenant(f"{account.name}'s Workspace") TenantService.create_tenant_member(tenant, account, role='owner') @@ -601,7 +623,7 @@ class RegisterService: 'email': account.email, 'workspace_id': tenant.id, } - expiryHours = current_app.config['INVITE_EXPIRY_HOURS'] + expiryHours = dify_config.INVITE_EXPIRY_HOURS redis_client.setex( cls._get_invitation_token_key(token), expiryHours * 60 * 60, diff --git a/api/services/app_generate_service.py b/api/services/app_generate_service.py index f73a88fdd1..e894570b97 100644 --- a/api/services/app_generate_service.py +++ b/api/services/app_generate_service.py @@ -1,12 +1,14 @@ from collections.abc import Generator from typing import Any, Union +from configs import dify_config from core.app.apps.advanced_chat.app_generator import AdvancedChatAppGenerator from core.app.apps.agent_chat.app_generator import AgentChatAppGenerator from core.app.apps.chat.app_generator import ChatAppGenerator from core.app.apps.completion.app_generator import CompletionAppGenerator from core.app.apps.workflow.app_generator import WorkflowAppGenerator from core.app.entities.app_invoke_entities import InvokeFrom +from core.app.features.rate_limiting import RateLimit from models.model import Account, App, AppMode, EndUser from services.workflow_service import WorkflowService @@ -29,52 +31,67 @@ class AppGenerateService: :param streaming: streaming :return: """ - if app_model.mode == AppMode.COMPLETION.value: - return CompletionAppGenerator().generate( - app_model=app_model, - user=user, - args=args, - invoke_from=invoke_from, - stream=streaming - ) - elif app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent: - return AgentChatAppGenerator().generate( - app_model=app_model, - user=user, - args=args, - invoke_from=invoke_from, - stream=streaming - ) - elif app_model.mode == AppMode.CHAT.value: - return ChatAppGenerator().generate( - app_model=app_model, - user=user, - args=args, - invoke_from=invoke_from, - stream=streaming - ) - elif app_model.mode == AppMode.ADVANCED_CHAT.value: - workflow = cls._get_workflow(app_model, invoke_from) - return AdvancedChatAppGenerator().generate( - app_model=app_model, - workflow=workflow, - user=user, - args=args, - invoke_from=invoke_from, - stream=streaming - ) - elif app_model.mode == AppMode.WORKFLOW.value: - workflow = cls._get_workflow(app_model, invoke_from) - return WorkflowAppGenerator().generate( - app_model=app_model, - workflow=workflow, - user=user, - args=args, - invoke_from=invoke_from, - stream=streaming - ) - else: - raise ValueError(f'Invalid app mode {app_model.mode}') + max_active_request = AppGenerateService._get_max_active_requests(app_model) + rate_limit = RateLimit(app_model.id, max_active_request) + request_id = RateLimit.gen_request_key() + try: + request_id = rate_limit.enter(request_id) + if app_model.mode == AppMode.COMPLETION.value: + return rate_limit.generate(CompletionAppGenerator().generate( + app_model=app_model, + user=user, + args=args, + invoke_from=invoke_from, + stream=streaming + ), request_id) + elif app_model.mode == AppMode.AGENT_CHAT.value or app_model.is_agent: + return rate_limit.generate(AgentChatAppGenerator().generate( + app_model=app_model, + user=user, + args=args, + invoke_from=invoke_from, + stream=streaming + ), request_id) + elif app_model.mode == AppMode.CHAT.value: + return rate_limit.generate(ChatAppGenerator().generate( + app_model=app_model, + user=user, + args=args, + invoke_from=invoke_from, + stream=streaming + ), request_id) + elif app_model.mode == AppMode.ADVANCED_CHAT.value: + workflow = cls._get_workflow(app_model, invoke_from) + return rate_limit.generate(AdvancedChatAppGenerator().generate( + app_model=app_model, + workflow=workflow, + user=user, + args=args, + invoke_from=invoke_from, + stream=streaming + ), request_id) + elif app_model.mode == AppMode.WORKFLOW.value: + workflow = cls._get_workflow(app_model, invoke_from) + return rate_limit.generate(WorkflowAppGenerator().generate( + app_model=app_model, + workflow=workflow, + user=user, + args=args, + invoke_from=invoke_from, + stream=streaming + ), request_id) + else: + raise ValueError(f'Invalid app mode {app_model.mode}') + finally: + if not streaming: + rate_limit.exit(request_id) + + @staticmethod + def _get_max_active_requests(app_model: App) -> int: + max_active_requests = app_model.max_active_requests + if app_model.max_active_requests is None: + max_active_requests = int(dify_config.APP_MAX_ACTIVE_REQUESTS) + return max_active_requests @classmethod def generate_single_iteration(cls, app_model: App, diff --git a/api/services/app_service.py b/api/services/app_service.py index 7f5b356772..ca3c8d4fdc 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -4,12 +4,13 @@ from datetime import datetime, timezone from typing import cast import yaml -from flask import current_app from flask_login import current_user from flask_sqlalchemy.pagination import Pagination +from configs import dify_config from constants.model_template import default_app_templates from core.agent.entities import AgentToolEntity +from core.app.features.rate_limiting import RateLimit from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType @@ -123,6 +124,8 @@ class AppService: app.icon = args['icon'] app.icon_background = args['icon_background'] app.tenant_id = tenant_id + app.api_rph = args.get('api_rph', 0) + app.api_rpm = args.get('api_rpm', 0) db.session.add(app) db.session.flush() @@ -322,11 +325,15 @@ class AppService: """ app.name = args.get('name') app.description = args.get('description', '') + app.max_active_requests = args.get('max_active_requests') app.icon = args.get('icon') app.icon_background = args.get('icon_background') app.updated_at = datetime.now(timezone.utc).replace(tzinfo=None) db.session.commit() + if app.max_active_requests is not None: + rate_limit = RateLimit(app.id, app.max_active_requests) + rate_limit.flush_cache(use_local_value=True) return app def update_app_name(self, app: App, name: str) -> App: @@ -439,7 +446,7 @@ class AppService: # get all tools tools = agent_config.get('tools', []) - url_prefix = (current_app.config.get("CONSOLE_API_URL") + url_prefix = (dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/builtin/") for tool in tools: diff --git a/api/services/audio_service.py b/api/services/audio_service.py index 965df918d8..58c950816f 100644 --- a/api/services/audio_service.py +++ b/api/services/audio_service.py @@ -1,11 +1,12 @@ import io +import logging from typing import Optional from werkzeug.datastructures import FileStorage from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType -from models.model import App, AppMode, AppModelConfig +from models.model import App, AppMode, AppModelConfig, Message from services.errors.audio import ( AudioTooLargeServiceError, NoAudioUploadedServiceError, @@ -18,6 +19,8 @@ FILE_SIZE = 30 FILE_SIZE_LIMIT = FILE_SIZE * 1024 * 1024 ALLOWED_EXTENSIONS = ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm', 'amr'] +logger = logging.getLogger(__name__) + class AudioService: @classmethod @@ -64,51 +67,74 @@ class AudioService: return {"text": model_instance.invoke_speech2text(file=buffer, user=end_user)} @classmethod - def transcript_tts(cls, app_model: App, text: str, streaming: bool, - voice: Optional[str] = None, end_user: Optional[str] = None): - if app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]: - workflow = app_model.workflow - if workflow is None: - raise ValueError("TTS is not enabled") + def transcript_tts(cls, app_model: App, text: Optional[str] = None, + voice: Optional[str] = None, end_user: Optional[str] = None, message_id: Optional[str] = None): + from collections.abc import Generator - features_dict = workflow.features_dict - if 'text_to_speech' not in features_dict or not features_dict['text_to_speech'].get('enabled'): - raise ValueError("TTS is not enabled") + from flask import Response, stream_with_context - voice = features_dict['text_to_speech'].get('voice') if voice is None else voice - else: - text_to_speech_dict = app_model.app_model_config.text_to_speech_dict + from app import app + from extensions.ext_database import db - if not text_to_speech_dict.get('enabled'): - raise ValueError("TTS is not enabled") + def invoke_tts(text_content: str, app_model, voice: Optional[str] = None): + with app.app_context(): + if app_model.mode in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]: + workflow = app_model.workflow + if workflow is None: + raise ValueError("TTS is not enabled") - voice = text_to_speech_dict.get('voice') if voice is None else voice + features_dict = workflow.features_dict + if 'text_to_speech' not in features_dict or not features_dict['text_to_speech'].get('enabled'): + raise ValueError("TTS is not enabled") - model_manager = ModelManager() - model_instance = model_manager.get_default_model_instance( - tenant_id=app_model.tenant_id, - model_type=ModelType.TTS - ) - if model_instance is None: - raise ProviderNotSupportTextToSpeechServiceError() - - try: - if not voice: - voices = model_instance.get_tts_voices() - if voices: - voice = voices[0].get('value') + voice = features_dict['text_to_speech'].get('voice') if voice is None else voice else: - raise ValueError("Sorry, no voice available.") + text_to_speech_dict = app_model.app_model_config.text_to_speech_dict - return model_instance.invoke_tts( - content_text=text.strip(), - user=end_user, - streaming=streaming, - tenant_id=app_model.tenant_id, - voice=voice - ) - except Exception as e: - raise e + if not text_to_speech_dict.get('enabled'): + raise ValueError("TTS is not enabled") + + voice = text_to_speech_dict.get('voice') if voice is None else voice + + model_manager = ModelManager() + model_instance = model_manager.get_default_model_instance( + tenant_id=app_model.tenant_id, + model_type=ModelType.TTS + ) + try: + if not voice: + voices = model_instance.get_tts_voices() + if voices: + voice = voices[0].get('value') + else: + raise ValueError("Sorry, no voice available.") + + return model_instance.invoke_tts( + content_text=text_content.strip(), + user=end_user, + tenant_id=app_model.tenant_id, + voice=voice + ) + except Exception as e: + raise e + + if message_id: + message = db.session.query(Message).filter( + Message.id == message_id + ).first() + if message.answer == '' and message.status == 'normal': + return None + + else: + response = invoke_tts(message.answer, app_model=app_model, voice=voice) + if isinstance(response, Generator): + return Response(stream_with_context(response), content_type='audio/mpeg') + return response + else: + response = invoke_tts(text, app_model, voice) + if isinstance(response, Generator): + return Response(stream_with_context(response), content_type='audio/mpeg') + return response @classmethod def transcript_tts_voices(cls, tenant_id: str, language: str): diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 6207a1a45c..fbaf44c9a4 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -6,10 +6,10 @@ import time import uuid from typing import Optional -from flask import current_app from flask_login import current_user from sqlalchemy import func +from configs import dify_config from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType @@ -21,11 +21,12 @@ from events.document_event import document_was_deleted from extensions.ext_database import db from extensions.ext_redis import redis_client from libs import helper -from models.account import Account +from models.account import Account, TenantAccountRole from models.dataset import ( AppDatasetJoin, Dataset, DatasetCollectionBinding, + DatasetPermission, DatasetProcessRule, DatasetQuery, Document, @@ -56,22 +57,55 @@ class DatasetService: @staticmethod def get_datasets(page, per_page, provider="vendor", tenant_id=None, user=None, search=None, tag_ids=None): + query = Dataset.query.filter(Dataset.provider == provider, Dataset.tenant_id == tenant_id).order_by( + Dataset.created_at.desc() + ) + if user: - permission_filter = db.or_(Dataset.created_by == user.id, - Dataset.permission == 'all_team_members') + # get permitted dataset ids + dataset_permission = DatasetPermission.query.filter_by( + account_id=user.id, + tenant_id=tenant_id + ).all() + permitted_dataset_ids = {dp.dataset_id for dp in dataset_permission} if dataset_permission else None + + if user.current_role == TenantAccountRole.DATASET_OPERATOR: + # only show datasets that the user has permission to access + if permitted_dataset_ids: + query = query.filter(Dataset.id.in_(permitted_dataset_ids)) + else: + return [], 0 + else: + # show all datasets that the user has permission to access + if permitted_dataset_ids: + query = query.filter( + db.or_( + Dataset.permission == 'all_team_members', + db.and_(Dataset.permission == 'only_me', Dataset.created_by == user.id), + db.and_(Dataset.permission == 'partial_members', Dataset.id.in_(permitted_dataset_ids)) + ) + ) + else: + query = query.filter( + db.or_( + Dataset.permission == 'all_team_members', + db.and_(Dataset.permission == 'only_me', Dataset.created_by == user.id) + ) + ) else: - permission_filter = Dataset.permission == 'all_team_members' - query = Dataset.query.filter( - db.and_(Dataset.provider == provider, Dataset.tenant_id == tenant_id, permission_filter)) \ - .order_by(Dataset.created_at.desc()) + # if no user, only show datasets that are shared with all team members + query = query.filter(Dataset.permission == 'all_team_members') + if search: - query = query.filter(db.and_(Dataset.name.ilike(f'%{search}%'))) + query = query.filter(Dataset.name.ilike(f'%{search}%')) + if tag_ids: target_ids = TagService.get_target_ids_by_tag_ids('knowledge', tenant_id, tag_ids) if target_ids: - query = query.filter(db.and_(Dataset.id.in_(target_ids))) + query = query.filter(Dataset.id.in_(target_ids)) else: return [], 0 + datasets = query.paginate( page=page, per_page=per_page, @@ -102,9 +136,12 @@ class DatasetService: @staticmethod def get_datasets_by_ids(ids, tenant_id): - datasets = Dataset.query.filter(Dataset.id.in_(ids), - Dataset.tenant_id == tenant_id).paginate( - page=1, per_page=len(ids), max_per_page=len(ids), error_out=False) + datasets = Dataset.query.filter( + Dataset.id.in_(ids), + Dataset.tenant_id == tenant_id + ).paginate( + page=1, per_page=len(ids), max_per_page=len(ids), error_out=False + ) return datasets.items, datasets.total @staticmethod @@ -112,7 +149,8 @@ class DatasetService: # check if dataset name already exists if Dataset.query.filter_by(name=name, tenant_id=tenant_id).first(): raise DatasetNameDuplicateError( - f'Dataset with name {name} already exists.') + f'Dataset with name {name} already exists.' + ) embedding_model = None if indexing_technique == 'high_quality': model_manager = ModelManager() @@ -151,13 +189,17 @@ class DatasetService: except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider.") + "in the Settings -> Model Provider." + ) except ProviderTokenNotInitError as ex: - raise ValueError(f"The dataset in unavailable, due to: " - f"{ex.description}") + raise ValueError( + f"The dataset in unavailable, due to: " + f"{ex.description}" + ) @staticmethod def update_dataset(dataset_id, data, user): + data.pop('partial_member_list', None) filtered_data = {k: v for k, v in data.items() if v is not None or k == 'description'} dataset = DatasetService.get_dataset(dataset_id) DatasetService.check_dataset_permission(dataset, user) @@ -190,12 +232,13 @@ class DatasetService: except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider.") + "in the Settings -> Model Provider." + ) except ProviderTokenNotInitError as ex: raise ValueError(ex.description) else: if data['embedding_model_provider'] != dataset.embedding_model_provider or \ - data['embedding_model'] != dataset.embedding_model: + data['embedding_model'] != dataset.embedding_model: action = 'update' try: model_manager = ModelManager() @@ -215,7 +258,8 @@ class DatasetService: except LLMBadRequestError: raise ValueError( "No Embedding Model available. Please configure a valid provider " - "in the Settings -> Model Provider.") + "in the Settings -> Model Provider." + ) except ProviderTokenNotInitError as ex: raise ValueError(ex.description) @@ -259,14 +303,41 @@ class DatasetService: def check_dataset_permission(dataset, user): if dataset.tenant_id != user.current_tenant_id: logging.debug( - f'User {user.id} does not have permission to access dataset {dataset.id}') + f'User {user.id} does not have permission to access dataset {dataset.id}' + ) raise NoPermissionError( - 'You do not have permission to access this dataset.') + 'You do not have permission to access this dataset.' + ) if dataset.permission == 'only_me' and dataset.created_by != user.id: logging.debug( - f'User {user.id} does not have permission to access dataset {dataset.id}') + f'User {user.id} does not have permission to access dataset {dataset.id}' + ) raise NoPermissionError( - 'You do not have permission to access this dataset.') + 'You do not have permission to access this dataset.' + ) + if dataset.permission == 'partial_members': + user_permission = DatasetPermission.query.filter_by( + dataset_id=dataset.id, account_id=user.id + ).first() + if not user_permission and dataset.tenant_id != user.current_tenant_id and dataset.created_by != user.id: + logging.debug( + f'User {user.id} does not have permission to access dataset {dataset.id}' + ) + raise NoPermissionError( + 'You do not have permission to access this dataset.' + ) + + @staticmethod + def check_dataset_operator_permission(user: Account = None, dataset: Dataset = None): + if dataset.permission == 'only_me': + if dataset.created_by != user.id: + raise NoPermissionError('You do not have permission to access this dataset.') + + elif dataset.permission == 'partial_members': + if not any( + dp.dataset_id == dataset.id for dp in DatasetPermission.query.filter_by(account_id=user.id).all() + ): + raise NoPermissionError('You do not have permission to access this dataset.') @staticmethod def get_dataset_queries(dataset_id: str, page: int, per_page: int): @@ -547,6 +618,7 @@ class DocumentService: redis_client.setex(sync_indexing_cache_key, 600, 1) sync_website_document_indexing_task.delay(dataset_id, document.id) + @staticmethod def get_documents_position(dataset_id): document = Document.query.filter_by(dataset_id=dataset_id).order_by(Document.position.desc()).first() @@ -556,9 +628,11 @@ class DocumentService: return 1 @staticmethod - def save_document_with_dataset_id(dataset: Dataset, document_data: dict, - account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, - created_from: str = 'web'): + def save_document_with_dataset_id( + dataset: Dataset, document_data: dict, + account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, + created_from: str = 'web' + ): # check document limit features = FeatureService.get_features(current_user.current_tenant_id) @@ -576,7 +650,7 @@ class DocumentService: elif document_data["data_source"]["type"] == "website_crawl": website_info = document_data["data_source"]['info_list']['website_info_list'] count = len(website_info['urls']) - batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT']) + batch_upload_limit = int(dify_config.BATCH_UPLOAD_LIMIT) if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") @@ -588,7 +662,7 @@ class DocumentService: if not dataset.indexing_technique: if 'indexing_technique' not in document_data \ - or document_data['indexing_technique'] not in Dataset.INDEXING_TECHNIQUE_LIST: + or document_data['indexing_technique'] not in Dataset.INDEXING_TECHNIQUE_LIST: raise ValueError("Indexing technique is required") dataset.indexing_technique = document_data["indexing_technique"] @@ -618,7 +692,8 @@ class DocumentService: } dataset.retrieval_model = document_data.get('retrieval_model') if document_data.get( - 'retrieval_model') else default_retrieval_model + 'retrieval_model' + ) else default_retrieval_model documents = [] batch = time.strftime('%Y%m%d%H%M%S') + str(random.randint(100000, 999999)) @@ -686,12 +761,14 @@ class DocumentService: documents.append(document) duplicate_document_ids.append(document.id) continue - document = DocumentService.build_document(dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, file_name, batch) + document = DocumentService.build_document( + dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, file_name, batch + ) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -732,12 +809,14 @@ class DocumentService: "notion_page_icon": page['page_icon'], "type": page['type'] } - document = DocumentService.build_document(dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, page['page_name'], batch) + document = DocumentService.build_document( + dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, page['page_name'], batch + ) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -759,12 +838,14 @@ class DocumentService: 'only_main_content': website_info.get('only_main_content', False), 'mode': 'crawl', } - document = DocumentService.build_document(dataset, dataset_process_rule.id, - document_data["data_source"]["type"], - document_data["doc_form"], - document_data["doc_language"], - data_source_info, created_from, position, - account, url, batch) + document = DocumentService.build_document( + dataset, dataset_process_rule.id, + document_data["data_source"]["type"], + document_data["doc_form"], + document_data["doc_language"], + data_source_info, created_from, position, + account, url, batch + ) db.session.add(document) db.session.flush() document_ids.append(document.id) @@ -785,13 +866,16 @@ class DocumentService: can_upload_size = features.documents_upload_quota.limit - features.documents_upload_quota.size if count > can_upload_size: raise ValueError( - f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.') + f'You have reached the limit of your subscription. Only {can_upload_size} documents can be uploaded.' + ) @staticmethod - def build_document(dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str, - document_language: str, data_source_info: dict, created_from: str, position: int, - account: Account, - name: str, batch: str): + def build_document( + dataset: Dataset, process_rule_id: str, data_source_type: str, document_form: str, + document_language: str, data_source_info: dict, created_from: str, position: int, + account: Account, + name: str, batch: str + ): document = Document( tenant_id=dataset.tenant_id, dataset_id=dataset.id, @@ -810,16 +894,20 @@ class DocumentService: @staticmethod def get_tenant_documents_count(): - documents_count = Document.query.filter(Document.completed_at.isnot(None), - Document.enabled == True, - Document.archived == False, - Document.tenant_id == current_user.current_tenant_id).count() + documents_count = Document.query.filter( + Document.completed_at.isnot(None), + Document.enabled == True, + Document.archived == False, + Document.tenant_id == current_user.current_tenant_id + ).count() return documents_count @staticmethod - def update_document_with_dataset_id(dataset: Dataset, document_data: dict, - account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, - created_from: str = 'web'): + def update_document_with_dataset_id( + dataset: Dataset, document_data: dict, + account: Account, dataset_process_rule: Optional[DatasetProcessRule] = None, + created_from: str = 'web' + ): DatasetService.check_dataset_model_setting(dataset) document = DocumentService.get_document(dataset.id, document_data["original_document_id"]) if document.display_status != 'available': @@ -940,7 +1028,7 @@ class DocumentService: elif document_data["data_source"]["type"] == "website_crawl": website_info = document_data["data_source"]['info_list']['website_info_list'] count = len(website_info['urls']) - batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT']) + batch_upload_limit = int(dify_config.BATCH_UPLOAD_LIMIT) if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") @@ -1007,7 +1095,7 @@ class DocumentService: DocumentService.process_rule_args_validate(args) else: if ('data_source' not in args and not args['data_source']) \ - and ('process_rule' not in args and not args['process_rule']): + and ('process_rule' not in args and not args['process_rule']): raise ValueError("Data source or Process rule is required") else: if args.get('data_source'): @@ -1069,7 +1157,7 @@ class DocumentService: raise ValueError("Process rule rules is invalid") if 'pre_processing_rules' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['pre_processing_rules'] is None: + or args['process_rule']['rules']['pre_processing_rules'] is None: raise ValueError("Process rule pre_processing_rules is required") if not isinstance(args['process_rule']['rules']['pre_processing_rules'], list): @@ -1094,21 +1182,21 @@ class DocumentService: args['process_rule']['rules']['pre_processing_rules'] = list(unique_pre_processing_rule_dicts.values()) if 'segmentation' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['segmentation'] is None: + or args['process_rule']['rules']['segmentation'] is None: raise ValueError("Process rule segmentation is required") if not isinstance(args['process_rule']['rules']['segmentation'], dict): raise ValueError("Process rule segmentation is invalid") if 'separator' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['separator']: + or not args['process_rule']['rules']['segmentation']['separator']: raise ValueError("Process rule segmentation separator is required") if not isinstance(args['process_rule']['rules']['segmentation']['separator'], str): raise ValueError("Process rule segmentation separator is invalid") if 'max_tokens' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['max_tokens']: + or not args['process_rule']['rules']['segmentation']['max_tokens']: raise ValueError("Process rule segmentation max_tokens is required") if not isinstance(args['process_rule']['rules']['segmentation']['max_tokens'], int): @@ -1144,7 +1232,7 @@ class DocumentService: raise ValueError("Process rule rules is invalid") if 'pre_processing_rules' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['pre_processing_rules'] is None: + or args['process_rule']['rules']['pre_processing_rules'] is None: raise ValueError("Process rule pre_processing_rules is required") if not isinstance(args['process_rule']['rules']['pre_processing_rules'], list): @@ -1169,21 +1257,21 @@ class DocumentService: args['process_rule']['rules']['pre_processing_rules'] = list(unique_pre_processing_rule_dicts.values()) if 'segmentation' not in args['process_rule']['rules'] \ - or args['process_rule']['rules']['segmentation'] is None: + or args['process_rule']['rules']['segmentation'] is None: raise ValueError("Process rule segmentation is required") if not isinstance(args['process_rule']['rules']['segmentation'], dict): raise ValueError("Process rule segmentation is invalid") if 'separator' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['separator']: + or not args['process_rule']['rules']['segmentation']['separator']: raise ValueError("Process rule segmentation separator is required") if not isinstance(args['process_rule']['rules']['segmentation']['separator'], str): raise ValueError("Process rule segmentation separator is invalid") if 'max_tokens' not in args['process_rule']['rules']['segmentation'] \ - or not args['process_rule']['rules']['segmentation']['max_tokens']: + or not args['process_rule']['rules']['segmentation']['max_tokens']: raise ValueError("Process rule segmentation max_tokens is required") if not isinstance(args['process_rule']['rules']['segmentation']['max_tokens'], int): @@ -1437,12 +1525,16 @@ class SegmentService: class DatasetCollectionBindingService: @classmethod - def get_dataset_collection_binding(cls, provider_name: str, model_name: str, - collection_type: str = 'dataset') -> DatasetCollectionBinding: + def get_dataset_collection_binding( + cls, provider_name: str, model_name: str, + collection_type: str = 'dataset' + ) -> DatasetCollectionBinding: dataset_collection_binding = db.session.query(DatasetCollectionBinding). \ - filter(DatasetCollectionBinding.provider_name == provider_name, - DatasetCollectionBinding.model_name == model_name, - DatasetCollectionBinding.type == collection_type). \ + filter( + DatasetCollectionBinding.provider_name == provider_name, + DatasetCollectionBinding.model_name == model_name, + DatasetCollectionBinding.type == collection_type + ). \ order_by(DatasetCollectionBinding.created_at). \ first() @@ -1458,12 +1550,77 @@ class DatasetCollectionBindingService: return dataset_collection_binding @classmethod - def get_dataset_collection_binding_by_id_and_type(cls, collection_binding_id: str, - collection_type: str = 'dataset') -> DatasetCollectionBinding: + def get_dataset_collection_binding_by_id_and_type( + cls, collection_binding_id: str, + collection_type: str = 'dataset' + ) -> DatasetCollectionBinding: dataset_collection_binding = db.session.query(DatasetCollectionBinding). \ - filter(DatasetCollectionBinding.id == collection_binding_id, - DatasetCollectionBinding.type == collection_type). \ + filter( + DatasetCollectionBinding.id == collection_binding_id, + DatasetCollectionBinding.type == collection_type + ). \ order_by(DatasetCollectionBinding.created_at). \ first() return dataset_collection_binding + + +class DatasetPermissionService: + @classmethod + def get_dataset_partial_member_list(cls, dataset_id): + user_list_query = db.session.query( + DatasetPermission.account_id, + ).filter( + DatasetPermission.dataset_id == dataset_id + ).all() + + user_list = [] + for user in user_list_query: + user_list.append(user.account_id) + + return user_list + + @classmethod + def update_partial_member_list(cls, tenant_id, dataset_id, user_list): + try: + db.session.query(DatasetPermission).filter(DatasetPermission.dataset_id == dataset_id).delete() + permissions = [] + for user in user_list: + permission = DatasetPermission( + tenant_id=tenant_id, + dataset_id=dataset_id, + account_id=user['user_id'], + ) + permissions.append(permission) + + db.session.add_all(permissions) + db.session.commit() + except Exception as e: + db.session.rollback() + raise e + + @classmethod + def check_permission(cls, user, dataset, requested_permission, requested_partial_member_list): + if not user.is_dataset_editor: + raise NoPermissionError('User does not have permission to edit this dataset.') + + if user.is_dataset_operator and dataset.permission != requested_permission: + raise NoPermissionError('Dataset operators cannot change the dataset permissions.') + + if user.is_dataset_operator and requested_permission == 'partial_members': + if not requested_partial_member_list: + raise ValueError('Partial member list is required when setting to partial members.') + + local_member_list = cls.get_dataset_partial_member_list(dataset.id) + request_member_list = [user['user_id'] for user in requested_partial_member_list] + if set(local_member_list) != set(request_member_list): + raise ValueError('Dataset operators cannot change the dataset permissions.') + + @classmethod + def clear_partial_member_list(cls, dataset_id): + try: + db.session.query(DatasetPermission).filter(DatasetPermission.dataset_id == dataset_id).delete() + db.session.commit() + except Exception as e: + db.session.rollback() + raise e diff --git a/api/services/entities/model_provider_entities.py b/api/services/entities/model_provider_entities.py index 853172ea13..e5e4d7e235 100644 --- a/api/services/entities/model_provider_entities.py +++ b/api/services/entities/model_provider_entities.py @@ -1,9 +1,9 @@ from enum import Enum from typing import Optional -from flask import current_app from pydantic import BaseModel, ConfigDict +from configs import dify_config from core.entities.model_entities import ModelWithProviderEntity, ProviderModelWithStatusEntity from core.entities.provider_entities import QuotaConfiguration from core.model_runtime.entities.common_entities import I18nObject @@ -67,7 +67,7 @@ class ProviderResponse(BaseModel): def __init__(self, **data) -> None: super().__init__(**data) - url_prefix = (current_app.config.get("CONSOLE_API_URL") + url_prefix = (dify_config.CONSOLE_API_URL + f"/console/api/workspaces/current/model-providers/{self.provider}") if self.icon_small is not None: self.icon_small = I18nObject( @@ -96,7 +96,7 @@ class ProviderWithModelsResponse(BaseModel): def __init__(self, **data) -> None: super().__init__(**data) - url_prefix = (current_app.config.get("CONSOLE_API_URL") + url_prefix = (dify_config.CONSOLE_API_URL + f"/console/api/workspaces/current/model-providers/{self.provider}") if self.icon_small is not None: self.icon_small = I18nObject( @@ -119,7 +119,7 @@ class SimpleProviderEntityResponse(SimpleProviderEntity): def __init__(self, **data) -> None: super().__init__(**data) - url_prefix = (current_app.config.get("CONSOLE_API_URL") + url_prefix = (dify_config.CONSOLE_API_URL + f"/console/api/workspaces/current/model-providers/{self.provider}") if self.icon_small is not None: self.icon_small = I18nObject( diff --git a/api/services/feature_service.py b/api/services/feature_service.py index 07d1448bf2..83e675a9d2 100644 --- a/api/services/feature_service.py +++ b/api/services/feature_service.py @@ -1,6 +1,6 @@ -from flask import current_app from pydantic import BaseModel, ConfigDict +from configs import dify_config from services.billing_service import BillingService from services.enterprise.enterprise_service import EnterpriseService @@ -30,6 +30,7 @@ class FeatureModel(BaseModel): docs_processing: str = 'standard' can_replace_logo: bool = False model_load_balancing_enabled: bool = False + dataset_operator_enabled: bool = False # pydantic configs model_config = ConfigDict(protected_namespaces=()) @@ -50,7 +51,7 @@ class FeatureService: cls._fulfill_params_from_env(features) - if current_app.config['BILLING_ENABLED']: + if dify_config.BILLING_ENABLED: cls._fulfill_params_from_billing_api(features, tenant_id) return features @@ -59,15 +60,16 @@ class FeatureService: def get_system_features(cls) -> SystemFeatureModel: system_features = SystemFeatureModel() - if current_app.config['ENTERPRISE_ENABLED']: + if dify_config.ENTERPRISE_ENABLED: cls._fulfill_params_from_enterprise(system_features) return system_features @classmethod def _fulfill_params_from_env(cls, features: FeatureModel): - features.can_replace_logo = current_app.config['CAN_REPLACE_LOGO'] - features.model_load_balancing_enabled = current_app.config['MODEL_LB_ENABLED'] + features.can_replace_logo = dify_config.CAN_REPLACE_LOGO + features.model_load_balancing_enabled = dify_config.MODEL_LB_ENABLED + features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED @classmethod def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str): diff --git a/api/services/file_service.py b/api/services/file_service.py index 6c308a09df..c686b190fe 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -4,11 +4,11 @@ import uuid from collections.abc import Generator from typing import Union -from flask import current_app from flask_login import current_user from werkzeug.datastructures import FileStorage from werkzeug.exceptions import NotFound +from configs import dify_config from core.file.upload_file_parser import UploadFileParser from core.rag.extractor.extract_processor import ExtractProcessor from extensions.ext_database import db @@ -35,7 +35,7 @@ class FileService: extension = file.filename.split('.')[-1] if len(filename) > 200: filename = filename.split('.')[0][:200] + '.' + extension - etl_type = current_app.config['ETL_TYPE'] + etl_type = dify_config.ETL_TYPE allowed_extensions = UNSTRUCTURED_ALLOWED_EXTENSIONS + IMAGE_EXTENSIONS if etl_type == 'Unstructured' \ else ALLOWED_EXTENSIONS + IMAGE_EXTENSIONS if extension.lower() not in allowed_extensions: @@ -50,9 +50,9 @@ class FileService: file_size = len(file_content) if extension.lower() in IMAGE_EXTENSIONS: - file_size_limit = current_app.config.get("UPLOAD_IMAGE_FILE_SIZE_LIMIT") * 1024 * 1024 + file_size_limit = dify_config.UPLOAD_IMAGE_FILE_SIZE_LIMIT * 1024 * 1024 else: - file_size_limit = current_app.config.get("UPLOAD_FILE_SIZE_LIMIT") * 1024 * 1024 + file_size_limit = dify_config.UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024 if file_size > file_size_limit: message = f'File size exceeded. {file_size} > {file_size_limit}' @@ -73,10 +73,9 @@ class FileService: storage.save(file_key, file_content) # save file to db - config = current_app.config upload_file = UploadFile( tenant_id=current_tenant_id, - storage_type=config['STORAGE_TYPE'], + storage_type=dify_config.STORAGE_TYPE, key=file_key, name=filename, size=file_size, @@ -106,10 +105,9 @@ class FileService: storage.save(file_key, text.encode('utf-8')) # save file to db - config = current_app.config upload_file = UploadFile( tenant_id=current_user.current_tenant_id, - storage_type=config['STORAGE_TYPE'], + storage_type=dify_config.STORAGE_TYPE, key=file_key, name=text_name + '.txt', size=len(text), @@ -138,7 +136,7 @@ class FileService: # extract text from file extension = upload_file.extension - etl_type = current_app.config['ETL_TYPE'] + etl_type = dify_config.ETL_TYPE allowed_extensions = UNSTRUCTURED_ALLOWED_EXTENSIONS if etl_type == 'Unstructured' else ALLOWED_EXTENSIONS if extension.lower() not in allowed_extensions: raise UnsupportedFileTypeError() diff --git a/api/services/hit_testing_service.py b/api/services/hit_testing_service.py index 0378370d88..9bcf828712 100644 --- a/api/services/hit_testing_service.py +++ b/api/services/hit_testing_service.py @@ -1,9 +1,6 @@ import logging import time -import numpy as np -from sklearn.manifold import TSNE - from core.rag.datasource.retrieval_service import RetrievalService from core.rag.models.document import Document from core.rag.retrieval.retrival_methods import RetrievalMethod @@ -101,29 +98,6 @@ class HitTestingService: "records": records } - @classmethod - def get_tsne_positions_from_embeddings(cls, embeddings: list): - embedding_length = len(embeddings) - if embedding_length <= 1: - return [{'x': 0, 'y': 0}] - - noise = np.random.normal(0, 1e-4, np.array(embeddings).shape) - concatenate_data = np.array(embeddings) + noise - concatenate_data = concatenate_data.reshape(embedding_length, -1) - - perplexity = embedding_length / 2 + 1 - if perplexity >= embedding_length: - perplexity = max(embedding_length - 1, 1) - - tsne = TSNE(n_components=2, perplexity=perplexity, early_exaggeration=12.0) - data_tsne = tsne.fit_transform(concatenate_data) - - tsne_position_data = [] - for i in range(len(data_tsne)): - tsne_position_data.append({'x': float(data_tsne[i][0]), 'y': float(data_tsne[i][1])}) - - return tsne_position_data - @classmethod def hit_testing_args_check(cls, args): query = args['query'] diff --git a/api/services/recommended_app_service.py b/api/services/recommended_app_service.py index d32ab2af33..c4733b6d3f 100644 --- a/api/services/recommended_app_service.py +++ b/api/services/recommended_app_service.py @@ -4,8 +4,8 @@ from os import path from typing import Optional import requests -from flask import current_app +from configs import dify_config from constants.languages import languages from extensions.ext_database import db from models.model import App, RecommendedApp @@ -25,7 +25,7 @@ class RecommendedAppService: :param language: language :return: """ - mode = current_app.config.get('HOSTED_FETCH_APP_TEMPLATES_MODE', 'remote') + mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE if mode == 'remote': try: result = cls._fetch_recommended_apps_from_dify_official(language) @@ -104,7 +104,7 @@ class RecommendedAppService: :param language: language :return: """ - domain = current_app.config.get('HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN', 'https://tmpl.dify.ai') + domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN url = f'{domain}/apps?language={language}' response = requests.get(url, timeout=(3, 10)) if response.status_code != 200: @@ -134,7 +134,7 @@ class RecommendedAppService: :param app_id: app id :return: """ - mode = current_app.config.get('HOSTED_FETCH_APP_TEMPLATES_MODE', 'remote') + mode = dify_config.HOSTED_FETCH_APP_TEMPLATES_MODE if mode == 'remote': try: result = cls._fetch_recommended_app_detail_from_dify_official(app_id) @@ -157,7 +157,7 @@ class RecommendedAppService: :param app_id: App ID :return: """ - domain = current_app.config.get('HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN', 'https://tmpl.dify.ai') + domain = dify_config.HOSTED_FETCH_APP_TEMPLATES_REMOTE_DOMAIN url = f'{domain}/apps/{app_id}' response = requests.get(url, timeout=(3, 10)) if response.status_code != 200: diff --git a/api/services/tools/api_tools_manage_service.py b/api/services/tools/api_tools_manage_service.py index 9a0d6ca8d9..ecc065d521 100644 --- a/api/services/tools/api_tools_manage_service.py +++ b/api/services/tools/api_tools_manage_service.py @@ -429,6 +429,7 @@ class ApiToolManageService: db_provider=provider, decrypt_credentials=True ) + user_provider.labels = labels # add icon ToolTransformService.repack_provider(user_provider) diff --git a/api/services/tools/tools_transform_service.py b/api/services/tools/tools_transform_service.py index 08023a4a92..2372699bd0 100644 --- a/api/services/tools/tools_transform_service.py +++ b/api/services/tools/tools_transform_service.py @@ -3,8 +3,7 @@ import logging from collections.abc import Generator from typing import Optional, Union -from flask import current_app - +from configs import dify_config from core.tools.entities.api_entities import UserTool, UserToolProvider from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_bundle import ApiToolBundle @@ -31,7 +30,7 @@ class ToolTransformService: """ get tool provider icon url """ - url_prefix = (current_app.config.get("CONSOLE_API_URL") + url_prefix = (dify_config.CONSOLE_API_URL + "/console/api/workspaces/current/tool-provider/") if provider_type == ToolProviderType.BUILT_IN.value: diff --git a/api/services/workspace_service.py b/api/services/workspace_service.py index 778b4e51d3..2bcbe5c6f6 100644 --- a/api/services/workspace_service.py +++ b/api/services/workspace_service.py @@ -1,7 +1,7 @@ -from flask import current_app from flask_login import current_user +from configs import dify_config from extensions.ext_database import db from models.account import Tenant, TenantAccountJoin, TenantAccountJoinRole from services.account_service import TenantService @@ -35,7 +35,7 @@ class WorkspaceService: if can_replace_logo and TenantService.has_roles(tenant, [TenantAccountJoinRole.OWNER, TenantAccountJoinRole.ADMIN]): - base_url = current_app.config.get('FILES_URL') + base_url = dify_config.FILES_URL replace_webapp_logo = f'{base_url}/files/workspaces/{tenant.id}/webapp-logo' if tenant.custom_config_dict.get('replace_webapp_logo') else None remove_webapp_brand = tenant.custom_config_dict.get('remove_webapp_brand', False) diff --git a/api/tasks/document_indexing_task.py b/api/tasks/document_indexing_task.py index 43d1cc13f9..cc93a1341e 100644 --- a/api/tasks/document_indexing_task.py +++ b/api/tasks/document_indexing_task.py @@ -4,8 +4,8 @@ import time import click from celery import shared_task -from flask import current_app +from configs import dify_config from core.indexing_runner import DocumentIsPausedException, IndexingRunner from extensions.ext_database import db from models.dataset import Dataset, Document @@ -32,7 +32,7 @@ def document_indexing_task(dataset_id: str, document_ids: list): if features.billing.enabled: vector_space = features.vector_space count = len(document_ids) - batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT']) + batch_upload_limit = int(dify_config.BATCH_UPLOAD_LIMIT) if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") if 0 < vector_space.limit <= vector_space.size: diff --git a/api/tasks/duplicate_document_indexing_task.py b/api/tasks/duplicate_document_indexing_task.py index 1854589e7f..884e222d1b 100644 --- a/api/tasks/duplicate_document_indexing_task.py +++ b/api/tasks/duplicate_document_indexing_task.py @@ -4,8 +4,8 @@ import time import click from celery import shared_task -from flask import current_app +from configs import dify_config from core.indexing_runner import DocumentIsPausedException, IndexingRunner from core.rag.index_processor.index_processor_factory import IndexProcessorFactory from extensions.ext_database import db @@ -33,7 +33,7 @@ def duplicate_document_indexing_task(dataset_id: str, document_ids: list): if features.billing.enabled: vector_space = features.vector_space count = len(document_ids) - batch_upload_limit = int(current_app.config['BATCH_UPLOAD_LIMIT']) + batch_upload_limit = int(dify_config.BATCH_UPLOAD_LIMIT) if count > batch_upload_limit: raise ValueError(f"You have reached the batch upload limit of {batch_upload_limit}.") if 0 < vector_space.limit <= vector_space.size: diff --git a/api/tasks/mail_invite_member_task.py b/api/tasks/mail_invite_member_task.py index 1f40c05077..a46eafa797 100644 --- a/api/tasks/mail_invite_member_task.py +++ b/api/tasks/mail_invite_member_task.py @@ -3,8 +3,9 @@ import time import click from celery import shared_task -from flask import current_app, render_template +from flask import render_template +from configs import dify_config from extensions.ext_mail import mail @@ -29,7 +30,7 @@ def send_invite_member_mail_task(language: str, to: str, token: str, inviter_nam # send invite member mail using different languages try: - url = f'{current_app.config.get("CONSOLE_WEB_URL")}/activate?token={token}' + url = f'{dify_config.CONSOLE_WEB_URL}/activate?token={token}' if language == 'zh-Hans': html_content = render_template('invite_member_mail_template_zh-CN.html', to=to, diff --git a/api/tasks/mail_reset_password_task.py b/api/tasks/mail_reset_password_task.py index 0e64c6f163..4e1b8a8913 100644 --- a/api/tasks/mail_reset_password_task.py +++ b/api/tasks/mail_reset_password_task.py @@ -3,8 +3,9 @@ import time import click from celery import shared_task -from flask import current_app, render_template +from flask import render_template +from configs import dify_config from extensions.ext_mail import mail @@ -24,7 +25,7 @@ def send_reset_password_mail_task(language: str, to: str, token: str): # send reset password mail using different languages try: - url = f'{current_app.config.get("CONSOLE_WEB_URL")}/forgot-password?token={token}' + url = f'{dify_config.CONSOLE_WEB_URL}/forgot-password?token={token}' if language == 'zh-Hans': html_content = render_template('reset_password_mail_template_zh-CN.html', to=to, diff --git a/api/tests/integration_tests/vdb/analyticdb/__init__.py b/api/tests/integration_tests/vdb/analyticdb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/integration_tests/vdb/analyticdb/test_analyticdb.py b/api/tests/integration_tests/vdb/analyticdb/test_analyticdb.py new file mode 100644 index 0000000000..d6067af73b --- /dev/null +++ b/api/tests/integration_tests/vdb/analyticdb/test_analyticdb.py @@ -0,0 +1,31 @@ +from core.rag.datasource.vdb.analyticdb.analyticdb_vector import AnalyticdbConfig, AnalyticdbVector +from tests.integration_tests.vdb.test_vector_store import AbstractVectorTest, setup_mock_redis + + +class AnalyticdbVectorTest(AbstractVectorTest): + def __init__(self): + super().__init__() + # Analyticdb requires collection_name length less than 60. + # it's ok for normal usage. + self.collection_name = self.collection_name.replace("_test", "") + self.vector = AnalyticdbVector( + collection_name=self.collection_name, + config=AnalyticdbConfig( + access_key_id="test_key_id", + access_key_secret="test_key_secret", + region_id="test_region", + instance_id="test_id", + account="test_account", + account_password="test_passwd", + namespace="difytest_namespace", + collection="difytest_collection", + namespace_password="test_passwd", + ), + ) + + def run_all_tests(self): + self.vector.delete() + return super().run_all_tests() + +def test_chroma_vector(setup_mock_redis): + AnalyticdbVectorTest().run_all_tests() \ No newline at end of file diff --git a/api/tests/integration_tests/vdb/myscale/__init__.py b/api/tests/integration_tests/vdb/myscale/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/integration_tests/vdb/myscale/test_myscale.py b/api/tests/integration_tests/vdb/myscale/test_myscale.py new file mode 100644 index 0000000000..b6260d549a --- /dev/null +++ b/api/tests/integration_tests/vdb/myscale/test_myscale.py @@ -0,0 +1,29 @@ +from core.rag.datasource.vdb.myscale.myscale_vector import MyScaleConfig, MyScaleVector +from tests.integration_tests.vdb.test_vector_store import ( + AbstractVectorTest, + setup_mock_redis, +) + + +class MyScaleVectorTest(AbstractVectorTest): + def __init__(self): + super().__init__() + self.vector = MyScaleVector( + collection_name=self.collection_name, + config=MyScaleConfig( + host="localhost", + port=8123, + user="default", + password="", + database="dify", + fts_params="", + ), + ) + + def get_ids_by_metadata_field(self): + ids = self.vector.get_ids_by_metadata_field(key='document_id', value=self.example_doc_id) + assert len(ids) == 1 + + +def test_myscale_vector(setup_mock_redis): + MyScaleVectorTest().run_all_tests() diff --git a/dev/pytest/pytest_vdb.sh b/dev/pytest/pytest_vdb.sh index c954c528fb..cb8aae6740 100755 --- a/dev/pytest/pytest_vdb.sh +++ b/dev/pytest/pytest_vdb.sh @@ -3,6 +3,7 @@ set -x pytest api/tests/integration_tests/vdb/chroma \ api/tests/integration_tests/vdb/milvus \ + api/tests/integration_tests/vdb/myscale \ api/tests/integration_tests/vdb/pgvecto_rs \ api/tests/integration_tests/vdb/pgvector \ api/tests/integration_tests/vdb/qdrant \ diff --git a/dev/reformat b/dev/reformat index ebee1efb40..f50ccb04c4 100755 --- a/dev/reformat +++ b/dev/reformat @@ -2,19 +2,14 @@ set -x -# python style checks rely on `ruff` in path -if ! command -v ruff &> /dev/null; then - echo "Installing Ruff ..." - pip install ruff +# style checks rely on commands in path +if ! command -v ruff &> /dev/null || ! command -v dotenv-linter &> /dev/null; then + echo "Installing linting tools (Ruff, dotenv-linter ...) ..." + poetry install -C api --only lint fi # run ruff linter ruff check --fix ./api -# env files linting relies on `dotenv-linter` in path -if ! command -v dotenv-linter &> /dev/null; then - echo "Installing dotenv-linter ..." - pip install dotenv-linter -fi - +# run dotenv-linter linter dotenv-linter ./api/.env.example ./web/.env.example diff --git a/docker-legacy/docker-compose.yaml b/docker-legacy/docker-compose.yaml index eadaaced2c..416a1b8c52 100644 --- a/docker-legacy/docker-compose.yaml +++ b/docker-legacy/docker-compose.yaml @@ -2,7 +2,7 @@ version: '3' services: # API service api: - image: langgenius/dify-api:0.6.12-fix1 + image: langgenius/dify-api:0.6.13 restart: always environment: # Startup mode, 'api' starts the API server. @@ -39,6 +39,8 @@ services: # File Access Time specifies a time interval in seconds for the file to be accessed. # The default value is 300 seconds. FILES_ACCESS_TIMEOUT: 300 + # The maximum number of active requests for the application, where 0 means unlimited, should be a non-negative integer. + APP_MAX_ACTIVE_REQUESTS: 0 # When enabled, migrations will be executed prior to application startup and the application will start after the migrations have completed. MIGRATION_ENABLED: 'true' # The configurations of postgres database connection. @@ -222,7 +224,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.6.12-fix1 + image: langgenius/dify-api:0.6.13 restart: always environment: CONSOLE_WEB_URL: '' @@ -388,7 +390,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.6.12-fix1 + image: langgenius/dify-web:0.6.13 restart: always environment: # The base URL of console application api server, refers to the Console base URL of WEB service if console domain is diff --git a/docker/.env.example b/docker/.env.example index fd2cbe2b9d..4f7e13e823 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -91,6 +91,9 @@ MIGRATION_ENABLED=true # The default value is 300 seconds. FILES_ACCESS_TIMEOUT=300 +# The maximum number of active requests for the application, where 0 means unlimited, should be a non-negative integer. +APP_MAX_ACTIVE_REQUESTS=0 + # ------------------------------ # Container Startup Related Configuration # Only effective when starting with docker image or docker-compose. @@ -240,7 +243,7 @@ TENCENT_COS_SCHEME=your-scheme # ------------------------------ # The type of vector store to use. -# Supported values are `weaviate`, `qdrant`, `milvus`, `relyt`, `pgvector`, `chroma`, `opensearch`, `tidb_vector`, `oracle`, `tencent`. +# Supported values are `weaviate`, `qdrant`, `milvus`, `myscale`, `relyt`, `pgvector`, `chroma`, `opensearch`, `tidb_vector`, `oracle`, `tencent`. VECTOR_STORE=weaviate # The Weaviate endpoint URL. Only available when VECTOR_STORE is `weaviate`. @@ -271,6 +274,16 @@ MILVUS_PASSWORD=Milvus # The milvus tls switch. MILVUS_SECURE=false +# MyScale configuration, only available when VECTOR_STORE is `myscale` +# For multi-language support, please set MYSCALE_FTS_PARAMS with referring to: +# https://myscale.com/docs/en/text-search/#understanding-fts-index-parameters +MYSCALE_HOST=myscale +MYSCALE_PORT=8123 +MYSCALE_USER=default +MYSCALE_PASSWORD= +MYSCALE_DATABASE=dify +MYSCALE_FTS_PARAMS= + # pgvector configurations, only available when VECTOR_STORE is `pgvecto-rs or pgvector` PGVECTOR_HOST=pgvector PGVECTOR_PORT=5432 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index d947532301..beca104a68 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -12,6 +12,7 @@ x-shared-env: &shared-api-worker-env OPENAI_API_BASE: ${OPENAI_API_BASE:-https://api.openai.com/v1} FILES_URL: ${FILES_URL:-} FILES_ACCESS_TIMEOUT: ${FILES_ACCESS_TIMEOUT:-300} + APP_MAX_ACTIVE_REQUESTS: ${APP_MAX_ACTIVE_REQUESTS:-0} MIGRATION_ENABLED: ${MIGRATION_ENABLED:-true} DEPLOY_ENV: ${DEPLOY_ENV:-PRODUCTION} DIFY_BIND_ADDRESS: ${DIFY_BIND_ADDRESS:-0.0.0.0} @@ -82,6 +83,12 @@ x-shared-env: &shared-api-worker-env MILVUS_USER: ${MILVUS_USER:-root} MILVUS_PASSWORD: ${MILVUS_PASSWORD:-Milvus} MILVUS_SECURE: ${MILVUS_SECURE:-false} + MYSCALE_HOST: ${MYSCALE_HOST:-myscale} + MYSCALE_PORT: ${MYSCALE_PORT:-8123} + MYSCALE_USER: ${MYSCALE_USER:-default} + MYSCALE_PASSWORD: ${MYSCALE_PASSWORD:-} + MYSCALE_DATABASE: ${MYSCALE_DATABASE:-dify} + MYSCALE_FTS_PARAMS: ${MYSCALE_FTS_PARAMS:-} RELYT_HOST: ${RELYT_HOST:-db} RELYT_PORT: ${RELYT_PORT:-5432} RELYT_USER: ${RELYT_USER:-postgres} @@ -108,6 +115,15 @@ x-shared-env: &shared-api-worker-env CHROMA_DATABASE: ${CHROMA_DATABASE:-default_database} CHROMA_AUTH_PROVIDER: ${CHROMA_AUTH_PROVIDER:-chromadb.auth.token_authn.TokenAuthClientProvider} CHROMA_AUTH_CREDENTIALS: ${CHROMA_AUTH_CREDENTIALS:-} + # AnalyticDB configuration + ANALYTICDB_KEY_ID: ${ANALYTICDB_KEY_ID:-} + ANALYTICDB_KEY_SECRET: ${ANALYTICDB_KEY_SECRET:-} + ANALYTICDB_REGION_ID: ${ANALYTICDB_REGION_ID:-} + ANALYTICDB_INSTANCE_ID: ${ANALYTICDB_INSTANCE_ID:-} + ANALYTICDB_ACCOUNT: ${ANALYTICDB_ACCOUNT:-} + ANALYTICDB_PASSWORD: ${ANALYTICDB_PASSWORD:-} + ANALYTICDB_NAMESPACE: ${ANALYTICDB_NAMESPACE:-dify} + ANALYTICDB_NAMESPACE_PASSWORD: ${ANALYTICDB_NAMESPACE_PASSWORD:-} OPENSEARCH_HOST: ${OPENSEARCH_HOST:-opensearch} OPENSEARCH_PORT: ${OPENSEARCH_PORT:-9200} OPENSEARCH_USER: ${OPENSEARCH_USER:-admin} @@ -161,7 +177,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:0.6.12-fix1 + image: langgenius/dify-api:0.6.13 restart: always environment: # Use the shared environment variables. @@ -181,7 +197,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.6.12-fix1 + image: langgenius/dify-api:0.6.13 restart: always environment: # Use the shared environment variables. @@ -200,7 +216,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.6.12-fix1 + image: langgenius/dify-web:0.6.13 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -522,6 +538,21 @@ services: depends_on: - opensearch + # MyScale vector database + myscale: + container_name: myscale + image: myscale/myscaledb:1.6 + profiles: + - myscale + restart: always + tty: true + volumes: + - ./volumes/myscale/data:/var/lib/clickhouse + - ./volumes/myscale/log:/var/log/clickhouse-server + - ./volumes/myscale/config/users.d/custom_users_config.xml:/etc/clickhouse-server/users.d/custom_users_config.xml + ports: + - "${MYSCALE_PORT:-8123}:${MYSCALE_PORT:-8123}" + networks: # create a network between sandbox, api and ssrf_proxy, and can not access outside. ssrf_proxy_network: diff --git a/docker/volumes/myscale/config/users.d/custom_users_config.xml b/docker/volumes/myscale/config/users.d/custom_users_config.xml new file mode 100644 index 0000000000..67f24b69ee --- /dev/null +++ b/docker/volumes/myscale/config/users.d/custom_users_config.xml @@ -0,0 +1,17 @@ + + + + + + ::1 + 127.0.0.1 + 10.0.0.0/8 + 172.16.0.0/12 + 192.168.0.0/16 + + default + default + 1 + + + \ No newline at end of file diff --git a/web/.husky/pre-commit b/web/.husky/pre-commit index 8d1ad1d09f..6df8b24b61 100755 --- a/web/.husky/pre-commit +++ b/web/.husky/pre-commit @@ -27,10 +27,14 @@ if $api_modified; then # python style checks rely on `ruff` in path if ! command -v ruff &> /dev/null; then - echo "Installing Ruff ..." - pip install ruff + echo "Installing linting tools (Ruff, dotenv-linter ...) ..." + poetry install -C api --only lint fi + # run Ruff linter auto-fixing + ruff check --fix ./api + + # run Ruff linter checks ruff check --preview ./api || status=$? status=${status:-0} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx index c51f7071f1..86bee98bcd 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx @@ -3,7 +3,6 @@ import type { FC } from 'react' import { useUnmount } from 'ahooks' import React, { useCallback, useEffect, useState } from 'react' import { usePathname, useRouter } from 'next/navigation' -import cn from 'classnames' import { RiDashboard2Fill, RiDashboard2Line, @@ -17,6 +16,7 @@ import { import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import s from './style.module.css' +import cn from '@/utils/classnames' import { useStore } from '@/app/components/app/store' import AppSideBar from '@/app/components/app-sidebar' import type { NavIcon } from '@/app/components/app-sidebar/navLink' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx index 6b65af0824..977e3f057c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-button.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { PopupProps } from './config-popup' import ConfigPopup from './config-popup' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' import { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx index 6c1f25af9b..287039fd9c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 3b8009c298..88c37d0b12 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -2,13 +2,13 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { usePathname } from 'next/navigation' import { useBoolean } from 'ahooks' import type { LangFuseConfig, LangSmithConfig } from './type' import { TracingProvider } from './type' import TracingIcon from './tracing-icon' import ConfigButton from './config-button' +import cn from '@/utils/classnames' import { LangfuseIcon, LangsmithIcon } from '@/app/components/base/icons/src/public/tracing' import Indicator from '@/app/components/header/indicator' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx index 54b211ab34..120fe29dff 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { TracingProvider } from './type' +import cn from '@/utils/classnames' import { LangfuseIconBig, LangsmithIconBig } from '@/app/components/base/icons/src/public/tracing' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx index 6eb324d923..0f51671b30 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/tracing-icon.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import { TracingIcon as Icon } from '@/app/components/base/icons/src/public/tracing' type Props = { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx index 7164a00be0..211b0b3677 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/layout.tsx @@ -1,11 +1,22 @@ +'use client' import type { FC } from 'react' -import React from 'react' +import React, { useEffect } from 'react' +import { useRouter } from 'next/navigation' +import { useAppContext } from '@/context/app-context' export type IAppDetail = { children: React.ReactNode } const AppDetail: FC = ({ children }) => { + const router = useRouter() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() + + useEffect(() => { + if (isCurrentWorkspaceDatasetOperator) + return router.replace('/datasets') + }, [isCurrentWorkspaceDatasetOperator]) + return ( <> {children} diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index f0007b7e41..53b31af7f0 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -4,9 +4,9 @@ import { useContext, useContextSelector } from 'use-context-selector' import { useRouter } from 'next/navigation' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiMoreFill } from '@remixicon/react' import s from './style.module.css' +import cn from '@/utils/classnames' import type { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' import { ToastContext } from '@/app/components/base/toast' @@ -300,7 +300,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => { /> -
+
} diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index a82ddd74b5..c16512bd50 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -1,6 +1,7 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' +import { useRouter } from 'next/navigation' import useSWRInfinite from 'swr/infinite' import { useTranslation } from 'react-i18next' import { useDebounceFn } from 'ahooks' @@ -50,7 +51,8 @@ const getKey = ( const Apps = () => { const { t } = useTranslation() - const { isCurrentWorkspaceEditor } = useAppContext() + const router = useRouter() + const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'all', @@ -87,6 +89,11 @@ const Apps = () => { } }, []) + useEffect(() => { + if (isCurrentWorkspaceDatasetOperator) + return router.replace('/datasets') + }, [isCurrentWorkspaceDatasetOperator]) + const hasMore = data?.at(-1)?.has_more ?? true useEffect(() => { let observer: IntersectionObserver | undefined diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx index feb4cb0821..76985de34f 100644 --- a/web/app/(commonLayout)/apps/page.tsx +++ b/web/app/(commonLayout)/apps/page.tsx @@ -1,6 +1,6 @@ -import classNames from 'classnames' import style from '../list.module.css' import Apps from './Apps' +import classNames from '@/utils/classnames' import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server' const AppList = async () => { diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index efba20e652..a1543230a9 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -4,7 +4,6 @@ import React, { useEffect } from 'react' import { usePathname } from 'next/navigation' import useSWR from 'swr' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import { useBoolean } from 'ahooks' import { Cog8ToothIcon, @@ -23,6 +22,7 @@ import { } from '@heroicons/react/24/solid' import Link from 'next/link' import s from './style.module.css' +import classNames from '@/utils/classnames' import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' import type { RelatedApp, RelatedAppResponse } from '@/models/datasets' import AppSideBar from '@/app/components/app-sidebar' @@ -38,6 +38,7 @@ import { useStore } from '@/app/components/app/store' import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import { getLocaleOnClient } from '@/i18n' +import { useAppContext } from '@/context/app-context' export type IAppDetailLayoutProps = { children: React.ReactNode @@ -187,6 +188,7 @@ const DatasetDetailLayout: FC = (props) => { const pathname = usePathname() const hideSideBar = /documents\/create$/.test(pathname) const { t } = useTranslation() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() const media = useBreakpoints() const isMobile = media === MediaType.mobile @@ -232,7 +234,7 @@ const DatasetDetailLayout: FC = (props) => { icon_background={datasetRes?.icon_background || '#F5F5F5'} desc={datasetRes?.description || '--'} navigation={navigation} - extraInfo={mode => } + extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => : undefined} iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} />} { const { t } = useTranslation() + const router = useRouter() + const { currentWorkspace } = useAppContext() const showTagManagementModal = useTagStore(s => s.showTagManagementModal) - const options = [ - { value: 'dataset', text: t('dataset.datasets') }, - { value: 'api', text: t('dataset.datasetsApi') }, - ] + const options = useMemo(() => { + return [ + { value: 'dataset', text: t('dataset.datasets') }, + ...(currentWorkspace.role === 'dataset_operator' ? [] : [{ value: 'api', text: t('dataset.datasetsApi') }]), + ] + }, [currentWorkspace.role, t]) const [activeTab, setActiveTab] = useTabSearchParams({ defaultTab: 'dataset', @@ -57,6 +63,11 @@ const Container = () => { handleTagsUpdate() } + useEffect(() => { + if (currentWorkspace.role === 'normal') + return router.replace('/apps') + }, [currentWorkspace]) + return (
diff --git a/web/app/(commonLayout)/datasets/DatasetCard.tsx b/web/app/(commonLayout)/datasets/DatasetCard.tsx index df122bc298..d4b83f8a1f 100644 --- a/web/app/(commonLayout)/datasets/DatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/DatasetCard.tsx @@ -4,10 +4,10 @@ import { useContext } from 'use-context-selector' import Link from 'next/link' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiMoreFill, } from '@remixicon/react' +import cn from '@/utils/classnames' import Confirm from '@/app/components/base/confirm' import { ToastContext } from '@/app/components/base/toast' import { checkIsUsedInApp, deleteDataset } from '@/service/datasets' @@ -20,6 +20,7 @@ import Divider from '@/app/components/base/divider' import RenameDatasetModal from '@/app/components/datasets/rename-modal' import type { Tag } from '@/app/components/base/tag-management/constant' import TagSelector from '@/app/components/base/tag-management/selector' +import { useAppContext } from '@/context/app-context' export type DatasetCardProps = { dataset: DataSet @@ -32,6 +33,7 @@ const DatasetCard = ({ }: DatasetCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) + const { isCurrentWorkspaceDatasetOperator } = useAppContext() const [tags, setTags] = useState(dataset.tags) const [showRenameModal, setShowRenameModal] = useState(false) @@ -61,7 +63,7 @@ const DatasetCard = ({ setShowConfirmDelete(false) }, [dataset.id, notify, onSuccess, t]) - const Operations = (props: HtmlContentProps) => { + const Operations = (props: HtmlContentProps & { showDelete: boolean }) => { const onMouseLeave = async () => { props.onClose?.() } @@ -82,15 +84,19 @@ const DatasetCard = ({
{t('common.operation.settings')}
- -
- - {t('common.operation.delete')} - -
+ {props.showDelete && ( + <> + +
+ + {t('common.operation.delete')} + +
+ + )}
) } @@ -174,7 +180,7 @@ const DatasetCard = ({
} + htmlContent={} position="br" trigger="click" btnElement={ diff --git a/web/app/(commonLayout)/tools/page.tsx b/web/app/(commonLayout)/tools/page.tsx index 066550b3a2..4e64d8c0df 100644 --- a/web/app/(commonLayout)/tools/page.tsx +++ b/web/app/(commonLayout)/tools/page.tsx @@ -1,16 +1,27 @@ 'use client' import type { FC } from 'react' +import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import React, { useEffect } from 'react' import ToolProviderList from '@/app/components/tools/provider-list' +import { useAppContext } from '@/context/app-context' const Layout: FC = () => { const { t } = useTranslation() + const router = useRouter() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() useEffect(() => { document.title = `${t('tools.title')} - Dify` + if (isCurrentWorkspaceDatasetOperator) + return router.replace('/datasets') }, []) + useEffect(() => { + if (isCurrentWorkspaceDatasetOperator) + return router.replace('/datasets') + }, [isCurrentWorkspaceDatasetOperator]) + return } export default React.memo(Layout) diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index 4394cef822..12f4152c6f 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -1,8 +1,8 @@ 'use client' -import cn from 'classnames' import { useRouter, useSearchParams } from 'next/navigation' import type { FC } from 'react' import React, { useEffect } from 'react' +import cn from '@/utils/classnames' import Toast from '@/app/components/base/toast' import { fetchSystemFeatures, fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' import { setAccessToken } from '@/app/components/share/utils' diff --git a/web/app/activate/activateForm.tsx b/web/app/activate/activateForm.tsx index 9004b5f404..3b1eed6f09 100644 --- a/web/app/activate/activateForm.tsx +++ b/web/app/activate/activateForm.tsx @@ -4,10 +4,10 @@ import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useSearchParams } from 'next/navigation' -import cn from 'classnames' import Link from 'next/link' import { CheckCircleIcon } from '@heroicons/react/24/solid' import style from './style.module.css' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { SimpleSelect } from '@/app/components/base/select' diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index d2c2bddac2..90874f50ce 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -1,8 +1,8 @@ import React from 'react' -import cn from 'classnames' import Header from '../signin/_header' import style from '../signin/page.module.css' import ActivateForm from './activateForm' +import cn from '@/utils/classnames' const Activate = () => { return ( diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index c2f3bfc9dd..c931afbe7f 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -1,12 +1,12 @@ import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' import React, { useCallback, useState } from 'react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' import s from './style.module.css' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, @@ -350,7 +350,7 @@ const AppInfo = ({ expand }: IAppInfoProps) => { 'w-full h-[256px] bg-center bg-no-repeat bg-contain rounded-xl', showSwitchTip === 'chat' && s.expertPic, showSwitchTip === 'completion' && s.completionPic, - )}/> + )} />
{showSwitchTip === 'chat' ? t('app.newApp.advanced') : t('app.types.workflow')} diff --git a/web/app/components/app-sidebar/navLink.tsx b/web/app/components/app-sidebar/navLink.tsx index 161b92b7d3..ec5277ce1a 100644 --- a/web/app/components/app-sidebar/navLink.tsx +++ b/web/app/components/app-sidebar/navLink.tsx @@ -1,8 +1,8 @@ 'use client' import { useSelectedLayoutSegment } from 'next/navigation' -import classNames from 'classnames' import Link from 'next/link' +import classNames from '@/utils/classnames' export type NavIcon = React.ComponentType< React.PropsWithoutRef> & { diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index ed84d0e05c..88ce23b9aa 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { RiDeleteBinLine } from '@remixicon/react' +import cn from '@/utils/classnames' import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx index f830755148..63788447de 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import Textarea from 'rc-textarea' -import cn from 'classnames' import { RiDeleteBinLine } from '@remixicon/react' +import cn from '@/utils/classnames' import { Robot, User } from '@/app/components/base/icons/src/public/avatar' import { Edit04 } from '@/app/components/base/icons/src/vender/line/general' import { Edit04 as EditSolid } from '@/app/components/base/icons/src/vender/solid/general' diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index 6268df65f0..ebbb4acef1 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { Fragment, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiAddLine, } from '@remixicon/react' @@ -16,6 +15,7 @@ import AddAnnotationModal from '../add-annotation-modal' import type { AnnotationItemBasic } from '../type' import BatchAddModal from '../batch-add-annotation-modal' import s from './style.module.css' +import cn from '@/utils/classnames' import CustomPopover from '@/app/components/base/popover' import { FileDownload02, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index 8294ae8b26..1e65d7a94f 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -4,7 +4,6 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Pagination } from 'react-headless-pagination' import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline' -import cn from 'classnames' import Toast from '../../base/toast' import Filter from './filter' import type { QueryParam } from './filter' @@ -14,6 +13,7 @@ import HeaderOpts from './header-opts' import s from './style.module.css' import { AnnotationEnableStatus, type AnnotationItem, type AnnotationItemBasic, JobStatus } from './type' import ViewAnnotationModal from './view-annotation-modal' +import cn from '@/utils/classnames' import Switch from '@/app/components/base/switch' import { addAnnotation, delAnnotation, fetchAnnotationConfig as doFetchAnnotationConfig, editAnnotation, fetchAnnotationList, queryAnnotationJobStatus, updateAnnotationScore, updateAnnotationStatus } from '@/service/annotation' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/app/annotation/list.tsx b/web/app/components/app/annotation/list.tsx index e6993fa5cb..bc3a35158f 100644 --- a/web/app/components/app/annotation/list.tsx +++ b/web/app/components/app/annotation/list.tsx @@ -2,12 +2,12 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiDeleteBinLine } from '@remixicon/react' import { Edit02 } from '../../base/icons/src/vender/line/general' import s from './style.module.css' import type { AnnotationItem } from './type' import RemoveAnnotationConfirmModal from './remove-annotation-confirm-modal' +import cn from '@/utils/classnames' import useTimestamp from '@/hooks/use-timestamp' type Props = { diff --git a/web/app/components/app/annotation/view-annotation-modal/index.tsx b/web/app/components/app/annotation/view-annotation-modal/index.tsx index ea7c18a929..3abc477d35 100644 --- a/web/app/components/app/annotation/view-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/view-annotation-modal/index.tsx @@ -2,13 +2,13 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { Pagination } from 'react-headless-pagination' import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline' import EditItem, { EditItemType } from '../edit-annotation-modal/edit-item' import type { AnnotationItem, HitHistoryItem } from '../type' import s from './style.module.css' import HitHistoryNoData from './hit-history-no-data' +import cn from '@/utils/classnames' import Drawer from '@/app/components/base/drawer-plus' import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication' import DeleteConfirmModal from '@/app/components/base/modal/delete-confirm-modal' diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx index 59f1ccca7e..a371eafde0 100644 --- a/web/app/components/app/app-publisher/suggested-action.tsx +++ b/web/app/components/app/app-publisher/suggested-action.tsx @@ -1,5 +1,5 @@ import type { HTMLProps, PropsWithChildren } from 'react' -import classNames from 'classnames' +import classNames from '@/utils/classnames' import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' export type SuggestedActionProps = PropsWithChildren & { diff --git a/web/app/components/app/configuration/base/feature-panel/index.tsx b/web/app/components/app/configuration/base/feature-panel/index.tsx index fbd8543009..1f6db9dee6 100644 --- a/web/app/components/app/configuration/base/feature-panel/index.tsx +++ b/web/app/components/app/configuration/base/feature-panel/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC, ReactNode } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import ParamsConfig from '@/app/components/app/configuration/config-voice/param-config' export type IFeaturePanelProps = { @@ -46,7 +46,7 @@ const FeaturePanel: FC = ({
{headerRight &&
{headerRight}
} {isShowTextToSpeech &&
- +
}
diff --git a/web/app/components/app/configuration/base/icons/remove-icon/index.tsx b/web/app/components/app/configuration/base/icons/remove-icon/index.tsx index 0ce648c0da..e07a462d49 100644 --- a/web/app/components/app/configuration/base/icons/remove-icon/index.tsx +++ b/web/app/components/app/configuration/base/icons/remove-icon/index.tsx @@ -1,6 +1,6 @@ 'use client' import React, { useState } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type IRemoveIconProps = { className?: string diff --git a/web/app/components/app/configuration/base/operation-btn/index.tsx b/web/app/components/app/configuration/base/operation-btn/index.tsx index 47b68c3d49..e9ffd14257 100644 --- a/web/app/components/app/configuration/base/operation-btn/index.tsx +++ b/web/app/components/app/configuration/base/operation-btn/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import { PlusIcon } from '@heroicons/react/20/solid' -import cn from 'classnames' +import cn from '@/utils/classnames' export type IOperationBtnProps = { className?: string diff --git a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx index 00f47328a4..641cdd7e23 100644 --- a/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/advanced-prompt-input.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React from 'react' import copy from 'copy-to-clipboard' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useBoolean } from 'ahooks' @@ -16,6 +15,7 @@ import s from './style.module.css' import MessageTypeSelector from './message-type-selector' import ConfirmAddVar from './confirm-add-var' import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' +import cn from '@/utils/classnames' import type { PromptRole, PromptVariable } from '@/models/debug' import { Clipboard, diff --git a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx index 8e8e08cd9a..d522292f76 100644 --- a/web/app/components/app/configuration/config-prompt/message-type-selector.tsx +++ b/web/app/components/app/configuration/config-prompt/message-type-selector.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useBoolean, useClickAway } from 'ahooks' -import cn from 'classnames' +import cn from '@/utils/classnames' import { PromptRole } from '@/models/debug' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' type Props = { diff --git a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx index 5d696cfda2..5e44e7f256 100644 --- a/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx +++ b/web/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react' import type { FC } from 'react' import { useDebounceFn } from 'ahooks' -import cn from 'classnames' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx index 83e835afc0..a15f538227 100644 --- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx +++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx @@ -3,7 +3,6 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' -import cn from 'classnames' import { RiQuestionLine, } from '@remixicon/react' @@ -12,6 +11,7 @@ import { useContext } from 'use-context-selector' import ConfirmAddVar from './confirm-add-var' import s from './style.module.css' import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap' +import cn from '@/utils/classnames' import { type PromptVariable } from '@/models/debug' import Tooltip from '@/app/components/base/tooltip' import { AppType } from '@/types/app' diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index becd5085a1..20fcf49de1 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -17,7 +17,6 @@ import Switch from '@/app/components/base/switch' import { ChangeType, InputVarType } from '@/app/components/workflow/types' const TEXT_MAX_LENGTH = 256 -const PARAGRAPH_MAX_LENGTH = 1032 * 32 export type IConfigModalProps = { isCreate?: boolean @@ -167,7 +166,7 @@ const ConfigModal: FC = ({ {isStringInput && ( - + )} diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.tsx index e853bdf0c0..bb5e700d11 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' import type { InputVarType } from '@/app/components/workflow/types' import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon' export type ISelectTypeItemProps = { diff --git a/web/app/components/app/configuration/config-var/select-var-type.tsx b/web/app/components/app/configuration/config-var/select-var-type.tsx index f3bfae82b6..137f62b2bb 100644 --- a/web/app/components/app/configuration/config-var/select-var-type.tsx +++ b/web/app/components/app/configuration/config-var/select-var-type.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React, { useState } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' import { PortalToFollowElem, diff --git a/web/app/components/app/configuration/config-vision/param-config.tsx b/web/app/components/app/configuration/config-vision/param-config.tsx index 5ea0a32907..f1e2475495 100644 --- a/web/app/components/app/configuration/config-vision/param-config.tsx +++ b/web/app/components/app/configuration/config-vision/param-config.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import VoiceParamConfig from './param-config-content' +import cn from '@/utils/classnames' import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, diff --git a/web/app/components/app/configuration/config-vision/radio-group/index.tsx b/web/app/components/app/configuration/config-vision/radio-group/index.tsx index 77e4d02184..a1cfb06e6a 100644 --- a/web/app/components/app/configuration/config-vision/radio-group/index.tsx +++ b/web/app/components/app/configuration/config-vision/radio-group/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type OPTION = { label: string diff --git a/web/app/components/app/configuration/config-voice/param-config-content.tsx b/web/app/components/app/configuration/config-voice/param-config-content.tsx index 10302d6221..cced3b0458 100644 --- a/web/app/components/app/configuration/config-voice/param-config-content.tsx +++ b/web/app/components/app/configuration/config-voice/param-config-content.tsx @@ -3,7 +3,6 @@ import useSWR from 'swr' import type { FC } from 'react' import { useContext } from 'use-context-selector' import React, { Fragment } from 'react' -import classNames from 'classnames' import { RiQuestionLine, } from '@remixicon/react' @@ -11,11 +10,14 @@ import { usePathname } from 'next/navigation' import { useTranslation } from 'react-i18next' import { Listbox, Transition } from '@headlessui/react' import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' +import classNames from '@/utils/classnames' +import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group' import type { Item } from '@/app/components/base/select' import ConfigContext from '@/context/debug-configuration' import { fetchAppVoices } from '@/service/apps' import Tooltip from '@/app/components/base/tooltip' import { languages } from '@/i18n/language' +import { TtsAutoPlay } from '@/types/app' const VoiceParamConfig: FC = () => { const { t } = useTranslation() const pathname = usePathname() @@ -27,12 +29,16 @@ const VoiceParamConfig: FC = () => { setTextToSpeechConfig, } = useContext(ConfigContext) - const languageItem = languages.find(item => item.value === textToSpeechConfig.language) + let languageItem = languages.find(item => item.value === textToSpeechConfig.language) const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') - + if (languages && !languageItem) + languageItem = languages[0] const language = languageItem?.value const voiceItems = useSWR({ appId, language }, fetchAppVoices).data - const voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) + let voiceItem = voiceItems?.find(item => item.value === textToSpeechConfig.voice) + if (voiceItems && !voiceItem) + voiceItem = voiceItems[0] + const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') return ( @@ -42,8 +48,9 @@ const VoiceParamConfig: FC = () => {
-
{t('appDebug.voice.voiceSettings.language')}
- +
{t('appDebug.voice.voiceSettings.language')}
+ {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => (
{item}
))} @@ -61,7 +68,8 @@ const VoiceParamConfig: FC = () => { }} >
- + {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} @@ -79,7 +87,8 @@ const VoiceParamConfig: FC = () => { leaveTo="opacity-0" > - + {languages.map((item: Item) => ( {
-
-
{t('appDebug.voice.voiceSettings.voice')}
+
{t('appDebug.voice.voiceSettings.voice')}
{ }} >
- - {voiceItem?.name ?? localVoicePlaceholder} + + {voiceItem?.name ?? localVoicePlaceholder} { leaveTo="opacity-0" > - + {voiceItems?.map((item: Item) => ( {
+
+
{t('appDebug.voice.voiceSettings.autoPlay')}
+ { + setTextToSpeechConfig({ + ...textToSpeechConfig, + autoPlay: value, + }) + }} + /> +
diff --git a/web/app/components/app/configuration/config-voice/param-config.tsx b/web/app/components/app/configuration/config-voice/param-config.tsx index 5ea0a32907..f1e2475495 100644 --- a/web/app/components/app/configuration/config-voice/param-config.tsx +++ b/web/app/components/app/configuration/config-voice/param-config.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import VoiceParamConfig from './param-config-content' +import cn from '@/utils/classnames' import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, diff --git a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx index cdd3ee3bd1..299dcb151d 100644 --- a/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx +++ b/web/app/components/app/configuration/config/agent/agent-setting/item-panel.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiQuestionLine } from '@remixicon/react' +import cn from '@/utils/classnames' import Tooltip from '@/app/components/base/tooltip' type Props = { className?: string diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 9a8bb45b5f..16f2257c38 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useContext } from 'use-context-selector' import produce from 'immer' import { @@ -12,6 +11,7 @@ import { } from '@remixicon/react' import { useFormattingChangedDispatcher } from '../../../debug/hooks' import SettingBuiltInTool from './setting-built-in-tool' +import cn from '@/utils/classnames' import Panel from '@/app/components/app/configuration/base/feature-panel' import Tooltip from '@/app/components/base/tooltip' import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index fa7f1f98a9..69e18e3136 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import cn from 'classnames' +import cn from '@/utils/classnames' import Drawer from '@/app/components/base/drawer-plus' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' diff --git a/web/app/components/app/configuration/config/agent/prompt-editor.tsx b/web/app/components/app/configuration/config/agent/prompt-editor.tsx index 1948e8fdbd..1532c96fb6 100644 --- a/web/app/components/app/configuration/config/agent/prompt-editor.tsx +++ b/web/app/components/app/configuration/config/agent/prompt-editor.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React from 'react' import copy from 'copy-to-clipboard' -import cn from 'classnames' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import { Clipboard, ClipboardCheck, diff --git a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx index faa44092fe..6bdf678f85 100644 --- a/web/app/components/app/configuration/config/assistant-type-picker/index.tsx +++ b/web/app/components/app/configuration/config/assistant-type-picker/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' import AgentSetting from '../agent/agent-setting' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx index 0d7ab4e02c..18623c11c3 100644 --- a/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx +++ b/web/app/components/app/configuration/config/feature/choose-feature/feature-item/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' import Switch from '@/app/components/base/switch' export type IFeatureItemProps = { diff --git a/web/app/components/app/configuration/dataset-config/card-item/index.tsx b/web/app/components/app/configuration/dataset-config/card-item/index.tsx index a784a7fd88..7b369d9d79 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/index.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/index.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import TypeIcon from '../type-icon' import RemoveIcon from '../../base/icons/remove-icon' import s from './style.module.css' +import cn from '@/utils/classnames' import type { DataSet } from '@/models/datasets' import { formatNumber } from '@/utils/format' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/app/configuration/dataset-config/context-var/index.tsx b/web/app/components/app/configuration/dataset-config/context-var/index.tsx index d320adcc77..be0ae47242 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/index.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/index.tsx @@ -2,12 +2,12 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiQuestionLine, } from '@remixicon/react' import type { Props } from './var-picker' import VarPicker from './var-picker' +import cn from '@/utils/classnames' import { BracketsX } from '@/app/components/base/icons/src/vender/line/development' import Tooltip from '@/app/components/base/tooltip' @@ -20,7 +20,7 @@ const ContextVar: FC = (props) => {
- +
{t('appDebug.feature.dataSet.queryVariable.title')}
= (props) => {
} selector='context-var-tooltip' > - +
diff --git a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx index 0778acddf7..bc31721ad7 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/var-picker.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { ChevronDownIcon } from '@heroicons/react/24/outline' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/app/configuration/dataset-config/params-config/index.tsx b/web/app/components/app/configuration/dataset-config/params-config/index.tsx index 708b2d687d..87d0d73b64 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/index.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/index.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import cn from 'classnames' import ConfigContent from './config-content' +import cn from '@/utils/classnames' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' import ConfigContext from '@/context/debug-configuration' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 94502a8cf8..602525f579 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -2,12 +2,12 @@ import type { FC } from 'react' import React, { useRef, useState } from 'react' import { useGetState, useInfiniteScroll } from 'ahooks' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import Link from 'next/link' import produce from 'immer' import TypeIcon from '../type-icon' import s from './style.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import type { DataSet } from '@/models/datasets' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index d87138506a..2f53fb7738 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -1,28 +1,32 @@ import type { FC } from 'react' import { useRef, useState } from 'react' +import { useMount } from 'ahooks' import { useTranslation } from 'react-i18next' import { isEqual } from 'lodash-es' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import { BookOpenIcon } from '@heroicons/react/24/outline' +import cn from '@/utils/classnames' import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio' import Button from '@/app/components/base/button' import type { DataSet } from '@/models/datasets' import { useToastContext } from '@/app/components/base/toast' import { updateDatasetSetting } from '@/service/datasets' +import { useAppContext } from '@/context/app-context' import { useModalContext } from '@/context/modal-context' import type { RetrievalConfig } from '@/types/app' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' -import PermissionsRadio from '@/app/components/datasets/settings/permissions-radio' +import PermissionSelector from '@/app/components/datasets/settings/permission-selector' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { fetchMembers } from '@/service/common' +import type { Member } from '@/models/common' type SettingsModalProps = { currentDataset: DataSet @@ -55,7 +59,11 @@ const SettingsModal: FC = ({ const { setShowAccountSettingModal } = useModalContext() const [loading, setLoading] = useState(false) + const { isCurrentWorkspaceDatasetOperator } = useAppContext() const [localeCurrentDataset, setLocaleCurrentDataset] = useState({ ...currentDataset }) + const [selectedMemberIDs, setSelectedMemberIDs] = useState(currentDataset.partial_member_list || []) + const [memberList, setMemberList] = useState([]) + const [indexMethod, setIndexMethod] = useState(currentDataset.indexing_technique) const [retrievalConfig, setRetrievalConfig] = useState(localeCurrentDataset?.retrieval_model_dict as RetrievalConfig) @@ -92,7 +100,7 @@ const SettingsModal: FC = ({ try { setLoading(true) const { id, name, description, permission } = localeCurrentDataset - await updateDatasetSetting({ + const requestParams = { datasetId: id, body: { name, @@ -106,7 +114,16 @@ const SettingsModal: FC = ({ embedding_model: localeCurrentDataset.embedding_model, embedding_model_provider: localeCurrentDataset.embedding_model_provider, }, - }) + } as any + if (permission === 'partial_members') { + requestParams.body.partial_member_list = selectedMemberIDs.map((id) => { + return { + user_id: id, + role: memberList.find(member => member.id === id)?.role, + } + }) + } + await updateDatasetSetting(requestParams) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) onSave({ ...localeCurrentDataset, @@ -122,6 +139,18 @@ const SettingsModal: FC = ({ } } + const getMembers = async () => { + const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) + if (!accounts) + setMemberList([]) + else + setMemberList(accounts) + } + + useMount(() => { + getMembers() + }) + return (
= ({
{t('datasetSettings.form.permissions')}
- handleValueChange('permission', v!)} - itemClassName='sm:!w-[280px]' + onMemberSelect={setSelectedMemberIDs} + memberList={memberList} />
diff --git a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx b/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx index b9c2ab3629..d007225bda 100644 --- a/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/opening-statement/index.tsx @@ -2,7 +2,6 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { RiAddLine, RiDeleteBinLine, @@ -12,6 +11,7 @@ import produce from 'immer' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { ReactSortable } from 'react-sortablejs' +import cn from '@/utils/classnames' import ConfigContext from '@/context/debug-configuration' import Panel from '@/app/components/app/configuration/base/feature-panel' import Button from '@/app/components/base/button' diff --git a/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx index 4bb66cb635..4c5db22513 100644 --- a/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx +++ b/web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx @@ -40,7 +40,6 @@ const TextToSpeech: FC = () => { { languageInfo?.example && ( diff --git a/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx index 1dcae64416..b2c6792107 100644 --- a/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx +++ b/web/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useRef, useState } from 'react' import { useHover } from 'ahooks' -import cn from 'classnames' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import { MessageCheckRemove, MessageFastPlus } from '@/app/components/base/icons/src/vender/line/communication' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' import { Edit04 } from '@/app/components/base/icons/src/vender/line/general' diff --git a/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx b/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx index b659e14f40..2e08a99122 100644 --- a/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx +++ b/web/app/components/app/configuration/toolbox/score-slider/base-slider/index.tsx @@ -1,6 +1,6 @@ import ReactSlider from 'react-slider' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type ISliderProps = { className?: string diff --git a/web/app/components/app/create-app-dialog/newAppDialog.tsx b/web/app/components/app/create-app-dialog/newAppDialog.tsx index 2d434de175..21459773a6 100644 --- a/web/app/components/app/create-app-dialog/newAppDialog.tsx +++ b/web/app/components/app/create-app-dialog/newAppDialog.tsx @@ -1,7 +1,7 @@ import { Fragment, useCallback } from 'react' import type { ReactNode } from 'react' import { Dialog, Transition } from '@headlessui/react' -import cn from 'classnames' +import cn from '@/utils/classnames' type DialogProps = { className?: string diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index 11e265e9ad..c4cedbb354 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -2,7 +2,6 @@ import type { MouseEventHandler } from 'react' import { useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine, RiQuestionLine, @@ -10,6 +9,7 @@ import { import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' import s from './style.module.css' +import cn from '@/utils/classnames' import AppsContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx index 39c50d3ba8..fa5554f9cf 100644 --- a/web/app/components/app/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { RiDeleteBinLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' +import cn from '@/utils/classnames' import { Yaml as YamlIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import { UploadCloud01 } from '@/app/components/base/icons/src/vender/line/general' @@ -98,13 +98,13 @@ const Uploader: FC = ({ {!file && (
- +
{t('datasetCreation.stepOne.uploader.button')} {t('datasetDocuments.list.batchModal.browse')}
- {dragging &&
} + {dragging &&
}
)} {file && ( diff --git a/web/app/components/app/duplicate-modal/index.tsx b/web/app/components/app/duplicate-modal/index.tsx index e9710add9e..6595972de6 100644 --- a/web/app/components/app/duplicate-modal/index.tsx +++ b/web/app/components/app/duplicate-modal/index.tsx @@ -1,8 +1,8 @@ 'use client' import React, { useState } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import s from './style.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/app/log-annotation/index.tsx b/web/app/components/app/log-annotation/index.tsx index 626671fa18..852e57035c 100644 --- a/web/app/components/app/log-annotation/index.tsx +++ b/web/app/components/app/log-annotation/index.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' +import cn from '@/utils/classnames' import Log from '@/app/components/app/log' import WorkflowLog from '@/app/components/app/workflow-log' import Annotation from '@/app/components/app/annotation' diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index b1c398dc0b..edd4bf21e6 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -17,9 +17,9 @@ import timezone from 'dayjs/plugin/timezone' import { createContext, useContext } from 'use-context-selector' import { useShallow } from 'zustand/react/shallow' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import s from './style.module.css' import VarPanel from './var-panel' +import cn from '@/utils/classnames' import { randomString } from '@/utils' import type { FeedbackFunc, Feedbacktype, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type' import type { Annotation, ChatConversationFullDetailResponse, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationFullDetailResponse, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log' diff --git a/web/app/components/app/overview/apikey-info-panel/index.tsx b/web/app/components/app/overview/apikey-info-panel/index.tsx index 3b155e6214..661a88e823 100644 --- a/web/app/components/app/overview/apikey-info-panel/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' import { IS_CE_EDITION } from '@/config' diff --git a/web/app/components/app/overview/apikey-info-panel/progress/index.tsx b/web/app/components/app/overview/apikey-info-panel/progress/index.tsx index 3e869f015a..3a4accbb43 100644 --- a/web/app/components/app/overview/apikey-info-panel/progress/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/progress/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' export type IProgressProps = { className?: string diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index f16fc81f16..9e5d5af0da 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import copy from 'copy-to-clipboard' import style from './style.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import copyStyle from '@/app/components/base/copy-btn/style.module.css' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/app/switch-app-modal/index.tsx b/web/app/components/app/switch-app-modal/index.tsx index b65c49c612..e5ac6ed55e 100644 --- a/web/app/components/app/switch-app-modal/index.tsx +++ b/web/app/components/app/switch-app-modal/index.tsx @@ -4,9 +4,9 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import s from './style.module.css' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index 79880630e3..3313c987c9 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiClipboardLine, } from '@remixicon/react' @@ -12,6 +11,7 @@ import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' import { useBoolean } from 'ahooks' import { HashtagIcon } from '@heroicons/react/24/solid' import ResultTab from './result-tab' +import cn from '@/utils/classnames' import { Markdown } from '@/app/components/base/markdown' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' @@ -73,7 +73,7 @@ export const SimpleBtn = ({ className, isDisabled, onClick, children }: { children: React.ReactNode }) => (
!isDisabled && onClick?.()} > {children} @@ -277,7 +277,7 @@ const GenerationItem: FC = ({ const [currentTab, setCurrentTab] = useState('DETAIL') return ( -
= ({ <>
diff --git a/web/app/components/app/text-generate/item/result-tab.tsx b/web/app/components/app/text-generate/item/result-tab.tsx index 3f48dd1b94..7ee1f4f3cd 100644 --- a/web/app/components/app/text-generate/item/result-tab.tsx +++ b/web/app/components/app/text-generate/item/result-tab.tsx @@ -2,8 +2,8 @@ import { memo, useEffect, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' // import Loading from '@/app/components/base/loading' import { Markdown } from '@/app/components/base/markdown' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' diff --git a/web/app/components/app/text-generate/saved-items/index.tsx b/web/app/components/app/text-generate/saved-items/index.tsx index 8cd16d5aae..8bfebbc17f 100644 --- a/web/app/components/app/text-generate/saved-items/index.tsx +++ b/web/app/components/app/text-generate/saved-items/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import copy from 'copy-to-clipboard' import NoData from './no-data' +import cn from '@/utils/classnames' import type { SavedMessage } from '@/models/debug' import { Markdown } from '@/app/components/base/markdown' import { SimpleBtn, copyIcon } from '@/app/components/app/text-generate/item' diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx index 6f6eb66fc4..2bd4f8d082 100644 --- a/web/app/components/app/type-selector/index.tsx +++ b/web/app/components/app/type-selector/index.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next' import React, { useState } from 'react' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, @@ -100,7 +100,7 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => { }}>
{t('app.typeSelector.chatbot')}
- {value === 'chatbot' && } + {value === 'chatbot' && }
{ onChange('agent') @@ -108,7 +108,7 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => { }}>
{t('app.typeSelector.agent')}
- {value === 'agent' && } + {value === 'agent' && }
{ onChange('workflow') @@ -116,7 +116,7 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => { }}>
{t('app.typeSelector.workflow')}
- {value === 'workflow' && } + {value === 'workflow' && }
diff --git a/web/app/components/app/workflow-log/list.tsx b/web/app/components/app/workflow-log/list.tsx index ace028af70..f4707dce59 100644 --- a/web/app/components/app/workflow-log/list.tsx +++ b/web/app/components/app/workflow-log/list.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import s from './style.module.css' import DetailPanel from './detail' +import cn from '@/utils/classnames' import type { WorkflowAppLogDetail, WorkflowLogsResponse } from '@/models/log' import type { App } from '@/types/app' import Loading from '@/app/components/base/loading' diff --git a/web/app/components/base/agent-log-modal/detail.tsx b/web/app/components/base/agent-log-modal/detail.tsx index 2b4f77f5a2..5b34d2e464 100644 --- a/web/app/components/base/agent-log-modal/detail.tsx +++ b/web/app/components/base/agent-log-modal/detail.tsx @@ -4,9 +4,9 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { flatten, uniq } from 'lodash-es' -import cn from 'classnames' import ResultPanel from './result' import TracingPanel from './tracing' +import cn from '@/utils/classnames' import { ToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' import { fetchAgentLogDetail } from '@/service/log' diff --git a/web/app/components/base/agent-log-modal/index.tsx b/web/app/components/base/agent-log-modal/index.tsx index b63266bd08..bbe1167f57 100644 --- a/web/app/components/base/agent-log-modal/index.tsx +++ b/web/app/components/base/agent-log-modal/index.tsx @@ -1,10 +1,10 @@ import type { FC } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import { useEffect, useRef, useState } from 'react' import { useClickAway } from 'ahooks' import AgentLogDetail from './detail' +import cn from '@/utils/classnames' import type { IChatItem } from '@/app/components/base/chat/chat/type' type AgentLogModalProps = { diff --git a/web/app/components/base/agent-log-modal/iteration.tsx b/web/app/components/base/agent-log-modal/iteration.tsx index 8b1af48d8f..2bb04d1f87 100644 --- a/web/app/components/base/agent-log-modal/iteration.tsx +++ b/web/app/components/base/agent-log-modal/iteration.tsx @@ -1,8 +1,8 @@ 'use client' import { useTranslation } from 'react-i18next' import type { FC } from 'react' -import cn from 'classnames' import ToolCall from './tool-call' +import cn from '@/utils/classnames' import type { AgentIteration } from '@/models/log' type Props = { diff --git a/web/app/components/base/agent-log-modal/tool-call.tsx b/web/app/components/base/agent-log-modal/tool-call.tsx index f223a26a3e..8d8e583126 100644 --- a/web/app/components/base/agent-log-modal/tool-call.tsx +++ b/web/app/components/base/agent-log-modal/tool-call.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' import { useState } from 'react' -import cn from 'classnames' import { RiCheckboxCircleLine, RiErrorWarningLine, } from '@remixicon/react' import { useContext } from 'use-context-selector' +import cn from '@/utils/classnames' import BlockIcon from '@/app/components/workflow/block-icon' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' diff --git a/web/app/components/base/app-icon/index.tsx b/web/app/components/base/app-icon/index.tsx index 9e2789c990..9d8cf28bed 100644 --- a/web/app/components/base/app-icon/index.tsx +++ b/web/app/components/base/app-icon/index.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' -import classNames from 'classnames' import data from '@emoji-mart/data' import { init } from 'emoji-mart' import style from './style.module.css' +import classNames from '@/utils/classnames' init({ data }) diff --git a/web/app/components/base/audio-btn/audio.player.manager.ts b/web/app/components/base/audio-btn/audio.player.manager.ts new file mode 100644 index 0000000000..03e9e21f93 --- /dev/null +++ b/web/app/components/base/audio-btn/audio.player.manager.ts @@ -0,0 +1,53 @@ +import AudioPlayer from '@/app/components/base/audio-btn/audio' +declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface AudioPlayerManager { + instance: AudioPlayerManager + } + +} + +export class AudioPlayerManager { + private static instance: AudioPlayerManager + private audioPlayers: AudioPlayer | null = null + private msgId: string | undefined + + private constructor() { + } + + public static getInstance(): AudioPlayerManager { + if (!AudioPlayerManager.instance) { + AudioPlayerManager.instance = new AudioPlayerManager() + this.instance = AudioPlayerManager.instance + } + + return AudioPlayerManager.instance + } + + public getAudioPlayer(url: string, isPublic: boolean, id: string | undefined, msgContent: string | null | undefined, voice: string | undefined, callback: ((event: string) => {}) | null): AudioPlayer { + if (this.msgId && this.msgId === id && this.audioPlayers) { + this.audioPlayers.setCallback(callback) + return this.audioPlayers + } + else { + if (this.audioPlayers) { + try { + this.audioPlayers.pauseAudio() + this.audioPlayers.cacheBuffers = [] + this.audioPlayers.sourceBuffer?.abort() + } + catch (e) { + } + } + + this.msgId = id + this.audioPlayers = new AudioPlayer(url, isPublic, id, msgContent, callback) + return this.audioPlayers + } + } + + public resetMsgId(msgId: string) { + this.msgId = msgId + this.audioPlayers?.resetMsgId(msgId) + } +} diff --git a/web/app/components/base/audio-btn/audio.ts b/web/app/components/base/audio-btn/audio.ts new file mode 100644 index 0000000000..239dfe0342 --- /dev/null +++ b/web/app/components/base/audio-btn/audio.ts @@ -0,0 +1,263 @@ +import Toast from '@/app/components/base/toast' +import { textToAudioStream } from '@/service/share' + +declare global { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface Window { + ManagedMediaSource: any + } +} + +export default class AudioPlayer { + mediaSource: MediaSource | null + audio: HTMLAudioElement + audioContext: AudioContext + sourceBuffer?: SourceBuffer + cacheBuffers: ArrayBuffer[] = [] + pauseTimer: number | null = null + msgId: string | undefined + msgContent: string | null | undefined = null + voice: string | undefined = undefined + isLoadData = false + url: string + isPublic: boolean + callback: ((event: string) => {}) | null + + constructor(streamUrl: string, isPublic: boolean, msgId: string | undefined, msgContent: string | null | undefined, callback: ((event: string) => {}) | null) { + this.audioContext = new AudioContext() + this.msgId = msgId + this.msgContent = msgContent + this.url = streamUrl + this.isPublic = isPublic + this.callback = callback + + // Compatible with iphone ios17 ManagedMediaSource + const MediaSource = window.MediaSource || window.ManagedMediaSource + if (!MediaSource) { + Toast.notify({ + message: 'Your browser does not support audio streaming, if you are using an iPhone, please update to iOS 17.1 or later.', + type: 'error', + }) + } + this.mediaSource = MediaSource ? new MediaSource() : null + this.audio = new Audio() + this.setCallback(callback) + this.audio.src = this.mediaSource ? URL.createObjectURL(this.mediaSource) : '' + this.audio.autoplay = true + + const source = this.audioContext.createMediaElementSource(this.audio) + source.connect(this.audioContext.destination) + this.listenMediaSource('audio/mpeg') + } + + public resetMsgId(msgId: string) { + this.msgId = msgId + } + + private listenMediaSource(contentType: string) { + this.mediaSource?.addEventListener('sourceopen', () => { + if (this.sourceBuffer) + return + + this.sourceBuffer = this.mediaSource?.addSourceBuffer(contentType) + // this.sourceBuffer?.addEventListener('update', () => { + // if (this.cacheBuffers.length && !this.sourceBuffer?.updating) { + // const cacheBuffer = this.cacheBuffers.shift()! + // this.sourceBuffer?.appendBuffer(cacheBuffer) + // } + // // this.pauseAudio() + // }) + // + // this.sourceBuffer?.addEventListener('updateend', () => { + // if (this.cacheBuffers.length && !this.sourceBuffer?.updating) { + // const cacheBuffer = this.cacheBuffers.shift()! + // this.sourceBuffer?.appendBuffer(cacheBuffer) + // } + // // this.pauseAudio() + // }) + }) + } + + public setCallback(callback: ((event: string) => {}) | null) { + this.callback = callback + if (callback) { + this.audio.addEventListener('ended', () => { + callback('ended') + }, false) + this.audio.addEventListener('paused', () => { + callback('paused') + }, true) + this.audio.addEventListener('loaded', () => { + callback('loaded') + }, true) + this.audio.addEventListener('play', () => { + callback('play') + }, true) + this.audio.addEventListener('timeupdate', () => { + callback('timeupdate') + }, true) + this.audio.addEventListener('loadeddate', () => { + callback('loadeddate') + }, true) + this.audio.addEventListener('canplay', () => { + callback('canplay') + }, true) + this.audio.addEventListener('error', () => { + callback('error') + }, true) + } + } + + private async loadAudio() { + try { + const audioResponse: any = await textToAudioStream(this.url, this.isPublic, { content_type: 'audio/mpeg' }, { + message_id: this.msgId, + streaming: true, + voice: this.voice, + text: this.msgContent, + }) + + if (audioResponse.status !== 200) { + this.isLoadData = false + if (this.callback) + this.callback('error') + } + + const reader = audioResponse.body.getReader() + while (true) { + const { value, done } = await reader.read() + + if (done) { + this.receiveAudioData(value) + break + } + + this.receiveAudioData(value) + } + } + catch (error) { + this.isLoadData = false + this.callback && this.callback('error') + } + } + + // play audio + public playAudio() { + if (this.isLoadData) { + if (this.audioContext.state === 'suspended') { + this.audioContext.resume().then((_) => { + this.audio.play() + this.callback && this.callback('play') + }) + } + else if (this.audio.ended) { + this.audio.play() + this.callback && this.callback('play') + } + if (this.callback) + this.callback('play') + } + else { + this.isLoadData = true + this.loadAudio() + } + } + + private theEndOfStream() { + const endTimer = setInterval(() => { + if (!this.sourceBuffer?.updating) { + this.mediaSource?.endOfStream() + clearInterval(endTimer) + } + console.log('finishStream endOfStream endTimer') + }, 10) + } + + private finishStream() { + const timer = setInterval(() => { + if (!this.cacheBuffers.length) { + this.theEndOfStream() + clearInterval(timer) + } + + if (this.cacheBuffers.length && !this.sourceBuffer?.updating) { + const arrayBuffer = this.cacheBuffers.shift()! + this.sourceBuffer?.appendBuffer(arrayBuffer) + } + console.log('finishStream timer') + }, 10) + } + + public async playAudioWithAudio(audio: string, play = true) { + if (!audio || !audio.length) { + this.finishStream() + return + } + + const audioContent = Buffer.from(audio, 'base64') + this.receiveAudioData(new Uint8Array(audioContent)) + if (play) { + this.isLoadData = true + if (this.audio.paused) { + this.audioContext.resume().then((_) => { + this.audio.play() + this.callback && this.callback('play') + }) + } + else if (this.audio.ended) { + this.audio.play() + this.callback && this.callback('play') + } + else if (this.audio.played) { /* empty */ } + + else { + this.audio.play() + this.callback && this.callback('play') + } + } + } + + public pauseAudio() { + this.callback && this.callback('paused') + this.audio.pause() + this.audioContext.suspend() + } + + private cancer() { + + } + + private receiveAudioData(unit8Array: Uint8Array) { + if (!unit8Array) { + this.finishStream() + return + } + const audioData = this.byteArrayToArrayBuffer(unit8Array) + if (!audioData.byteLength) { + if (this.mediaSource?.readyState === 'open') + this.finishStream() + return + } + + if (this.sourceBuffer?.updating) { + this.cacheBuffers.push(audioData) + } + else { + if (this.cacheBuffers.length && !this.sourceBuffer?.updating) { + this.cacheBuffers.push(audioData) + const cacheBuffer = this.cacheBuffers.shift()! + this.sourceBuffer?.appendBuffer(cacheBuffer) + } + else { + this.sourceBuffer?.appendBuffer(audioData) + } + } + } + + private byteArrayToArrayBuffer(byteArray: Uint8Array): ArrayBuffer { + const arrayBuffer = new ArrayBuffer(byteArray.length) + const uint8Array = new Uint8Array(arrayBuffer) + uint8Array.set(byteArray) + return arrayBuffer + } +} diff --git a/web/app/components/base/audio-btn/index.tsx b/web/app/components/base/audio-btn/index.tsx index 0dd8a35edd..48081c170c 100644 --- a/web/app/components/base/audio-btn/index.tsx +++ b/web/app/components/base/audio-btn/index.tsx @@ -1,124 +1,78 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { useRef, useState } from 'react' import { t } from 'i18next' import { useParams, usePathname } from 'next/navigation' import s from './style.module.css' import Tooltip from '@/app/components/base/tooltip' import { randomString } from '@/utils' -import { textToAudio } from '@/service/share' import Loading from '@/app/components/base/loading' +import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' type AudioBtnProps = { - value: string + id?: string voice?: string + value?: string className?: string isAudition?: boolean - noCache: boolean + noCache?: boolean } type AudioState = 'initial' | 'loading' | 'playing' | 'paused' | 'ended' const AudioBtn = ({ - value, + id, voice, + value, className, isAudition, - noCache, }: AudioBtnProps) => { - const audioRef = useRef(null) const [audioState, setAudioState] = useState('initial') const selector = useRef(`play-tooltip-${randomString(4)}`) const params = useParams() const pathname = usePathname() - const removeCodeBlocks = (inputText: any) => { - const codeBlockRegex = /```[\s\S]*?```/g - if (inputText) - return inputText.replace(codeBlockRegex, '') - return '' - } - - const loadAudio = async () => { - const formData = new FormData() - formData.append('text', removeCodeBlocks(value)) - formData.append('voice', removeCodeBlocks(voice)) - - if (value !== '') { - setAudioState('loading') - - let url = '' - let isPublic = false - - if (params.token) { - url = '/text-to-audio' - isPublic = true - } - else if (params.appId) { - if (pathname.search('explore/installed') > -1) - url = `/installed-apps/${params.appId}/text-to-audio` - else - url = `/apps/${params.appId}/text-to-audio` - } - - try { - const audioResponse = await textToAudio(url, isPublic, formData) - const blob_bytes = Buffer.from(audioResponse.data, 'latin1') - const blob = new Blob([blob_bytes], { type: 'audio/wav' }) - const audioUrl = URL.createObjectURL(blob) - audioRef.current!.src = audioUrl - } - catch (error) { - setAudioState('initial') - console.error('Error playing audio:', error) - } - } - } - - const handleToggle = async () => { - if (audioState === 'initial' || noCache) { - await loadAudio() - } - else if (audioRef.current) { - if (audioState === 'playing') { - audioRef.current.pause() - setAudioState('paused') - } - else { - audioRef.current.play() + const audio_finished_call = (event: string): any => { + switch (event) { + case 'ended': + setAudioState('ended') + break + case 'paused': + setAudioState('ended') + break + case 'loaded': + setAudioState('loading') + break + case 'play': setAudioState('playing') - } + break + case 'error': + setAudioState('ended') + break } } + let url = '' + let isPublic = false - useEffect(() => { - const currentAudio = audioRef.current - - const handleLoading = () => { + if (params.token) { + url = '/text-to-audio' + isPublic = true + } + else if (params.appId) { + if (pathname.search('explore/installed') > -1) + url = `/installed-apps/${params.appId}/text-to-audio` + else + url = `/apps/${params.appId}/text-to-audio` + } + const handleToggle = async () => { + if (audioState === 'playing' || audioState === 'loading') { + setAudioState('paused') + AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).pauseAudio() + } + else { setAudioState('loading') + AudioPlayerManager.getInstance().getAudioPlayer(url, isPublic, id, value, voice, audio_finished_call).playAudio() } - - const handlePlay = () => { - currentAudio?.play() - setAudioState('playing') - } - - const handleEnded = () => { - setAudioState('ended') - } - - currentAudio?.addEventListener('progress', handleLoading) - currentAudio?.addEventListener('canplaythrough', handlePlay) - currentAudio?.addEventListener('ended', handleEnded) - - return () => { - currentAudio?.removeEventListener('progress', handleLoading) - currentAudio?.removeEventListener('canplaythrough', handlePlay) - currentAudio?.removeEventListener('ended', handleEnded) - URL.revokeObjectURL(currentAudio?.src || '') - currentAudio?.pause() - currentAudio?.setAttribute('src', '') - } - }, []) + } const tooltipContent = { initial: t('appApi.play'), @@ -151,7 +105,6 @@ const AudioBtn = ({ )} -
) } diff --git a/web/app/components/base/auto-height-textarea/common.tsx b/web/app/components/base/auto-height-textarea/common.tsx index 6ec4612c2e..c71df04395 100644 --- a/web/app/components/base/auto-height-textarea/common.tsx +++ b/web/app/components/base/auto-height-textarea/common.tsx @@ -1,5 +1,5 @@ import { forwardRef, useEffect, useRef } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type AutoHeightTextareaProps = & React.DetailedHTMLProps, HTMLTextAreaElement> diff --git a/web/app/components/base/auto-height-textarea/index.tsx b/web/app/components/base/auto-height-textarea/index.tsx index f1abbe3c57..f55db79f91 100644 --- a/web/app/components/base/auto-height-textarea/index.tsx +++ b/web/app/components/base/auto-height-textarea/index.tsx @@ -1,5 +1,5 @@ import { forwardRef, useEffect, useRef } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import { sleep } from '@/utils' type IProps = { diff --git a/web/app/components/base/avatar/index.tsx b/web/app/components/base/avatar/index.tsx index c3410d0bc8..fd7fb58687 100644 --- a/web/app/components/base/avatar/index.tsx +++ b/web/app/components/base/avatar/index.tsx @@ -1,6 +1,6 @@ 'use client' -import cn from 'classnames' import { useState } from 'react' +import cn from '@/utils/classnames' type AvatarProps = { name: string diff --git a/web/app/components/base/block-input/index.tsx b/web/app/components/base/block-input/index.tsx index e0745f4c01..f2b6b5d6dc 100644 --- a/web/app/components/base/block-input/index.tsx +++ b/web/app/components/base/block-input/index.tsx @@ -2,10 +2,10 @@ import type { ChangeEvent, FC } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' -import classNames from 'classnames' import { useTranslation } from 'react-i18next' import { varHighlightHTML } from '../../app/configuration/base/var-highlight' import Toast from '../toast' +import classNames from '@/utils/classnames' import { checkKeys } from '@/utils/var' // regex to match the {{}} and replace it with a span diff --git a/web/app/components/base/button/add-button.tsx b/web/app/components/base/button/add-button.tsx index 67f7524c44..e2e544cc00 100644 --- a/web/app/components/base/button/add-button.tsx +++ b/web/app/components/base/button/add-button.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiAddLine } from '@remixicon/react' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/base/button/index.css b/web/app/components/base/button/index.css index f3800d0375..17932166ca 100644 --- a/web/app/components/base/button/index.css +++ b/web/app/components/base/button/index.css @@ -40,4 +40,8 @@ .btn-ghost { @apply bg-transparent hover:bg-gray-200 border-transparent shadow-none text-gray-700; } + + .btn-tertiary { + @apply bg-[#F2F4F7] hover:bg-[#E9EBF0] border-transparent shadow-none text-gray-700; + } } \ No newline at end of file diff --git a/web/app/components/base/button/index.tsx b/web/app/components/base/button/index.tsx index ca14f6debb..959b2dbe7b 100644 --- a/web/app/components/base/button/index.tsx +++ b/web/app/components/base/button/index.tsx @@ -1,8 +1,8 @@ import type { CSSProperties } from 'react' import React from 'react' import { type VariantProps, cva } from 'class-variance-authority' -import classNames from 'classnames' import Spinner from '../spinner' +import classNames from '@/utils/classnames' const buttonVariants = cva( 'btn disabled:btn-disabled', @@ -14,6 +14,7 @@ const buttonVariants = cva( 'secondary': 'btn-secondary', 'secondary-accent': 'btn-secondary-accent', 'ghost': 'btn-ghost', + 'tertiary': 'btn-tertiary', }, size: { small: 'btn-small', diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 3e6b07083f..78a0842595 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -8,6 +8,7 @@ import type { ChatConfig, ChatItem, } from '../../types' +import { useChatContext } from '../context' import Operation from './operation' import AgentContent from './agent-content' import BasicContent from './basic-content' @@ -59,23 +60,25 @@ const Answer: FC = ({ } = item const hasAgentThoughts = !!agent_thoughts?.length - const [containerWidth, setContainerWidth] = useState(0) + const [containerWidth] = useState(0) const [contentWidth, setContentWidth] = useState(0) const containerRef = useRef(null) const contentRef = useRef(null) - const getContainerWidth = () => { - if (containerRef.current) - setContainerWidth(containerRef.current?.clientWidth + 16) - } + const { + config: chatContextConfig, + } = useChatContext() + + const voiceRef = useRef(chatContextConfig?.text_to_speech?.voice) const getContentWidth = () => { if (contentRef.current) setContentWidth(contentRef.current?.clientWidth) } useEffect(() => { - getContainerWidth() - }, []) + voiceRef.current = chatContextConfig?.text_to_speech?.voice + } + , [chatContextConfig?.text_to_speech?.voice]) useEffect(() => { if (!responding) diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index 5b45557eb0..e3e912d289 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -4,10 +4,10 @@ import { useMemo, useState, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import type { ChatItem } from '../../types' import { useChatContext } from '../context' +import cn from '@/utils/classnames' import CopyBtn from '@/app/components/base/copy-btn' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' import AudioBtn from '@/app/components/base/audio-btn' @@ -117,11 +117,11 @@ const Operation: FC = ({ )} {(config?.text_to_speech?.enabled) && ( <> -
+
diff --git a/web/app/components/base/chat/chat/answer/workflow-process.tsx b/web/app/components/base/chat/chat/answer/workflow-process.tsx index acdd539824..5f36e40c40 100644 --- a/web/app/components/base/chat/chat/answer/workflow-process.tsx +++ b/web/app/components/base/chat/chat/answer/workflow-process.tsx @@ -4,7 +4,6 @@ import { useMemo, useState, } from 'react' -import cn from 'classnames' import { RiArrowRightSLine, RiErrorWarningFill, @@ -12,6 +11,7 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import type { ChatItem, WorkflowProcess } from '../../types' +import cn from '@/utils/classnames' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import NodePanel from '@/app/components/workflow/run/node' diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index be1269858e..4f012ece2a 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -6,6 +6,8 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { produce, setAutoFreeze } from 'immer' +import { useParams, usePathname } from 'next/navigation' +import { v4 as uuidV4 } from 'uuid' import type { ChatConfig, ChatItem, @@ -20,6 +22,7 @@ import { replaceStringWithValues } from '@/app/components/app/configuration/prom import type { Annotation } from '@/models/log' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import useTimestamp from '@/hooks/use-timestamp' +import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' type GetAbortController = (abortController: AbortController) => void type SendCallback = { @@ -91,7 +94,8 @@ export const useChat = ( const conversationMessagesAbortControllerRef = useRef(null) const suggestedQuestionsAbortControllerRef = useRef(null) const checkPromptVariables = useCheckPromptVariables() - + const params = useParams() + const pathname = usePathname() useEffect(() => { setAutoFreeze(false) return () => { @@ -262,6 +266,19 @@ export const useChat = ( let isAgentMode = false let hasSetResponseId = false + let ttsUrl = '' + let ttsIsPublic = false + if (params.token) { + ttsUrl = '/text-to-audio' + ttsIsPublic = true + } + else if (params.appId) { + if (pathname.search('explore/installed') > -1) + ttsUrl = `/installed-apps/${params.appId}/text-to-audio` + else + ttsUrl = `/apps/${params.appId}/text-to-audio` + } + const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => {}) ssePost( url, { @@ -530,6 +547,15 @@ export const useChat = ( } })) }, + onTTSChunk: (messageId: string, audio: string) => { + if (!audio || audio === '') + return + player.playAudioWithAudio(audio, true) + AudioPlayerManager.getInstance().resetMsgId(messageId) + }, + onTTSEnd: (messageId: string, audio: string) => { + player.playAudioWithAudio(audio, false) + }, }) return true }, [ diff --git a/web/app/components/base/chat/chat/index.tsx b/web/app/components/base/chat/chat/index.tsx index 489cb920fb..c5d9af45c2 100644 --- a/web/app/components/base/chat/chat/index.tsx +++ b/web/app/components/base/chat/chat/index.tsx @@ -11,7 +11,6 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import { debounce } from 'lodash-es' -import classNames from 'classnames' import { useShallow } from 'zustand/react/shallow' import type { ChatConfig, @@ -25,6 +24,7 @@ import Answer from './answer' import ChatInput from './chat-input' import TryToAsk from './try-to-ask' import { ChatContextProvider } from './context' +import classNames from '@/utils/classnames' import type { Emoji } from '@/app/components/tools/types' import Button from '@/app/components/base/button' import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' diff --git a/web/app/components/base/chat/chat/thought/tool.tsx b/web/app/components/base/chat/chat/thought/tool.tsx index 707bc2d5e4..7d6a1a0e6f 100644 --- a/web/app/components/base/chat/chat/thought/tool.tsx +++ b/web/app/components/base/chat/chat/thought/tool.tsx @@ -3,13 +3,13 @@ import type { FC } from 'react' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiArrowDownSLine, RiLoader2Line, } from '@remixicon/react' import type { ToolInfoInThought } from '../type' import Panel from './panel' +import cn from '@/utils/classnames' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought' import type { Emoji } from '@/app/components/tools/types' diff --git a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx index 6b895ae319..0d59331819 100644 --- a/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx +++ b/web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo } from 'react' -import cn from 'classnames' import Chat from '../chat' import type { ChatConfig, @@ -9,6 +8,7 @@ import { useChat } from '../chat/hooks' import { useEmbeddedChatbotContext } from './context' import ConfigPanel from './config-panel' import { isDify } from './utils' +import cn from '@/utils/classnames' import { fetchSuggestedQuestions, getUrl, diff --git a/web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx b/web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx index b3e6c2c532..81f57a04ae 100644 --- a/web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/config-panel/index.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useEmbeddedChatbotContext } from '../context' import { useThemeContext } from '../theme/theme-context' import { CssTransform } from '../theme/utils' import Form from './form' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import AppIcon from '@/app/components/base/app-icon' import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication' diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index 9f3de8d589..02063a3d1e 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -2,7 +2,6 @@ import { useEffect, useState, } from 'react' -import cn from 'classnames' import { useAsyncEffect } from 'ahooks' import { EmbeddedChatbotContext, @@ -11,6 +10,7 @@ import { import { useEmbeddedChatbot } from './hooks' import { isDify } from './utils' import { useThemeContext } from './theme/theme-context' +import cn from '@/utils/classnames' import { checkOrSetAccessToken } from '@/app/components/share/utils' import AppUnavailable from '@/app/components/base/app-unavailable' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' diff --git a/web/app/components/base/checkbox/index.tsx b/web/app/components/base/checkbox/index.tsx index c3c834e5bd..fe95155b3c 100644 --- a/web/app/components/base/checkbox/index.tsx +++ b/web/app/components/base/checkbox/index.tsx @@ -1,5 +1,5 @@ -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' type CheckboxProps = { checked?: boolean diff --git a/web/app/components/base/confirm/common.tsx b/web/app/components/base/confirm/common.tsx index 8e142b694f..1c7fd303ae 100644 --- a/web/app/components/base/confirm/common.tsx +++ b/web/app/components/base/confirm/common.tsx @@ -1,11 +1,11 @@ import type { FC, ReactElement } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine, RiErrorWarningFill, } from '@remixicon/react' import s from './common.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import Button from '@/app/components/base/button' diff --git a/web/app/components/base/dialog/index.tsx b/web/app/components/base/dialog/index.tsx index aaf7edea63..e74e6319c8 100644 --- a/web/app/components/base/dialog/index.tsx +++ b/web/app/components/base/dialog/index.tsx @@ -1,7 +1,7 @@ import { Fragment, useCallback } from 'react' import type { ElementType, ReactNode } from 'react' import { Dialog, Transition } from '@headlessui/react' -import classNames from 'classnames' +import classNames from '@/utils/classnames' // https://headlessui.com/react/dialog diff --git a/web/app/components/base/drawer-plus/index.tsx b/web/app/components/base/drawer-plus/index.tsx index 106b489dc0..894bea20d8 100644 --- a/web/app/components/base/drawer-plus/index.tsx +++ b/web/app/components/base/drawer-plus/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React, { useRef } from 'react' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' +import cn from '@/utils/classnames' import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' diff --git a/web/app/components/base/drawer/index.tsx b/web/app/components/base/drawer/index.tsx index de46ac69f8..c2285b5c53 100644 --- a/web/app/components/base/drawer/index.tsx +++ b/web/app/components/base/drawer/index.tsx @@ -1,9 +1,9 @@ 'use client' -import cn from 'classnames' import { Dialog } from '@headlessui/react' import { useTranslation } from 'react-i18next' import { XMarkIcon } from '@heroicons/react/24/outline' import Button from '../button' +import cn from '@/utils/classnames' export type IDrawerProps = { title?: string diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx index 8c3a7f04ee..f861bcb20c 100644 --- a/web/app/components/base/emoji-picker/index.tsx +++ b/web/app/components/base/emoji-picker/index.tsx @@ -5,12 +5,12 @@ import React, { useState } from 'react' import data from '@emoji-mart/data' import type { Emoji, EmojiMartData } from '@emoji-mart/data' import { SearchIndex, init } from 'emoji-mart' -import cn from 'classnames' import { MagnifyingGlassIcon, } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import s from './style.module.css' +import cn from '@/utils/classnames' import Divider from '@/app/components/base/divider' import Button from '@/app/components/base/button' diff --git a/web/app/components/base/features/feature-choose/feature-item/index.tsx b/web/app/components/base/features/feature-choose/feature-item/index.tsx index 5c56fda41a..9a470d633a 100644 --- a/web/app/components/base/features/feature-choose/feature-item/index.tsx +++ b/web/app/components/base/features/feature-choose/feature-item/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import produce from 'immer' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' import Switch from '@/app/components/base/switch' import { FeatureEnum } from '@/app/components/base/features/types' import { useFeaturesStore } from '@/app/components/base/features/hooks' diff --git a/web/app/components/base/features/feature-panel/file-upload/param-config.tsx b/web/app/components/base/features/feature-panel/file-upload/param-config.tsx index f72c35b91b..805fe8fb3e 100644 --- a/web/app/components/base/features/feature-panel/file-upload/param-config.tsx +++ b/web/app/components/base/features/feature-panel/file-upload/param-config.tsx @@ -2,9 +2,9 @@ import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { OnFeaturesChange } from '../../types' import ParamConfigContent from './param-config-content' +import cn from '@/utils/classnames' import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, diff --git a/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx b/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx index 77e4d02184..a1cfb06e6a 100644 --- a/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx +++ b/web/app/components/base/features/feature-panel/file-upload/radio-group/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type OPTION = { label: string diff --git a/web/app/components/base/features/feature-panel/opening-statement/index.tsx b/web/app/components/base/features/feature-panel/opening-statement/index.tsx index b49e47856a..54bf8bd937 100644 --- a/web/app/components/base/features/feature-panel/opening-statement/index.tsx +++ b/web/app/components/base/features/feature-panel/opening-statement/index.tsx @@ -3,7 +3,6 @@ import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import produce from 'immer' -import cn from 'classnames' import { RiAddLine, RiDeleteBinLine, @@ -16,6 +15,7 @@ import { useFeaturesStore, } from '../../hooks' import type { OnFeaturesChange } from '../../types' +import cn from '@/utils/classnames' import Panel from '@/app/components/app/configuration/base/feature-panel' import Button from '@/app/components/base/button' import OperationBtn from '@/app/components/app/configuration/base/operation-btn' diff --git a/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx b/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx index b659e14f40..2e08a99122 100644 --- a/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx +++ b/web/app/components/base/features/feature-panel/score-slider/base-slider/index.tsx @@ -1,6 +1,6 @@ import ReactSlider from 'react-slider' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type ISliderProps = { className?: string diff --git a/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx b/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx index 3fb12745ff..ea1d789d0a 100644 --- a/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx +++ b/web/app/components/base/features/feature-panel/text-to-speech/param-config-content.tsx @@ -2,7 +2,6 @@ import useSWR from 'swr' import produce from 'immer' import React, { Fragment } from 'react' -import classNames from 'classnames' import { RiQuestionLine, } from '@remixicon/react' @@ -15,10 +14,13 @@ import { useFeaturesStore, } from '../../hooks' import type { OnFeaturesChange } from '../../types' +import classNames from '@/utils/classnames' import type { Item } from '@/app/components/base/select' import { fetchAppVoices } from '@/service/apps' import Tooltip from '@/app/components/base/tooltip' import { languages } from '@/i18n/language' +import RadioGroup from '@/app/components/app/configuration/config-vision/radio-group' +import { TtsAutoPlay } from '@/types/app' type VoiceParamConfigProps = { onChange?: OnFeaturesChange @@ -33,12 +35,16 @@ const VoiceParamConfig = ({ const text2speech = useFeatures(state => state.features.text2speech) const featuresStore = useFeaturesStore() - const languageItem = languages.find(item => item.value === text2speech.language) + let languageItem = languages.find(item => item.value === text2speech?.language) + if (languages && !languageItem) + languageItem = languages[0] const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select') const language = languageItem?.value const voiceItems = useSWR({ appId, language }, fetchAppVoices).data - const voiceItem = voiceItems?.find(item => item.value === text2speech.voice) + let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice) + if (voiceItems && !voiceItem) + voiceItem = voiceItems[0] const localVoicePlaceholder = voiceItem?.name || t('common.placeholder.select') const handleChange = (value: Record) => { @@ -66,13 +72,14 @@ const VoiceParamConfig = ({
-
{t('appDebug.voice.voiceSettings.language')}
- +
{t('appDebug.voice.voiceSettings.language')}
+ {t('appDebug.voice.voiceSettings.resolutionTooltip').split('\n').map(item => (
{item}
))}
} selector='config-resolution-tooltip'> - +
- + {languageItem?.name ? t(`common.voice.language.${languageItem?.value.replace('-', '')}`) : localLanguagePlaceholder} @@ -102,7 +110,8 @@ const VoiceParamConfig = ({ leaveTo="opacity-0" > - + {languages.map((item: Item) => ( {t(`common.voice.language.${(item.value).toString().replace('-', '')}`)} - {(selected || item.value === text2speech.language) && ( + {(selected || item.value === text2speech?.language) && ( - )} @@ -137,7 +146,8 @@ const VoiceParamConfig = ({
-
{t('appDebug.voice.voiceSettings.voice')}
+
{t('appDebug.voice.voiceSettings.voice')}
- - {voiceItem?.name ?? localVoicePlaceholder} + + {voiceItem?.name ?? localVoicePlaceholder} - + {voiceItems?.map((item: Item) => ( ( <> {item.name} - {(selected || item.value === text2speech.voice) && ( + {(selected || item.value === text2speech?.voice) && ( - )} @@ -196,6 +209,29 @@ const VoiceParamConfig = ({
+
+
{t('appDebug.voice.voiceSettings.autoPlay')}
+ { + handleChange({ + autoPlay: value, + }) + }} + /> +
diff --git a/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx b/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx index e748f5f349..095fd6cce8 100644 --- a/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx +++ b/web/app/components/base/features/feature-panel/text-to-speech/params-config.tsx @@ -1,9 +1,9 @@ 'use client' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { OnFeaturesChange } from '../../types' import ParamConfigContent from './param-config-content' +import cn from '@/utils/classnames' import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' import { PortalToFollowElem, diff --git a/web/app/components/base/features/types.ts b/web/app/components/base/features/types.ts index 2ac2326ec3..cdf6b0da1f 100644 --- a/web/app/components/base/features/types.ts +++ b/web/app/components/base/features/types.ts @@ -1,4 +1,4 @@ -import type { TransferMethod } from '@/types/app' +import type { TransferMethod, TtsAutoPlay } from '@/types/app' export type EnabledOrDisabled = { enabled?: boolean @@ -14,6 +14,7 @@ export type SuggestedQuestionsAfterAnswer = EnabledOrDisabled export type TextToSpeech = EnabledOrDisabled & { language?: string voice?: string + autoPlay?: TtsAutoPlay } export type SpeechToText = EnabledOrDisabled diff --git a/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg b/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg new file mode 100644 index 0000000000..36c82d10d5 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/solid/users/users-plus.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/script.js b/web/app/components/base/icons/script.js index f892c45ff3..0ff6a2a483 100644 --- a/web/app/components/base/icons/script.js +++ b/web/app/components/base/icons/script.js @@ -107,7 +107,7 @@ const generateImageComponent = async (entry, pathList) => { // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import s from './<%= fileName %>.module.css' const Icon = React.forwardRef, HTMLSpanElement>>(( diff --git a/web/app/components/base/icons/src/image/llm/BaichuanTextCn.tsx b/web/app/components/base/icons/src/image/llm/BaichuanTextCn.tsx index 5ae8f57d65..5206d02622 100644 --- a/web/app/components/base/icons/src/image/llm/BaichuanTextCn.tsx +++ b/web/app/components/base/icons/src/image/llm/BaichuanTextCn.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './BaichuanTextCn.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/Minimax.tsx b/web/app/components/base/icons/src/image/llm/Minimax.tsx index de07044dd0..7b75ff6f61 100644 --- a/web/app/components/base/icons/src/image/llm/Minimax.tsx +++ b/web/app/components/base/icons/src/image/llm/Minimax.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './Minimax.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/MinimaxText.tsx b/web/app/components/base/icons/src/image/llm/MinimaxText.tsx index 747c9ed7ea..490a977517 100644 --- a/web/app/components/base/icons/src/image/llm/MinimaxText.tsx +++ b/web/app/components/base/icons/src/image/llm/MinimaxText.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './MinimaxText.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/Tongyi.tsx b/web/app/components/base/icons/src/image/llm/Tongyi.tsx index 98d85ff0b5..543b4ce63d 100644 --- a/web/app/components/base/icons/src/image/llm/Tongyi.tsx +++ b/web/app/components/base/icons/src/image/llm/Tongyi.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './Tongyi.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/TongyiText.tsx b/web/app/components/base/icons/src/image/llm/TongyiText.tsx index 1aaffab437..16e3920780 100644 --- a/web/app/components/base/icons/src/image/llm/TongyiText.tsx +++ b/web/app/components/base/icons/src/image/llm/TongyiText.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './TongyiText.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/TongyiTextCn.tsx b/web/app/components/base/icons/src/image/llm/TongyiTextCn.tsx index 225c5df46e..c14d323c60 100644 --- a/web/app/components/base/icons/src/image/llm/TongyiTextCn.tsx +++ b/web/app/components/base/icons/src/image/llm/TongyiTextCn.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './TongyiTextCn.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/Wxyy.tsx b/web/app/components/base/icons/src/image/llm/Wxyy.tsx index 070c8967fe..312e325043 100644 --- a/web/app/components/base/icons/src/image/llm/Wxyy.tsx +++ b/web/app/components/base/icons/src/image/llm/Wxyy.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './Wxyy.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/WxyyText.tsx b/web/app/components/base/icons/src/image/llm/WxyyText.tsx index 07a9f98d2a..fd618e8d34 100644 --- a/web/app/components/base/icons/src/image/llm/WxyyText.tsx +++ b/web/app/components/base/icons/src/image/llm/WxyyText.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './WxyyText.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/image/llm/WxyyTextCn.tsx b/web/app/components/base/icons/src/image/llm/WxyyTextCn.tsx index 7938dd3288..01acc26241 100644 --- a/web/app/components/base/icons/src/image/llm/WxyyTextCn.tsx +++ b/web/app/components/base/icons/src/image/llm/WxyyTextCn.tsx @@ -2,8 +2,8 @@ // DON NOT EDIT IT MANUALLY import * as React from 'react' -import cn from 'classnames' import s from './WxyyTextCn.module.css' +import cn from '@/utils/classnames' const Icon = React.forwardRef, HTMLSpanElement>>(( { className, ...restProps }, diff --git a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json new file mode 100644 index 0000000000..a70117f655 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.json @@ -0,0 +1,77 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "users-plus" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Solid" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M20 15C20 14.4477 19.5523 14 19 14C18.4477 14 18 14.4477 18 15V17H16C15.4477 17 15 17.4477 15 18C15 18.5523 15.4477 19 16 19H18V21C18 21.5523 18.4477 22 19 22C19.5523 22 20 21.5523 20 21V19H22C22.5523 19 23 18.5523 23 18C23 17.4477 22.5523 17 22 17H20V15Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M12.181 14.1635C12.4632 14.3073 12.6927 14.5368 12.8365 14.819C12.9896 15.1194 13.0001 15.4476 13 15.7769C13 15.7847 13 15.7924 13 15.8C13 17.2744 12.9995 18.7488 13 20.2231C13.0001 20.3422 13.0001 20.4845 12.9899 20.6098C12.978 20.755 12.9476 20.963 12.8365 21.181C12.6927 21.4632 12.4632 21.6927 12.181 21.8365C11.963 21.9476 11.7551 21.978 11.6098 21.9899C11.4845 22.0001 11.3423 22.0001 11.2231 22C8.4077 21.999 5.59226 21.999 2.77682 22C2.65755 22.0001 2.51498 22.0001 2.38936 21.9898C2.24364 21.9778 2.03523 21.9472 1.81695 21.8356C1.53435 21.6911 1.30428 21.46 1.16109 21.1767C1.05079 20.9585 1.02087 20.7506 1.0095 20.6046C0.999737 20.4791 1.00044 20.3369 1.00103 20.2185C1.00619 19.1792 0.975203 18.0653 1.38061 17.0866C1.88808 15.8614 2.86145 14.8881 4.08659 14.3806C4.59629 14.1695 5.13457 14.0819 5.74331 14.0404C6.33532 14 7.06273 14 7.96449 14C9.05071 14 10.1369 14.0004 11.2231 14C11.5524 13.9999 11.8806 14.0104 12.181 14.1635Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M14.5731 2.91554C14.7803 2.40361 15.3633 2.1566 15.8752 2.36382C17.7058 3.10481 19 4.90006 19 7C19 9.09994 17.7058 10.8952 15.8752 11.6362C15.3633 11.8434 14.7803 11.5964 14.5731 11.0845C14.3658 10.5725 14.6129 9.98953 15.1248 9.7823C16.2261 9.33652 17 8.25744 17 7C17 5.74256 16.2261 4.66348 15.1248 4.2177C14.6129 4.01047 14.3658 3.42748 14.5731 2.91554Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "fill-rule": "evenodd", + "clip-rule": "evenodd", + "d": "M4.50001 7C4.50001 4.23858 6.73858 2 9.50001 2C12.2614 2 14.5 4.23858 14.5 7C14.5 9.76142 12.2614 12 9.50001 12C6.73858 12 4.50001 9.76142 4.50001 7Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "UsersPlus" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx new file mode 100644 index 0000000000..a2294960f7 --- /dev/null +++ b/web/app/components/base/icons/src/vender/solid/users/UsersPlus.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './UsersPlus.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'UsersPlus' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/solid/users/index.ts b/web/app/components/base/icons/src/vender/solid/users/index.ts index 7047a62edc..4c969bffd7 100644 --- a/web/app/components/base/icons/src/vender/solid/users/index.ts +++ b/web/app/components/base/icons/src/vender/solid/users/index.ts @@ -1,3 +1,4 @@ export { default as User01 } from './User01' export { default as UserEdit02 } from './UserEdit02' export { default as Users01 } from './Users01' +export { default as UsersPlus } from './UsersPlus' diff --git a/web/app/components/base/image-gallery/index.tsx b/web/app/components/base/image-gallery/index.tsx index fd85bb1551..dc52251df9 100644 --- a/web/app/components/base/image-gallery/index.tsx +++ b/web/app/components/base/image-gallery/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React, { useState } from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' import ImagePreview from '@/app/components/base/image-uploader/image-preview' type Props = { diff --git a/web/app/components/base/image-uploader/chat-image-uploader.tsx b/web/app/components/base/image-uploader/chat-image-uploader.tsx index b79dc565b5..742965be1a 100644 --- a/web/app/components/base/image-uploader/chat-image-uploader.tsx +++ b/web/app/components/base/image-uploader/chat-image-uploader.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import Uploader from './uploader' import ImageLinkInput from './image-link-input' +import cn from '@/utils/classnames' import { ImagePlus } from '@/app/components/base/icons/src/vender/line/images' import { TransferMethod } from '@/types/app' import { diff --git a/web/app/components/base/image-uploader/image-list.tsx b/web/app/components/base/image-uploader/image-list.tsx index 90ac0e1a35..ac622cb8a9 100644 --- a/web/app/components/base/image-uploader/image-list.tsx +++ b/web/app/components/base/image-uploader/image-list.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine, RiLoader2Line, } from '@remixicon/react' +import cn from '@/utils/classnames' import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import TooltipPlus from '@/app/components/base/tooltip-plus' diff --git a/web/app/components/base/logo/logo-site.tsx b/web/app/components/base/logo/logo-site.tsx index 65569c8c99..2db61a9cbb 100644 --- a/web/app/components/base/logo/logo-site.tsx +++ b/web/app/components/base/logo/logo-site.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import classNames from 'classnames' +import classNames from '@/utils/classnames' type LogoSiteProps = { className?: string diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx index 11db2f727c..3adb4d75e1 100644 --- a/web/app/components/base/markdown.tsx +++ b/web/app/components/base/markdown.tsx @@ -8,8 +8,8 @@ import SyntaxHighlighter from 'react-syntax-highlighter' import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs' import type { RefObject } from 'react' import { memo, useEffect, useMemo, useRef, useState } from 'react' -import cn from 'classnames' import type { CodeComponent } from 'react-markdown/lib/ast-to-react' +import cn from '@/utils/classnames' import CopyBtn from '@/app/components/base/copy-btn' import SVGBtn from '@/app/components/base/svg' import Flowchart from '@/app/components/base/mermaid' diff --git a/web/app/components/base/mermaid/index.tsx b/web/app/components/base/mermaid/index.tsx index bef26b7a36..dc01338a8c 100644 --- a/web/app/components/base/mermaid/index.tsx +++ b/web/app/components/base/mermaid/index.tsx @@ -1,6 +1,8 @@ import React, { useEffect, useRef, useState } from 'react' import mermaid from 'mermaid' +import { usePrevious } from 'ahooks' import CryptoJS from 'crypto-js' +import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' import LoadingAnim from '@/app/components/base/chat/chat/loading-anim' let mermaidAPI: any @@ -40,32 +42,15 @@ const Flowchart = React.forwardRef((props: { }, ref) => { const [svgCode, setSvgCode] = useState(null) const chartId = useRef(`flowchart_${CryptoJS.MD5(props.PrimitiveCode).toString()}`) - const [isRender, setIsRender] = useState(false) + const prevPrimitiveCode = usePrevious(props.PrimitiveCode) const [isLoading, setIsLoading] = useState(true) - - const clearFlowchartCache = () => { - for (let i = localStorage.length - 1; i >= 0; --i) { - const key = localStorage.key(i) - if (key && key.startsWith('flowchart_')) - localStorage.removeItem(key) - } - } + const timeRef = useRef() + const [errMsg, setErrMsg] = useState('') const renderFlowchart = async (PrimitiveCode: string) => { try { - const cachedSvg: any = localStorage.getItem(chartId.current) - if (cachedSvg) { - setSvgCode(cachedSvg) - setIsLoading(false) - return - } - if (typeof window !== 'undefined' && mermaidAPI) { const svgGraph = await mermaidAPI.render(chartId.current, PrimitiveCode) - const dom = new DOMParser().parseFromString(svgGraph.svg, 'text/xml') - if (!dom.querySelector('g.main')) - throw new Error('empty svg') - const base64Svg: any = await svgToBase64(svgGraph.svg) setSvgCode(base64Svg) setIsLoading(false) @@ -74,30 +59,26 @@ const Flowchart = React.forwardRef((props: { } } catch (error) { - clearFlowchartCache() - // eslint-disable-next-line @typescript-eslint/no-use-before-define - handleReRender() + if (prevPrimitiveCode === props.PrimitiveCode) { + setIsLoading(false) + setErrMsg((error as Error).message) + } } } - const handleReRender = () => { - setIsRender(false) - setSvgCode(null) - if (chartId.current) - localStorage.removeItem(chartId.current) - - setTimeout(() => { - setIsRender(true) - renderFlowchart(props.PrimitiveCode) - }, 100) - } - useEffect(() => { - setIsRender(false) - setTimeout(() => { - setIsRender(true) + const cachedSvg: any = localStorage.getItem(chartId.current) + if (cachedSvg) { + setSvgCode(cachedSvg) + setIsLoading(false) + return + } + if (timeRef.current) + clearTimeout(timeRef.current) + + timeRef.current = setTimeout(() => { renderFlowchart(props.PrimitiveCode) - }, 100) + }, 300) }, [props.PrimitiveCode]) return ( @@ -105,16 +86,24 @@ const Flowchart = React.forwardRef((props: { // @ts-expect-error
{ - isRender - &&
- {svgCode && Mermaid chart} -
+ svgCode + &&
+ {svgCode && Mermaid chart} +
} {isLoading &&
} + { + errMsg + &&
+ +   + {errMsg} +
+ }
) }) diff --git a/web/app/components/base/message-log-modal/index.tsx b/web/app/components/base/message-log-modal/index.tsx index ef5a494579..45130b126f 100644 --- a/web/app/components/base/message-log-modal/index.tsx +++ b/web/app/components/base/message-log-modal/index.tsx @@ -1,10 +1,10 @@ import type { FC } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useCallback, useEffect, useRef, useState } from 'react' import { useBoolean, useClickAway } from 'ahooks' import { RiCloseLine } from '@remixicon/react' import IterationResultPanel from '../../workflow/run/iteration-result-panel' +import cn from '@/utils/classnames' import type { IChatItem } from '@/app/components/base/chat/chat/type' import Run from '@/app/components/workflow/run' import type { NodeTracing } from '@/types/workflow' diff --git a/web/app/components/base/modal/index.tsx b/web/app/components/base/modal/index.tsx index 4e1f184dab..9a80fc0486 100644 --- a/web/app/components/base/modal/index.tsx +++ b/web/app/components/base/modal/index.tsx @@ -1,7 +1,7 @@ import { Dialog, Transition } from '@headlessui/react' import { Fragment } from 'react' import { XMarkIcon } from '@heroicons/react/24/outline' -import classNames from 'classnames' +import classNames from '@/utils/classnames' // https://headlessui.com/react/dialog type IModal = { diff --git a/web/app/components/base/notion-icon/index.tsx b/web/app/components/base/notion-icon/index.tsx index 599592a1c5..273d90c5a2 100644 --- a/web/app/components/base/notion-icon/index.tsx +++ b/web/app/components/base/notion-icon/index.tsx @@ -1,5 +1,5 @@ -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' import type { DataSourceNotionPage } from '@/models/common' type IconTypes = 'workspace' | 'page' diff --git a/web/app/components/base/notion-page-selector/base.tsx b/web/app/components/base/notion-page-selector/base.tsx index 9afc158cf6..63aff09a93 100644 --- a/web/app/components/base/notion-page-selector/base.tsx +++ b/web/app/components/base/notion-page-selector/base.tsx @@ -1,10 +1,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import useSWR from 'swr' -import cn from 'classnames' import s from './base.module.css' import WorkspaceSelector from './workspace-selector' import SearchInput from './search-input' import PageSelector from './page-selector' +import cn from '@/utils/classnames' import { preImportNotionPages } from '@/service/datasets' import { NotionConnector } from '@/app/components/datasets/create/step-one' import type { DataSourceNotionPageMap, DataSourceNotionWorkspace, NotionPage } from '@/models/common' diff --git a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx index 944eb4781f..b120ef94b2 100644 --- a/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx +++ b/web/app/components/base/notion-page-selector/notion-page-selector-modal/index.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { XMarkIcon } from '@heroicons/react/24/outline' import NotionPageSelector from '../base' import type { NotionPageSelectorValue } from '../base' import s from './index.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' type NotionPageSelectorModalProps = { @@ -36,7 +36,7 @@ const NotionPageSelectorModal = ({ {}} + onClose={() => { }} >
{t('common.dataSource.notion.selector.addPages')}
diff --git a/web/app/components/base/notion-page-selector/page-selector/index.tsx b/web/app/components/base/notion-page-selector/page-selector/index.tsx index b679e33b82..b61fa34567 100644 --- a/web/app/components/base/notion-page-selector/page-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/page-selector/index.tsx @@ -2,10 +2,10 @@ import { memo, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { FixedSizeList as List, areEqual } from 'react-window' import type { ListChildComponentProps } from 'react-window' -import cn from 'classnames' import Checkbox from '../../checkbox' import NotionIcon from '../../notion-icon' import s from './index.module.css' +import cn from '@/utils/classnames' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' type PageSelectorProps = { diff --git a/web/app/components/base/notion-page-selector/search-input/index.tsx b/web/app/components/base/notion-page-selector/search-input/index.tsx index 1a41a4c099..8bf55273b7 100644 --- a/web/app/components/base/notion-page-selector/search-input/index.tsx +++ b/web/app/components/base/notion-page-selector/search-input/index.tsx @@ -1,8 +1,8 @@ import { useCallback } from 'react' import type { ChangeEvent } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' type SearchInputProps = { value: string diff --git a/web/app/components/base/notion-page-selector/workspace-selector/index.tsx b/web/app/components/base/notion-page-selector/workspace-selector/index.tsx index bc340e43ad..66227d4f4d 100644 --- a/web/app/components/base/notion-page-selector/workspace-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/workspace-selector/index.tsx @@ -2,9 +2,9 @@ import { useTranslation } from 'react-i18next' import { Fragment } from 'react' import { Menu, Transition } from '@headlessui/react' -import cn from 'classnames' import NotionIcon from '../../notion-icon' import s from './index.module.css' +import cn from '@/utils/classnames' import type { DataSourceNotionWorkspace } from '@/models/common' type WorkspaceSelectorProps = { diff --git a/web/app/components/base/popover/index.tsx b/web/app/components/base/popover/index.tsx index 92c7c34e36..141ac8ff70 100644 --- a/web/app/components/base/popover/index.tsx +++ b/web/app/components/base/popover/index.tsx @@ -1,7 +1,7 @@ import { Popover, Transition } from '@headlessui/react' import { Fragment, cloneElement, useRef } from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' export type HtmlContentProps = { onClose?: () => void diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 24d30eb526..4a380e6abd 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -16,7 +16,7 @@ import { } from '@floating-ui/react' import type { OffsetOptions, Placement } from '@floating-ui/react' -import cn from 'classnames' +import cn from '@/utils/classnames' export type PortalToFollowElemOptions = { /* * top, bottom, left, right diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/external-tool-option.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/external-tool-option.tsx deleted file mode 100644 index ffaf08a0f5..0000000000 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/external-tool-option.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { memo } from 'react' -import { MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin' - -export class VariableOption extends MenuOption { - title: string - icon?: JSX.Element - extraElement?: JSX.Element - keywords: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - - constructor( - title: string, - options: { - icon?: JSX.Element - extraElement?: JSX.Element - keywords?: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - }, - ) { - super(title) - this.title = title - this.keywords = options.keywords || [] - this.icon = options.icon - this.extraElement = options.extraElement - this.keyboardShortcut = options.keyboardShortcut - this.onSelect = options.onSelect.bind(this) - } -} - -type VariableMenuItemProps = { - isSelected: boolean - onClick: () => void - onMouseEnter: () => void - option: VariableOption - queryString: string | null -} -export const VariableMenuItem = memo(({ - isSelected, - onClick, - onMouseEnter, - option, - queryString, -}: VariableMenuItemProps) => { - const title = option.title - let before = title - let middle = '' - let after = '' - - if (queryString) { - const regex = new RegExp(queryString, 'i') - const match = regex.exec(option.title) - - if (match) { - before = title.substring(0, match.index) - middle = match[0] - after = title.substring(match.index + match[0].length) - } - } - - return ( -
-
- {option.icon} -
-
- {before} - {middle} - {after} -
- {option.extraElement} -
- ) -}) -VariableMenuItem.displayName = 'VariableMenuItem' diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx index bb21a46366..b14bf8112b 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/hooks.tsx @@ -15,8 +15,9 @@ import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block' import { INSERT_QUERY_BLOCK_COMMAND } from '../query-block' import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '../variable-block' import { $createCustomTextNode } from '../custom-text/node' -import { PromptOption } from './prompt-option' -import { VariableOption } from './variable-option' +import { PromptMenuItem } from './prompt-option' +import { VariableMenuItem } from './variable-option' +import { PickerBlockMenuOption } from './menu' import { File05 } from '@/app/components/base/icons/src/vender/solid/files' import { MessageClockCircle, @@ -35,62 +36,111 @@ export const usePromptOptions = ( const { t } = useTranslation() const [editor] = useLexicalComposerContext() - return useMemo(() => { - return [ - ...contextBlock?.show - ? [ - new PromptOption(t('common.promptEditor.context.item.title'), { - icon: , - onSelect: () => { - if (!contextBlock?.selectable) - return - editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined) - }, - disabled: !contextBlock?.selectable, - }), - ] - : [], - ...queryBlock?.show - ? [ - new PromptOption(t('common.promptEditor.query.item.title'), { - icon: , - onSelect: () => { - if (!queryBlock?.selectable) - return - editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined) - }, - disabled: !queryBlock?.selectable, - }), - ] - : [], - ...historyBlock?.show - ? [ - new PromptOption(t('common.promptEditor.history.item.title'), { - icon: , - onSelect: () => { - if (!historyBlock?.selectable) - return - editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined) - }, - disabled: !historyBlock?.selectable, - }), - ] - : [], - ] - }, [contextBlock, editor, historyBlock, queryBlock, t]) + const promptOptions: PickerBlockMenuOption[] = [] + if (contextBlock?.show) { + promptOptions.push(new PickerBlockMenuOption({ + key: t('common.promptEditor.context.item.title'), + group: 'prompt context', + render: ({ isSelected, onSelect, onSetHighlight }) => { + return } + disabled={!contextBlock.selectable} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + }, + onSelect: () => { + if (!contextBlock?.selectable) + return + editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined) + }, + })) + } + + if (queryBlock?.show) { + promptOptions.push( + new PickerBlockMenuOption({ + key: t('common.promptEditor.query.item.title'), + group: 'prompt query', + render: ({ isSelected, onSelect, onSetHighlight }) => { + return ( + } + disabled={!queryBlock.selectable} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, + onSelect: () => { + if (!queryBlock?.selectable) + return + editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined) + }, + }), + ) + } + + if (historyBlock?.show) { + promptOptions.push( + new PickerBlockMenuOption({ + key: t('common.promptEditor.history.item.title'), + group: 'prompt history', + render: ({ isSelected, onSelect, onSetHighlight }) => { + return ( + } + disabled={!historyBlock.selectable + } + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, + onSelect: () => { + if (!historyBlock?.selectable) + return + editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined) + }, + }), + ) + } + return promptOptions } export const useVariableOptions = ( variableBlock?: VariableBlockType, queryString?: string, -) => { +): PickerBlockMenuOption[] => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() const options = useMemo(() => { - const baseOptions = (variableBlock?.variables || []).map((item) => { - return new VariableOption(item.value, { - icon: , + if (!variableBlock?.variables) + return [] + + const baseOptions = (variableBlock.variables).map((item) => { + return new PickerBlockMenuOption({ + key: item.value, + group: 'prompt variable', + render: ({ queryString, isSelected, onSelect, onSetHighlight }) => { + return ( + } + queryString={queryString} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, onSelect: () => { editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`) }, @@ -101,12 +151,25 @@ export const useVariableOptions = ( const regex = new RegExp(queryString, 'i') - return baseOptions.filter(option => regex.test(option.title) || option.keywords.some(keyword => regex.test(keyword))) + return baseOptions.filter(option => regex.test(option.key)) }, [editor, queryString, variableBlock]) const addOption = useMemo(() => { - return new VariableOption(t('common.promptEditor.variable.modal.add'), { - icon: , + return new PickerBlockMenuOption({ + key: t('common.promptEditor.variable.modal.add'), + group: 'prompt variable', + render: ({ queryString, isSelected, onSelect, onSetHighlight }) => { + return ( + } + queryString={queryString} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, onSelect: () => { editor.update(() => { const prefixNode = $createCustomTextNode('{{') @@ -131,16 +194,31 @@ export const useExternalToolOptions = ( const [editor] = useLexicalComposerContext() const options = useMemo(() => { - const baseToolOptions = (externalToolBlockType?.externalTools || []).map((item) => { - return new VariableOption(item.name, { - icon: ( - - ), - extraElement:
{item.variableName}
, + if (!externalToolBlockType?.externalTools) + return [] + const baseToolOptions = (externalToolBlockType.externalTools).map((item) => { + return new PickerBlockMenuOption({ + key: item.name, + group: 'external tool', + render: ({ queryString, isSelected, onSelect, onSetHighlight }) => { + return ( + + } + extraElement={
{item.variableName}
} + queryString={queryString} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, onSelect: () => { editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`) }, @@ -151,16 +229,28 @@ export const useExternalToolOptions = ( const regex = new RegExp(queryString, 'i') - return baseToolOptions.filter(option => regex.test(option.title) || option.keywords.some(keyword => regex.test(keyword))) + return baseToolOptions.filter(option => regex.test(option.key)) }, [editor, queryString, externalToolBlockType]) const addOption = useMemo(() => { - return new VariableOption(t('common.promptEditor.variable.modal.addTool'), { - icon: , - extraElement: , + return new PickerBlockMenuOption({ + key: t('common.promptEditor.variable.modal.addTool'), + group: 'external tool', + render: ({ queryString, isSelected, onSelect, onSetHighlight }) => { + return ( + } + extraElement={< ArrowUpRight className='w-3 h-3 text-gray-400' />} + queryString={queryString} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, onSelect: () => { - if (externalToolBlockType?.onAddExternalTool) - externalToolBlockType.onAddExternalTool() + externalToolBlockType?.onAddExternalTool?.() }, }) }, [externalToolBlockType, t]) @@ -191,11 +281,8 @@ export const useOptions = ( return useMemo(() => { return { - promptOptions, - variableOptions, - externalToolOptions, workflowVariableOptions, - allOptions: [...promptOptions, ...variableOptions, ...externalToolOptions], + allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions], } }, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions]) } diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index 2500f72e8b..15b07ded17 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -1,11 +1,11 @@ import { + Fragment, memo, useCallback, useState, } from 'react' import ReactDOM from 'react-dom' import { - FloatingPortal, flip, offset, shift, @@ -27,11 +27,8 @@ import { useBasicTypeaheadTriggerMatch } from '../../hooks' import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block' import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '../variable-block' import { $splitNodeContainingQuery } from '../../utils' -import type { PromptOption } from './prompt-option' -import PromptMenu from './prompt-menu' -import VariableMenu from './variable-menu' -import type { VariableOption } from './variable-option' import { useOptions } from './hooks' +import type { PickerBlockMenuOption } from './menu' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import { useEventEmitterContextContext } from '@/context/event-emitter' @@ -54,11 +51,13 @@ const ComponentPicker = ({ workflowVariableBlock, }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() - const { refs, floatingStyles, elements } = useFloating({ + const { refs, floatingStyles, isPositioned } = useFloating({ placement: 'bottom-start', middleware: [ offset(0), // fix hide cursor - shift(), + shift({ + padding: 8, + }), flip(), ], }) @@ -76,10 +75,7 @@ const ComponentPicker = ({ }) const { - allOptions, - promptOptions, - variableOptions, - externalToolOptions, + allFlattenOptions, workflowVariableOptions, } = useOptions( contextBlock, @@ -92,18 +88,15 @@ const ComponentPicker = ({ const onSelectOption = useCallback( ( - selectedOption: PromptOption | VariableOption, + selectedOption: PickerBlockMenuOption, nodeToRemove: TextNode | null, closeMenu: () => void, - matchingString: string, ) => { editor.update(() => { if (nodeToRemove && selectedOption?.key) nodeToRemove.remove() - if (selectedOption?.onSelect) - selectedOption.onSelect(matchingString) - + selectedOption.onSelectMenuOption() closeMenu() }) }, @@ -123,157 +116,93 @@ const ComponentPicker = ({ editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) }, [editor, checkForTriggerMatch, triggerString]) - const renderMenu = useCallback>(( + const renderMenu = useCallback>(( anchorElementRef, - { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, + { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, ) => { - if (anchorElementRef.current && (allOptions.length || workflowVariableBlock?.show)) { - return ( - <> - { - ReactDOM.createPortal( -
, - anchorElementRef.current, - ) - } - { - elements.reference && ( - -
- { - !!promptOptions.length && ( - <> - { - if (option.disabled) - return - setHighlightedIndex(index) - selectOptionAndCleanUp(option) - }} - onMouseEnter={(index, option) => { - if (option.disabled) - return - setHighlightedIndex(index) - }} - /> - - ) - } - { - !!variableOptions.length && ( - <> - { - !!promptOptions.length && ( -
- ) - } - { - if (option.disabled) - return - setHighlightedIndex(index) - selectOptionAndCleanUp(option) - }} - onMouseEnter={(index, option) => { - if (option.disabled) - return - setHighlightedIndex(index) - }} - queryString={queryString} - /> - - ) - } - { - !!externalToolOptions.length && ( - <> - { - (!!promptOptions.length || !!variableOptions.length) && ( -
- ) - } - { - if (option.disabled) - return - setHighlightedIndex(index) - selectOptionAndCleanUp(option) - }} - onMouseEnter={(index, option) => { - if (option.disabled) - return - setHighlightedIndex(index) - }} - queryString={queryString} - /> - - ) - } - { - workflowVariableBlock?.show && ( - <> - { - (!!promptOptions.length || !!variableOptions.length || !!externalToolOptions.length) && ( -
- ) - } -
- { - handleSelectWorkflowVariable(variables) - }} - /> -
- - ) - } -
-
- ) - } - - ) - } + if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) + return null + refs.setReference(anchorElementRef.current) - return null - }, [ - allOptions, - promptOptions, - variableOptions, - externalToolOptions, - queryString, - workflowVariableBlock?.show, - workflowVariableOptions, - handleSelectWorkflowVariable, - elements, - floatingStyles, - refs, - ]) + return ( + <> + { + ReactDOM.createPortal( + // The `LexicalMenu` will try to calculate the position of the floating menu based on the first child. + // Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected. + // See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493 +
+
+ { + options.map((option, index) => ( + + { + // Divider + index !== 0 && options.at(index - 1)?.group !== option.group && ( +
+ ) + } + {option.renderMenuOption({ + queryString, + isSelected: selectedIndex === index, + onSelect: () => { + selectOptionAndCleanUp(option) + }, + onSetHighlight: () => { + setHighlightedIndex(index) + }, + })} +
+ )) + } + { + workflowVariableBlock?.show && ( + <> + { + (!!options.length) && ( +
+ ) + } +
+ { + handleSelectWorkflowVariable(variables) + }} + /> +
+ + ) + } +
+
, + anchorElementRef.current, + ) + } + + ) + }, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable]) return ( diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/menu.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/menu.tsx new file mode 100644 index 0000000000..d8c7156926 --- /dev/null +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/menu.tsx @@ -0,0 +1,31 @@ +import { MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin' +import { Fragment } from 'react' + +/** + * Corresponds to the `MenuRenderFn` type from `@lexical/react/LexicalTypeaheadMenuPlugin`. + */ +type MenuOptionRenderProps = { + isSelected: boolean + onSelect: () => void + onSetHighlight: () => void + queryString: string | null +} + +export class PickerBlockMenuOption extends MenuOption { + public group?: string + + constructor( + private data: { + key: string + group?: string + onSelect?: () => void + render: (menuRenderProps: MenuOptionRenderProps) => JSX.Element + }, + ) { + super(data.key) + this.group = data.group + } + + public onSelectMenuOption = () => this.data.onSelect?.() + public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => {this.data.render(menuRenderProps)} +} diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-menu.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-menu.tsx deleted file mode 100644 index 6f16fcc2ba..0000000000 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-menu.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { memo } from 'react' -import { PromptMenuItem } from './prompt-option' - -type PromptMenuProps = { - startIndex: number - selectedIndex: number | null - options: any[] - onClick: (index: number, option: any) => void - onMouseEnter: (index: number, option: any) => void -} -const PromptMenu = ({ - startIndex, - selectedIndex, - options, - onClick, - onMouseEnter, -}: PromptMenuProps) => { - return ( -
- { - options.map((option, index: number) => ( - - )) - } -
- ) -} - -export default memo(PromptMenu) diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-option.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-option.tsx index 6937872786..7aabbe4b26 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-option.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/prompt-option.tsx @@ -1,64 +1,44 @@ import { memo } from 'react' -import { MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin' - -export class PromptOption extends MenuOption { - title: string - icon?: JSX.Element - keywords: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - disabled?: boolean - - constructor( - title: string, - options: { - icon?: JSX.Element - keywords?: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - disabled?: boolean - }, - ) { - super(title) - this.title = title - this.keywords = options.keywords || [] - this.icon = options.icon - this.keyboardShortcut = options.keyboardShortcut - this.onSelect = options.onSelect.bind(this) - this.disabled = options.disabled - } -} type PromptMenuItemMenuItemProps = { - startIndex: number - index: number + icon: JSX.Element + title: string + disabled?: boolean isSelected: boolean - onClick: (index: number, option: PromptOption) => void - onMouseEnter: (index: number, option: PromptOption) => void - option: PromptOption + onClick: () => void + onMouseEnter: () => void + setRefElement?: (element: HTMLDivElement) => void } export const PromptMenuItem = memo(({ - startIndex, - index, + icon, + title, + disabled, isSelected, onClick, onMouseEnter, - option, + setRefElement, }: PromptMenuItemMenuItemProps) => { return (
onMouseEnter(index + startIndex, option)} - onClick={() => onClick(index + startIndex, option)}> - {option.icon} -
{option.title}
+ ref={setRefElement} + onMouseEnter={() => { + if (disabled) + return + onMouseEnter() + }} + onClick={() => { + if (disabled) + return + onClick() + }}> + {icon} +
{title}
) }) diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-menu.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-menu.tsx deleted file mode 100644 index fefd93cb0f..0000000000 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-menu.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { memo } from 'react' -import { VariableMenuItem } from './variable-option' - -type VariableMenuProps = { - startIndex: number - selectedIndex: number | null - options: any[] - onClick: (index: number, option: any) => void - onMouseEnter: (index: number, option: any) => void - queryString: string | null -} -const VariableMenu = ({ - startIndex, - selectedIndex, - options, - onClick, - onMouseEnter, - queryString, -}: VariableMenuProps) => { - return ( -
- { - options.map((option, index: number) => ( - - )) - } -
- ) -} - -export default memo(VariableMenu) diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-option.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-option.tsx index 76f76c8491..27a88ab665 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-option.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/variable-option.tsx @@ -1,60 +1,32 @@ import { memo } from 'react' -import { MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin' -export class VariableOption extends MenuOption { +type VariableMenuItemProps = { title: string icon?: JSX.Element extraElement?: JSX.Element - keywords: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - - constructor( - title: string, - options: { - icon?: JSX.Element - extraElement?: JSX.Element - keywords?: Array - keyboardShortcut?: string - onSelect: (queryString: string) => void - }, - ) { - super(title) - this.title = title - this.keywords = options.keywords || [] - this.icon = options.icon - this.extraElement = options.extraElement - this.keyboardShortcut = options.keyboardShortcut - this.onSelect = options.onSelect.bind(this) - } -} - -type VariableMenuItemProps = { - startIndex: number - index: number isSelected: boolean - onClick: (index: number, option: VariableOption) => void - onMouseEnter: (index: number, option: VariableOption) => void - option: VariableOption queryString: string | null + onClick: () => void + onMouseEnter: () => void + setRefElement?: (element: HTMLDivElement) => void } export const VariableMenuItem = memo(({ - startIndex, - index, + title, + icon, + extraElement, isSelected, + queryString, onClick, onMouseEnter, - option, - queryString, + setRefElement, }: VariableMenuItemProps) => { - const title = option.title let before = title let middle = '' let after = '' if (queryString) { const regex = new RegExp(queryString, 'i') - const match = regex.exec(option.title) + const match = regex.exec(title) if (match) { before = title.substring(0, match.index) @@ -65,24 +37,23 @@ export const VariableMenuItem = memo(({ return (
onMouseEnter(index + startIndex, option)} - onClick={() => onClick(index + startIndex, option)}> + ref={setRefElement} + onMouseEnter={onMouseEnter} + onClick={onClick}>
- {option.icon} + {icon}
-
+
{before} {middle} {after}
- {option.extraElement} + {extraElement}
) }) diff --git a/web/app/components/base/prompt-editor/plugins/placeholder.tsx b/web/app/components/base/prompt-editor/plugins/placeholder.tsx index b9db0617bf..f5a45faa77 100644 --- a/web/app/components/base/prompt-editor/plugins/placeholder.tsx +++ b/web/app/components/base/prompt-editor/plugins/placeholder.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' const Placeholder = ({ compact, diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index 77842ca631..a0743ddb9f 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -9,7 +9,6 @@ import { } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' -import cn from 'classnames' import { RiErrorWarningFill, } from '@remixicon/react' @@ -20,6 +19,7 @@ import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, UPDATE_WORKFLOW_NODES_MAP, } from './index' +import cn from '@/utils/classnames' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' @@ -88,7 +88,7 @@ const WorkflowVariableBlockComponent = ({
) } -
{node?.title}
diff --git a/web/app/components/base/radio-card/index.tsx b/web/app/components/base/radio-card/index.tsx index 616e55aedf..5945c1c8be 100644 --- a/web/app/components/base/radio-card/index.tsx +++ b/web/app/components/base/radio-card/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type Props = { className?: string @@ -24,7 +24,7 @@ const RadioCard: FC = ({ description, noRadio, isChosen, - onChosen = () => {}, + onChosen = () => { }, chosenConfig, chosenConfigWrapClassName, }) => { diff --git a/web/app/components/base/radio-card/simple/index.tsx b/web/app/components/base/radio-card/simple/index.tsx index f739552e51..8d2bf837d3 100644 --- a/web/app/components/base/radio-card/simple/index.tsx +++ b/web/app/components/base/radio-card/simple/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import s from './style.module.css' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/base/radio/component/group/index.tsx b/web/app/components/base/radio/component/group/index.tsx index 6c75619660..e195c891cc 100644 --- a/web/app/components/base/radio/component/group/index.tsx +++ b/web/app/components/base/radio/component/group/index.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react' -import cn from 'classnames' import RadioGroupContext from '../../context' import s from '../../style.module.css' +import cn from '@/utils/classnames' export type TRadioGroupProps = { children?: ReactElement | ReactElement[] diff --git a/web/app/components/base/radio/component/radio/index.tsx b/web/app/components/base/radio/component/radio/index.tsx index c6f237ab74..a880bae599 100644 --- a/web/app/components/base/radio/component/radio/index.tsx +++ b/web/app/components/base/radio/component/radio/index.tsx @@ -1,9 +1,9 @@ import type { ReactElement } from 'react' import { useId } from 'react' -import cn from 'classnames' import { useContext } from 'use-context-selector' import RadioGroupContext from '../../context' import s from '../../style.module.css' +import cn from '@/utils/classnames' export type IRadioProps = { className?: string diff --git a/web/app/components/base/radio/ui.tsx b/web/app/components/base/radio/ui.tsx index 517a709b3f..234560a690 100644 --- a/web/app/components/base/radio/ui.tsx +++ b/web/app/components/base/radio/ui.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Props = { isChecked: boolean diff --git a/web/app/components/base/retry-button/index.tsx b/web/app/components/base/retry-button/index.tsx index c9e09b8a5b..689827af7b 100644 --- a/web/app/components/base/retry-button/index.tsx +++ b/web/app/components/base/retry-button/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useEffect, useReducer } from 'react' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import useSWR from 'swr' import s from './style.module.css' +import classNames from '@/utils/classnames' import Divider from '@/app/components/base/divider' import { getErrorDocs, retryErrorDocs } from '@/service/datasets' import type { IndexingStatusResponse } from '@/models/datasets' diff --git a/web/app/components/base/search-input/index.tsx b/web/app/components/base/search-input/index.tsx index 7e37306f13..a85bc2db8a 100644 --- a/web/app/components/base/search-input/index.tsx +++ b/web/app/components/base/search-input/index.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiSearchLine } from '@remixicon/react' +import cn from '@/utils/classnames' import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' type SearchInputProps = { @@ -37,7 +37,7 @@ const SearchInput: FC = ({ type="text" name="query" className={cn( - 'grow block h-[18px] bg-gray-200 rounded-md border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600', + 'grow block h-[18px] bg-gray-200 border-0 text-gray-700 text-[13px] placeholder:text-gray-500 appearance-none outline-none group-hover:bg-gray-300 caret-blue-600', focus && '!bg-white hover:bg-white group-hover:bg-white placeholder:!text-gray-400', !focus && value && 'hover:!bg-gray-200 group-hover:!bg-gray-200', white && '!bg-white hover:!bg-white group-hover:!bg-white placeholder:!text-gray-400', diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index b342ef29bb..24da8855fa 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { Fragment, useEffect, useState } from 'react' import { Combobox, Listbox, Transition } from '@headlessui/react' -import classNames from 'classnames' import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid' import { useTranslation } from 'react-i18next' +import classNames from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/base/simple-pie-chart/index.tsx b/web/app/components/base/simple-pie-chart/index.tsx index 4cc5186b70..7de539cbb1 100644 --- a/web/app/components/base/simple-pie-chart/index.tsx +++ b/web/app/components/base/simple-pie-chart/index.tsx @@ -2,8 +2,8 @@ import type { CSSProperties } from 'react' import { memo, useMemo } from 'react' import ReactECharts from 'echarts-for-react' import type { EChartsOption } from 'echarts' -import classNames from 'classnames' import style from './index.module.css' +import classNames from '@/utils/classnames' export type SimplePieChartProps = { percentage?: number diff --git a/web/app/components/base/slider/index.tsx b/web/app/components/base/slider/index.tsx index 0586a78152..b81bc2af23 100644 --- a/web/app/components/base/slider/index.tsx +++ b/web/app/components/base/slider/index.tsx @@ -1,5 +1,5 @@ import ReactSlider from 'react-slider' -import cn from 'classnames' +import cn from '@/utils/classnames' import './style.css' type ISliderProps = { diff --git a/web/app/components/base/switch/index.tsx b/web/app/components/base/switch/index.tsx index 7d13b0cb9a..0b025ab38b 100644 --- a/web/app/components/base/switch/index.tsx +++ b/web/app/components/base/switch/index.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useEffect, useState } from 'react' -import classNames from 'classnames' import { Switch as OriginalSwitch } from '@headlessui/react' +import classNames from '@/utils/classnames' type SwitchProps = { onChange?: (value: boolean) => void diff --git a/web/app/components/base/tab-header/index.tsx b/web/app/components/base/tab-header/index.tsx index 6ae5b69738..47edc5d561 100644 --- a/web/app/components/base/tab-header/index.tsx +++ b/web/app/components/base/tab-header/index.tsx @@ -1,9 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' - import s from './style.module.css' +import cn from '@/utils/classnames' type Item = { id: string diff --git a/web/app/components/base/tab-slider-new/index.tsx b/web/app/components/base/tab-slider-new/index.tsx index 05b330212a..4a7f856a54 100644 --- a/web/app/components/base/tab-slider-new/index.tsx +++ b/web/app/components/base/tab-slider-new/index.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Option = { value: string diff --git a/web/app/components/base/tab-slider-plain/index.tsx b/web/app/components/base/tab-slider-plain/index.tsx index 78a88231c7..84846d5d71 100644 --- a/web/app/components/base/tab-slider-plain/index.tsx +++ b/web/app/components/base/tab-slider-plain/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Option = { value: string diff --git a/web/app/components/base/tab-slider/index.tsx b/web/app/components/base/tab-slider/index.tsx index 011ad3096c..03296a9dee 100644 --- a/web/app/components/base/tab-slider/index.tsx +++ b/web/app/components/base/tab-slider/index.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Option = { value: string diff --git a/web/app/components/base/tag-input/index.tsx b/web/app/components/base/tag-input/index.tsx index e5f8294eec..7eab355a0d 100644 --- a/web/app/components/base/tag-input/index.tsx +++ b/web/app/components/base/tag-input/index.tsx @@ -3,7 +3,7 @@ import type { ChangeEvent, FC, KeyboardEvent } from 'react' import { } from 'use-context-selector' import { useTranslation } from 'react-i18next' import AutosizeInput from 'react-18-input-autosize' -import cn from 'classnames' +import cn from '@/utils/classnames' import { X } from '@/app/components/base/icons/src/vender/line/general' import { useToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/base/tag-management/filter.tsx b/web/app/components/base/tag-management/filter.tsx index a0dcffb575..560f238262 100644 --- a/web/app/components/base/tag-management/filter.tsx +++ b/web/app/components/base/tag-management/filter.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useDebounceFn, useMount } from 'ahooks' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' import { useStore as useTagStore } from './store' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, @@ -95,7 +95,7 @@ const TagFilter: FC = ({ )} {!value.length && (
- +
)} {!!value.length && ( @@ -103,7 +103,7 @@ const TagFilter: FC = ({ e.stopPropagation() onChange([]) }}> - +
)}
@@ -121,7 +121,7 @@ const TagFilter: FC = ({ onClick={() => selectTag(tag)} >
{tag.name}
- {value.includes(tag.id) && } + {value.includes(tag.id) && }
))} {!filteredTagList.length && ( diff --git a/web/app/components/base/tag-management/selector.tsx b/web/app/components/base/tag-management/selector.tsx index 01eb09c41f..74e0357064 100644 --- a/web/app/components/base/tag-management/selector.tsx +++ b/web/app/components/base/tag-management/selector.tsx @@ -3,9 +3,9 @@ import { useMemo, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useUnmount } from 'ahooks' -import cn from 'classnames' import { RiAddLine } from '@remixicon/react' import { useStore as useTagStore } from './store' +import cn from '@/utils/classnames' import type { HtmlContentProps } from '@/app/components/base/popover' import CustomPopover from '@/app/components/base/popover' import Divider from '@/app/components/base/divider' @@ -156,7 +156,7 @@ const Panel = (props: PanelProps) => { {}} + onCheck={() => { }} />
{tag.name}
@@ -170,7 +170,7 @@ const Panel = (props: PanelProps) => { {}} + onCheck={() => { }} />
{tag.name}
diff --git a/web/app/components/base/tag-management/tag-item-editor.tsx b/web/app/components/base/tag-management/tag-item-editor.tsx index 1fee27e8ec..f20e61a43c 100644 --- a/web/app/components/base/tag-management/tag-item-editor.tsx +++ b/web/app/components/base/tag-management/tag-item-editor.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import { useState } from 'react' -import cn from 'classnames' import { RiDeleteBinLine, RiEditLine, @@ -10,6 +9,7 @@ import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useStore as useTagStore } from './store' import TagRemoveModal from './tag-remove-modal' +import cn from '@/utils/classnames' import type { Tag } from '@/app/components/base/tag-management/constant' import { ToastContext } from '@/app/components/base/toast' import { diff --git a/web/app/components/base/tag-management/tag-remove-modal.tsx b/web/app/components/base/tag-management/tag-remove-modal.tsx index 681d379ed5..3e4d08fe3d 100644 --- a/web/app/components/base/tag-management/tag-remove-modal.tsx +++ b/web/app/components/base/tag-management/tag-remove-modal.tsx @@ -1,9 +1,9 @@ 'use client' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import s from './style.module.css' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' diff --git a/web/app/components/base/tag/index.tsx b/web/app/components/base/tag/index.tsx index 21c1d8038c..d7b9d3ed2b 100644 --- a/web/app/components/base/tag/index.tsx +++ b/web/app/components/base/tag/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import classNames from 'classnames' +import classNames from '@/utils/classnames' export type ITagProps = { children: string | React.ReactNode diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index db8c578244..06069f57e8 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -1,5 +1,4 @@ 'use client' -import classNames from 'classnames' import type { ReactNode } from 'react' import React, { useEffect, useState } from 'react' import { createRoot } from 'react-dom/client' @@ -10,6 +9,7 @@ import { XCircleIcon, } from '@heroicons/react/20/solid' import { createContext, useContext } from 'use-context-selector' +import classNames from '@/utils/classnames' export type IToastProps = { type?: 'success' | 'error' | 'warning' | 'info' diff --git a/web/app/components/base/tooltip-plus/index.tsx b/web/app/components/base/tooltip-plus/index.tsx index fc6e43a7fb..1dd2ac3ad6 100644 --- a/web/app/components/base/tooltip-plus/index.tsx +++ b/web/app/components/base/tooltip-plus/index.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { useBoolean } from 'ahooks' import type { OffsetOptions, Placement } from '@floating-ui/react' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' export type TooltipProps = { position?: Placement diff --git a/web/app/components/base/tooltip/index.tsx b/web/app/components/base/tooltip/index.tsx index 49b139f946..e7795c6537 100644 --- a/web/app/components/base/tooltip/index.tsx +++ b/web/app/components/base/tooltip/index.tsx @@ -1,8 +1,8 @@ 'use client' -import classNames from 'classnames' import type { FC } from 'react' import React from 'react' import { Tooltip as ReactTooltip } from 'react-tooltip' // fixed version to 5.8.3 https://github.com/ReactTooltip/react-tooltip/issues/972 +import classNames from '@/utils/classnames' import 'react-tooltip/dist/react-tooltip.css' type TooltipProps = { diff --git a/web/app/components/base/voice-input/index.tsx b/web/app/components/base/voice-input/index.tsx index 30a04f6c67..b42e3b849e 100644 --- a/web/app/components/base/voice-input/index.tsx +++ b/web/app/components/base/voice-input/index.tsx @@ -1,7 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useParams, usePathname } from 'next/navigation' -import cn from 'classnames' import { RiCloseLine, RiLoader2Line, @@ -10,6 +9,7 @@ import Recorder from 'js-audio-recorder' import { useRafInterval } from 'ahooks' import { convertToMp3 } from './utils' import s from './index.module.css' +import cn from '@/utils/classnames' import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import { audioToText } from '@/service/share' diff --git a/web/app/components/billing/annotation-full/index.tsx b/web/app/components/billing/annotation-full/index.tsx index 7283828f29..26d149a828 100644 --- a/web/app/components/billing/annotation-full/index.tsx +++ b/web/app/components/billing/annotation-full/index.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import Usage from './usage' import s from './style.module.css' +import cn from '@/utils/classnames' import GridMask from '@/app/components/base/grid-mask' const AnnotationFull: FC = () => { diff --git a/web/app/components/billing/annotation-full/modal.tsx b/web/app/components/billing/annotation-full/modal.tsx index c30abeccb1..274e709985 100644 --- a/web/app/components/billing/annotation-full/modal.tsx +++ b/web/app/components/billing/annotation-full/modal.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import Modal from '../../base/modal' import Usage from './usage' import s from './style.module.css' +import cn from '@/utils/classnames' import GridMask from '@/app/components/base/grid-mask' type Props = { diff --git a/web/app/components/billing/apps-full-in-dialog/index.tsx b/web/app/components/billing/apps-full-in-dialog/index.tsx index dfbb3e56bb..37abfebc50 100644 --- a/web/app/components/billing/apps-full-in-dialog/index.tsx +++ b/web/app/components/billing/apps-full-in-dialog/index.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import AppsInfo from '../usage-info/apps-info' import s from './style.module.css' +import cn from '@/utils/classnames' import GridMask from '@/app/components/base/grid-mask' const AppsFull: FC<{ loc: string }> = ({ diff --git a/web/app/components/billing/apps-full/index.tsx b/web/app/components/billing/apps-full/index.tsx index f37ba9af7f..9167d46352 100644 --- a/web/app/components/billing/apps-full/index.tsx +++ b/web/app/components/billing/apps-full/index.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import s from './style.module.css' +import cn from '@/utils/classnames' import GridMask from '@/app/components/base/grid-mask' const AppsFull: FC = () => { diff --git a/web/app/components/billing/header-billing-btn/index.tsx b/web/app/components/billing/header-billing-btn/index.tsx index 7fece4ae84..a8415524fd 100644 --- a/web/app/components/billing/header-billing-btn/index.tsx +++ b/web/app/components/billing/header-billing-btn/index.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import { Plan } from '../type' +import cn from '@/utils/classnames' import { useProviderContext } from '@/context/provider-context' type Props = { diff --git a/web/app/components/billing/plan/index.tsx b/web/app/components/billing/plan/index.tsx index 6ab8e889b2..baf4110127 100644 --- a/web/app/components/billing/plan/index.tsx +++ b/web/app/components/billing/plan/index.tsx @@ -1,7 +1,6 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { Plan } from '../type' import VectorSpaceInfo from '../usage-info/vector-space-info' @@ -10,6 +9,7 @@ import UpgradeBtn from '../upgrade-btn' import { User01 } from '../../base/icons/src/vender/line/users' import { MessageFastPlus } from '../../base/icons/src/vender/line/communication' import { FileUpload } from '../../base/icons/src/vender/line/files' +import cn from '@/utils/classnames' import { useProviderContext } from '@/context/provider-context' import UsageInfo from '@/app/components/billing/usage-info' diff --git a/web/app/components/billing/pricing/plan-item.tsx b/web/app/components/billing/pricing/plan-item.tsx index 260167d1e3..87a20437c3 100644 --- a/web/app/components/billing/pricing/plan-item.tsx +++ b/web/app/components/billing/pricing/plan-item.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiQuestionLine, } from '@remixicon/react' @@ -12,6 +11,7 @@ import { ALL_PLANS, NUM_INFINITE, contactSalesUrl, contractSales, unAvailable } import Toast from '../../base/toast' import TooltipPlus from '../../base/tooltip-plus' import { PlanRange } from './select-plan-range' +import cn from '@/utils/classnames' import { useAppContext } from '@/context/app-context' import { fetchSubscriptionUrls } from '@/service/billing' import { LanguagesSupported } from '@/i18n/language' diff --git a/web/app/components/billing/pricing/select-plan-range.tsx b/web/app/components/billing/pricing/select-plan-range.tsx index eb4626ec2f..8caffaa9d2 100644 --- a/web/app/components/billing/pricing/select-plan-range.tsx +++ b/web/app/components/billing/pricing/select-plan-range.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' export enum PlanRange { monthly = 'monthly', yearly = 'yearly', @@ -26,9 +26,9 @@ const ITem: FC<{ isActive: boolean; value: PlanRange; text: string; onClick: (va const ArrowIcon = ( - - - + + + ) diff --git a/web/app/components/billing/type.ts b/web/app/components/billing/type.ts index c6eae4858e..d78eab2ae3 100644 --- a/web/app/components/billing/type.ts +++ b/web/app/components/billing/type.ts @@ -66,6 +66,7 @@ export type CurrentPlanInfoBackend = { docs_processing: DocumentProcessingPriority can_replace_logo: boolean model_load_balancing_enabled: boolean + dataset_operator_enabled: boolean } export type SubscriptionItem = { diff --git a/web/app/components/billing/upgrade-btn/index.tsx b/web/app/components/billing/upgrade-btn/index.tsx index e53bada514..d7885d7569 100644 --- a/web/app/components/billing/upgrade-btn/index.tsx +++ b/web/app/components/billing/upgrade-btn/index.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { GoldCoin } from '../../base/icons/src/vender/solid/FinanceAndECommerce' import { Sparkles } from '../../base/icons/src/public/billing' import s from './style.module.css' +import cn from '@/utils/classnames' import { useModalContext } from '@/context/modal-context' type Props = { diff --git a/web/app/components/billing/vector-space-full/index.tsx b/web/app/components/billing/vector-space-full/index.tsx index 1ba564ec1c..5cfe5c9bde 100644 --- a/web/app/components/billing/vector-space-full/index.tsx +++ b/web/app/components/billing/vector-space-full/index.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import UpgradeBtn from '../upgrade-btn' import VectorSpaceInfo from '../usage-info/vector-space-info' import s from './style.module.css' +import cn from '@/utils/classnames' import { useProviderContext } from '@/context/provider-context' import GridMask from '@/app/components/base/grid-mask' diff --git a/web/app/components/datasets/common/retrieval-param-config/index.tsx b/web/app/components/datasets/common/retrieval-param-config/index.tsx index 786bf44761..79148606b7 100644 --- a/web/app/components/datasets/common/retrieval-param-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/index.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiQuestionLine, } from '@remixicon/react' +import cn from '@/utils/classnames' import TopKItem from '@/app/components/base/param-item/top-k-item' import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' import { RETRIEVE_METHOD } from '@/types/app' diff --git a/web/app/components/datasets/create/embedding-process/index.tsx b/web/app/components/datasets/create/embedding-process/index.tsx index d687e0f512..1e340d692f 100644 --- a/web/app/components/datasets/create/embedding-process/index.tsx +++ b/web/app/components/datasets/create/embedding-process/index.tsx @@ -5,11 +5,11 @@ import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { omit } from 'lodash-es' import { ArrowRightIcon } from '@heroicons/react/24/solid' -import cn from 'classnames' import { RiErrorWarningFill, } from '@remixicon/react' import s from './index.module.css' +import cn from '@/utils/classnames' import { FieldInfo } from '@/app/components/datasets/documents/detail/metadata' import Button from '@/app/components/base/button' import type { FullDocumentDetail, IndexingStatusResponse, ProcessRuleResponse } from '@/models/datasets' diff --git a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx index 9a1f64fbe0..e9247c49df 100644 --- a/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx +++ b/web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx @@ -3,8 +3,8 @@ import React, { useState } from 'react' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Input from '@/app/components/base/input' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/create/file-preview/index.tsx b/web/app/components/datasets/create/file-preview/index.tsx index cd3dcd2e45..e20af64386 100644 --- a/web/app/components/datasets/create/file-preview/index.tsx +++ b/web/app/components/datasets/create/file-preview/index.tsx @@ -1,9 +1,9 @@ 'use client' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { XMarkIcon } from '@heroicons/react/20/solid' import s from './index.module.css' +import cn from '@/utils/classnames' import type { CustomFile as File } from '@/models/datasets' import { fetchFilePreview } from '@/service/common' @@ -26,7 +26,7 @@ const FilePreview = ({ setPreviewContent(res.content) setLoading(false) } - catch {} + catch { } } const getFileName = (currentFile?: File) => { @@ -57,7 +57,7 @@ const FilePreview = ({
- {loading &&
} + {loading &&
} {!loading && (
{previewContent}
)} diff --git a/web/app/components/datasets/create/file-uploader/index.tsx b/web/app/components/datasets/create/file-uploader/index.tsx index cf3542604a..adb4bed0d1 100644 --- a/web/app/components/datasets/create/file-uploader/index.tsx +++ b/web/app/components/datasets/create/file-uploader/index.tsx @@ -2,9 +2,9 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import cn from 'classnames' import useSWR from 'swr' import s from './index.module.css' +import cn from '@/utils/classnames' import type { CustomFile as File, FileItem } from '@/models/datasets' import { ToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/datasets/create/notion-page-preview/index.tsx b/web/app/components/datasets/create/notion-page-preview/index.tsx index 82a8cead98..8225e56f04 100644 --- a/web/app/components/datasets/create/notion-page-preview/index.tsx +++ b/web/app/components/datasets/create/notion-page-preview/index.tsx @@ -1,9 +1,9 @@ 'use client' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { XMarkIcon } from '@heroicons/react/20/solid' import s from './index.module.css' +import cn from '@/utils/classnames' import type { NotionPage } from '@/models/common' import NotionIcon from '@/app/components/base/notion-icon' import { fetchNotionPagePreview } from '@/service/datasets' @@ -33,7 +33,7 @@ const NotionPagePreview = ({ setPreviewContent(res.content) setLoading(false) } - catch {} + catch { } } useEffect(() => { @@ -62,7 +62,7 @@ const NotionPagePreview = ({
- {loading &&
} + {loading &&
} {!loading && (
{previewContent}
)} diff --git a/web/app/components/datasets/create/step-one/index.tsx b/web/app/components/datasets/create/step-one/index.tsx index 0416a68abc..c2d77f4cec 100644 --- a/web/app/components/datasets/create/step-one/index.tsx +++ b/web/app/components/datasets/create/step-one/index.tsx @@ -1,7 +1,6 @@ 'use client' import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import FilePreview from '../file-preview' import FileUploader from '../file-uploader' import NotionPagePreview from '../notion-page-preview' @@ -9,6 +8,7 @@ import EmptyDatasetCreationModal from '../empty-dataset-creation-modal' import Website from '../website' import WebsitePreview from '../website/preview' import s from './index.module.css' +import cn from '@/utils/classnames' import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets' import type { NotionPage } from '@/models/common' import { DataSourceType } from '@/models/datasets' diff --git a/web/app/components/datasets/create/step-three/index.tsx b/web/app/components/datasets/create/step-three/index.tsx index 4cd2ed6133..804a196ed5 100644 --- a/web/app/components/datasets/create/step-three/index.tsx +++ b/web/app/components/datasets/create/step-three/index.tsx @@ -1,10 +1,10 @@ 'use client' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import EmbeddingProcess from '../embedding-process' import s from './index.module.css' +import cn from '@/utils/classnames' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import type { FullDocumentDetail, createDocumentResponse } from '@/models/datasets' @@ -33,7 +33,7 @@ const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: Step
{t('datasetCreation.stepThree.label')}
{datasetName || creationCache?.dataset?.name}
-
+
)} {datasetId && ( @@ -52,7 +52,7 @@ const StepThree = ({ datasetId, datasetName, indexingType, creationCache }: Step
{!isMobile &&
- +
{t('datasetCreation.stepThree.sideTipTitle')}
{t('datasetCreation.stepThree.sideTipContent')}
diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index 8630cc2b9c..3849f817d6 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -5,7 +5,6 @@ import { useContext } from 'use-context-selector' import { useBoolean } from 'ahooks' import { XMarkIcon } from '@heroicons/react/20/solid' import { RocketLaunchIcon } from '@heroicons/react/24/outline' -import cn from 'classnames' import { RiCloseLine, RiQuestionLine, @@ -16,6 +15,7 @@ import RetrievalMethodInfo from '../../common/retrieval-method-info' import PreviewItem, { PreviewType } from './preview-item' import LanguageSelect from './language-select' import s from './index.module.css' +import cn from '@/utils/classnames' import type { CrawlOptions, CrawlResultItem, CreateDocumentReq, CustomFile, FileIndexingEstimateResponse, FullDocumentDetail, IndexingEstimateParams, IndexingEstimateResponse, NotionInfo, PreProcessingRule, ProcessRule, Rules, createDocumentResponse } from '@/models/datasets' import { createDocument, diff --git a/web/app/components/datasets/create/step-two/language-select/index.tsx b/web/app/components/datasets/create/step-two/language-select/index.tsx index 89975f3d9c..f8709c89f3 100644 --- a/web/app/components/datasets/create/step-two/language-select/index.tsx +++ b/web/app/components/datasets/create/step-two/language-select/index.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' +import cn from '@/utils/classnames' import Popover from '@/app/components/base/popover' import { languages } from '@/i18n/language' diff --git a/web/app/components/datasets/create/steps-nav-bar/index.tsx b/web/app/components/datasets/create/steps-nav-bar/index.tsx index 340d2c9603..70724a308c 100644 --- a/web/app/components/datasets/create/steps-nav-bar/index.tsx +++ b/web/app/components/datasets/create/steps-nav-bar/index.tsx @@ -2,9 +2,9 @@ import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import cn from 'classnames' import { useCallback } from 'react' import s from './index.module.css' +import cn from '@/utils/classnames' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' type IStepsNavBarProps = { diff --git a/web/app/components/datasets/create/stop-embedding-modal/index.tsx b/web/app/components/datasets/create/stop-embedding-modal/index.tsx index c3d7f7aa5b..929b581829 100644 --- a/web/app/components/datasets/create/stop-embedding-modal/index.tsx +++ b/web/app/components/datasets/create/stop-embedding-modal/index.tsx @@ -1,8 +1,8 @@ 'use client' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' diff --git a/web/app/components/datasets/create/website/firecrawl/base/checkbox-with-label.tsx b/web/app/components/datasets/create/website/firecrawl/base/checkbox-with-label.tsx index ed5d2efd51..5c574ebe3e 100644 --- a/web/app/components/datasets/create/website/firecrawl/base/checkbox-with-label.tsx +++ b/web/app/components/datasets/create/website/firecrawl/base/checkbox-with-label.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import Checkbox from '@/app/components/base/checkbox' type Props = { diff --git a/web/app/components/datasets/create/website/firecrawl/base/error-message.tsx b/web/app/components/datasets/create/website/firecrawl/base/error-message.tsx index 3af234e09f..aa337ec4bf 100644 --- a/web/app/components/datasets/create/website/firecrawl/base/error-message.tsx +++ b/web/app/components/datasets/create/website/firecrawl/base/error-message.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' type Props = { diff --git a/web/app/components/datasets/create/website/firecrawl/base/field.tsx b/web/app/components/datasets/create/website/firecrawl/base/field.tsx index 1ba800d144..b1b7858d78 100644 --- a/web/app/components/datasets/create/website/firecrawl/base/field.tsx +++ b/web/app/components/datasets/create/website/firecrawl/base/field.tsx @@ -1,11 +1,11 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiQuestionLine, } from '@remixicon/react' import Input from './input' +import cn from '@/utils/classnames' import TooltipPlus from '@/app/components/base/tooltip-plus' type Props = { diff --git a/web/app/components/datasets/create/website/firecrawl/base/options-wrap.tsx b/web/app/components/datasets/create/website/firecrawl/base/options-wrap.tsx index ca58fe6cac..652401a20f 100644 --- a/web/app/components/datasets/create/website/firecrawl/base/options-wrap.tsx +++ b/web/app/components/datasets/create/website/firecrawl/base/options-wrap.tsx @@ -3,7 +3,7 @@ import { useBoolean } from 'ahooks' import type { FC } from 'react' import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' const I18N_PREFIX = 'datasetCreation.stepOne.website' diff --git a/web/app/components/datasets/create/website/firecrawl/crawled-result-item.tsx b/web/app/components/datasets/create/website/firecrawl/crawled-result-item.tsx index 1730314b47..5531d3e140 100644 --- a/web/app/components/datasets/create/website/firecrawl/crawled-result-item.tsx +++ b/web/app/components/datasets/create/website/firecrawl/crawled-result-item.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' import type { CrawlResultItem as CrawlResultItemType } from '@/models/datasets' import Checkbox from '@/app/components/base/checkbox' diff --git a/web/app/components/datasets/create/website/firecrawl/crawled-result.tsx b/web/app/components/datasets/create/website/firecrawl/crawled-result.tsx index ebda7952d9..2bd51e4d73 100644 --- a/web/app/components/datasets/create/website/firecrawl/crawled-result.tsx +++ b/web/app/components/datasets/create/website/firecrawl/crawled-result.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import CheckboxWithLabel from './base/checkbox-with-label' import CrawledResultItem from './crawled-result-item' +import cn from '@/utils/classnames' import type { CrawlResultItem } from '@/models/datasets' const I18N_PREFIX = 'datasetCreation.stepOne.website' diff --git a/web/app/components/datasets/create/website/firecrawl/crawling.tsx b/web/app/components/datasets/create/website/firecrawl/crawling.tsx index 97b2b01d2e..ee26e7671a 100644 --- a/web/app/components/datasets/create/website/firecrawl/crawling.tsx +++ b/web/app/components/datasets/create/website/firecrawl/crawling.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import { RowStruct } from '@/app/components/base/icons/src/public/other' type Props = { diff --git a/web/app/components/datasets/create/website/firecrawl/index.tsx b/web/app/components/datasets/create/website/firecrawl/index.tsx index 55a9f6b7ef..de4f8bb129 100644 --- a/web/app/components/datasets/create/website/firecrawl/index.tsx +++ b/web/app/components/datasets/create/website/firecrawl/index.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import Header from './header' import UrlInput from './base/url-input' import OptionsWrap from './base/options-wrap' @@ -10,6 +9,7 @@ import Options from './options' import CrawledResult from './crawled-result' import Crawling from './crawling' import ErrorMessage from './base/error-message' +import cn from '@/utils/classnames' import { useModalContext } from '@/context/modal-context' import type { CrawlOptions, CrawlResultItem } from '@/models/datasets' import Toast from '@/app/components/base/toast' diff --git a/web/app/components/datasets/create/website/firecrawl/options.tsx b/web/app/components/datasets/create/website/firecrawl/options.tsx index a066711051..20cc4f073f 100644 --- a/web/app/components/datasets/create/website/firecrawl/options.tsx +++ b/web/app/components/datasets/create/website/firecrawl/options.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' import React, { useCallback } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import CheckboxWithLabel from './base/checkbox-with-label' import Field from './base/field' +import cn from '@/utils/classnames' import type { CrawlOptions } from '@/models/datasets' const I18N_PREFIX = 'datasetCreation.stepOne.website' diff --git a/web/app/components/datasets/create/website/preview.tsx b/web/app/components/datasets/create/website/preview.tsx index 322ce43b17..65abe83ed7 100644 --- a/web/app/components/datasets/create/website/preview.tsx +++ b/web/app/components/datasets/create/website/preview.tsx @@ -1,9 +1,9 @@ 'use client' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { XMarkIcon } from '@heroicons/react/20/solid' import s from '../file-preview/index.module.css' +import cn from '@/utils/classnames' import type { CrawlResultItem } from '@/models/datasets' type IProps = { diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx index f0f5cca56c..edac3e2833 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx @@ -1,12 +1,12 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { RiDeleteBinLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' +import cn from '@/utils/classnames' import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' @@ -101,7 +101,7 @@ const CSVUploader: FC = ({ {t('datasetDocuments.list.batchModal.browse')}
- {dragging &&
} + {dragging &&
}
)} {file && ( diff --git a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx index fa5eabd31d..fdc0cf1198 100644 --- a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx +++ b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import React, { useState } from 'react' -import cn from 'classnames' import { ArrowUpRightIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import { @@ -11,6 +10,7 @@ import { StatusItem } from '../../list' import { DocumentTitle } from '../index' import s from './style.module.css' import { SegmentIndexTag } from './index' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx index 03285ecc74..80973ee631 100644 --- a/web/app/components/datasets/documents/detail/completed/index.tsx +++ b/web/app/components/datasets/documents/detail/completed/index.tsx @@ -5,7 +5,6 @@ import { HashtagIcon } from '@heroicons/react/24/solid' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { debounce, isNil, omitBy } from 'lodash-es' -import cn from 'classnames' import { RiCloseLine, RiEditLine, @@ -15,6 +14,7 @@ import { DocumentContext } from '../index' import { ProcessStatus } from '../segment-add' import s from './style.module.css' import InfiniteVirtualList from './InfiniteVirtualList' +import cn from '@/utils/classnames' import { formatNumber } from '@/utils/format' import Modal from '@/app/components/base/modal' import Switch from '@/app/components/base/switch' diff --git a/web/app/components/datasets/documents/detail/embedding/index.tsx b/web/app/components/datasets/documents/detail/embedding/index.tsx index 4a30999106..79b031549a 100644 --- a/web/app/components/datasets/documents/detail/embedding/index.tsx +++ b/web/app/components/datasets/documents/detail/embedding/index.tsx @@ -6,12 +6,12 @@ import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { omit } from 'lodash-es' import { ArrowRightIcon } from '@heroicons/react/24/solid' -import cn from 'classnames' import SegmentCard from '../completed/SegmentCard' import { FieldInfo } from '../metadata' import style from '../completed/style.module.css' import { DocumentContext } from '../index' import s from './style.module.css' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import Divider from '@/app/components/base/divider' import { ToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index 8e2bac6926..4f1e850fc8 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -7,7 +7,6 @@ import { createContext, useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' import { omit } from 'lodash-es' -import cn from 'classnames' import { OperationAction, StatusItem } from '../list' import s from '../style.module.css' import Completed from './completed' @@ -16,6 +15,7 @@ import Metadata from './metadata' import SegmentAdd, { ProcessStatus } from './segment-add' import BatchModal from './batch-modal' import style from './style.module.css' +import cn from '@/utils/classnames' import Divider from '@/app/components/base/divider' import Loading from '@/app/components/base/loading' import type { MetadataType } from '@/service/datasets' diff --git a/web/app/components/datasets/documents/detail/metadata/index.tsx b/web/app/components/datasets/documents/detail/metadata/index.tsx index d034abece9..9210926022 100644 --- a/web/app/components/datasets/documents/detail/metadata/index.tsx +++ b/web/app/components/datasets/documents/detail/metadata/index.tsx @@ -5,9 +5,9 @@ import { PencilIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { get } from 'lodash-es' -import cn from 'classnames' import { DocumentContext } from '../index' import s from './style.module.css' +import cn from '@/utils/classnames' import Input from '@/app/components/base/input' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' diff --git a/web/app/components/datasets/documents/detail/segment-add/index.tsx b/web/app/components/datasets/documents/detail/segment-add/index.tsx index 5b8d41adfd..e69f3e9ab0 100644 --- a/web/app/components/datasets/documents/detail/segment-add/index.tsx +++ b/web/app/components/datasets/documents/detail/segment-add/index.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiErrorWarningFill, RiLoader2Line, } from '@remixicon/react' +import cn from '@/utils/classnames' import { FilePlus02 } from '@/app/components/base/icons/src/vender/line/files' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import Popover from '@/app/components/base/popover' @@ -38,8 +38,8 @@ const SegmentAdd: FC = ({ <> {(importStatus === ProcessStatus.WAITING || importStatus === ProcessStatus.PROCESSING) && (
- {importStatus === ProcessStatus.WAITING &&
} - {importStatus === ProcessStatus.PROCESSING &&
} + {importStatus === ProcessStatus.WAITING &&
} + {importStatus === ProcessStatus.PROCESSING &&
} {t('datasetDocuments.list.batchModal.processing')}
diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index d5166cc542..dbdbffeb95 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -13,13 +13,13 @@ import { import { useContext } from 'use-context-selector' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import dayjs from 'dayjs' import { Edit03 } from '../../base/icons/src/vender/solid/general' import TooltipPlus from '../../base/tooltip-plus' import { Globe01 } from '../../base/icons/src/vender/line/mapsAndTravel' import s from './style.module.css' import RenameModal from './rename-modal' +import cn from '@/utils/classnames' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import Popover from '@/app/components/base/popover' diff --git a/web/app/components/datasets/hit-testing/hit-detail.tsx b/web/app/components/datasets/hit-testing/hit-detail.tsx index 5af022202b..70e43176d9 100644 --- a/web/app/components/datasets/hit-testing/hit-detail.tsx +++ b/web/app/components/datasets/hit-testing/hit-detail.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { SegmentIndexTag } from '../documents/detail/completed' import s from '../documents/detail/completed/style.module.css' +import cn from '@/utils/classnames' import type { SegmentDetailModel } from '@/models/datasets' import Divider from '@/app/components/base/divider' diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx index 8c665b1889..505cd98fa7 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -4,7 +4,6 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { omit } from 'lodash-es' -import cn from 'classnames' import { useBoolean } from 'ahooks' import { useContext } from 'use-context-selector' import SegmentCard from '../documents/detail/completed/SegmentCard' @@ -13,6 +12,7 @@ import Textarea from './textarea' import s from './style.module.css' import HitDetail from './hit-detail' import ModifyRetrievalModal from './modify-retrieval-modal' +import cn from '@/utils/classnames' import type { HitTestingResponse, HitTesting as HitTestingType } from '@/models/datasets' import Loading from '@/app/components/base/loading' import Modal from '@/app/components/base/modal' diff --git a/web/app/components/datasets/hit-testing/textarea.tsx b/web/app/components/datasets/hit-testing/textarea.tsx index 3432204f5f..5c146ae368 100644 --- a/web/app/components/datasets/hit-testing/textarea.tsx +++ b/web/app/components/datasets/hit-testing/textarea.tsx @@ -1,11 +1,11 @@ import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import Button from '../../base/button' import Tag from '../../base/tag' import Tooltip from '../../base/tooltip' import { getIcon } from '../common/retrieval-method-info' import s from './style.module.css' +import cn from '@/utils/classnames' import DatasetDetailContext from '@/context/dataset-detail' import type { HitTestingResponse } from '@/models/datasets' import { hitTesting } from '@/service/datasets' diff --git a/web/app/components/datasets/rename-modal/index.tsx b/web/app/components/datasets/rename-modal/index.tsx index 352efaa37b..a7e9e6e335 100644 --- a/web/app/components/datasets/rename-modal/index.tsx +++ b/web/app/components/datasets/rename-modal/index.tsx @@ -1,12 +1,12 @@ 'use client' import type { MouseEventHandler } from 'react' -import cn from 'classnames' import { useState } from 'react' import { RiCloseLine } from '@remixicon/react' import { BookOpenIcon } from '@heroicons/react/24/outline' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' import { ToastContext } from '@/app/components/base/toast' diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index 77910c1a61..1f75636dd1 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -1,31 +1,33 @@ 'use client' -import { useEffect, useState } from 'react' -import type { Dispatch } from 'react' +import { useState } from 'react' +import { useMount } from 'ahooks' import { useContext } from 'use-context-selector' import { BookOpenIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useSWRConfig } from 'swr' import { unstable_serialize } from 'swr/infinite' -import PermissionsRadio from '../permissions-radio' +import PermissionSelector from '../permission-selector' import IndexMethodRadio from '../index-method-radio' +import cn from '@/utils/classnames' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' import { updateDatasetSetting } from '@/service/datasets' -import type { DataSet, DataSetListResponse } from '@/models/datasets' +import type { DataSetListResponse } from '@/models/datasets' import DatasetDetailContext from '@/context/dataset-detail' import { type RetrievalConfig } from '@/types/app' -import { useModalContext } from '@/context/modal-context' +import { useAppContext } from '@/context/app-context' import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' import { useModelList, useModelListAndDefaultModelAndCurrentProviderAndModel, } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { fetchMembers } from '@/service/common' +import type { Member } from '@/models/common' const rowClass = ` flex justify-between py-4 flex-wrap gap-y-2 @@ -36,11 +38,6 @@ const labelClass = ` const inputClass = ` w-full max-w-[480px] px-3 bg-gray-100 text-sm text-gray-800 rounded-lg outline-none appearance-none ` -const useInitialValue: (depend: T, dispatch: Dispatch) => void = (depend, dispatch) => { - useEffect(() => { - dispatch(depend) - }, [depend]) -} const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { if (!pageIndex || previousPageData.has_more) @@ -52,12 +49,14 @@ const Form = () => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const { mutate } = useSWRConfig() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() const { dataset: currentDataset, mutateDatasetRes: mutateDatasets } = useContext(DatasetDetailContext) - const { setShowAccountSettingModal } = useModalContext() const [loading, setLoading] = useState(false) const [name, setName] = useState(currentDataset?.name ?? '') const [description, setDescription] = useState(currentDataset?.description ?? '') const [permission, setPermission] = useState(currentDataset?.permission) + const [selectedMemberIDs, setSelectedMemberIDs] = useState(currentDataset?.partial_member_list || []) + const [memberList, setMemberList] = useState([]) const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique) const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) const [embeddingModel, setEmbeddingModel] = useState( @@ -78,6 +77,18 @@ const Form = () => { } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const { data: embeddingModelList } = useModelList(ModelTypeEnum.textEmbedding) + const getMembers = async () => { + const { accounts } = await fetchMembers({ url: '/workspaces/current/members', params: {} }) + if (!accounts) + setMemberList([]) + else + setMemberList(accounts) + } + + useMount(() => { + getMembers() + }) + const handleSave = async () => { if (loading) return @@ -104,7 +115,7 @@ const Form = () => { }) try { setLoading(true) - await updateDatasetSetting({ + const requestParams = { datasetId: currentDataset!.id, body: { name, @@ -118,7 +129,16 @@ const Form = () => { embedding_model: embeddingModel.model, embedding_model_provider: embeddingModel.provider, }, - }) + } as any + if (permission === 'partial_members') { + requestParams.body.partial_member_list = selectedMemberIDs.map((id) => { + return { + user_id: id, + role: memberList.find(member => member.id === id)?.role, + } + }) + } + await updateDatasetSetting(requestParams) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) if (mutateDatasets) { await mutateDatasets() @@ -133,11 +153,6 @@ const Form = () => { } } - useInitialValue(currentDataset?.name ?? '', setName) - useInitialValue(currentDataset?.description ?? '', setDescription) - useInitialValue(currentDataset?.permission, setPermission) - useInitialValue(currentDataset?.indexing_technique, setIndexMethod) - return (
@@ -174,10 +189,13 @@ const Form = () => {
{t('datasetSettings.form.permissions')}
- setPermission(v)} + onMemberSelect={setSelectedMemberIDs} + memberList={memberList} />
diff --git a/web/app/components/datasets/settings/index-method-radio/index.tsx b/web/app/components/datasets/settings/index-method-radio/index.tsx index 5bd25b6a3e..2bf6f36ce1 100644 --- a/web/app/components/datasets/settings/index-method-radio/index.tsx +++ b/web/app/components/datasets/settings/index-method-radio/index.tsx @@ -1,7 +1,7 @@ 'use client' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import s from './index.module.css' +import classNames from '@/utils/classnames' import type { DataSet } from '@/models/datasets' const itemClass = ` diff --git a/web/app/components/datasets/settings/permission-selector/index.tsx b/web/app/components/datasets/settings/permission-selector/index.tsx new file mode 100644 index 0000000000..2405f9512b --- /dev/null +++ b/web/app/components/datasets/settings/permission-selector/index.tsx @@ -0,0 +1,174 @@ +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import React, { useMemo, useState } from 'react' +import { useDebounceFn } from 'ahooks' +import { RiArrowDownSLine } from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Avatar from '@/app/components/base/avatar' +import SearchInput from '@/app/components/base/search-input' +import { Check } from '@/app/components/base/icons/src/vender/line/general' +import { Users01, UsersPlus } from '@/app/components/base/icons/src/vender/solid/users' +import type { DatasetPermission } from '@/models/datasets' +import { useAppContext } from '@/context/app-context' +import type { Member } from '@/models/common' +export type RoleSelectorProps = { + disabled?: boolean + permission?: DatasetPermission + value: string[] + memberList: Member[] + onChange: (permission?: DatasetPermission) => void + onMemberSelect: (v: string[]) => void +} + +const PermissionSelector = ({ disabled, permission, value, memberList, onChange, onMemberSelect }: RoleSelectorProps) => { + const { t } = useTranslation() + const { userProfile } = useAppContext() + const [open, setOpen] = useState(false) + + const [keywords, setKeywords] = useState('') + const [searchKeywords, setSearchKeywords] = useState('') + const { run: handleSearch } = useDebounceFn(() => { + setSearchKeywords(keywords) + }, { wait: 500 }) + const handleKeywordsChange = (value: string) => { + setKeywords(value) + handleSearch() + } + const selectMember = (member: Member) => { + if (value.includes(member.id)) + onMemberSelect(value.filter(v => v !== member.id)) + else + onMemberSelect([...value, member.id]) + } + + const selectedMembers = useMemo(() => { + return [ + userProfile, + ...memberList.filter(member => member.id !== userProfile.id).filter(member => value.includes(member.id)), + ].map(member => member.name).join(', ') + }, [userProfile, value, memberList]) + const showMe = useMemo(() => { + return userProfile.name.includes(searchKeywords) || userProfile.email.includes(searchKeywords) + }, [searchKeywords, userProfile]) + const filteredMemberList = useMemo(() => { + return memberList.filter(member => (member.name.includes(searchKeywords) || member.email.includes(searchKeywords)) && member.id !== userProfile.id && ['owner', 'admin', 'editor', 'dataset_operator'].includes(member.role)) + }, [memberList, searchKeywords, userProfile]) + + return ( + +
+ !disabled && setOpen(v => !v)} + className='block' + > + {permission === 'only_me' && ( +
+ +
{t('datasetSettings.form.permissionsOnlyMe')}
+ {!disabled && } +
+ )} + {permission === 'all_team_members' && ( +
+
+ +
+
{t('datasetSettings.form.permissionsAllMember')}
+ {!disabled && } +
+ )} + {permission === 'partial_members' && ( +
+
+ +
+
{selectedMembers}
+ {!disabled && } +
+ )} +
+ +
+
+
{ + onChange('only_me') + setOpen(false) + }}> +
+ +
{t('datasetSettings.form.permissionsOnlyMe')}
+ {permission === 'only_me' && } +
+
+
{ + onChange('all_team_members') + setOpen(false) + }}> +
+
+ +
+
{t('datasetSettings.form.permissionsAllMember')}
+ {permission === 'all_team_members' && } +
+
+
{ + onChange('partial_members') + onMemberSelect([userProfile.id]) + }}> +
+
+ +
+
{t('datasetSettings.form.permissionsInvitedMembers')}
+ {permission === 'partial_members' && } +
+
+
+ {permission === 'partial_members' && ( +
+
+ +
+ {showMe && ( +
+ +
+
+ {userProfile.name} + {t('datasetSettings.form.me')} +
+
{userProfile.email}
+
+ +
+ )} + {filteredMemberList.map(member => ( +
selectMember(member)}> + +
+
{member.name}
+
{member.email}
+
+ {value.includes(member.id) && } +
+ ))} +
+ )} +
+
+
+
+ ) +} + +export default PermissionSelector diff --git a/web/app/components/datasets/settings/permissions-radio/assets/user.svg b/web/app/components/datasets/settings/permissions-radio/assets/user.svg deleted file mode 100644 index f5974c94a8..0000000000 --- a/web/app/components/datasets/settings/permissions-radio/assets/user.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web/app/components/datasets/settings/permissions-radio/index.module.css b/web/app/components/datasets/settings/permissions-radio/index.module.css deleted file mode 100644 index 372c1bedbb..0000000000 --- a/web/app/components/datasets/settings/permissions-radio/index.module.css +++ /dev/null @@ -1,46 +0,0 @@ -.user-icon { - width: 24px; - height: 24px; - background: url(./assets/user.svg) center center; - background-size: contain; -} - -.wrapper .item:hover { - background-color: #ffffff; - border-color: #B2CCFF; - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); -} - -.wrapper .item-active { - background-color: #ffffff; - border-width: 1.5px; - border-color: #528BFF; - box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); -} - -.wrapper .item-active .radio { - border-width: 5px; - border-color: #155EEF; -} - -.wrapper .item-active:hover { - border-width: 1.5px; - border-color: #528BFF; - box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); -} - -.wrapper .item.disable { - @apply opacity-60; -} -.wrapper .item-active.disable { - @apply opacity-60; -} -.wrapper .item.disable:hover { - @apply bg-gray-25 border border-gray-100 shadow-none cursor-default opacity-60; -} -.wrapper .item-active.disable:hover { - @apply cursor-default opacity-60; - border-width: 1.5px; - border-color: #528BFF; - box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); -} \ No newline at end of file diff --git a/web/app/components/datasets/settings/permissions-radio/index.tsx b/web/app/components/datasets/settings/permissions-radio/index.tsx index 4c851ad2f6..5270cfad81 100644 --- a/web/app/components/datasets/settings/permissions-radio/index.tsx +++ b/web/app/components/datasets/settings/permissions-radio/index.tsx @@ -1,7 +1,7 @@ 'use client' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import s from './index.module.css' +import classNames from '@/utils/classnames' import type { DataSet } from '@/models/datasets' const itemClass = ` diff --git a/web/app/components/develop/code.tsx b/web/app/components/develop/code.tsx index cef583f33e..c1fbaa1cf8 100644 --- a/web/app/components/develop/code.tsx +++ b/web/app/components/develop/code.tsx @@ -8,9 +8,8 @@ import { useState, } from 'react' import { Tab } from '@headlessui/react' -import classNames from 'classnames' - import { Tag } from './tag' +import classNames from '@/utils/classnames' const languageNames = { js: 'JavaScript', diff --git a/web/app/components/develop/md.tsx b/web/app/components/develop/md.tsx index 0f622c9f25..87f7b35aaf 100644 --- a/web/app/components/develop/md.tsx +++ b/web/app/components/develop/md.tsx @@ -1,5 +1,5 @@ 'use client' -import classNames from 'classnames' +import classNames from '@/utils/classnames' type IChildrenProps = { children: React.ReactNode diff --git a/web/app/components/develop/tag.tsx b/web/app/components/develop/tag.tsx index c7816fafbc..0b797f9f6f 100644 --- a/web/app/components/develop/tag.tsx +++ b/web/app/components/develop/tag.tsx @@ -1,5 +1,5 @@ 'use client' -import classNames from 'classnames' +import classNames from '@/utils/classnames' const variantStyles = { medium: 'rounded-lg px-1.5 ring-1 ring-inset', diff --git a/web/app/components/develop/template/template_workflow.en.mdx b/web/app/components/develop/template/template_workflow.en.mdx index 806cf992e0..9580c2aa0b 100644 --- a/web/app/components/develop/template/template_workflow.en.mdx +++ b/web/app/components/develop/template/template_workflow.en.mdx @@ -213,7 +213,7 @@ Workflow applications offers non-session support and is ideal for translation, a --- ### Request Example - + ```bash {{ title: 'cURL' }} - curl -X POST '${props.appDetail.api_base_url}/workflows/:task_id/stop' \ + curl -X POST '${props.appDetail.api_base_url}/workflows/tasks/:task_id/stop' \ -H 'Authorization: Bearer {api_key}' \ -H 'Content-Type: application/json' \ --data-raw '{ diff --git a/web/app/components/develop/template/template_workflow.zh.mdx b/web/app/components/develop/template/template_workflow.zh.mdx index 090823c504..6264eb57e5 100644 --- a/web/app/components/develop/template/template_workflow.zh.mdx +++ b/web/app/components/develop/template/template_workflow.zh.mdx @@ -209,7 +209,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等 --- ### Request Example - + ```bash {{ title: 'cURL' }} - curl -X POST '${props.appDetail.api_base_url}/workflows/:task_id/stop' \ + curl -X POST '${props.appDetail.api_base_url}/workflows/tasks/:task_id/stop' \ -H 'Authorization: Bearer {api_key}' \ -H 'Content-Type: application/json' \ --data-raw '{ diff --git a/web/app/components/explore/app-card/index.tsx b/web/app/components/explore/app-card/index.tsx index 42e21376c3..51c1ca6ce9 100644 --- a/web/app/components/explore/app-card/index.tsx +++ b/web/app/components/explore/app-card/index.tsx @@ -1,8 +1,8 @@ 'use client' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { PlusIcon } from '@heroicons/react/20/solid' import Button from '../../base/button' +import cn from '@/utils/classnames' import type { App } from '@/models/explore' import AppIcon from '@/app/components/base/app-icon' import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication' diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 8a69d2f5cb..b465893410 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -1,13 +1,13 @@ 'use client' import React, { useMemo, useState } from 'react' -import cn from 'classnames' import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import useSWR from 'swr' import Toast from '../../base/toast' import s from './style.module.css' +import cn from '@/utils/classnames' import ExploreContext from '@/context/explore-context' import type { App } from '@/models/explore' import Category from '@/app/components/explore/category' @@ -149,7 +149,7 @@ const Apps = ({ {pageType !== PageType.EXPLORE && ( <> -
+
)} = ({ className={itemClassName(isAllCategories)} onClick={() => onChange(allCategoriesEn)} > - + {t('explore.apps.allCategories')}
{list.map(name => ( diff --git a/web/app/components/explore/index.tsx b/web/app/components/explore/index.tsx index 2be9868810..cef6573bff 100644 --- a/web/app/components/explore/index.tsx +++ b/web/app/components/explore/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import React, { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' import { useTranslation } from 'react-i18next' import ExploreContext from '@/context/explore-context' import Sidebar from '@/app/components/explore/sidebar' @@ -16,8 +17,9 @@ const Explore: FC = ({ children, }) => { const { t } = useTranslation() + const router = useRouter() const [controlUpdateInstalledApps, setControlUpdateInstalledApps] = useState(0) - const { userProfile } = useAppContext() + const { userProfile, isCurrentWorkspaceDatasetOperator } = useAppContext() const [hasEditPermission, setHasEditPermission] = useState(false) const [installedApps, setInstalledApps] = useState([]) @@ -32,6 +34,11 @@ const Explore: FC = ({ })() }, []) + useEffect(() => { + if (isCurrentWorkspaceDatasetOperator) + return router.replace('/datasets') + }, [isCurrentWorkspaceDatasetOperator]) + return (
= ({ }} >
- + {isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}
{isShowRenameConversation && (
- + {t('explore.sidebar.action.rename')}
)} diff --git a/web/app/components/explore/sidebar/app-nav-item/index.tsx b/web/app/components/explore/sidebar/app-nav-item/index.tsx index aa6416a6b4..2a7f3342ab 100644 --- a/web/app/components/explore/sidebar/app-nav-item/index.tsx +++ b/web/app/components/explore/sidebar/app-nav-item/index.tsx @@ -1,10 +1,10 @@ 'use client' -import cn from 'classnames' import React, { useRef } from 'react' import { useRouter } from 'next/navigation' import { useHover } from 'ahooks' import s from './style.module.css' +import cn from '@/utils/classnames' import ItemOperation from '@/app/components/explore/item-operation' import AppIcon from '@/app/components/base/app-icon' diff --git a/web/app/components/explore/sidebar/index.tsx b/web/app/components/explore/sidebar/index.tsx index 8ed24fd9de..2d12752c48 100644 --- a/web/app/components/explore/sidebar/index.tsx +++ b/web/app/components/explore/sidebar/index.tsx @@ -3,11 +3,11 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import cn from 'classnames' import { useSelectedLayoutSegments } from 'next/navigation' import Link from 'next/link' import Toast from '../../base/toast' import Item from './app-nav-item' +import cn from '@/utils/classnames' import { fetchInstalledAppList as doFetchInstalledAppList, uninstallApp, updatePinStatus } from '@/service/explore' import ExploreContext from '@/context/explore-context' import Confirm from '@/app/components/base/confirm' diff --git a/web/app/components/header/HeaderWrapper.tsx b/web/app/components/header/HeaderWrapper.tsx index a872a7b306..ba0047fe44 100644 --- a/web/app/components/header/HeaderWrapper.tsx +++ b/web/app/components/header/HeaderWrapper.tsx @@ -1,7 +1,7 @@ 'use client' -import classNames from 'classnames' import { usePathname } from 'next/navigation' import s from './index.module.css' +import classNames from '@/utils/classnames' type HeaderWrapperProps = { children: React.ReactNode diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index cffeb9031a..e79d6c5725 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -1,10 +1,10 @@ 'use client' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import Link from 'next/link' import dayjs from 'dayjs' import { RiCloseLine } from '@remixicon/react' import s from './index.module.css' +import classNames from '@/utils/classnames' import Modal from '@/app/components/base/modal' import type { LangGeniusVersionResponse } from '@/models/common' import { IS_CE_EDITION } from '@/config' diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 9ce8b63600..006c0311e0 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next' import { Fragment, useState } from 'react' import { useRouter } from 'next/navigation' import { useContext } from 'use-context-selector' -import classNames from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' import Link from 'next/link' import { Menu, Transition } from '@headlessui/react' import Indicator from '../indicator' import AccountAbout from '../account-about' import WorkplaceSelector from './workplace-selector' +import classNames from '@/utils/classnames' import I18n from '@/context/i18n' import Avatar from '@/app/components/base/avatar' import { logout } from '@/service/common' diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index ca93e9e19d..801f0b3d52 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -2,8 +2,8 @@ import { Fragment } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' import { Menu, Transition } from '@headlessui/react' -import cn from 'classnames' import s from './index.module.css' +import cn from '@/utils/classnames' import { switchWorkspace } from '@/service/common' import { useWorkspacesContext } from '@/context/workspace-context' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' diff --git a/web/app/components/header/account-setting/Integrations-page/index.tsx b/web/app/components/header/account-setting/Integrations-page/index.tsx index a26ab4f16d..dc5e924c99 100644 --- a/web/app/components/header/account-setting/Integrations-page/index.tsx +++ b/web/app/components/header/account-setting/Integrations-page/index.tsx @@ -1,10 +1,10 @@ 'use client' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import useSWR from 'swr' import Link from 'next/link' import s from './index.module.css' +import classNames from '@/utils/classnames' import { fetchAccountIntegrates } from '@/service/common' const titleClassName = ` diff --git a/web/app/components/header/account-setting/account-page/index.tsx b/web/app/components/header/account-setting/account-page/index.tsx index 25fd05bfbd..a8a51b1c77 100644 --- a/web/app/components/header/account-setting/account-page/index.tsx +++ b/web/app/components/header/account-setting/account-page/index.tsx @@ -1,7 +1,6 @@ 'use client' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import classNames from 'classnames' import { RiCloseLine, RiErrorWarningFill, @@ -10,6 +9,7 @@ import { useContext } from 'use-context-selector' import Collapse from '../collapse' import type { IItem } from '../collapse' import s from './index.module.css' +import classNames from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import { updateUserProfile } from '@/service/common' diff --git a/web/app/components/header/account-setting/collapse/index.tsx b/web/app/components/header/account-setting/collapse/index.tsx index 837a4aeb16..a70dca16e5 100644 --- a/web/app/components/header/account-setting/collapse/index.tsx +++ b/web/app/components/header/account-setting/collapse/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline' -import classNames from 'classnames' +import classNames from '@/utils/classnames' export type IItem = { key: string diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx index 19ec75c6c6..21f7660ef1 100644 --- a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx @@ -3,10 +3,10 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' -import cn from 'classnames' import Panel from '../panel' import { DataSourceType } from '../panel/types' import ConfigFirecrawlModal from './config-firecrawl-modal' +import cn from '@/utils/classnames' import { fetchDataSources, removeDataSourceApiKeyBinding } from '@/service/datasets' import type { diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx index fa410dcfbb..2a05808e2a 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiDeleteBinLine, } from '@remixicon/react' @@ -10,6 +9,7 @@ import Indicator from '../../../indicator' import Operate from '../data-source-notion/operate' import { DataSourceType } from './types' import s from './style.module.css' +import cn from '@/utils/classnames' export type ConfigItemType = { id: string diff --git a/web/app/components/header/account-setting/data-source-page/panel/index.tsx b/web/app/components/header/account-setting/data-source-page/panel/index.tsx index 95475059e8..988aedcaf7 100644 --- a/web/app/components/header/account-setting/data-source-page/panel/index.tsx +++ b/web/app/components/header/account-setting/data-source-page/panel/index.tsx @@ -3,12 +3,12 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import { PlusIcon } from '@heroicons/react/24/solid' -import cn from 'classnames' import type { ConfigItemType } from './config-item' import ConfigItem from './config-item' import s from './style.module.css' import { DataSourceType } from './types' +import cn from '@/utils/classnames' type Props = { type: DataSourceType diff --git a/web/app/components/header/account-setting/index.tsx b/web/app/components/header/account-setting/index.tsx index 21f1e0dda8..253b9f1b4c 100644 --- a/web/app/components/header/account-setting/index.tsx +++ b/web/app/components/header/account-setting/index.tsx @@ -1,7 +1,6 @@ 'use client' import { useTranslation } from 'react-i18next' import { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import { RiAccountCircleFill, RiAccountCircleLine, @@ -30,11 +29,13 @@ import ApiBasedExtensionPage from './api-based-extension-page' import DataSourcePage from './data-source-page' import ModelProviderPage from './model-provider-page' import s from './index.module.css' +import cn from '@/utils/classnames' import BillingPage from '@/app/components/billing/billing-page' import CustomPage from '@/app/components/custom/custom-page' import Modal from '@/app/components/base/modal' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useProviderContext } from '@/context/provider-context' +import { useAppContext } from '@/context/app-context' const iconClassName = ` w-4 h-4 ml-3 mr-2 @@ -64,8 +65,11 @@ export default function AccountSetting({ const [activeMenu, setActiveMenu] = useState(activeTab) const { t } = useTranslation() const { enableBilling, enableReplaceWebAppLogo } = useProviderContext() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() const workplaceGroupItems = (() => { + if (isCurrentWorkspaceDatasetOperator) + return [] return [ { key: 'provider', @@ -172,7 +176,9 @@ export default function AccountSetting({ { menuItems.map(menuItem => (
-
{menuItem.name}
+ {!isCurrentWorkspaceDatasetOperator && ( +
{menuItem.name}
+ )}
{ menuItem.items.map(item => ( diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index 51a453e4a7..711e772684 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -29,6 +29,7 @@ const MembersPage = () => { owner: t('common.members.owner'), admin: t('common.members.admin'), editor: t('common.members.editor'), + dataset_operator: t('common.members.datasetOperator'), normal: t('common.members.normal'), } const { locale } = useContext(I18n) diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 2418a4775f..7d43495362 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -1,13 +1,12 @@ 'use client' -import { Fragment, useCallback, useMemo, useState } from 'react' +import { useCallback, useState } from 'react' import { useContext } from 'use-context-selector' import { XMarkIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' import { ReactMultiEmail } from 'react-multi-email' -import { Listbox, Transition } from '@headlessui/react' -import { CheckIcon } from '@heroicons/react/20/solid' -import cn from 'classnames' +import RoleSelector from './role-selector' import s from './index.module.css' +import cn from '@/utils/classnames' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' import { inviteMember } from '@/service/common' @@ -31,29 +30,14 @@ const InviteModal = ({ const { notify } = useContext(ToastContext) const { locale } = useContext(I18n) - - const InvitingRoles = useMemo(() => [ - { - name: 'normal', - description: t('common.members.normalTip'), - }, - { - name: 'editor', - description: t('common.members.editorTip'), - }, - { - name: 'admin', - description: t('common.members.adminTip'), - }, - ], [t]) - const [role, setRole] = useState(InvitingRoles[0]) + const [role, setRole] = useState('normal') const handleSend = useCallback(async () => { if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) { try { const { result, invitation_results } = await inviteMember({ url: '/workspaces/current/members/invite-email', - body: { emails, role: role.name, language: locale }, + body: { emails, role, language: locale }, }) if (result === 'success') { @@ -70,7 +54,7 @@ const InviteModal = ({ return (
- {}} className={cn(s.modal)}> + { }} className={cn(s.modal)}>
{t('common.members.inviteTeamMember')}
@@ -99,53 +83,9 @@ const InviteModal = ({ placeholder={t('common.members.emailPlaceholder') || ''} />
- -
- - {t('common.members.invitedAsRole', { role: t(`common.members.${role.name}`) })} - - - - {InvitingRoles.map(role => - - `${active ? ' bg-gray-50 rounded-xl' : ' bg-transparent'} - cursor-default select-none relative py-2 px-4 mx-2 flex flex-col` - } - value={role} - > - {({ selected }) => ( -
- - {selected && ( -
- - {t(`common.members.${role.name}`)} - - - {role.description} - -
-
- )} -
, - )} -
-
-
-
+
+ +
-
- +
@@ -51,7 +51,7 @@ const MethodSelector: FC = ({
onChange('llm')}>
- {value === 'llm' && } + {value === 'llm' && }
{t('tools.createTool.toolInput.methodParameter')}
@@ -60,7 +60,7 @@ const MethodSelector: FC = ({
onChange('form')}>
- {value === 'form' && } + {value === 'form' && }
{t('tools.createTool.toolInput.methodSetting')}
diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 8d50c7c7b5..8925649226 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -2,7 +2,6 @@ import { useMemo, useState, } from 'react' -import cn from 'classnames' import type { OnSelectBlock, ToolWithProvider, @@ -11,6 +10,7 @@ import { useStore } from '../store' import { ToolTypeEnum } from './types' import Tools from './tools' import { useToolTabs } from './hooks' +import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' type AllToolsProps = { diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index e969612d37..aac95b5392 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -69,7 +69,7 @@ const Blocks = ({ key={block.type} selector={`workflow-block-${block.type}`} position='right' - className='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !bg-transparent !rounded-xl !shadow-lg' + className='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' htmlContent={(
{ + const groups = items.reduce((acc, item) => { + const firstChar = getFirstChar(item) + if (!firstChar || firstChar.length === 0) + return acc + + let letter + + // transform Chinese to pinyin + if (/[\u4E00-\u9FA5]/.test(firstChar)) + letter = pinyin(firstChar, { pattern: 'first', toneType: 'none' })[0].toUpperCase() + else + letter = firstChar.toUpperCase() + + if (!/[A-Z]/.test(letter)) + letter = '#' + + if (!acc[letter]) + acc[letter] = [] + + acc[letter].push(item) + return acc + }, {}) + + const letters = Object.keys(groups).sort() + // move '#' to the end + const hashIndex = letters.indexOf('#') + if (hashIndex !== -1) { + letters.splice(hashIndex, 1) + letters.push('#') + } + return { letters, groups } +} + +const IndexBar = ({ letters, itemRefs }) => { + const handleIndexClick = (letter) => { + const element = itemRefs.current[letter] + if (element) + element.scrollIntoView({ behavior: 'smooth' }) + } + return ( +
+ {letters.map(letter => ( +
handleIndexClick(letter)}> + {letter} +
+ ))} +
+ ) +} + +export default IndexBar diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index fa798326c7..5c4be8e3d1 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -3,13 +3,13 @@ import { memo, useState, } from 'react' -import cn from 'classnames' import type { BlockEnum } from '../types' import { useTabs } from './hooks' import type { ToolDefaultValue } from './types' import { TabsEnum } from './types' import Blocks from './blocks' import AllTools from './all-tools' +import cn from '@/utils/classnames' export type TabsProps = { searchText: string @@ -36,7 +36,7 @@ const Tabs: FC = ({
tool.label[language][0]) + const toolRefs = useRef({}) + const renderGroup = useCallback((toolWithProvider: ToolWithProvider) => { const list = toolWithProvider.tools @@ -41,7 +46,7 @@ const Blocks = ({ key={tool.name} selector={`workflow-block-tool-${tool.name}`} position='right' - className='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !bg-transparent !rounded-xl !shadow-lg' + className='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg' htmlContent={(
{ + const tools = groupedTools[letter] + return ( +
(toolRefs.current[letter] = el)} + > + {tools.map(renderGroup)} +
+ ) + } + return (
{ @@ -90,12 +107,11 @@ const Blocks = ({ } {!tools.length && showWorkflowEmpty && (
- +
)} - { - !!tools.length && tools.map(renderGroup) - } + {!!tools.length && letters.map(renderLetterGroup)} + {tools.length > 10 && }
) } diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 1786ca4b47..aa4545cefb 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -360,7 +360,7 @@ export const HTTP_REQUEST_OUTPUT_STRUCT: Var[] = [ }, { variable: 'headers', - type: VarType.string, + type: VarType.object, }, { variable: 'files', diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index c113395571..5e945790d8 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -3,7 +3,6 @@ import { useCallback, useState, } from 'react' -import cn from 'classnames' import { intersection } from 'lodash-es' import type { EdgeProps } from 'reactflow' import { @@ -22,6 +21,7 @@ import type { OnSelectBlock, } from './types' import { ITERATION_CHILDREN_Z_INDEX } from './constants' +import cn from '@/utils/classnames' const CustomEdge = ({ id, diff --git a/web/app/components/workflow/header/checklist.tsx b/web/app/components/workflow/header/checklist.tsx index 936415708d..ee2876acb6 100644 --- a/web/app/components/workflow/header/checklist.tsx +++ b/web/app/components/workflow/header/checklist.tsx @@ -11,7 +11,6 @@ import { RiCloseLine, RiListCheck3, } from '@remixicon/react' -import cn from 'classnames' import BlockIcon from '../block-icon' import { useChecklist, @@ -21,6 +20,7 @@ import type { CommonEdgeType, CommonNodeType, } from '../types' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, @@ -122,7 +122,9 @@ const WorkflowChecklist = ({ className='mr-1.5' toolIcon={node.toolIcon} /> - {node.title} + + {node.title} +
{ diff --git a/web/app/components/workflow/header/run-and-history.tsx b/web/app/components/workflow/header/run-and-history.tsx index c2f4687839..bb3982ed32 100644 --- a/web/app/components/workflow/header/run-and-history.tsx +++ b/web/app/components/workflow/header/run-and-history.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiLoader2Line, RiPlayLargeFill, @@ -14,6 +13,7 @@ import { } from '../hooks' import { WorkflowRunningStatus } from '../types' import ViewHistory from './view-history' +import cn from '@/utils/classnames' import { StopCircle, } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' diff --git a/web/app/components/workflow/header/view-history.tsx b/web/app/components/workflow/header/view-history.tsx index 6092d2538c..44d572557f 100644 --- a/web/app/components/workflow/header/view-history.tsx +++ b/web/app/components/workflow/header/view-history.tsx @@ -2,7 +2,6 @@ import { memo, useState, } from 'react' -import cn from 'classnames' import useSWR from 'swr' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' @@ -19,6 +18,7 @@ import { useWorkflowRun, } from '../hooks' import { WorkflowRunningStatus } from '../types' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/header/view-workflow-history.tsx b/web/app/components/workflow/header/view-workflow-history.tsx index 8fa49d59e5..c8c71a0062 100644 --- a/web/app/components/workflow/header/view-workflow-history.tsx +++ b/web/app/components/workflow/header/view-workflow-history.tsx @@ -4,7 +4,6 @@ import { useMemo, useState, } from 'react' -import cn from 'classnames' import { RiCloseLine, RiHistoryLine, @@ -18,6 +17,7 @@ import { } from '../hooks' import TipPopup from '../operator/tip-popup' import type { WorkflowHistoryState } from '../workflow-history-store' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 0067b58e7f..8a7c150556 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -4,6 +4,8 @@ import { useStoreApi, } from 'reactflow' import produce from 'immer' +import { v4 as uuidV4 } from 'uuid' +import { usePathname } from 'next/navigation' import { useWorkflowStore } from '../store' import { useNodesSyncDraft } from '../hooks' import { @@ -19,6 +21,7 @@ import { stopWorkflowRun, } from '@/service/workflow' import { useFeaturesStore } from '@/app/components/base/features/hooks' +import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager' export const useWorkflowRun = () => { const store = useStoreApi() @@ -27,6 +30,7 @@ export const useWorkflowRun = () => { const featuresStore = useFeaturesStore() const { doSyncWorkflowDraft } = useNodesSyncDraft() const { handleUpdateWorkflowCanvas } = useWorkflowUpdate() + const pathname = usePathname() const handleBackupDraft = useCallback(() => { const { @@ -134,6 +138,20 @@ export const useWorkflowRun = () => { let isInIteration = false let iterationLength = 0 + let ttsUrl = '' + let ttsIsPublic = false + if (params.token) { + ttsUrl = '/text-to-audio' + ttsIsPublic = true + } + else if (params.appId) { + if (pathname.search('explore/installed') > -1) + ttsUrl = `/installed-apps/${params.appId}/text-to-audio` + else + ttsUrl = `/apps/${params.appId}/text-to-audio` + } + const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', (_: any): any => {}) + ssePost( url, { @@ -468,6 +486,15 @@ export const useWorkflowRun = () => { draft.resultText = text })) }, + onTTSChunk: (messageId: string, audio: string, audioType?: string) => { + if (!audio || audio === '') + return + player.playAudioWithAudio(audio, true) + AudioPlayerManager.getInstance().resetMsgId(messageId) + }, + onTTSEnd: (messageId: string, audio: string, audioType?: string) => { + player.playAudioWithAudio(audio, false) + }, ...restCallback, }, ) diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index a9a4b40ef3..aca8935f62 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -21,6 +21,7 @@ import ReactFlow, { useNodesState, useOnViewportChange, useReactFlow, + useStoreApi, } from 'reactflow' import type { Viewport, @@ -278,6 +279,15 @@ const Workflow: FC = memo(({ { exactMatch: true, useCapture: true }, ) + const store = useStoreApi() + if (process.env.NODE_ENV === 'development') { + store.getState().onError = (code, message) => { + if (code === '002') + return + console.warn(message) + } + } + return (
= ({ values, onChange, }) => { + const mapKeysWithSameValueSelector = useMemo(() => { + const keysWithSameValueSelector = (key: string) => { + const targetValueSelector = inputs.find( + item => item.variable === key, + )?.value_selector + if (!targetValueSelector) + return [key] + + const result: string[] = [] + inputs.forEach((item) => { + if (item.value_selector?.join('.') === targetValueSelector.join('.')) + result.push(item.variable) + }) + return result + } + + const m = new Map() + for (const input of inputs) + m.set(input.variable, keysWithSameValueSelector(input.variable)) + + return m + }, [inputs]) + const handleChange = useCallback((key: string) => { + const mKeys = mapKeysWithSameValueSelector.get(key) ?? [key] return (value: any) => { const newValues = produce(values, (draft) => { - draft[key] = value + for (const k of mKeys) + draft[k] = value }) onChange(newValues) } - }, [values, onChange]) + }, [values, onChange, mapKeysWithSameValueSelector]) const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(inputs[0]?.type) const isContext = inputs[0]?.type === InputVarType.contexts const handleAddContext = useCallback(() => { diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index b73ba08053..6a3da3cf24 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -2,13 +2,13 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine, RiLoader2Line, } from '@remixicon/react' import type { Props as FormProps } from './form' import Form from './form' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { StopCircle } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' import Split from '@/app/components/workflow/nodes/_base/components/split' diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index e6f529550b..12b77c6499 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React, { useCallback, useRef, useState } from 'react' import copy from 'copy-to-clipboard' -import cn from 'classnames' import Wrap from './wrap' +import cn from '@/utils/classnames' import PromptEditorHeightResizeWrap from '@/app/components/app/configuration/config-prompt/prompt-editor-height-resize-wrap' import { Clipboard, diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx index c084a838ba..6ca3af958a 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { Props as EditorProps } from '.' import Editor from '.' +import cn from '@/utils/classnames' import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' import type { NodeOutPutVar, Variable } from '@/app/components/workflow/types' diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index a13395dce7..c4348871d2 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import Editor, { loader } from '@monaco-editor/react' import React, { useEffect, useRef, useState } from 'react' -import cn from 'classnames' import Base from '../base' +import cn from '@/utils/classnames' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import './style.css' diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index bda83cfc7d..1301e9f2ed 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -1,13 +1,13 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiArrowDownSLine, RiQuestionLine, } from '@remixicon/react' import { useBoolean } from 'ahooks' import type { DefaultTFuncReturn } from 'i18next' +import cn from '@/utils/classnames' import TooltipPlus from '@/app/components/base/tooltip-plus' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index ed46d22765..3f4e7d8c46 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' import React, { useEffect } from 'react' -import cn from 'classnames' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import type { Node, NodeOutPutVar, diff --git a/web/app/components/workflow/nodes/_base/components/memory-config.tsx b/web/app/components/workflow/nodes/_base/components/memory-config.tsx index 24a279dcbd..44848772e5 100644 --- a/web/app/components/workflow/nodes/_base/components/memory-config.tsx +++ b/web/app/components/workflow/nodes/_base/components/memory-config.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import produce from 'immer' -import cn from 'classnames' import type { Memory } from '../../../types' import { MemoryRole } from '../../../types' +import cn from '@/utils/classnames' import Field from '@/app/components/workflow/nodes/_base/components/field' import Switch from '@/app/components/base/switch' import Slider from '@/app/components/base/slider' diff --git a/web/app/components/workflow/nodes/_base/components/node-resizer.tsx b/web/app/components/workflow/nodes/_base/components/node-resizer.tsx index 9de9484581..4c83bea8d6 100644 --- a/web/app/components/workflow/nodes/_base/components/node-resizer.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-resizer.tsx @@ -2,16 +2,16 @@ import { memo, useCallback, } from 'react' -import cn from 'classnames' import type { OnResize } from 'reactflow' import { NodeResizeControl } from 'reactflow' import { useNodesInteractions } from '../../../hooks' import type { CommonNodeType } from '../../../types' +import cn from '@/utils/classnames' const Icon = () => { return ( - + ) } diff --git a/web/app/components/workflow/nodes/_base/components/output-vars.tsx b/web/app/components/workflow/nodes/_base/components/output-vars.tsx index b61b795680..401a5d6a3e 100644 --- a/web/app/components/workflow/nodes/_base/components/output-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/output-vars.tsx @@ -2,8 +2,8 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useBoolean } from 'ahooks' +import cn from '@/utils/classnames' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' type Props = { diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index 6cf4fe20b6..4f36e137ba 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -1,7 +1,6 @@ 'use client' import type { FC } from 'react' import React, { useCallback, useRef } from 'react' -import cn from 'classnames' import { RiDeleteBinLine, } from '@remixicon/react' @@ -17,6 +16,7 @@ import type { import Wrap from '../editor/wrap' import { CodeLanguage } from '../../../code/types' +import cn from '@/utils/classnames' import ToggleExpandBtn from '@/app/components/workflow/nodes/_base/components/toggle-expand-btn' import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-toggle-expend' import PromptEditor from '@/app/components/base/prompt-editor' diff --git a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx index 2138ea8f52..46b4c67fff 100644 --- a/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/readonly-input-with-select-var.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' +import cn from 'classnames' import { useWorkflow } from '../../../hooks' import { BlockEnum } from '../../../types' import { VarBlockIcon } from '../../../block-icon' @@ -10,6 +11,7 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop type Props = { nodeId: string value: string + className?: string } const VAR_PLACEHOLDER = '@#!@#!' @@ -17,6 +19,7 @@ const VAR_PLACEHOLDER = '@#!@#!' const ReadonlyInputWithSelectVar: FC = ({ nodeId, value, + className, }) => { const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow() const availableNodes = getBeforeNodesInSameBranchIncludeParent(nodeId) @@ -64,7 +67,7 @@ const ReadonlyInputWithSelectVar: FC = ({ })() return ( -
+
{res}
) diff --git a/web/app/components/workflow/nodes/_base/components/remove-button.tsx b/web/app/components/workflow/nodes/_base/components/remove-button.tsx index 22b03b6c86..70b268e1d8 100644 --- a/web/app/components/workflow/nodes/_base/components/remove-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/remove-button.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' import { RiDeleteBinLine } from '@remixicon/react' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/workflow/nodes/_base/components/selector.tsx b/web/app/components/workflow/nodes/_base/components/selector.tsx index 5a1019a4dc..dcdc2a445d 100644 --- a/web/app/components/workflow/nodes/_base/components/selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/selector.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useBoolean, useClickAway } from 'ahooks' -import cn from 'classnames' +import cn from '@/utils/classnames' import { ChevronSelectorVertical } from '@/app/components/base/icons/src/vender/line/arrows' import { Check } from '@/app/components/base/icons/src/vender/line/general' type Item = { diff --git a/web/app/components/workflow/nodes/_base/components/split.tsx b/web/app/components/workflow/nodes/_base/components/split.tsx index 0363f6c23a..7b6ca1f38d 100644 --- a/web/app/components/workflow/nodes/_base/components/split.tsx +++ b/web/app/components/workflow/nodes/_base/components/split.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' type Props = { className?: string diff --git a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx index 921ad57172..cf8cbbc2ca 100644 --- a/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/support-var-input/index.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' type Props = { isFocus?: boolean diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx new file mode 100644 index 0000000000..a13e44097f --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -0,0 +1,66 @@ +import { useMemo } from 'react' +import { useNodes } from 'reactflow' +import { capitalize } from 'lodash-es' +import { VarBlockIcon } from '@/app/components/workflow/block-icon' +import type { + CommonNodeType, + ValueSelector, + VarType, +} from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { Line3 } from '@/app/components/base/icons/src/public/common' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' + +type VariableTagProps = { + valueSelector: ValueSelector + varType: VarType +} +const VariableTag = ({ + valueSelector, + varType, +}: VariableTagProps) => { + const nodes = useNodes() + const node = useMemo(() => { + if (isSystemVar(valueSelector)) + return nodes.find(node => node.data.type === BlockEnum.Start) + + return nodes.find(node => node.id === valueSelector[0]) + }, [nodes, valueSelector]) + + const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.') + + return ( +
+ { + node && ( + + ) + } +
+ {node?.data.title} +
+ + +
+ {variableName} +
+ { + varType && ( +
{capitalize(varType)}
+ ) + } +
+ ) +} + +export default VariableTag diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 09891ff05e..c868da8540 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiArrowDownSLine, RiCloseLine, @@ -11,6 +10,7 @@ import produce from 'immer' import { useStoreApi } from 'reactflow' import VarReferencePopup from './var-reference-popup' import { getNodeInfoById, getVarType, isSystemVar, toNodeAvailableVars } from './utils' +import cn from '@/utils/classnames' import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types' import { BlockEnum } from '@/app/components/workflow/types' import { VarBlockIcon } from '@/app/components/workflow/block-icon' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 951d0bd237..893cc2a6e0 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useBoolean, useHover } from 'ahooks' -import cn from 'classnames' import { RiSearchLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import { type NodeOutPutVar, type ValueSelector, type Var, VarType } from '@/app/components/workflow/types' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx index 1981057f92..c976bdfbfb 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-type-picker.tsx @@ -1,8 +1,8 @@ 'use client' import type { FC } from 'react' import React, { useCallback, useState } from 'react' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index a3ffcbcc1f..75fcb7dcc7 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -337,6 +337,7 @@ const useOneStepRun = ({ variable: item.variable, type: InputVarType.textInput, required: true, + value_selector: item.value_selector, } } return { diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index b1cddd30b5..6d1e522e66 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -9,7 +9,6 @@ import { useMemo, useRef, } from 'react' -import cn from 'classnames' import { RiCheckboxCircleLine, RiErrorWarningLine, @@ -32,6 +31,7 @@ import { import NodeResizer from './components/node-resizer' import NodeControl from './components/node-control' import AddVariablePopupWithPosition from './components/add-variable-popup-with-position' +import cn from '@/utils/classnames' import BlockIcon from '@/app/components/workflow/block-icon' type BaseNodeProps = { diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index 54eb4413f6..83d05cbff8 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -11,7 +11,6 @@ import { RiCloseLine, RiPlayLargeLine, } from '@remixicon/react' -import cn from 'classnames' import { useShallow } from 'zustand/react/shallow' import { useTranslation } from 'react-i18next' import NextStep from './components/next-step' @@ -22,6 +21,7 @@ import { TitleInput, } from './components/title-description-input' import { useResizePanel } from './hooks/use-resize-panel' +import cn from '@/utils/classnames' import BlockIcon from '@/app/components/workflow/block-icon' import { WorkflowHistoryEvent, @@ -107,7 +107,7 @@ const BasePanel: FC = ({
{ + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + const handleSelectVariable = useCallback((valueSelector: ValueSelector, varItem: Var) => { + onSelectVariable(caseId, valueSelector, varItem) + setOpen(false) + }, [caseId, onSelectVariable, setOpen]) + + return ( + + setOpen(!open)}> + + + +
+ +
+
+
+ ) +} + +export default ConditionAdd diff --git a/web/app/components/workflow/nodes/if-else/components/condition-item.tsx b/web/app/components/workflow/nodes/if-else/components/condition-item.tsx deleted file mode 100644 index 3720312f3e..0000000000 --- a/web/app/components/workflow/nodes/if-else/components/condition-item.tsx +++ /dev/null @@ -1,250 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useCallback, useEffect } from 'react' -import { useTranslation } from 'react-i18next' -import cn from 'classnames' -import { - RiDeleteBinLine, -} from '@remixicon/react' -import VarReferencePicker from '../../_base/components/variable/var-reference-picker' -import { isComparisonOperatorNeedTranslate } from '../utils' -import { VarType } from '../../../types' -import type { Condition } from '@/app/components/workflow/nodes/if-else/types' -import { ComparisonOperator, LogicalOperator } from '@/app/components/workflow/nodes/if-else/types' -import type { ValueSelector, Var } from '@/app/components/workflow/types' -import { RefreshCw05 } from '@/app/components/base/icons/src/vender/line/arrows' -import Selector from '@/app/components/workflow/nodes/_base/components/selector' -import Toast from '@/app/components/base/toast' - -const i18nPrefix = 'workflow.nodes.ifElse' - -const Line = ( - - - - - - - - - -) - -const getOperators = (type?: VarType) => { - switch (type) { - case VarType.string: - return [ - ComparisonOperator.contains, - ComparisonOperator.notContains, - ComparisonOperator.startWith, - ComparisonOperator.endWith, - ComparisonOperator.is, - ComparisonOperator.isNot, - ComparisonOperator.empty, - ComparisonOperator.notEmpty, - ] - case VarType.number: - return [ - ComparisonOperator.equal, - ComparisonOperator.notEqual, - ComparisonOperator.largerThan, - ComparisonOperator.lessThan, - ComparisonOperator.largerThanOrEqual, - ComparisonOperator.lessThanOrEqual, - ComparisonOperator.is, - ComparisonOperator.isNot, - ComparisonOperator.empty, - ComparisonOperator.notEmpty, - ] - case VarType.arrayString: - case VarType.arrayNumber: - return [ - ComparisonOperator.contains, - ComparisonOperator.notContains, - ComparisonOperator.empty, - ComparisonOperator.notEmpty, - ] - case VarType.array: - case VarType.arrayObject: - return [ - ComparisonOperator.empty, - ComparisonOperator.notEmpty, - ] - default: - return [ - ComparisonOperator.is, - ComparisonOperator.isNot, - ComparisonOperator.empty, - ComparisonOperator.notEmpty, - ] - } -} - -type ItemProps = { - readonly: boolean - nodeId: string - payload: Condition - varType?: VarType - onChange: (newItem: Condition) => void - canRemove: boolean - onRemove?: () => void - isShowLogicalOperator?: boolean - logicalOperator: LogicalOperator - onLogicalOperatorToggle: () => void - filterVar: (varPayload: Var) => boolean -} - -const Item: FC = ({ - readonly, - nodeId, - payload, - varType = VarType.string, - onChange, - canRemove, - onRemove = () => { }, - isShowLogicalOperator, - logicalOperator, - onLogicalOperatorToggle, - filterVar, -}) => { - const { t } = useTranslation() - const isValueReadOnly = payload.comparison_operator ? [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull].includes(payload.comparison_operator) : false - - const handleVarReferenceChange = useCallback((value: ValueSelector | string) => { - onChange({ - ...payload, - variable_selector: value as ValueSelector, - }) - }, [onChange, payload]) - - // change to default operator if the variable type is changed - useEffect(() => { - if (varType && payload.comparison_operator) { - if (!getOperators(varType).includes(payload.comparison_operator)) { - onChange({ - ...payload, - comparison_operator: getOperators(varType)[0], - }) - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [varType, payload]) - - const handleValueChange = useCallback((e: React.ChangeEvent) => { - onChange({ - ...payload, - value: e.target.value, - }) - }, [onChange, payload]) - - const handleComparisonOperatorChange = useCallback((v: ComparisonOperator) => { - onChange({ - ...payload, - comparison_operator: v, - }) - }, [onChange, payload]) - - return ( -
- {isShowLogicalOperator && ( -
-
- {Line} -
-
{t(`${i18nPrefix}.${logicalOperator === LogicalOperator.and ? 'and' : 'or'}`)}
- -
-
- {Line} -
-
-
- ) - } - -
- - - { - if (readonly) { - e.stopPropagation() - return - } - if (!payload.variable_selector || payload.variable_selector.length === 0) { - e.stopPropagation() - Toast.notify({ - message: t(`${i18nPrefix}.notSetVariable`), - type: 'error', - }) - } - }} - className={cn(!readonly && 'cursor-pointer', 'shrink-0 w-[100px] whitespace-nowrap flex items-center h-8 justify-between px-2.5 rounded-lg bg-gray-100 capitalize')} - > - { - !payload.comparison_operator - ?
{t(`${i18nPrefix}.operator`)}
- :
{isComparisonOperatorNeedTranslate(payload.comparison_operator) ? t(`${i18nPrefix}.comparisonOperator.${payload.comparison_operator}`) : payload.comparison_operator}
- } - -
- } - readonly={readonly} - value={payload.comparison_operator || ''} - options={getOperators(varType).map((o) => { - return { - label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o, - value: o, - } - })} - onChange={handleComparisonOperatorChange} - /> - - { - if (readonly) - return - - if (!varType) { - Toast.notify({ - message: t(`${i18nPrefix}.notSetVariable`), - type: 'error', - }) - } - }} - value={!isValueReadOnly ? payload.value : ''} - onChange={handleValueChange} - placeholder={(!readonly && !isValueReadOnly) ? t(`${i18nPrefix}.enterValue`)! : ''} - className='min-w-[80px] flex-grow h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200' - type='text' - /> - {!readonly && ( -
{ }} - > - -
- )} -
-
- - ) -} -export default React.memo(Item) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list.tsx deleted file mode 100644 index d2ea197fca..0000000000 --- a/web/app/components/workflow/nodes/if-else/components/condition-list.tsx +++ /dev/null @@ -1,91 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useCallback } from 'react' -import produce from 'immer' -import cn from 'classnames' -import type { Var, VarType } from '../../../types' -import Item from './condition-item' -import type { Condition, LogicalOperator } from '@/app/components/workflow/nodes/if-else/types' - -type Props = { - nodeId: string - className?: string - readonly: boolean - list: Condition[] - varTypesList: (VarType | undefined)[] - onChange: (newList: Condition[]) => void - logicalOperator: LogicalOperator - onLogicalOperatorToggle: () => void - filterVar: (varPayload: Var) => boolean -} - -const ConditionList: FC = ({ - className, - readonly, - nodeId, - list, - varTypesList, - onChange, - logicalOperator, - onLogicalOperatorToggle, - filterVar, -}) => { - const handleItemChange = useCallback((index: number) => { - return (newItem: Condition) => { - const newList = produce(list, (draft) => { - draft[index] = newItem - }) - onChange(newList) - } - }, [list, onChange]) - - const handleItemRemove = useCallback((index: number) => { - return () => { - const newList = produce(list, (draft) => { - draft.splice(index, 1) - }) - onChange(newList) - } - }, [list, onChange]) - - const canRemove = list.length > 1 - - if (list.length === 0) - return null - return ( -
- - { - list.length > 1 && ( - list.slice(1).map((item, i) => ( - - ))) - } -
- ) -} -export default React.memo(ConditionList) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx new file mode 100644 index 0000000000..c393aaaa58 --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx @@ -0,0 +1,56 @@ +import { useTranslation } from 'react-i18next' +import { useStore } from '@/app/components/workflow/store' +import PromptEditor from '@/app/components/base/prompt-editor' +import { BlockEnum } from '@/app/components/workflow/types' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' + +type ConditionInputProps = { + disabled?: boolean + value: string + onChange: (value: string) => void + nodesOutputVars: NodeOutPutVar[] + availableNodes: Node[] +} +const ConditionInput = ({ + value, + onChange, + disabled, + nodesOutputVars, + availableNodes, +}: ConditionInputProps) => { + const { t } = useTranslation() + const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) + + return ( + { + acc[node.id] = { + title: node.data.title, + type: node.data.type, + } + if (node.data.type === BlockEnum.Start) { + acc.sys = { + title: t('workflow.blocks.start'), + type: BlockEnum.Start, + } + } + return acc + }, {} as any), + }} + onChange={onChange} + editable={!disabled} + /> + ) +} + +export default ConditionInput diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx new file mode 100644 index 0000000000..c6cb580118 --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-item.tsx @@ -0,0 +1,132 @@ +import { + useCallback, + useState, +} from 'react' +import { RiDeleteBinLine } from '@remixicon/react' +import type { VarType as NumberVarType } from '../../../tool/types' +import type { + ComparisonOperator, + Condition, + HandleRemoveCondition, + HandleUpdateCondition, +} from '../../types' +import { comparisonOperatorNotRequireValue } from '../../utils' +import ConditionNumberInput from '../condition-number-input' +import ConditionOperator from './condition-operator' +import ConditionInput from './condition-input' +import VariableTag from '@/app/components/workflow/nodes/_base/components/variable-tag' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' +import { VarType } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' + +type ConditionItemProps = { + disabled?: boolean + caseId: string + condition: Condition + onRemoveCondition: HandleRemoveCondition + onUpdateCondition: HandleUpdateCondition + nodesOutputVars: NodeOutPutVar[] + availableNodes: Node[] + numberVariables: NodeOutPutVar[] +} +const ConditionItem = ({ + disabled, + caseId, + condition, + onRemoveCondition, + onUpdateCondition, + nodesOutputVars, + availableNodes, + numberVariables, +}: ConditionItemProps) => { + const [isHovered, setIsHovered] = useState(false) + + const handleUpdateConditionOperator = useCallback((value: ComparisonOperator) => { + const newCondition = { + ...condition, + comparison_operator: value, + } + onUpdateCondition(caseId, condition.id, newCondition) + }, [caseId, condition, onUpdateCondition]) + + const handleUpdateConditionValue = useCallback((value: string) => { + const newCondition = { + ...condition, + value, + } + onUpdateCondition(caseId, condition.id, newCondition) + }, [caseId, condition, onUpdateCondition]) + + const handleUpdateConditionNumberVarType = useCallback((numberVarType: NumberVarType) => { + const newCondition = { + ...condition, + numberVarType, + value: '', + } + onUpdateCondition(caseId, condition.id, newCondition) + }, [caseId, condition, onUpdateCondition]) + + return ( +
+
+
+
+ +
+
+ +
+ { + !comparisonOperatorNotRequireValue(condition.comparison_operator) && condition.varType !== VarType.number && ( +
+ +
+ ) + } + { + !comparisonOperatorNotRequireValue(condition.comparison_operator) && condition.varType === VarType.number && ( +
+ +
+ ) + } +
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onClick={() => onRemoveCondition(caseId, condition.id)} + > + +
+
+ ) +} + +export default ConditionItem diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx new file mode 100644 index 0000000000..3ae1a93b0a --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-operator.tsx @@ -0,0 +1,91 @@ +import { + useMemo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { RiArrowDownSLine } from '@remixicon/react' +import { getOperators, isComparisonOperatorNeedTranslate } from '../../utils' +import type { ComparisonOperator } from '../../types' +import Button from '@/app/components/base/button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import type { VarType } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' +const i18nPrefix = 'workflow.nodes.ifElse' + +type ConditionOperatorProps = { + disabled?: boolean + varType: VarType + value?: string + onSelect: (value: ComparisonOperator) => void +} +const ConditionOperator = ({ + disabled, + varType, + value, + onSelect, +}: ConditionOperatorProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + + const options = useMemo(() => { + return getOperators(varType).map((o) => { + return { + label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o, + value: o, + } + }) + }, [t, varType]) + const selectedOption = options.find(o => o.value === value) + + return ( + + setOpen(v => !v)}> + + + +
+ { + options.map(option => ( +
{ + onSelect(option.value) + setOpen(false) + }} + > + {option.label} +
+ )) + } +
+
+
+ ) +} + +export default ConditionOperator diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx new file mode 100644 index 0000000000..b97b0a05ac --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/index.tsx @@ -0,0 +1,75 @@ +import { RiLoopLeftLine } from '@remixicon/react' +import { LogicalOperator } from '../../types' +import type { + CaseItem, + HandleRemoveCondition, + HandleUpdateCondition, + HandleUpdateConditionLogicalOperator, +} from '../../types' +import ConditionItem from './condition-item' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' + +type ConditionListProps = { + disabled?: boolean + caseItem: CaseItem + onUpdateCondition: HandleUpdateCondition + onUpdateConditionLogicalOperator: HandleUpdateConditionLogicalOperator + onRemoveCondition: HandleRemoveCondition + nodesOutputVars: NodeOutPutVar[] + availableNodes: Node[] + numberVariables: NodeOutPutVar[] +} +const ConditionList = ({ + disabled, + caseItem, + onUpdateCondition, + onUpdateConditionLogicalOperator, + onRemoveCondition, + nodesOutputVars, + availableNodes, + numberVariables, +}: ConditionListProps) => { + const { conditions, logical_operator } = caseItem + + return ( +
+ { + conditions.length > 1 && ( +
+
+
+
{ + onUpdateConditionLogicalOperator(caseItem.case_id, caseItem.logical_operator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and) + }} + > + {logical_operator.toUpperCase()} + +
+
+ ) + } + { + caseItem.conditions.map(condition => ( + + )) + } +
+ ) +} + +export default ConditionList diff --git a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx new file mode 100644 index 0000000000..c8c1616e25 --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx @@ -0,0 +1,153 @@ +import { + memo, + useCallback, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import { RiArrowDownSLine } from '@remixicon/react' +import { capitalize } from 'lodash-es' +import { VarType as NumberVarType } from '../../tool/types' +import VariableTag from '../../_base/components/variable-tag' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' +import cn from '@/utils/classnames' +import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars' +import type { + NodeOutPutVar, + ValueSelector, +} from '@/app/components/workflow/types' +import { VarType } from '@/app/components/workflow/types' +import { variableTransformer } from '@/app/components/workflow/utils' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' + +const options = [ + NumberVarType.variable, + NumberVarType.constant, +] + +type ConditionNumberInputProps = { + numberVarType?: NumberVarType + onNumberVarTypeChange: (v: NumberVarType) => void + value: string + onValueChange: (v: string) => void + variables: NodeOutPutVar[] +} +const ConditionNumberInput = ({ + numberVarType = NumberVarType.constant, + onNumberVarTypeChange, + value, + onValueChange, + variables, +}: ConditionNumberInputProps) => { + const { t } = useTranslation() + const [numberVarTypeVisible, setNumberVarTypeVisible] = useState(false) + const [variableSelectorVisible, setVariableSelectorVisible] = useState(false) + + const handleSelectVariable = useCallback((valueSelector: ValueSelector) => { + onValueChange(variableTransformer(valueSelector) as string) + setVariableSelectorVisible(false) + }, [onValueChange]) + + return ( +
+ + setNumberVarTypeVisible(v => !v)}> + + + +
+ { + options.map(option => ( +
{ + onNumberVarTypeChange(option) + setNumberVarTypeVisible(false) + }} + > + {capitalize(option)} +
+ )) + } +
+
+
+
+
+ { + numberVarType === NumberVarType.variable && ( + + setVariableSelectorVisible(v => !v)}> + { + value && ( + + ) + } + { + !value && ( +
+ + {t('workflow.nodes.ifElse.selectVariable')} +
+ ) + } +
+ +
+ +
+
+
+ ) + } + { + numberVarType === NumberVarType.constant && ( + onValueChange(e.target.value)} + placeholder={t('workflow.nodes.ifElse.enterValue') || ''} + /> + ) + } +
+
+ ) +} + +export default memo(ConditionNumberInput) diff --git a/web/app/components/workflow/nodes/if-else/components/condition-value.tsx b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx new file mode 100644 index 0000000000..904ecc8e81 --- /dev/null +++ b/web/app/components/workflow/nodes/if-else/components/condition-value.tsx @@ -0,0 +1,70 @@ +import { + memo, + useMemo, +} from 'react' +import { useTranslation } from 'react-i18next' +import type { ComparisonOperator } from '../types' +import { + comparisonOperatorNotRequireValue, + isComparisonOperatorNeedTranslate, +} from '../utils' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import cn from '@/utils/classnames' +import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' + +type ConditionValueProps = { + variableSelector: string[] + operator: ComparisonOperator + value: string +} +const ConditionValue = ({ + variableSelector, + operator, + value, +}: ConditionValueProps) => { + const { t } = useTranslation() + const variableName = isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.') + const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator + const notHasValue = comparisonOperatorNotRequireValue(operator) + + const formatValue = useMemo(() => { + if (notHasValue) + return '' + + return value.replace(/{{#([^#]*)#}}/g, (a, b) => { + const arr = b.split('.') + if (isSystemVar(arr)) + return `{{${b}}}` + + return `{{${arr.slice(1).join('.')}}}` + }) + }, [notHasValue, value]) + + return ( +
+ +
+ {variableName} +
+
+ {operatorName} +
+ { + !notHasValue && ( +
{formatValue}
+ ) + } +
+ ) +} + +export default memo(ConditionValue) diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index befd2de2e1..af65c7b46c 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -9,15 +9,20 @@ const nodeDefault: NodeDefault = { _targetBranches: [ { id: 'true', - name: 'IS TRUE', + name: 'IF', }, { id: 'false', - name: 'IS FALSE', + name: 'ELSE', + }, + ], + cases: [ + { + case_id: 'true', + logical_operator: LogicalOperator.and, + conditions: [], }, ], - logical_operator: LogicalOperator.and, - conditions: [], }, getAvailablePrevNodes(isChatMode: boolean) { const nodes = isChatMode @@ -31,17 +36,22 @@ const nodeDefault: NodeDefault = { }, checkValid(payload: IfElseNodeType, t: any) { let errorMessages = '' - const { conditions } = payload - if (!conditions || conditions.length === 0) + const { cases } = payload + if (!cases || cases.length === 0) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: 'IF' }) - conditions.forEach((condition) => { - if (!errorMessages && (!condition.variable_selector || condition.variable_selector.length === 0)) - errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) }) - if (!errorMessages && !condition.comparison_operator) - errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.ifElse.operator') }) - if (!errorMessages && !isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value) - errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) + cases.forEach((caseItem, index) => { + if (!caseItem.conditions.length) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: index === 0 ? 'IF' : 'ELIF' }) + + caseItem.conditions.forEach((condition) => { + if (!errorMessages && (!condition.variable_selector || condition.variable_selector.length === 0)) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variable`) }) + if (!errorMessages && !condition.comparison_operator) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t('workflow.nodes.ifElse.operator') }) + if (!errorMessages && !isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) }) + }) }) return { isValid: !errorMessages, diff --git a/web/app/components/workflow/nodes/if-else/node.tsx b/web/app/components/workflow/nodes/if-else/node.tsx index bb062d991e..67ce6529a6 100644 --- a/web/app/components/workflow/nodes/if-else/node.tsx +++ b/web/app/components/workflow/nodes/if-else/node.tsx @@ -3,51 +3,62 @@ import React from 'react' import { useTranslation } from 'react-i18next' import type { NodeProps } from 'reactflow' import { NodeSourceHandle } from '../_base/components/node-handle' -import { isComparisonOperatorNeedTranslate, isEmptyRelatedOperator } from './utils' +import { isEmptyRelatedOperator } from './utils' import type { IfElseNodeType } from './types' -import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import ConditionValue from './components/condition-value' const i18nPrefix = 'workflow.nodes.ifElse' const IfElseNode: FC> = (props) => { const { data } = props const { t } = useTranslation() - const { conditions, logical_operator } = data + const { cases } = data + const casesLength = cases.length return (
-
-
IF
- -
-
- {conditions.map((condition, i) => ( -
- {(condition.variable_selector?.length > 0 && condition.comparison_operator && (isEmptyRelatedOperator(condition.comparison_operator!) ? true : !!condition.value)) - ? ( -
- - {condition.variable_selector.slice(-1)[0]} - {isComparisonOperatorNeedTranslate(condition.comparison_operator) ? t(`${i18nPrefix}.comparisonOperator.${condition.comparison_operator}`) : condition.comparison_operator} - {!isEmptyRelatedOperator(condition.comparison_operator!) && {condition.value}} + { + cases.map((caseItem, index) => ( +
+
+
+
+ {casesLength > 1 && `CASE ${index + 1}`}
- ) - : ( -
- {t(`${i18nPrefix}.conditionNotSetup`)} +
{index === 0 ? 'IF' : 'ELIF'}
+
+ +
+
+ {caseItem.conditions.map((condition, i) => ( +
+ {(condition.variable_selector?.length > 0 && condition.comparison_operator && (isEmptyRelatedOperator(condition.comparison_operator!) ? true : !!condition.value)) + ? ( + + ) + : ( +
+ {t(`${i18nPrefix}.conditionNotSetup`)} +
+ )} + {i !== caseItem.conditions.length - 1 && ( +
{t(`${i18nPrefix}.${caseItem.logical_operator}`)}
+ )}
- )} - {i !== conditions.length - 1 && ( -
{t(`${i18nPrefix}.${logical_operator}`)}
- )} + ))} +
- ))} -
+ )) + }
-
ELSE
+
ELSE
> = ({ @@ -15,52 +26,130 @@ const Panel: FC> = ({ data, }) => { const { t } = useTranslation() - + const getAvailableVars = useGetAvailableVars() const { readOnly, inputs, - handleConditionsChange, - handleAddCondition, - handleLogicalOperatorToggle, - varTypesList, filterVar, + filterNumberVar, + handleAddCase, + handleRemoveCase, + handleSortCase, + handleAddCondition, + handleUpdateCondition, + handleRemoveCondition, + handleUpdateConditionLogicalOperator, + nodesOutputVars, + availableNodes, } = useConfig(id, data) + const [willDeleteCaseId, setWillDeleteCaseId] = useState('') + const cases = inputs.cases || [] + const casesLength = cases.length + return ( -
-
- + ({ ...caseItem, id: caseItem.case_id }))} + setList={handleSortCase} + handle='.handle' + ghostClass='bg-components-panel-bg' + animation={150} + > + { + cases.map((item, index) => ( +
+
+ 1 && 'group-hover:block', + )} /> +
+ { + index === 0 ? 'IF' : 'ELIF' + } + { + casesLength > 1 && ( +
CASE {index + 1}
+ ) + } +
+ { + !!item.conditions.length && ( +
+ +
+ ) + } +
+ + { + ((index === 0 && casesLength > 1) || (index > 0)) && ( + + ) + } +
+
+
+
+ )) + } +
+
+
+
+ +
{t(`${i18nPrefix}.elseDescription`)}
+
) } -export default React.memo(Panel) +export default memo(Panel) diff --git a/web/app/components/workflow/nodes/if-else/types.ts b/web/app/components/workflow/nodes/if-else/types.ts index 45adf375e5..693dce1784 100644 --- a/web/app/components/workflow/nodes/if-else/types.ts +++ b/web/app/components/workflow/nodes/if-else/types.ts @@ -1,4 +1,10 @@ -import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types' +import type { VarType as NumberVarType } from '../tool/types' +import type { + CommonNodeType, + ValueSelector, + Var, + VarType, +} from '@/app/components/workflow/types' export enum LogicalOperator { and = 'and', @@ -26,12 +32,26 @@ export enum ComparisonOperator { export type Condition = { id: string + varType: VarType variable_selector: ValueSelector comparison_operator?: ComparisonOperator value: string + numberVarType?: NumberVarType } -export type IfElseNodeType = CommonNodeType & { +export type CaseItem = { + case_id: string logical_operator: LogicalOperator conditions: Condition[] } + +export type IfElseNodeType = CommonNodeType & { + logical_operator?: LogicalOperator + conditions?: Condition[] + cases: CaseItem[] +} + +export type HandleAddCondition = (caseId: string, valueSelector: ValueSelector, varItem: Var) => void +export type HandleRemoveCondition = (caseId: string, conditionId: string) => void +export type HandleUpdateCondition = (caseId: string, conditionId: string, newCondition: Condition) => void +export type HandleUpdateConditionLogicalOperator = (caseId: string, value: LogicalOperator) => void diff --git a/web/app/components/workflow/nodes/if-else/use-config.ts b/web/app/components/workflow/nodes/if-else/use-config.ts index b20a5b56ea..d3e2785986 100644 --- a/web/app/components/workflow/nodes/if-else/use-config.ts +++ b/web/app/components/workflow/nodes/if-else/use-config.ts @@ -1,76 +1,177 @@ import { useCallback } from 'react' import produce from 'immer' -import type { Var } from '../../types' +import { v4 as uuid4 } from 'uuid' +import type { + Var, +} from '../../types' import { VarType } from '../../types' -import { getVarType } from '../_base/components/variable/utils' -import useNodeInfo from '../_base/hooks/use-node-info' import { LogicalOperator } from './types' -import type { Condition, IfElseNodeType } from './types' +import type { + CaseItem, + HandleAddCondition, + HandleRemoveCondition, + HandleUpdateCondition, + HandleUpdateConditionLogicalOperator, + IfElseNodeType, +} from './types' +import { + branchNameCorrect, + getOperators, +} from './utils' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { - useIsChatMode, + useEdgesInteractions, useNodesReadOnly, - useWorkflow, } from '@/app/components/workflow/hooks' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' const useConfig = (id: string, payload: IfElseNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() - const { getBeforeNodesInSameBranch } = useWorkflow() - const { - parentNode, - } = useNodeInfo(id) - const isChatMode = useIsChatMode() - const beforeNodes = getBeforeNodesInSameBranch(id) - + const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions() const { inputs, setInputs } = useNodeCrud(id, payload) - const handleConditionsChange = useCallback((newConditions: Condition[]) => { - const newInputs = produce(inputs, (draft) => { - draft.conditions = newConditions - }) - setInputs(newInputs) - }, [inputs, setInputs]) - - const handleAddCondition = useCallback(() => { - const newInputs = produce(inputs, (draft) => { - draft.conditions.push({ - id: `${Date.now()}`, - variable_selector: [], - comparison_operator: undefined, - value: '', - }) - }) - setInputs(newInputs) - }, [inputs, setInputs]) - - const handleLogicalOperatorToggle = useCallback(() => { - const newInputs = produce(inputs, (draft) => { - draft.logical_operator = draft.logical_operator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and - }) - setInputs(newInputs) - }, [inputs, setInputs]) - const filterVar = useCallback((varPayload: Var) => { return varPayload.type !== VarType.arrayFile }, []) - const varTypesList = (inputs.conditions || []).map((condition) => { - return getVarType({ - parentNode, - valueSelector: condition.variable_selector, - availableNodes: beforeNodes, - isChatMode, - }) + const { + availableVars, + availableNodesWithParent, + } = useAvailableVarList(id, { + onlyLeafNodeVar: false, + filterVar, }) + const filterNumberVar = useCallback((varPayload: Var) => { + return varPayload.type === VarType.number + }, []) + + const { + availableVars: availableNumberVars, + availableNodesWithParent: availableNumberNodesWithParent, + } = useAvailableVarList(id, { + onlyLeafNodeVar: false, + filterVar: filterNumberVar, + }) + + const handleAddCase = useCallback(() => { + const newInputs = produce(inputs, () => { + if (inputs.cases) { + const case_id = uuid4() + inputs.cases.push({ + case_id, + logical_operator: LogicalOperator.and, + conditions: [], + }) + if (inputs._targetBranches) { + const elseCaseIndex = inputs._targetBranches.findIndex(branch => branch.id === 'false') + if (elseCaseIndex > -1) { + inputs._targetBranches = branchNameCorrect([ + ...inputs._targetBranches.slice(0, elseCaseIndex), + { + id: case_id, + name: '', + }, + ...inputs._targetBranches.slice(elseCaseIndex), + ]) + } + } + } + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleRemoveCase = useCallback((caseId: string) => { + const newInputs = produce(inputs, (draft) => { + draft.cases = draft.cases?.filter(item => item.case_id !== caseId) + + if (draft._targetBranches) + draft._targetBranches = branchNameCorrect(draft._targetBranches.filter(branch => branch.id !== caseId)) + + handleEdgeDeleteByDeleteBranch(id, caseId) + }) + setInputs(newInputs) + }, [inputs, setInputs, id, handleEdgeDeleteByDeleteBranch]) + + const handleSortCase = useCallback((newCases: (CaseItem & { id: string })[]) => { + const newInputs = produce(inputs, (draft) => { + draft.cases = newCases.filter(Boolean).map(item => ({ + id: item.id, + case_id: item.case_id, + logical_operator: item.logical_operator, + conditions: item.conditions, + })) + + draft._targetBranches = branchNameCorrect([ + ...newCases.filter(Boolean).map(item => ({ id: item.case_id, name: '' })), + { id: 'false', name: '' }, + ]) + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleAddCondition = useCallback((caseId, valueSelector, varItem) => { + const newInputs = produce(inputs, (draft) => { + const targetCase = draft.cases?.find(item => item.case_id === caseId) + if (targetCase) { + targetCase.conditions.push({ + id: uuid4(), + varType: varItem.type, + variable_selector: valueSelector, + comparison_operator: getOperators(varItem.type)[0], + value: '', + }) + } + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleRemoveCondition = useCallback((caseId, conditionId) => { + const newInputs = produce(inputs, (draft) => { + const targetCase = draft.cases?.find(item => item.case_id === caseId) + if (targetCase) + targetCase.conditions = targetCase.conditions.filter(item => item.id !== conditionId) + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleUpdateCondition = useCallback((caseId, conditionId, newCondition) => { + const newInputs = produce(inputs, (draft) => { + const targetCase = draft.cases?.find(item => item.case_id === caseId) + if (targetCase) { + const targetCondition = targetCase.conditions.find(item => item.id === conditionId) + if (targetCondition) + Object.assign(targetCondition, newCondition) + } + }) + setInputs(newInputs) + }, [inputs, setInputs]) + + const handleUpdateConditionLogicalOperator = useCallback((caseId, value) => { + const newInputs = produce(inputs, (draft) => { + const targetCase = draft.cases?.find(item => item.case_id === caseId) + if (targetCase) + targetCase.logical_operator = value + }) + setInputs(newInputs) + }, [inputs, setInputs]) + return { readOnly, inputs, - handleConditionsChange, - handleAddCondition, - handleLogicalOperatorToggle, - varTypesList, filterVar, + filterNumberVar, + handleAddCase, + handleRemoveCase, + handleSortCase, + handleAddCondition, + handleRemoveCondition, + handleUpdateCondition, + handleUpdateConditionLogicalOperator, + nodesOutputVars: availableVars, + availableNodes: availableNodesWithParent, + nodesOutputNumberVars: availableNumberVars, + availableNumberNodes: availableNumberNodesWithParent, } } diff --git a/web/app/components/workflow/nodes/if-else/utils.ts b/web/app/components/workflow/nodes/if-else/utils.ts index 51858c64aa..ffb6758bba 100644 --- a/web/app/components/workflow/nodes/if-else/utils.ts +++ b/web/app/components/workflow/nodes/if-else/utils.ts @@ -1,4 +1,6 @@ import { ComparisonOperator } from './types' +import { VarType } from '@/app/components/workflow/types' +import type { Branch } from '@/app/components/workflow/types' export const isEmptyRelatedOperator = (operator: ComparisonOperator) => { return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull].includes(operator) @@ -15,3 +17,80 @@ export const isComparisonOperatorNeedTranslate = (operator?: ComparisonOperator) return false return !notTranslateKey.includes(operator) } + +export const getOperators = (type?: VarType) => { + switch (type) { + case VarType.string: + return [ + ComparisonOperator.contains, + ComparisonOperator.notContains, + ComparisonOperator.startWith, + ComparisonOperator.endWith, + ComparisonOperator.is, + ComparisonOperator.isNot, + ComparisonOperator.empty, + ComparisonOperator.notEmpty, + ] + case VarType.number: + return [ + ComparisonOperator.equal, + ComparisonOperator.notEqual, + ComparisonOperator.largerThan, + ComparisonOperator.lessThan, + ComparisonOperator.largerThanOrEqual, + ComparisonOperator.lessThanOrEqual, + ComparisonOperator.empty, + ComparisonOperator.notEmpty, + ] + case VarType.arrayString: + case VarType.arrayNumber: + return [ + ComparisonOperator.contains, + ComparisonOperator.notContains, + ComparisonOperator.empty, + ComparisonOperator.notEmpty, + ] + case VarType.array: + case VarType.arrayObject: + return [ + ComparisonOperator.empty, + ComparisonOperator.notEmpty, + ] + default: + return [ + ComparisonOperator.is, + ComparisonOperator.isNot, + ComparisonOperator.empty, + ComparisonOperator.notEmpty, + ] + } +} + +export const comparisonOperatorNotRequireValue = (operator?: ComparisonOperator) => { + if (!operator) + return false + + return [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull].includes(operator) +} + +export const branchNameCorrect = (branches: Branch[]) => { + const branchLength = branches.length + if (branchLength < 2) + throw new Error('if-else node branch number must than 2') + + if (branchLength === 2) { + return branches.map((branch) => { + return { + ...branch, + name: branch.id === 'false' ? 'ELSE' : 'IF', + } + }) + } + + return branches.map((branch, index) => { + return { + ...branch, + name: branch.id === 'false' ? 'ELSE' : `CASE ${index + 1}`, + } + }) +} diff --git a/web/app/components/workflow/nodes/iteration/add-block.tsx b/web/app/components/workflow/nodes/iteration/add-block.tsx index 3c77e7d115..fb61dede28 100644 --- a/web/app/components/workflow/nodes/iteration/add-block.tsx +++ b/web/app/components/workflow/nodes/iteration/add-block.tsx @@ -3,7 +3,6 @@ import { useCallback, } from 'react' import produce from 'immer' -import cn from 'classnames' import { RiAddLine, } from '@remixicon/react' @@ -21,6 +20,7 @@ import { import { NODES_INITIAL_DATA } from '../../constants' import InsertBlock from './insert-block' import type { IterationNodeType } from './types' +import cn from '@/utils/classnames' import BlockSelector from '@/app/components/workflow/block-selector' import { IterationStart } from '@/app/components/base/icons/src/vender/workflow' import type { diff --git a/web/app/components/workflow/nodes/iteration/insert-block.tsx b/web/app/components/workflow/nodes/iteration/insert-block.tsx index 9f74302800..d041fe1c74 100644 --- a/web/app/components/workflow/nodes/iteration/insert-block.tsx +++ b/web/app/components/workflow/nodes/iteration/insert-block.tsx @@ -3,13 +3,13 @@ import { useCallback, useState, } from 'react' -import cn from 'classnames' import { useNodesInteractions } from '../../hooks' import type { BlockEnum, OnSelectBlock, } from '../../types' import BlockSelector from '../../block-selector' +import cn from '@/utils/classnames' type InsertBlockProps = { startNodeId: string diff --git a/web/app/components/workflow/nodes/iteration/node.tsx b/web/app/components/workflow/nodes/iteration/node.tsx index 84ad9d48db..f4520402f3 100644 --- a/web/app/components/workflow/nodes/iteration/node.tsx +++ b/web/app/components/workflow/nodes/iteration/node.tsx @@ -8,10 +8,10 @@ import { useNodesInitialized, useViewport, } from 'reactflow' -import cn from 'classnames' import { useNodeIterationInteractions } from './use-interactions' import type { IterationNodeType } from './types' import AddBlock from './add-block' +import cn from '@/utils/classnames' import type { NodeProps } from '@/app/components/workflow/types' const Node: FC> = ({ diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx index fa9446fce7..a739f7fb1c 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/retrieval-config.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiArrowDownSLine } from '@remixicon/react' import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' import type { ModelConfig } from '../../../types' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx index 297862136a..7a0ee15378 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt.tsx @@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next' import produce from 'immer' import { ReactSortable } from 'react-sortablejs' import { v4 as uuid4 } from 'uuid' -import cn from 'classnames' import type { PromptItem, ValueSelector, Var, Variable } from '../../../types' import { EditionType, PromptRole } from '../../../types' import useAvailableVarList from '../../_base/hooks/use-available-var-list' import ConfigPromptItem from './config-prompt-item' +import cn from '@/utils/classnames' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import AddButton from '@/app/components/workflow/nodes/_base/components/add-button' import { DragHandle } from '@/app/components/base/icons/src/vender/line/others' diff --git a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx index 2d0a39ba69..6ea48b1f72 100644 --- a/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx +++ b/web/app/components/workflow/nodes/llm/components/resolution-picker.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' import { Resolution } from '@/types/app' const i18nPrefix = 'workflow.nodes.llm' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx index 7a1c524da1..76432b70ae 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx @@ -4,10 +4,10 @@ import { memo, useCallback, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import BlockSelector from '../../../../block-selector' import type { Param, ParamType } from '../../types' +import cn from '@/utils/classnames' import { useStore } from '@/app/components/workflow/store' import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' import type { ToolParameter } from '@/app/components/tools/types' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx index 2620bea5a5..2ac331558b 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import React, { useCallback, useState } from 'react' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { Param } from '../../types' import { ParamType } from '../../types' +import cn from '@/utils/classnames' import AddButton from '@/app/components/base/button/add-button' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' diff --git a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx index 596bad1ae0..9c77759d1a 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx +++ b/web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' import React, { useCallback } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { ReasoningModeType } from '../types' import Field from '../../_base/components/field' +import cn from '@/utils/classnames' const i18nPrefix = 'workflow.nodes.parameterExtractor' diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx index f992ce7906..07ba826221 100644 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import React, { useCallback, useState } from 'react' import produce from 'immer' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import type { ToolVarInputs } from '../types' import { VarType as VarKindType } from '../types' +import cn from '@/utils/classnames' import type { ValueSelector, Var } from '@/app/components/workflow/types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' diff --git a/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx index f4ed17ae96..79c50afae7 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx @@ -3,9 +3,9 @@ import { useCallback, useState, } from 'react' -import cn from 'classnames' import { useVariableAssigner } from '../../hooks' import type { VariableAssignerNodeType } from '../../types' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx index ce40c17f3a..337dcd2460 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx @@ -2,7 +2,6 @@ import { memo, useMemo, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useNodes } from 'reactflow' import { useStore } from '../../../store' @@ -20,6 +19,7 @@ import { import { filterVar } from '../utils' import AddVariable from './add-variable' import NodeVariableItem from './node-variable-item' +import cn from '@/utils/classnames' import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' const i18nPrefix = 'workflow.nodes.variableAssigner' diff --git a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx index 9628d59038..7e049e15b9 100644 --- a/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx @@ -1,5 +1,5 @@ import { memo } from 'react' -import cn from 'classnames' +import cn from '@/utils/classnames' import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' diff --git a/web/app/components/workflow/nodes/variable-assigner/panel.tsx b/web/app/components/workflow/nodes/variable-assigner/panel.tsx index 5e50663732..f94303a6ea 100644 --- a/web/app/components/workflow/nodes/variable-assigner/panel.tsx +++ b/web/app/components/workflow/nodes/variable-assigner/panel.tsx @@ -1,12 +1,12 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import Field from '../_base/components/field' import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm' import useConfig from './use-config' import type { VariableAssignerNodeType } from './types' import VarGroupItem from './components/var-group-item' +import cn from '@/utils/classnames' import { type NodePanelProps } from '@/app/components/workflow/types' import Split from '@/app/components/workflow/nodes/_base/components/split' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' diff --git a/web/app/components/workflow/note-node/index.tsx b/web/app/components/workflow/note-node/index.tsx index 850c6b730a..ec2bb84f68 100644 --- a/web/app/components/workflow/note-node/index.tsx +++ b/web/app/components/workflow/note-node/index.tsx @@ -3,7 +3,6 @@ import { useCallback, useRef, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useClickAway } from 'ahooks' import type { NodeProps } from 'reactflow' @@ -21,11 +20,12 @@ import { import { THEME_MAP } from './constants' import { useNote } from './hooks' import type { NoteNodeType } from './types' +import cn from '@/utils/classnames' const Icon = () => { return ( - + ) } diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx index 702f84b9e0..c9f4562941 100644 --- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx +++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx @@ -13,7 +13,6 @@ import { } from '@floating-ui/react' import { useTranslation } from 'react-i18next' import { useClickAway } from 'ahooks' -import cn from 'classnames' import { RiEditLine, RiExternalLinkLine, @@ -21,6 +20,7 @@ import { } from '@remixicon/react' import { useStore } from '../../store' import { useLink } from './hooks' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' type LinkEditorComponentProps = { diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx index 429188a89b..75565e7d4f 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx @@ -2,9 +2,9 @@ import { memo, useState, } from 'react' -import cn from 'classnames' import { NoteTheme } from '../../types' import { THEME_MAP } from '../../constants' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx index e0afdf6d6c..e72ff9adaf 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/command.tsx @@ -3,7 +3,6 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiBold, RiItalic, @@ -13,6 +12,7 @@ import { } from '@remixicon/react' import { useStore } from '../store' import { useCommand } from './hooks' +import cn from '@/utils/classnames' import TooltipPlus from '@/app/components/base/tooltip-plus' type CommandProps = { diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx index 0657b91f5a..38cff5361f 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/font-size-selector.tsx @@ -1,8 +1,8 @@ import { memo } from 'react' -import cn from 'classnames' import { RiFontSize } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { useFontSize } from './hooks' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx b/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx index 5bc5d02c02..f22d699935 100644 --- a/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx +++ b/web/app/components/workflow/note-node/note-editor/toolbar/operator.tsx @@ -3,8 +3,8 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiMoreFill } from '@remixicon/react' +import cn from '@/utils/classnames' import ShortcutsName from '@/app/components/workflow/shortcuts-name' import { PortalToFollowElem, diff --git a/web/app/components/workflow/operator/add-block.tsx b/web/app/components/workflow/operator/add-block.tsx index dd88517f6d..48222cc528 100644 --- a/web/app/components/workflow/operator/add-block.tsx +++ b/web/app/components/workflow/operator/add-block.tsx @@ -3,7 +3,6 @@ import { useCallback, useState, } from 'react' -import cn from 'classnames' import { RiAddCircleFill } from '@remixicon/react' import { useStoreApi } from 'reactflow' import { useTranslation } from 'react-i18next' @@ -19,6 +18,7 @@ import { import { NODES_INITIAL_DATA } from '../constants' import { useWorkflowStore } from '../store' import TipPopup from './tip-popup' +import cn from '@/utils/classnames' import BlockSelector from '@/app/components/workflow/block-selector' import type { OnSelectBlock, diff --git a/web/app/components/workflow/operator/control.tsx b/web/app/components/workflow/operator/control.tsx index c88d1ee0d9..daf628a2e3 100644 --- a/web/app/components/workflow/operator/control.tsx +++ b/web/app/components/workflow/operator/control.tsx @@ -4,7 +4,6 @@ import { useCallback, } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCursorLine, RiFunctionAddLine, @@ -22,6 +21,7 @@ import { useStore } from '../store' import AddBlock from './add-block' import TipPopup from './tip-popup' import { useOperator } from './hooks' +import cn from '@/utils/classnames' const Control = () => { const { t } = useTranslation() diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index cdbbe16c3f..13047cdafc 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -5,7 +5,6 @@ import { useCallback, useState, } from 'react' -import cn from 'classnames' import { RiZoomInLine, RiZoomOutLine, @@ -27,6 +26,7 @@ import { } from '../utils' import ShortcutsName from '../shortcuts-name' import TipPopup from './tip-popup' +import cn from '@/utils/classnames' import { PortalToFollowElem, PortalToFollowElemContent, diff --git a/web/app/components/workflow/panel-contextmenu.tsx b/web/app/components/workflow/panel-contextmenu.tsx index 823a9ea6b9..0ce9978984 100644 --- a/web/app/components/workflow/panel-contextmenu.tsx +++ b/web/app/components/workflow/panel-contextmenu.tsx @@ -2,7 +2,6 @@ import { memo, useRef, } from 'react' -import cn from 'classnames' import { useTranslation } from 'react-i18next' import { useClickAway } from 'ahooks' import ShortcutsName from './shortcuts-name' @@ -15,6 +14,7 @@ import { } from './hooks' import AddBlock from './operator/add-block' import { useOperator } from './operator/hooks' +import cn from '@/utils/classnames' const PanelContextmenu = () => { const { t } = useTranslation() diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx index 0b766776ed..72a601bed9 100644 --- a/web/app/components/workflow/panel/debug-and-preview/index.tsx +++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx @@ -3,7 +3,6 @@ import { useRef, } from 'react' import { useKeyPress } from 'ahooks' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { @@ -12,6 +11,7 @@ import { useWorkflowInteractions, } from '../../hooks' import ChatWrapper from './chat-wrapper' +import cn from '@/utils/classnames' import Button from '@/app/components/base/button' import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' diff --git a/web/app/components/workflow/panel/index.tsx b/web/app/components/workflow/panel/index.tsx index e5d8aa4e20..7983b36615 100644 --- a/web/app/components/workflow/panel/index.tsx +++ b/web/app/components/workflow/panel/index.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react' import { memo } from 'react' import { useNodes } from 'reactflow' -import cn from 'classnames' import { useShallow } from 'zustand/react/shallow' import type { CommonNodeType } from '../types' import { Panel as NodePanel } from '../nodes' @@ -14,6 +13,7 @@ import DebugAndPreview from './debug-and-preview' import Record from './record' import WorkflowPreview from './workflow-preview' import ChatRecord from './chat-record' +import cn from '@/utils/classnames' import { useStore as useAppStore } from '@/app/components/app/store' import MessageLogModal from '@/app/components/base/message-log-modal' diff --git a/web/app/components/workflow/panel/workflow-preview.tsx b/web/app/components/workflow/panel/workflow-preview.tsx index fa021b8a0a..ca1a8ba59a 100644 --- a/web/app/components/workflow/panel/workflow-preview.tsx +++ b/web/app/components/workflow/panel/workflow-preview.tsx @@ -5,7 +5,6 @@ import { // useRef, useState, } from 'react' -import cn from 'classnames' import { RiClipboardLine, RiCloseLine, @@ -27,6 +26,7 @@ import { SimpleBtn } from '../../app/text-generate/item' import Toast from '../../base/toast' import IterationResultPanel from '../run/iteration-result-panel' import InputsPanel from './inputs-panel' +import cn from '@/utils/classnames' import Loading from '@/app/components/base/loading' import type { NodeTracing } from '@/types/workflow' diff --git a/web/app/components/workflow/run/index.tsx b/web/app/components/workflow/run/index.tsx index 795c80038e..8b9981346b 100644 --- a/web/app/components/workflow/run/index.tsx +++ b/web/app/components/workflow/run/index.tsx @@ -3,13 +3,13 @@ import type { FC } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useContext } from 'use-context-selector' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { useBoolean } from 'ahooks' import { BlockEnum } from '../types' import OutputPanel from './output-panel' import ResultPanel from './result-panel' import TracingPanel from './tracing-panel' import IterationResultPanel from './iteration-result-panel' +import cn from '@/utils/classnames' import { ToastContext } from '@/app/components/base/toast' import Loading from '@/app/components/base/loading' import { fetchRunDetail, fetchTracingList } from '@/service/log' diff --git a/web/app/components/workflow/run/iteration-result-panel.tsx b/web/app/components/workflow/run/iteration-result-panel.tsx index bc156c7808..c833ea0342 100644 --- a/web/app/components/workflow/run/iteration-result-panel.tsx +++ b/web/app/components/workflow/run/iteration-result-panel.tsx @@ -2,10 +2,10 @@ import type { FC } from 'react' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' import { RiCloseLine } from '@remixicon/react' import { ArrowNarrowLeft } from '../../base/icons/src/vender/line/arrows' import NodePanel from './node' +import cn from '@/utils/classnames' import type { NodeTracing } from '@/types/workflow' const i18nPrefix = 'workflow.singleRun' diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 51531fdf3f..f5df961d21 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -2,7 +2,6 @@ import { useTranslation } from 'react-i18next' import type { FC } from 'react' import { useCallback, useEffect, useState } from 'react' -import cn from 'classnames' import { RiArrowRightSLine, RiCheckboxCircleLine, @@ -12,6 +11,7 @@ import { import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import Split from '../nodes/_base/components/split' +import cn from '@/utils/classnames' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback' diff --git a/web/app/components/workflow/run/status.tsx b/web/app/components/workflow/run/status.tsx index 51566657ae..2eeafca95d 100644 --- a/web/app/components/workflow/run/status.tsx +++ b/web/app/components/workflow/run/status.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import { useTranslation } from 'react-i18next' -import cn from 'classnames' +import cn from '@/utils/classnames' import Indicator from '@/app/components/header/indicator' type ResultProps = { @@ -68,7 +68,7 @@ const StatusPanel: FC = ({
{t('runLog.resultPanel.time')}
{status === 'running' && ( -
+
)} {status !== 'running' && ( {`${time?.toFixed(3)}s`} @@ -79,7 +79,7 @@ const StatusPanel: FC = ({
{t('runLog.resultPanel.tokens')}
{status === 'running' && ( -
+
)} {status !== 'running' && ( {`${tokens || 0} Tokens`} @@ -89,7 +89,7 @@ const StatusPanel: FC = ({
{status === 'failed' && error && ( <> -
+
{error}
)} diff --git a/web/app/components/workflow/shortcuts-name.tsx b/web/app/components/workflow/shortcuts-name.tsx index dfd44940e0..129753c198 100644 --- a/web/app/components/workflow/shortcuts-name.tsx +++ b/web/app/components/workflow/shortcuts-name.tsx @@ -1,6 +1,6 @@ import { memo } from 'react' -import cn from 'classnames' import { getKeyboardKeyNameBySystem } from './utils' +import cn from '@/utils/classnames' type ShortcutsNameProps = { keys: string[] diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 69b488344d..b960542167 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -132,6 +132,7 @@ export type InputVar = { required: boolean hint?: string options?: string[] + value_selector?: ValueSelector } export type ModelConfig = { diff --git a/web/app/components/workflow/utils.ts b/web/app/components/workflow/utils.ts index 4ad9c6591c..0d07b2e568 100644 --- a/web/app/components/workflow/utils.ts +++ b/web/app/components/workflow/utils.ts @@ -14,6 +14,7 @@ import type { InputVar, Node, ToolWithProvider, + ValueSelector, } from './types' import { BlockEnum } from './types' import { @@ -23,6 +24,8 @@ import { START_INITIAL_POSITION, } from './constants' import type { QuestionClassifierNodeType } from './nodes/question-classifier/types' +import type { IfElseNodeType } from './nodes/if-else/types' +import { branchNameCorrect } from './nodes/if-else/utils' import type { ToolNodeType } from './nodes/tool/types' import { CollectionType } from '@/app/components/tools/types' import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' @@ -114,16 +117,21 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { node.data._connectedTargetHandleIds = connectedEdges.filter(edge => edge.target === node.id).map(edge => edge.targetHandle || 'target') if (node.data.type === BlockEnum.IfElse) { - node.data._targetBranches = [ - { - id: 'true', - name: 'IS TRUE', - }, - { - id: 'false', - name: 'IS FALSE', - }, - ] + const nodeData = node.data as IfElseNodeType + + if (!nodeData.cases && nodeData.logical_operator && nodeData.conditions) { + (node.data as IfElseNodeType).cases = [ + { + case_id: 'true', + logical_operator: nodeData.logical_operator, + conditions: nodeData.conditions, + }, + ] + } + node.data._targetBranches = branchNameCorrect([ + ...(node.data as IfElseNodeType).cases.map(item => ({ id: item.case_id, name: '' })), + { id: 'false', name: '' }, + ]) } if (node.data.type === BlockEnum.QuestionClassifier) { @@ -184,6 +192,7 @@ export const initialEdges = (originEdges: Edge[], originNodes: Node[]) => { _connectedNodeIsSelected: edge.source === selectedNode.id || edge.target === selectedNode.id, } as any } + return edge }) } @@ -463,3 +472,10 @@ export const isEventTargetInputArea = (target: HTMLElement) => { if (target.contentEditable === 'true') return true } + +export const variableTransformer = (v: ValueSelector | string) => { + if (typeof v === 'string') + return v.replace(/^{{#|#}}$/g, '').split('.') + + return `{{#${v.join('.')}#}}` +} diff --git a/web/app/init/page.tsx b/web/app/init/page.tsx index 8df5d812c3..37ac180505 100644 --- a/web/app/init/page.tsx +++ b/web/app/init/page.tsx @@ -1,7 +1,7 @@ import React from 'react' -import classNames from 'classnames' import style from '../signin/page.module.css' import InitPasswordPopup from './InitPasswordPopup' +import classNames from '@/utils/classnames' const Install = () => { return ( diff --git a/web/app/install/installForm.tsx b/web/app/install/installForm.tsx index fd540e50db..0db88c8e25 100644 --- a/web/app/install/installForm.tsx +++ b/web/app/install/installForm.tsx @@ -9,8 +9,8 @@ import type { SubmitHandler } from 'react-hook-form' import { useForm } from 'react-hook-form' import { z } from 'zod' import { zodResolver } from '@hookform/resolvers/zod' -import classNames from 'classnames' import Loading from '../components/base/loading' +import classNames from '@/utils/classnames' import Button from '@/app/components/base/button' import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/common' diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index b8faf57951..9fa38dd15e 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -1,8 +1,8 @@ import React from 'react' -import classNames from 'classnames' import Header from '../signin/_header' import style from '../signin/page.module.css' import InstallForm from './installForm' +import classNames from '@/utils/classnames' const Install = () => { return ( diff --git a/web/app/layout.tsx b/web/app/layout.tsx index fedf66045a..9acc131029 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -27,7 +27,7 @@ const LocaleLayout = ({ const locale = getLocaleOnServer() return ( - + diff --git a/web/app/signin/forms.tsx b/web/app/signin/forms.tsx index c6b48ef5e4..70a34c26fa 100644 --- a/web/app/signin/forms.tsx +++ b/web/app/signin/forms.tsx @@ -2,9 +2,9 @@ import React from 'react' import { useSearchParams } from 'next/navigation' -import cn from 'classnames' import NormalForm from './normalForm' import OneMoreStep from './oneMoreStep' +import cn from '@/utils/classnames' const Forms = () => { const searchParams = useSearchParams() diff --git a/web/app/signin/normalForm.tsx b/web/app/signin/normalForm.tsx index 40912c6e1f..7f23c7d22e 100644 --- a/web/app/signin/normalForm.tsx +++ b/web/app/signin/normalForm.tsx @@ -2,11 +2,11 @@ import React, { useEffect, useReducer, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' -import classNames from 'classnames' import useSWR from 'swr' import Link from 'next/link' import Toast from '../components/base/toast' import style from './page.module.css' +import classNames from '@/utils/classnames' import { IS_CE_EDITION, SUPPORT_MAIL_LOGIN, apiPrefix, emailRegex } from '@/config' import Button from '@/app/components/base/button' import { login, oauth } from '@/service/common' diff --git a/web/app/signin/page.tsx b/web/app/signin/page.tsx index b0ee172a95..5865f40e71 100644 --- a/web/app/signin/page.tsx +++ b/web/app/signin/page.tsx @@ -1,12 +1,12 @@ 'use client' import React, { useEffect, useState } from 'react' -import cn from 'classnames' import Script from 'next/script' import Loading from '../components/base/loading' import Forms from './forms' import Header from './_header' import style from './page.module.css' import UserSSOForm from './userSSOForm' +import cn from '@/utils/classnames' import { IS_CE_EDITION } from '@/config' import type { SystemFeatures } from '@/types/feature' diff --git a/web/app/signin/userSSOForm.tsx b/web/app/signin/userSSOForm.tsx index ca93b4cddc..9cd889a0a5 100644 --- a/web/app/signin/userSSOForm.tsx +++ b/web/app/signin/userSSOForm.tsx @@ -1,9 +1,9 @@ 'use client' -import cn from 'classnames' import { useRouter, useSearchParams } from 'next/navigation' import type { FC } from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import cn from '@/utils/classnames' import Toast from '@/app/components/base/toast' import { getUserOAuth2SSOUrl, getUserOIDCSSOUrl, getUserSAMLSSOUrl } from '@/service/sso' import Button from '@/app/components/base/button' diff --git a/web/app/styles/globals.css b/web/app/styles/globals.css index 31e5af97b7..1c13e7eb5f 100644 --- a/web/app/styles/globals.css +++ b/web/app/styles/globals.css @@ -2,6 +2,13 @@ @tailwind base; @tailwind components; +@import '../../themes/light.css'; +@import '../../themes/dark.css'; + +html[data-changing-theme] * { + transition: none !important; +} + :root { --max-width: 1100px; --border-radius: 12px; @@ -121,6 +128,505 @@ button:focus-within { line-height: 1.5; } +/* font define start */ +.system-kbd { + font-size: 12px; + font-weight: 500; + line-height: 16px; +} + +.system-2xs-regular-uppercase { + font-size: 10px; + font-weight: 400; + text-transform: uppercase; + line-height: 12px; +} + +.system-2xs-medium { + font-size: 10px; + font-weight: 500; + line-height: 12px; +} + +.system-2xs-medium-uppercase { + font-size: 10px; + font-weight: 500; + text-transform: uppercase; + line-height: 12px; +} + +.system-2xs-semibold-uppercase { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + line-height: 12px; +} + +.system-xs-regular { + font-size: 12px; + font-weight: 400; + line-height: 16px; +} + +.system-xs-regular-uppercase { + font-size: 12px; + font-weight: 400; + text-transform: uppercase; + line-height: 16px; +} + +.system-xs-medium { + font-size: 12px; + font-weight: 500; + line-height: 16px; +} + +.system-xs-medium-uppercase { + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + line-height: 16px; +} + +.system-xs-semibold { + font-size: 12px; + font-weight: 600; + line-height: 16px; +} + +.system-xs-semibold-uppercase { + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + line-height: 16px; +} + +.system-sm-regular { + font-size: 13px; + font-weight: 400; + line-height: 16px; +} + +.system-sm-medium { + font-size: 13px; + font-weight: 500; + line-height: 16px; +} + +.system-sm-medium-uppercase { + font-size: 13px; + font-weight: 500; + text-transform: uppercase; + line-height: 16px; +} + +.system-sm-semibold { + font-size: 13px; + font-weight: 600; + line-height: 16px; +} + +.system-sm-semibold-uppercase { + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + line-height: 16px; +} + +.system-md-regular { + font-size: 14px; + font-weight: 400; + line-height: 20px; +} + +.system-md-medium { + font-size: 14px; + font-weight: 500; + line-height: 20px; +} + +.system-md-semibold { + font-size: 14px; + font-weight: 600; + line-height: 20px; +} + +.system-md-semibold-uppercase { + font-size: 14px; + font-weight: 600; + text-transform: uppercase; + line-height: 20px; +} + +.system-xl-regular { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +.system-xl-medium { + font-size: 16px; + font-weight: 500; + line-height: 24px; +} + +.system-xl-semibold { + font-size: 16px; + font-weight: 600; + line-height: 24px; +} + +.code-xs-regular { + font-size: 12px; + font-weight: 400; + line-height: 1.5; +} + +.code-xs-semibold { + font-size: 12px; + font-weight: 600; + line-height: 1.5; +} + +.code-sm-regular { + font-size: 13px; + font-weight: 400; + line-height: 1.5; +} + +.code-sm-semibold { + font-size: 13px; + font-weight: 600; + line-height: 1.5; +} + +.code-md-regular { + font-size: 14px; + font-weight: 400; + line-height: 1.5; +} + +.code-md-semibold { + font-size: 14px; + font-weight: 600; + line-height: 1.5; +} + +.body-xs-light { + font-size: 12px; + font-weight: 300; + line-height: 16px; +} + +.body-xs-regular { + font-size: 12px; + font-weight: 400; + line-height: 16px; +} + +.body-xs-medium { + font-size: 12px; + font-weight: 500; + line-height: 16px; +} + +.body-sm-light { + font-size: 13px; + font-weight: 300; + line-height: 16px; +} + +.body-sm-regular { + font-size: 13px; + font-weight: 400; + line-height: 16px; +} + +.body-sm-medium { + font-size: 13px; + font-weight: 500; + line-height: 16px; +} + +.body-md-light { + font-size: 14px; + font-weight: 300; + line-height: 20px; +} + +.body-md-regular { + font-size: 14px; + font-weight: 400; + line-height: 20px; +} + +.body-md-medium { + font-size: 14px; + font-weight: 500; + line-height: 20px; +} + +.body-lg-light { + font-size: 15px; + font-weight: 300; + line-height: 20px; +} + +.body-lg-regular { + font-size: 15px; + font-weight: 400; + line-height: 20px; +} + +.body-lg-medium { + font-size: 15px; + font-weight: 500; + line-height: 20px; +} + +.body-xl-regular { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +.body-xl-medium { + font-size: 16px; + font-weight: 500; + line-height: 24px; +} + +.body-xl-light { + font-size: 16px; + font-weight: 300; + line-height: 24px; +} + +.body-2xl-light { + font-size: 18px; + font-weight: 300; + line-height: 1.5; +} + +.body-2xl-regular { + font-size: 18px; + font-weight: 400; + line-height: 1.5; +} + +.body-2xl-medium { + font-size: 18px; + font-weight: 500; + line-height: 1.5; +} + +.title-xs-semi-bold { + font-size: 12px; + font-weight: 600; + line-height: 16px; +} + +.title-xs-bold { + font-size: 12px; + font-weight: 700; + line-height: 16px; +} + +.title-sm-semi-bold { + font-size: 13px; + font-weight: 600; + line-height: 16px; +} + +.title-sm-bold { + font-size: 13px; + font-weight: 700; + line-height: 16px; +} + +.title-md-semi-bold { + font-size: 14px; + font-weight: 600; + line-height: 20px; +} + +.title-md-bold { + font-size: 14px; + font-weight: 700; + line-height: 20px; +} + +.title-lg-semi-bold { + font-size: 15px; + font-weight: 600; + line-height: 1.2; +} + +.title-lg-bold { + font-size: 15px; + font-weight: 700; + line-height: 1.2; +} + +.title-xl-semi-bold { + font-size: 16px; + font-weight: 600; + line-height: 1.2; +} + +.title-xl-bold { + font-size: 16px; + font-weight: 700; + line-height: 1.2; +} + +.title-2xl-semi-bold { + font-size: 18px; + font-weight: 600; + line-height: 1.2; +} + +.title-2xl-bold { + font-size: 18px; + font-weight: 700; + line-height: 1.2; +} + +.title-3xl-semi-bold { + font-size: 20px; + font-weight: 600; + line-height: 1.2; +} + +.title-3xl-bold { + font-size: 20px; + font-weight: 700; + line-height: 1.2; +} + +.title-4xl-semi-bold { + font-size: 24px; + font-weight: 600; + line-height: 1.2; +} + +.title-4xl-bold { + font-size: 24px; + font-weight: 700; + line-height: 1.2; +} + +.title-5xl-semi-bold { + font-size: 30px; + font-weight: 600; + line-height: 1.2; +} + +.title-5xl-bold { + font-size: 30px; + font-weight: 700; + line-height: 1.2; +} + +.title-6xl-semi-bold { + font-size: 36px; + font-weight: 600; + line-height: 1.2; +} + +.title-6xl-bold { + font-size: 36px; + font-weight: 700; + line-height: 1.2; +} + +.title-7xl-semi-bold { + font-size: 48px; + font-weight: 600; + line-height: 1.2; +} + +.title-7xl-bold { + font-size: 48px; + font-weight: 700; + line-height: 1.2; +} + +.title-8xl-semi-bold { + font-size: 60px; + font-weight: 600; + line-height: 1.2; +} + +.title-8xl-bold { + font-size: 60px; + font-weight: 700; + line-height: 1.2; +} +/* font define end */ + +/* border radius start */ +.radius-2xs { + border-radius: 2px; +} + +.radius-xs { + border-radius: 4px; +} + +.radius-sm { + border-radius: 6px; +} + +.radius-md { + border-radius: 8px; +} + +.radius-lg { + border-radius: 10px; +} + +.radius-xl { + border-radius: 12px; +} + +.radius-2xl { + border-radius: 16px; +} + +.radius-3xl { + border-radius: 20px; +} + +.radius-4xl { + border-radius: 24px; +} + +.radius-5xl { + border-radius: 24px; +} + +.radius-6xl { + border-radius: 28px; +} + +.radius-7xl { + border-radius: 32px; +} + +.radius-8xl { + border-radius: 40px; +} + +.radius-9xl { + border-radius: 48px; +} + +.radius-full { + border-radius: 64px; +} +/* border radius end */ + .link { @apply text-blue-600 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out; } @@ -150,4 +656,4 @@ button:focus-within { @import '../components/base/button/index.css'; @import '../components/base/modal/index.css'; -@tailwind utilities; +@tailwind utilities; \ No newline at end of file diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx index 93cf1a59cb..d141a78212 100644 --- a/web/context/app-context.tsx +++ b/web/context/app-context.tsx @@ -20,6 +20,7 @@ export type AppContextValue = { isCurrentWorkspaceManager: boolean isCurrentWorkspaceOwner: boolean isCurrentWorkspaceEditor: boolean + isCurrentWorkspaceDatasetOperator: boolean mutateCurrentWorkspace: VoidFunction pageContainerRef: React.RefObject langeniusVersionInfo: LangGeniusVersionResponse @@ -61,6 +62,7 @@ const AppContext = createContext({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, isCurrentWorkspaceEditor: false, + isCurrentWorkspaceDatasetOperator: false, mutateUserProfile: () => { }, mutateCurrentWorkspace: () => { }, pageContainerRef: createRef(), @@ -89,6 +91,7 @@ export const AppContextProvider: FC = ({ children }) => const isCurrentWorkspaceManager = useMemo(() => ['owner', 'admin'].includes(currentWorkspace.role), [currentWorkspace.role]) const isCurrentWorkspaceOwner = useMemo(() => currentWorkspace.role === 'owner', [currentWorkspace.role]) const isCurrentWorkspaceEditor = useMemo(() => ['owner', 'admin', 'editor'].includes(currentWorkspace.role), [currentWorkspace.role]) + const isCurrentWorkspaceDatasetOperator = useMemo(() => currentWorkspace.role === 'dataset_operator', [currentWorkspace.role]) const updateUserProfileAndVersion = useCallback(async () => { if (userProfileResponse && !userProfileResponse.bodyUsed) { const result = await userProfileResponse.json() @@ -125,6 +128,7 @@ export const AppContextProvider: FC = ({ children }) => isCurrentWorkspaceManager, isCurrentWorkspaceOwner, isCurrentWorkspaceEditor, + isCurrentWorkspaceDatasetOperator, mutateCurrentWorkspace, }}>
diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 04e3f19bfe..75747ba79c 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -34,6 +34,7 @@ type ProviderContextState = { onPlanInfoChanged: () => void enableReplaceWebAppLogo: boolean modelLoadBalancingEnabled: boolean + datasetOperatorEnabled: boolean } const ProviderContext = createContext({ modelProviders: [], @@ -47,12 +48,14 @@ const ProviderContext = createContext({ buildApps: 12, teamMembers: 1, annotatedResponse: 1, + documentsUploadQuota: 50, }, total: { vectorSpace: 200, buildApps: 50, teamMembers: 1, annotatedResponse: 10, + documentsUploadQuota: 500, }, }, isFetchedPlan: false, @@ -60,6 +63,7 @@ const ProviderContext = createContext({ onPlanInfoChanged: () => { }, enableReplaceWebAppLogo: false, modelLoadBalancingEnabled: false, + datasetOperatorEnabled: false, }) export const useProviderContext = () => useContext(ProviderContext) @@ -86,6 +90,7 @@ export const ProviderContextProvider = ({ const [enableBilling, setEnableBilling] = useState(true) const [enableReplaceWebAppLogo, setEnableReplaceWebAppLogo] = useState(false) const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false) + const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false) const fetchPlan = async () => { const data = await fetchCurrentPlanInfo() @@ -98,6 +103,8 @@ export const ProviderContextProvider = ({ } if (data.model_load_balancing_enabled) setModelLoadBalancingEnabled(true) + if (data.dataset_operator_enabled) + setDatasetOperatorEnabled(true) } useEffect(() => { fetchPlan() @@ -115,6 +122,7 @@ export const ProviderContextProvider = ({ onPlanInfoChanged: fetchPlan, enableReplaceWebAppLogo, modelLoadBalancingEnabled, + datasetOperatorEnabled, }}> {children} diff --git a/web/i18n/de-DE/share-app.ts b/web/i18n/de-DE/share-app.ts index eeb048dd9b..6a35b959a5 100644 --- a/web/i18n/de-DE/share-app.ts +++ b/web/i18n/de-DE/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Willkommen bei', + welcome: '', appUnavailable: 'App ist nicht verfügbar', appUnkonwError: 'App ist nicht verfügbar', }, diff --git a/web/i18n/de-DE/workflow.ts b/web/i18n/de-DE/workflow.ts index 4d3ac07f4f..038ff1e675 100644 --- a/web/i18n/de-DE/workflow.ts +++ b/web/i18n/de-DE/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'Bild-URL', upload_file_id: 'Hochgeladene Datei-ID', }, + json: 'von einem Tool generiertes JSON', }, }, questionClassifiers: { diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index a859d927d7..2f130c049a 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -323,6 +323,9 @@ const translation = { language: 'Language', resolutionTooltip: 'Text-to-speech voice support language。', voice: 'Voice', + autoPlay: 'Auto Play', + autoPlayEnabled: 'Turn On', + autoPlayDisabled: 'Turn Off', }, }, openingStatement: { diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 2f583dc033..0514763b62 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -181,6 +181,8 @@ const translation = { builderTip: 'Can build & edit own apps', editor: 'Editor', editorTip: 'Can build & edit apps', + datasetOperator: 'Knowledge Admin', + datasetOperatorTip: 'Only can manage the knowledge base', inviteTeamMember: 'Add team member', inviteTeamMemberTip: 'They can access your team data directly after signing in.', email: 'Email', diff --git a/web/i18n/en-US/dataset-settings.ts b/web/i18n/en-US/dataset-settings.ts index 4fcd2a3f58..8ca24596f8 100644 --- a/web/i18n/en-US/dataset-settings.ts +++ b/web/i18n/en-US/dataset-settings.ts @@ -12,6 +12,8 @@ const translation = { permissions: 'Permissions', permissionsOnlyMe: 'Only me', permissionsAllMember: 'All team members', + permissionsInvitedMembers: 'Partial team members', + me: '(You)', indexMethod: 'Index Method', indexMethodHighQuality: 'High Quality', indexMethodHighQualityTip: 'Call Embedding model for processing to provide higher accuracy when users query.', diff --git a/web/i18n/en-US/share-app.ts b/web/i18n/en-US/share-app.ts index c3420c280a..f66e923561 100644 --- a/web/i18n/en-US/share-app.ts +++ b/web/i18n/en-US/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Welcome to use', + welcome: '', appUnavailable: 'App is unavailable', appUnkonwError: 'App is unavailable', }, diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 4ac3e82a95..568823bb3a 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -364,6 +364,7 @@ const translation = { enterValue: 'Enter value', addCondition: 'Add Condition', conditionNotSetup: 'Condition NOT setup', + selectVariable: 'Select variable...', }, variableAssigner: { title: 'Assign variables', diff --git a/web/i18n/es-ES/share-app.ts b/web/i18n/es-ES/share-app.ts index ad242df478..2e436c4327 100644 --- a/web/i18n/es-ES/share-app.ts +++ b/web/i18n/es-ES/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Bienvenido/a al uso', + welcome: '', appUnavailable: 'La aplicación no está disponible', appUnkonwError: 'La aplicación no está disponible', }, diff --git a/web/i18n/fr-FR/share-app.ts b/web/i18n/fr-FR/share-app.ts index 97f0d992e0..8f9e04e941 100644 --- a/web/i18n/fr-FR/share-app.ts +++ b/web/i18n/fr-FR/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Bienvenue à l\'utilisation', + welcome: '', appUnavailable: 'L\'application n\'est pas disponible', appUnkonwError: 'L\'application n\'est pas disponible', }, diff --git a/web/i18n/fr-FR/workflow.ts b/web/i18n/fr-FR/workflow.ts index 12a9f7817d..e9cd1834fe 100644 --- a/web/i18n/fr-FR/workflow.ts +++ b/web/i18n/fr-FR/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL de l\'image', upload_file_id: 'ID du fichier téléchargé', }, + json: 'JSON généré par un outil', }, }, questionClassifiers: { diff --git a/web/i18n/hi-IN/workflow.ts b/web/i18n/hi-IN/workflow.ts index 7a0843863f..740fa09988 100644 --- a/web/i18n/hi-IN/workflow.ts +++ b/web/i18n/hi-IN/workflow.ts @@ -405,6 +405,7 @@ const translation = { url: 'इमेज यूआरएल', upload_file_id: 'अपलोड फ़ाइल आईडी', }, + json: 'उपकरण द्वारा उत्पन्न JSON', }, }, questionClassifiers: { diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index af5c94bb9d..1537655f0e 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -319,6 +319,9 @@ const translation = { language: '言語', resolutionTooltip: 'テキスト読み上げの音声言語をサポートします。', voice: '音声', + autoPlay: '自動再生', + autoPlayEnabled: '開ける', + autoPlayDisabled: '關閉', }, }, openingStatement: { diff --git a/web/i18n/ja-JP/share-app.ts b/web/i18n/ja-JP/share-app.ts index c73d0bb3f6..503972dc48 100644 --- a/web/i18n/ja-JP/share-app.ts +++ b/web/i18n/ja-JP/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: '利用していただきありがとうございます', + welcome: '', appUnavailable: 'アプリが利用できません', appUnkonwError: 'アプリが利用できません', }, diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 2e1f8e0807..b757251af9 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -389,6 +389,7 @@ const translation = { url: '画像URL', upload_file_id: 'アップロードファイルID', }, + json: 'ツールで生成されたJSON', }, }, questionClassifiers: { diff --git a/web/i18n/ko-KR/share-app.ts b/web/i18n/ko-KR/share-app.ts index c677323b10..9c0738b3d7 100644 --- a/web/i18n/ko-KR/share-app.ts +++ b/web/i18n/ko-KR/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: '이용해주셔서 감사합니다', + welcome: '', appUnavailable: '앱을 사용할 수 없습니다', appUnkonwError: '앱을 사용할 수 없습니다', }, diff --git a/web/i18n/ko-KR/workflow.ts b/web/i18n/ko-KR/workflow.ts index 5eef217cac..1cb6384c19 100644 --- a/web/i18n/ko-KR/workflow.ts +++ b/web/i18n/ko-KR/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: '이미지 URL', upload_file_id: '업로드된 파일 ID', }, + json: '도구로 생성된 JSON', }, }, questionClassifiers: { diff --git a/web/i18n/pl-PL/share-app.ts b/web/i18n/pl-PL/share-app.ts index d286a9c900..eb5573c1a1 100644 --- a/web/i18n/pl-PL/share-app.ts +++ b/web/i18n/pl-PL/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Witaj w użyciu', + welcome: '', appUnavailable: 'Aplikacja jest niedostępna', appUnkonwError: 'Aplikacja jest niedostępna', }, diff --git a/web/i18n/pl-PL/workflow.ts b/web/i18n/pl-PL/workflow.ts index 0b56b41ad7..6cbe0588bc 100644 --- a/web/i18n/pl-PL/workflow.ts +++ b/web/i18n/pl-PL/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL obrazu', upload_file_id: 'ID przesłanego pliku', }, + json: 'JSON wygenerowany przez narzędzien', }, }, questionClassifiers: { diff --git a/web/i18n/pt-BR/share-app.ts b/web/i18n/pt-BR/share-app.ts index 89870fc023..27baf35275 100644 --- a/web/i18n/pt-BR/share-app.ts +++ b/web/i18n/pt-BR/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Bem-vindo ao usar', + welcome: '', appUnavailable: 'O aplicativo não está disponível', appUnkonwError: 'O aplicativo encontrou um erro desconhecido', }, diff --git a/web/i18n/pt-BR/workflow.ts b/web/i18n/pt-BR/workflow.ts index e5fd21dd4d..6579a1aed4 100644 --- a/web/i18n/pt-BR/workflow.ts +++ b/web/i18n/pt-BR/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL da imagem', upload_file_id: 'ID do arquivo enviado', }, + json: 'JSON gerado por ferramenta', }, }, questionClassifiers: { diff --git a/web/i18n/ro-RO/share-app.ts b/web/i18n/ro-RO/share-app.ts index d6c1032f1b..06cf083a04 100644 --- a/web/i18n/ro-RO/share-app.ts +++ b/web/i18n/ro-RO/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Bun venit la utilizare', + welcome: '', appUnavailable: 'Aplicația nu este disponibilă', appUnkonwError: 'Aplicația nu este disponibilă', }, diff --git a/web/i18n/ro-RO/workflow.ts b/web/i18n/ro-RO/workflow.ts index b97d043c0a..e1f0943179 100644 --- a/web/i18n/ro-RO/workflow.ts +++ b/web/i18n/ro-RO/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL imagine', upload_file_id: 'ID fișier încărcat', }, + json: 'JSON generat de instrument', }, }, questionClassifiers: { diff --git a/web/i18n/uk-UA/share-app.ts b/web/i18n/uk-UA/share-app.ts index c0deddb23e..9a121aaadc 100644 --- a/web/i18n/uk-UA/share-app.ts +++ b/web/i18n/uk-UA/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Ласкаво просимо до використання', + welcome: '', appUnavailable: 'Додаток недоступний', appUnkonwError: 'Додаток недоступний', }, diff --git a/web/i18n/uk-UA/workflow.ts b/web/i18n/uk-UA/workflow.ts index b8e43bf46f..399b7bde76 100644 --- a/web/i18n/uk-UA/workflow.ts +++ b/web/i18n/uk-UA/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL зображення', upload_file_id: 'ID завантаженого файлу', }, + json: 'JSON, згенерований інструментом', }, }, questionClassifiers: { diff --git a/web/i18n/vi-VN/share-app.ts b/web/i18n/vi-VN/share-app.ts index 14fc638fb3..5ca2dc55b5 100644 --- a/web/i18n/vi-VN/share-app.ts +++ b/web/i18n/vi-VN/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: 'Chào mừng đến với', + welcome: '', appUnavailable: 'Ứng dụng không khả dụng', appUnkonwError: 'Ứng dụng không khả dụng', }, diff --git a/web/i18n/vi-VN/workflow.ts b/web/i18n/vi-VN/workflow.ts index 40fd91144e..4d4a41d14f 100644 --- a/web/i18n/vi-VN/workflow.ts +++ b/web/i18n/vi-VN/workflow.ts @@ -388,6 +388,7 @@ const translation = { url: 'URL hình ảnh', upload_file_id: 'ID tệp đã tải lên', }, + json: 'JSON được tạo bởi công cụ', }, }, questionClassifiers: { diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index 801f7d31bf..b193152bdf 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -319,6 +319,9 @@ const translation = { language: '语言', resolutionTooltip: '文本转语音音色支持语言。', voice: '音色', + autoPlay: '自动播放', + autoPlayEnabled: '开启', + autoPlayDisabled: '关闭', }, }, openingStatement: { diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 49fe6f6cad..20bdd6b02d 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -179,6 +179,8 @@ const translation = { normalTip: '只能使用应用程序,不能建立应用程序', editor: '编辑', editorTip: '能够建立并编辑应用程序,不能管理团队设置', + datasetOperator: '知识库管理员', + datasetOperatorTip: '只能管理知识库', inviteTeamMember: '添加团队成员', inviteTeamMemberTip: '对方在登录后可以访问你的团队数据。', email: '邮箱', diff --git a/web/i18n/zh-Hans/dataset-settings.ts b/web/i18n/zh-Hans/dataset-settings.ts index 4cb1301526..8fd1cc0971 100644 --- a/web/i18n/zh-Hans/dataset-settings.ts +++ b/web/i18n/zh-Hans/dataset-settings.ts @@ -12,6 +12,8 @@ const translation = { permissions: '可见权限', permissionsOnlyMe: '只有我', permissionsAllMember: '所有团队成员', + permissionsInvitedMembers: '部分团队成员', + me: '(你)', indexMethod: '索引模式', indexMethodHighQuality: '高质量', indexMethodHighQualityTip: '调用 Embedding 模型进行处理,以在用户查询时提供更高的准确度。', diff --git a/web/i18n/zh-Hans/share-app.ts b/web/i18n/zh-Hans/share-app.ts index 334eb0a8bc..bb8e1574fd 100644 --- a/web/i18n/zh-Hans/share-app.ts +++ b/web/i18n/zh-Hans/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: '欢迎使用', + welcome: '', appUnavailable: '应用不可用', appUnkonwError: '应用不可用', }, diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index a71b22c8e0..2b9af83f6c 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -364,6 +364,7 @@ const translation = { enterValue: '输入值', addCondition: '添加条件', conditionNotSetup: '条件未设置', + selectVariable: '选择变量', }, variableAssigner: { title: '变量赋值', diff --git a/web/i18n/zh-Hant/app-debug.ts b/web/i18n/zh-Hant/app-debug.ts index 18dee05a20..2b7d76bbb1 100644 --- a/web/i18n/zh-Hant/app-debug.ts +++ b/web/i18n/zh-Hant/app-debug.ts @@ -318,6 +318,9 @@ const translation = { language: '語言', resolutionTooltip: '文字轉語音音色支援語言。', voice: '音色', + autoPlay: '自動播放', + autoPlayEnabled: '開啟', + autoPlayDisabled: '關閉', }, }, openingStatement: { diff --git a/web/i18n/zh-Hant/share-app.ts b/web/i18n/zh-Hant/share-app.ts index 426a23eff5..e91cbaf121 100644 --- a/web/i18n/zh-Hant/share-app.ts +++ b/web/i18n/zh-Hant/share-app.ts @@ -1,6 +1,6 @@ const translation = { common: { - welcome: '歡迎使用', + welcome: '', appUnavailable: '應用不可用', appUnkonwError: '應用不可用', }, diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 86974f75e6..a83fae330e 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -387,6 +387,7 @@ const translation = { url: '圖片鏈接', upload_file_id: '上傳文件ID', }, + json: '工具生成的JSON', }, }, questionClassifiers: { diff --git a/web/models/common.ts b/web/models/common.ts index f9ade855f0..78f09bee09 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -65,7 +65,7 @@ export type TenantInfoResponse = { export type Member = Pick & { avatar: string status: 'pending' | 'active' | 'banned' | 'closed' - role: 'owner' | 'admin' | 'editor' | 'normal' + role: 'owner' | 'admin' | 'editor' | 'normal' | 'dataset_operator' } export enum ProviderName { @@ -126,7 +126,7 @@ export type IWorkspace = { } export type ICurrentWorkspace = Omit & { - role: 'owner' | 'admin' | 'editor' | 'normal' + role: 'owner' | 'admin' | 'editor' | 'dataset_operator' | 'normal' providers: Provider[] in_trail: boolean trial_end_reason?: string diff --git a/web/models/datasets.ts b/web/models/datasets.ts index a28798ba68..0d2a80ea75 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -8,13 +8,15 @@ export enum DataSourceType { WEB = 'website_crawl', } +export type DatasetPermission = 'only_me' | 'all_team_members' | 'partial_members' + export type DataSet = { id: string name: string icon: string icon_background: string description: string - permission: 'only_me' | 'all_team_members' + permission: DatasetPermission data_source_type: DataSourceType indexing_technique: 'high_quality' | 'economy' created_by: string @@ -29,6 +31,7 @@ export type DataSet = { retrieval_model_dict: RetrievalConfig retrieval_model: RetrievalConfig tags: Tag[] + partial_member_list?: any[] } export type CustomFile = File & { diff --git a/web/models/debug.ts b/web/models/debug.ts index 930b4c6760..d610a9eba3 100644 --- a/web/models/debug.ts +++ b/web/models/debug.ts @@ -1,4 +1,4 @@ -import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem } from '@/types/app' +import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' export type Inputs = Record export enum PromptMode { @@ -79,6 +79,7 @@ export type TextToSpeechConfig = { enabled: boolean voice?: string language?: string + autoPlay?: TtsAutoPlay } export type CitationConfig = MoreLikeThisConfig diff --git a/web/next.config.js b/web/next.config.js index 806a6fd1ee..7785b80676 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -34,6 +34,7 @@ const nextConfig = { // https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors ignoreBuildErrors: true, }, + reactStrictMode: true, async redirects() { return [ { diff --git a/web/package.json b/web/package.json index 71819c176c..13c65726c5 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,10 @@ { "name": "dify-web", - "version": "0.6.12-fix1", + "version": "0.6.13", "private": true, + "engines": { + "node": ">=18.17.0" + }, "scripts": { "dev": "next dev", "build": "next build", @@ -56,6 +59,7 @@ "negotiator": "^0.6.3", "next": "^14.1.1", "next-nprogress-bar": "^2.3.8", + "pinyin-pro": "^3.23.0", "qrcode.react": "^3.1.0", "qs": "^6.11.1", "rc-textarea": "^1.5.2", @@ -87,6 +91,7 @@ "sharp": "^0.33.2", "sortablejs": "^1.15.0", "swr": "^2.1.0", + "tailwind-merge": "^2.4.0", "use-context-selector": "^1.4.1", "uuid": "^9.0.1", "zod": "^3.23.6", @@ -122,10 +127,14 @@ "lint-staged": "^13.2.2", "postcss": "^8.4.31", "sass": "^1.61.0", - "tailwindcss": "^3.3.3", + "tailwindcss": "^3.4.4", "typescript": "4.9.5", "uglify-js": "^3.17.4" }, + "resolutions": { + "@types/react": "~18.2.0", + "@types/react-dom": "~18.2.0" + }, "lint-staged": { "**/*.js?(x)": [ "eslint --fix" @@ -133,12 +142,5 @@ "**/*.ts?(x)": [ "eslint --fix" ] - }, - "engines": { - "node": ">=18.17.0" - }, - "resolutions": { - "@types/react": "~18.2.0", - "@types/react-dom": "~18.2.0" } } diff --git a/web/public/embed.js b/web/public/embed.js index 586abbf61c..46345d9b84 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -6,85 +6,220 @@ // attention: This JavaScript script must be placed after the element. Otherwise, the script will not work. -document.body.onload = embedChatbot; +(function () { + // Constants for DOM element IDs and configuration key + const configKey = "difyChatbotConfig"; + const buttonId = "dify-chatbot-bubble-button"; + const iframeId = "dify-chatbot-bubble-window"; -async function embedChatbot () { - const difyChatbotConfig = window.difyChatbotConfig; - if (!difyChatbotConfig || !difyChatbotConfig.token) { - console.error('difyChatbotConfig is empty or token is not provided') - return; - } - const isDev = !!difyChatbotConfig.isDev - const baseUrl = difyChatbotConfig.baseUrl || `https://${isDev ? 'dev.' : ''}udify.app` - const openIcon = ` - - ` - const closeIcon = ` - - ` + // SVG icons for open and close states + const svgIcons = { + open: ` + + `, + close: ` + + ` + }; - // create iframe - function createIframe () { - const iframe = document.createElement('iframe'); - iframe.allow = "fullscreen;microphone" - iframe.title = "dify chatbot bubble window" - iframe.id = 'dify-chatbot-bubble-window' - iframe.src = `${baseUrl}/chatbot/${difyChatbotConfig.token}` - iframe.style.cssText = 'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; max-height: calc(100vh - 6rem);border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;' - document.body.appendChild(iframe); - } + // Main function to embed the chatbot + function embedChatbot() { + const config = window[configKey]; + if (!config || !config.token) { + console.error(`${configKey} is empty or token is not provided`); + return; + } - const targetButton = document.getElementById('dify-chatbot-bubble-button') - if (!targetButton) { - // create button - const containerDiv = document.createElement("div"); - containerDiv.id = 'dify-chatbot-bubble-button' - containerDiv.style.cssText = `position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}`; - const displayDiv = document.createElement('div'); - displayDiv.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;'; - displayDiv.innerHTML = openIcon - containerDiv.appendChild(displayDiv); - document.body.appendChild(containerDiv); - // add click event to control iframe display - containerDiv.addEventListener('click', function () { - const targetIframe = document.getElementById('dify-chatbot-bubble-window') - if (!targetIframe) { - createIframe() - displayDiv.innerHTML = closeIcon - return; + const baseUrl = + config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`; + + // Function to create the iframe for the chatbot + function createIframe() { + const iframe = document.createElement("iframe"); + iframe.allow = "fullscreen;microphone"; + iframe.title = "dify chatbot bubble window"; + iframe.id = iframeId; + iframe.src = `${baseUrl}/chatbot/${config.token}`; + iframe.style.cssText = ` + border: none; position: fixed; flex-direction: column; justify-content: space-between; + box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; + bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; + max-height: calc(100vh - 6rem); border-radius: 0.75rem; display: flex; z-index: 2147483647; + overflow: hidden; left: unset; background-color: #F3F4F6; + `; + + document.body.appendChild(iframe); + } + + // Function to reset the iframe position + function resetIframePosition() { + const targetIframe = document.getElementById(iframeId); + const targetButton = document.getElementById(buttonId); + if (targetIframe && targetButton) { + const buttonRect = targetButton.getBoundingClientRect(); + const buttonBottom = window.innerHeight - buttonRect.bottom; + const buttonRight = window.innerWidth - buttonRect.right; + const buttonLeft = buttonRect.left; + + // Adjust iframe position to stay within viewport + targetIframe.style.bottom = `${ + buttonBottom + buttonRect.height + 5 + targetIframe.clientHeight > window.innerHeight + ? buttonBottom - targetIframe.clientHeight - 5 + : buttonBottom + buttonRect.height + 5 + }px`; + + targetIframe.style.right = `${ + buttonRight + targetIframe.clientWidth > window.innerWidth + ? window.innerWidth - buttonLeft - targetIframe.clientWidth + : buttonRight + }px`; } - if (targetIframe.style.display === 'none') { - targetIframe.style.display = 'block'; - displayDiv.innerHTML = closeIcon - } else { - targetIframe.style.display = 'none'; - displayDiv.innerHTML = openIcon + } + + // Function to create the chat button + function createButton() { + const containerDiv = document.createElement("div"); + // Apply custom properties from config + Object.entries(config.containerProps || {}).forEach(([key, value]) => { + if (key === "className") { + containerDiv.classList.add(...value.split(" ")); + } else if (key === "style") { + if (typeof value === "object") { + Object.assign(containerDiv.style, value); + } else { + containerDiv.style.cssText = value; + } + } else if (typeof value === "function") { + containerDiv.addEventListener( + key.replace(/^on/, "").toLowerCase(), + value + ); + } else { + containerDiv[key] = value; + } + }); + + containerDiv.id = buttonId; + + // Add styles for the button + const styleSheet = document.createElement("style"); + document.head.appendChild(styleSheet); + styleSheet.sheet.insertRule(` + #${containerDiv.id} { + position: fixed; + bottom: var(--${containerDiv.id}-bottom, 1rem); + right: var(--${containerDiv.id}-right, 1rem); + left: var(--${containerDiv.id}-left, unset); + top: var(--${containerDiv.id}-top, unset); + width: var(--${containerDiv.id}-width, 50px); + height: var(--${containerDiv.id}-height, 50px); + border-radius: var(--${containerDiv.id}-border-radius, 25px); + background-color: var(--${containerDiv.id}-bg-color, #155EEF); + box-shadow: var(--${containerDiv.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px); + cursor: pointer; + z-index: 2147483647; + transition: all 0.2s ease-in-out 0s; + } + `); + styleSheet.sheet.insertRule(` + #${containerDiv.id}:hover { + transform: var(--${containerDiv.id}-hover-transform, scale(1.1)); + } + `); + + // Create display div for the button icon + const displayDiv = document.createElement("div"); + displayDiv.style.cssText = + "display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;"; + displayDiv.innerHTML = svgIcons.open; + containerDiv.appendChild(displayDiv); + document.body.appendChild(containerDiv); + + // Add click event listener to toggle chatbot + containerDiv.addEventListener("click", function () { + const targetIframe = document.getElementById(iframeId); + if (!targetIframe) { + createIframe(); + resetIframePosition(); + displayDiv.innerHTML = svgIcons.close; + return; + } + targetIframe.style.display = targetIframe.style.display === "none" ? "block" : "none"; + displayDiv.innerHTML = targetIframe.style.display === "none" ? svgIcons.open : svgIcons.close; + + resetIframePosition(); + }); + + // Enable dragging if specified in config + if (config.draggable) { + enableDragging(containerDiv, config.dragAxis || "both"); } - }); + } + + // Function to enable dragging of the chat button + function enableDragging(element, axis) { + let isDragging = false; + let startX, startY; + + element.addEventListener("mousedown", startDragging); + document.addEventListener("mousemove", drag); + document.addEventListener("mouseup", stopDragging); + + function startDragging(e) { + isDragging = true; + startX = e.clientX - element.offsetLeft; + startY = e.clientY - element.offsetTop; + } + + function drag(e) { + if (!isDragging) return; + + element.style.transition = "none"; + element.style.cursor = "grabbing"; + + // Hide iframe while dragging + const targetIframe = document.getElementById(iframeId); + if (targetIframe) { + targetIframe.style.display = "none"; + element.querySelector("div").innerHTML = svgIcons.open; + } + + const newLeft = e.clientX - startX; + const newBottom = window.innerHeight - e.clientY - startY; + + const elementRect = element.getBoundingClientRect(); + const maxX = window.innerWidth - elementRect.width; + const maxY = window.innerHeight - elementRect.height; + + // Update position based on drag axis + if (axis === "x" || axis === "both") { + element.style.setProperty( + `--${buttonId}-left`, + `${Math.max(0, Math.min(newLeft, maxX))}px` + ); + } + + if (axis === "y" || axis === "both") { + element.style.setProperty( + `--${buttonId}-bottom`, + `${Math.max(0, Math.min(newBottom, maxY))}px` + ); + } + } + + function stopDragging() { + isDragging = false; + element.style.transition = ""; + element.style.cursor = "pointer"; + } + } + + // Create the chat button if it doesn't exist + if (!document.getElementById(buttonId)) { + createButton(); + } } -} + + // Set the embedChatbot function to run when the body is loaded + document.body.onload = embedChatbot; +})(); diff --git a/web/public/embed.min.js b/web/public/embed.min.js index 25985f4f16..51a78a39eb 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -1,30 +1 @@ -async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){var e=!!t.isDev;const o=t.baseUrl||`https://${e?"dev.":""}udify.app`,n=` - - `,i=` - - `;if(!document.getElementById("dify-chatbot-bubble-button")){e=document.createElement("div");e.id="dify-chatbot-bubble-button",e.style.cssText="position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}";const d=document.createElement("div");d.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",d.innerHTML=n,e.appendChild(d),document.body.appendChild(e),e.addEventListener("click",function(){var e=document.getElementById("dify-chatbot-bubble-window");e?"none"===e.style.display?(e.style.display="block",d.innerHTML=i):(e.style.display="none",d.innerHTML=n):((e=document.createElement("iframe")).allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id="dify-chatbot-bubble-window",e.src=o+"/chatbot/"+t.token,e.style.cssText="border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; max-height: calc(100vh - 6rem);border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;",document.body.appendChild(e),d.innerHTML=i)})}}else console.error("difyChatbotConfig is empty or token is not provided")}document.body.onload=embedChatbot; \ No newline at end of file +!function(){const e="difyChatbotConfig",t="dify-chatbot-bubble-button",n="dify-chatbot-bubble-window",o={open:'\n \n ',close:'\n \n '};document.body.onload=function(){const i=window[e];if(!i||!i.token)return void console.error(`${e} is empty or token is not provided`);const d=i.baseUrl||`https://${i.isDev?"dev.":""}udify.app`;function r(){const e=document.getElementById(n),o=document.getElementById(t);if(e&&o){const t=o.getBoundingClientRect(),n=window.innerHeight-t.bottom,i=window.innerWidth-t.right,d=t.left;e.style.bottom=`${n+t.height+5+e.clientHeight>window.innerHeight?n-e.clientHeight-5:n+t.height+5}px`,e.style.right=`${i+e.clientWidth>window.innerWidth?window.innerWidth-d-e.clientWidth:i}px`}}document.getElementById(t)||function(){const e=document.createElement("div");Object.entries(i.containerProps||{}).forEach(([t,n])=>{"className"===t?e.classList.add(...n.split(" ")):"style"===t?"object"==typeof n?Object.assign(e.style,n):e.style.cssText=n:"function"==typeof n?e.addEventListener(t.replace(/^on/,"").toLowerCase(),n):e[t]=n}),e.id=t;const s=document.createElement("style");document.head.appendChild(s),s.sheet.insertRule(`\n #${e.id} {\n position: fixed; \n bottom: var(--${e.id}-bottom, 1rem);\n right: var(--${e.id}-right, 1rem);\n left: var(--${e.id}-left, unset);\n top: var(--${e.id}-top, unset);\n width: var(--${e.id}-width, 50px);\n height: var(--${e.id}-height, 50px);\n border-radius: var(--${e.id}-border-radius, 25px); \n background-color: var(--${e.id}-bg-color, #155EEF);\n box-shadow: var(--${e.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px);\n cursor: pointer;\n z-index: 2147483647; \n transition: all 0.2s ease-in-out 0s; \n }\n `),s.sheet.insertRule(`\n #${e.id}:hover {\n transform: var(--${e.id}-hover-transform, scale(1.1));\n }\n `);const l=document.createElement("div");l.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",l.innerHTML=o.open,e.appendChild(l),document.body.appendChild(e),e.addEventListener("click",function(){const e=document.getElementById(n);if(!e)return function(){const e=document.createElement("iframe");e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=n,e.src=`${d}/chatbot/${i.token}`,e.style.cssText="\n border: none; position: fixed; flex-direction: column; justify-content: space-between; \n box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; \n bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; \n max-height: calc(100vh - 6rem); border-radius: 0.75rem; display: flex; z-index: 2147483647; \n overflow: hidden; left: unset; background-color: #F3F4F6;\n ",document.body.appendChild(e)}(),r(),void(l.innerHTML=o.close);e.style.display="none"===e.style.display?"block":"none",l.innerHTML="none"===e.style.display?o.open:o.close,r()}),i.draggable&&function(e,i){let d,r,s=!1;e.addEventListener("mousedown",function(t){s=!0,d=t.clientX-e.offsetLeft,r=t.clientY-e.offsetTop}),document.addEventListener("mousemove",function(l){if(!s)return;e.style.transition="none",e.style.cursor="grabbing";const c=document.getElementById(n);c&&(c.style.display="none",e.querySelector("div").innerHTML=o.open);const a=l.clientX-d,h=window.innerHeight-l.clientY-r,p=e.getBoundingClientRect(),u=window.innerWidth-p.width,C=window.innerHeight-p.height;"x"!==i&&"both"!==i||e.style.setProperty(`--${t}-left`,`${Math.max(0,Math.min(a,u))}px`),"y"!==i&&"both"!==i||e.style.setProperty(`--${t}-bottom`,`${Math.max(0,Math.min(h,C))}px`)}),document.addEventListener("mouseup",function(){s=!1,e.style.transition="",e.style.cursor="pointer"})}(e,i.dragAxis||"both")}()}}(); diff --git a/web/service/apps.ts b/web/service/apps.ts index cd71ceadae..1da792646f 100644 --- a/web/service/apps.ts +++ b/web/service/apps.ts @@ -120,6 +120,7 @@ export const generationIntroduction: Fetcher = ({ appId, language }) => { + language = language || 'en-US' return get(`apps/${appId}/text-to-audio/voices?language=${language}`) } diff --git a/web/service/base.ts b/web/service/base.ts index ccf731f476..7d9aac5ba2 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -19,6 +19,7 @@ const TIME_OUT = 100000 const ContentType = { json: 'application/json', stream: 'text/event-stream', + audio: 'audio/mpeg', form: 'application/x-www-form-urlencoded; charset=UTF-8', download: 'application/octet-stream', // for download upload: 'multipart/form-data', // for upload @@ -59,6 +60,8 @@ export type IOnIterationStarted = (workflowStarted: IterationStartedResponse) => export type IOnIterationNexted = (workflowStarted: IterationNextedResponse) => void export type IOnIterationFinished = (workflowFinished: IterationFinishedResponse) => void export type IOnTextChunk = (textChunk: TextChunkResponse) => void +export type IOnTTSChunk = (messageId: string, audioStr: string, audioType?: string) => void +export type IOnTTSEnd = (messageId: string, audioStr: string, audioType?: string) => void export type IOnTextReplace = (textReplace: TextReplaceResponse) => void export type IOtherOptions = { @@ -84,6 +87,8 @@ export type IOtherOptions = { onIterationNext?: IOnIterationNexted onIterationFinish?: IOnIterationFinished onTextChunk?: IOnTextChunk + onTTSChunk?: IOnTTSChunk + onTTSEnd?: IOnTTSEnd onTextReplace?: IOnTextReplace } @@ -135,6 +140,8 @@ const handleStream = ( onIterationNext?: IOnIterationNexted, onIterationFinish?: IOnIterationFinished, onTextChunk?: IOnTextChunk, + onTTSChunk?: IOnTTSChunk, + onTTSEnd?: IOnTTSEnd, onTextReplace?: IOnTextReplace, ) => { if (!response.ok) @@ -227,6 +234,12 @@ const handleStream = ( else if (bufferObj.event === 'text_replace') { onTextReplace?.(bufferObj as TextReplaceResponse) } + else if (bufferObj.event === 'tts_message') { + onTTSChunk?.(bufferObj.message_id, bufferObj.audio, bufferObj.audio_type) + } + else if (bufferObj.event === 'tts_message_end') { + onTTSEnd?.(bufferObj.message_id, bufferObj.audio) + } } }) buffer = lines[lines.length - 1] @@ -390,9 +403,10 @@ const baseFetch = ( } // return data - const data: Promise = options.headers.get('Content-type') === ContentType.download ? res.blob() : res.json() + if (options.headers.get('Content-type') === ContentType.download || options.headers.get('Content-type') === ContentType.audio) + resolve(needAllResponseContent ? resClone : res.blob()) - resolve(needAllResponseContent ? resClone : data) + else resolve(needAllResponseContent ? resClone : res.json()) }) .catch((err) => { if (!silent) @@ -475,6 +489,8 @@ export const ssePost = ( onIterationNext, onIterationFinish, onTextChunk, + onTTSChunk, + onTTSEnd, onTextReplace, onError, getAbortController, @@ -527,7 +543,7 @@ export const ssePost = ( return } onData?.(str, isFirstMessage, moreInfo) - }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onTextChunk, onTextReplace) + }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace) }).catch((e) => { if (e.toString() !== 'AbortError: The user aborted a request.') Toast.notify({ type: 'error', message: e }) diff --git a/web/service/datasets.ts b/web/service/datasets.ts index a0905208fa..c861a73c37 100644 --- a/web/service/datasets.ts +++ b/web/service/datasets.ts @@ -53,7 +53,7 @@ export const fetchDatasetDetail: Fetcher = (datasetId: string) export const updateDatasetSetting: Fetcher> }> = ({ datasetId, body }) => { return patch(`/datasets/${datasetId}`, { body }) diff --git a/web/service/share.ts b/web/service/share.ts index d4de81ddc7..f5a695f6c3 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -1,4 +1,4 @@ -import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnIterationFinished, IOnIterationNexted, IOnIterationStarted, IOnMessageEnd, IOnMessageReplace, IOnNodeFinished, IOnNodeStarted, IOnTextChunk, IOnTextReplace, IOnThought, IOnWorkflowFinished, IOnWorkflowStarted } from './base' +import type { IOnCompleted, IOnData, IOnError, IOnFile, IOnIterationFinished, IOnIterationNexted, IOnIterationStarted, IOnMessageEnd, IOnMessageReplace, IOnNodeFinished, IOnNodeStarted, IOnTTSChunk, IOnTTSEnd, IOnTextChunk, IOnTextReplace, IOnThought, IOnWorkflowFinished, IOnWorkflowStarted } from './base' import { del as consoleDel, get as consoleGet, patch as consolePatch, post as consolePost, delPublic as del, getPublic as get, patchPublic as patch, postPublic as post, ssePost, @@ -30,7 +30,7 @@ export function getUrl(url: string, isInstalledApp: boolean, installedAppId: str return isInstalledApp ? `installed-apps/${installedAppId}/${url.startsWith('/') ? url.slice(1) : url}` : url } -export const sendChatMessage = async (body: Record, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace }: { +export const sendChatMessage = async (body: Record, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace, onTTSChunk, onTTSEnd }: { onData: IOnData onCompleted: IOnCompleted onFile: IOnFile @@ -39,13 +39,15 @@ export const sendChatMessage = async (body: Record, { onData, onCom onMessageEnd?: IOnMessageEnd onMessageReplace?: IOnMessageReplace getAbortController?: (abortController: AbortController) => void + onTTSChunk?: IOnTTSChunk + onTTSEnd?: IOnTTSEnd }, isInstalledApp: boolean, installedAppId = '') => { return ssePost(getUrl('chat-messages', isInstalledApp, installedAppId), { body: { ...body, response_mode: 'streaming', }, - }, { onData, onCompleted, onThought, onFile, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace }) + }, { onData, onCompleted, onThought, onFile, isPublicAPI: !isInstalledApp, onError, getAbortController, onMessageEnd, onMessageReplace, onTTSChunk, onTTSEnd }) } export const stopChatMessageResponding = async (appId: string, taskId: string, isInstalledApp: boolean, installedAppId = '') => { @@ -214,6 +216,10 @@ export const textToAudio = (url: string, isPublicAPI: boolean, body: FormData) = return (getAction('post', !isPublicAPI))(url, { body }, { bodyStringify: false, deleteContentType: true }) as Promise<{ data: string }> } +export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { content_type: string }, body: { streaming: boolean; voice?: string; message_id?: string; text?: string | null | undefined }) => { + return (getAction('post', !isPublicAPI))(url, { body, header }, { needAllResponseContent: true }) +} + export const fetchAccessToken = async (appCode: string) => { const headers = new Headers() headers.append('X-App-Code', appCode) diff --git a/web/tailwind.config.js b/web/tailwind.config.js index e569ce23b2..1c1b6dccb6 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -1,4 +1,5 @@ /** @type {import('tailwindcss').Config} */ +import tailwindThemeVarDefine from './themes/tailwind-theme-var-define' module.exports = { content: [ './app/**/*.{js,ts,jsx,tsx}', @@ -60,6 +61,7 @@ module.exports = { 600: '#444CE7', 800: '#2D31A6', }, + ...tailwindThemeVarDefine, }, screens: { mobile: '100px', diff --git a/web/themes/dark.css b/web/themes/dark.css new file mode 100644 index 0000000000..75534b14ed --- /dev/null +++ b/web/themes/dark.css @@ -0,0 +1,562 @@ +/* Attention: Generate by code. Don't update by hand!!! */ +html[data-theme="dark"] { + --color-components-input-bg-normal: #FFFFFF14; + --color-components-input-text-placeholder: #C8CEDA4D; + --color-components-input-bg-hover: #FFFFFF08; + --color-components-input-bg-active: #FFFFFF0D; + --color-components-input-border-active: #747481; + --color-components-input-border-destructive: #F97066; + --color-components-input-text-filled: #F4F4F5; + --color-components-input-bg-destructive: #FFFFFF03; + --color-components-input-bg-disabled: #FFFFFF08; + --color-components-input-text-disabled: #C8CEDA4D; + --color-components-input-text-filled-disabled: #C8CEDA66; + --color-components-input-border-hover: #3A3A40; + --color-components-input-border-active-prompt-1: #36BFFA; + --color-components-input-border-active-prompt-2: #296DFF; + + --color-components-kbd-bg-gray: #FFFFFF08; + --color-components-kbd-bg-white: #FFFFFF1F; + + --color-components-tooltip-bg: #18181BF2; + + --color-components-button-primary-text: #FFFFFFF2; + --color-components-button-primary-bg: #155AEF; + --color-components-button-primary-border: #FFFFFF1F; + --color-components-button-primary-bg-hover: #296DFF; + --color-components-button-primary-border-hover: #FFFFFF33; + --color-components-button-primary-bg-disabled: #FFFFFF08; + --color-components-button-primary-border-disabled: #FFFFFF14; + --color-components-button-primary-text-disabled: #FFFFFF33; + + --color-components-button-secondary-text: #FFFFFFCC; + --color-components-button-secondary-text-disabled: #FFFFFF33; + --color-components-button-secondary-bg: #FFFFFF1F; + --color-components-button-secondary-bg-hover: #FFFFFF33; + --color-components-button-secondary-bg-disabled: #FFFFFF08; + --color-components-button-secondary-border: #FFFFFF14; + --color-components-button-secondary-border-hover: #FFFFFF1F; + --color-components-button-secondary-border-disabled: #FFFFFF0D; + + --color-components-button-tertiary-text: #D9D9DE; + --color-components-button-tertiary-text-disabled: #FFFFFF33; + --color-components-button-tertiary-bg: #FFFFFF14; + --color-components-button-tertiary-bg-hover: #FFFFFF1F; + --color-components-button-tertiary-bg-disabled: #FFFFFF08; + + --color-components-button-ghost-text: #D9D9DE; + --color-components-button-ghost-text-disabled: #FFFFFF33; + --color-components-button-ghost-bg-hover: #C8CEDA14; + + --color-components-button-destructive-primary-text: #FFFFFFF2; + --color-components-button-destructive-primary-text-disabled: #FFFFFF33; + --color-components-button-destructive-primary-bg: #D92D20; + --color-components-button-destructive-primary-bg-hover: #F04438; + --color-components-button-destructive-primary-bg-disabled: #F0443824; + --color-components-button-destructive-primary-border: #FFFFFF1F; + --color-components-button-destructive-primary-border-hover: #FFFFFF33; + --color-components-button-destructive-primary-border-disabled: #FFFFFF14; + + --color-components-button-destructive-secondary-text: #F04438; + --color-components-button-destructive-secondary-text-disabled: #F0443833; + --color-components-button-destructive-secondary-bg: #FFFFFF1F; + --color-components-button-destructive-secondary-bg-hover: #F0443824; + --color-components-button-destructive-secondary-bg-disabled: #F0443814; + --color-components-button-destructive-secondary-border: #FFFFFF14; + --color-components-button-destructive-secondary-border-hover: #FFFFFF1F; + --color-components-button-destructive-secondary-border-disabled: #F0443814; + + --color-components-button-destructive-tertiary-text: #F04438; + --color-components-button-destructive-tertiary-text-disabled: #F0443833; + --color-components-button-destructive-tertiary-bg: #F0443824; + --color-components-button-destructive-tertiary-bg-hover: #F0443840; + --color-components-button-destructive-tertiary-bg-disabled: #F0443814; + + --color-components-button-destructive-ghost-text: #F04438; + --color-components-button-destructive-ghost-text-disabled: #F0443833; + --color-components-button-destructive-ghost-bg-hover: #F0443824; + + --color-components-button-secondary-accent-text: #FFFFFFCC; + --color-components-button-secondary-accent-text-disabled: #FFFFFF33; + --color-components-button-secondary-accent-bg: #FFFFFF0D; + --color-components-button-secondary-accent-bg-hover: #FFFFFF14; + --color-components-button-secondary-accent-bg-disabled: #FFFFFF08; + --color-components-button-secondary-accent-border: #FFFFFF14; + --color-components-button-secondary-accent-border-hover: #FFFFFF1F; + --color-components-button-secondary-accent-border-disabled: #FFFFFF0D; + + --color-components-checkbox-icon: #FFFFFFF2; + --color-components-checkbox-icon-disabled: #FFFFFF33; + --color-components-checkbox-bg: #296DFF; + --color-components-checkbox-bg-hover: #5289FF; + --color-components-checkbox-bg-disabled: #FFFFFF08; + --color-components-checkbox-border: #FFFFFF66; + --color-components-checkbox-border-hover: #FFFFFF99; + --color-components-checkbox-border-disabled: #FFFFFF03; + --color-components-checkbox-bg-unchecked: #FFFFFF08; + --color-components-checkbox-bg-unchecked-hover: #FFFFFF0D; + + --color-components-radio-border-checked: #296DFF; + --color-components-radio-border-checked-hover: #5289FF; + --color-components-radio-border-checked-disabled: #FFFFFF14; + --color-components-radio-bg-disabled: #FFFFFF08; + --color-components-radio-border: #FFFFFF66; + --color-components-radio-border-hover: #FFFFFF99; + --color-components-radio-border-disabled: #FFFFFF03; + --color-components-radio-bg: #FFFFFF00; + --color-components-radio-bg-hover: #FFFFFF0D; + + --color-components-toggle-knob: #F4F4F5; + --color-components-toggle-knob-disabled: #FFFFFF33; + --color-components-toggle-bg: #296DFF; + --color-components-toggle-bg-hover: #5289FF; + --color-components-toggle-bg-disabled: #FFFFFF14; + --color-components-toggle-bg-unchecked: #FFFFFF33; + --color-components-toggle-bg-unchecked-hover: #FFFFFF4D; + --color-components-toggle-bg-unchecked-disabled: #FFFFFF14; + --color-components-toggle-knob-hover: #FEFEFE; + + --color-components-card-bg: #222225; + --color-components-card-border: #FFFFFF08; + --color-components-card-bg-alt: #27272B; + + --color-components-menu-item-text: #C8CEDA99; + --color-components-menu-item-text-active: #FFFFFFF2; + --color-components-menu-item-text-hover: #C8CEDACC; + --color-components-menu-item-text-active-accent: #FFFFFFF2; + + --color-components-panel-bg: #222225; + --color-components-panel-bg-blur: #2C2C30F2; + --color-components-panel-border: #C8CEDA24; + --color-components-panel-border-subtle: #C8CEDA14; + --color-components-panel-gradient-2: #222225; + --color-components-panel-gradient-1: #27272B; + --color-components-panel-bg-alt: #222225; + --color-components-panel-on-panel-item-bg: #27272B; + --color-components-panel-on-panel-item-bg-hover: #3A3A40; + --color-components-panel-on-panel-item-bg-alt: #3A3A40; + + --color-components-main-nav-nav-button-text: #C8CEDA99; + --color-components-main-nav-nav-button-text-active: #F4F4F5; + --color-components-main-nav-nav-button-bg: #FFFFFF00; + --color-components-main-nav-nav-button-bg-active: #C8CEDA24; + --color-components-main-nav-nav-button-border: #FFFFFF14; + --color-components-main-nav-nav-button-bg-hover: #C8CEDA0A; + + --color-components-main-nav-nav-user-border: #FFFFFF0D; + + --color-components-silder-knob: #F4F4F5; + --color-components-silder-knob-hover: #FEFEFE; + --color-components-silder-knob-disabled: #FFFFFF33; + --color-components-silder-range: #296DFF; + --color-components-silder-track: #FFFFFF33; + --color-components-silder-knob-border-hover: #1018284D; + --color-components-silder-knob-border: #10182833; + + --color-components-segmented-control-item-active-bg: #FFFFFF14; + --color-components-segmented-control-item-active-border: #C8CEDA14; + --color-components-segmented-control-bg-normal: #18181BB2; + --color-components-segmented-control-item-active-accent-bg: #155AEF33; + --color-components-segmented-control-item-active-accent-border: #155AEF4D; + + --color-components-option-card-option-bg: #C8CEDA0A; + --color-components-option-card-option-selected-bg: #FFFFFF0D; + --color-components-option-card-option-selected-border: #5289FF; + --color-components-option-card-option-border: #C8CEDA33; + --color-components-option-card-option-bg-hover: #C8CEDA24; + --color-components-option-card-option-border-hover: #C8CEDA4D; + + --color-components-tab-active: #296DFF; + + --color-components-badge-white-to-dark: #18181BCC; + --color-components-badge-status-light-success-bg: #17B26A; + --color-components-badge-status-light-success-border-inner: #47CD89; + --color-components-badge-status-light-success-halo: #17B26A4D; + + --color-components-badge-status-light-border-outer: #222225; + --color-components-badge-status-light-high-light: #FFFFFF4D; + --color-components-badge-status-light-warning-bg: #F79009; + --color-components-badge-status-light-warning-border-inner: #FDB022; + --color-components-badge-status-light-warning-halo: #F790094D; + + --color-components-badge-status-light-error-bg: #F04438; + --color-components-badge-status-light-error-border-inner: #F97066; + --color-components-badge-status-light-error-halo: #F044384D; + + --color-components-badge-status-light-normal-bg: #0BA5EC; + --color-components-badge-status-light-normal-border-inner: #36BFFA; + --color-components-badge-status-light-normal-halo: #0BA5EC4D; + + --color-components-badge-status-light-disabled-bg: #676F83; + --color-components-badge-status-light-disabled-border-inner: #98A2B2; + --color-components-badge-status-light-disabled-halo: #C8CEDA14; + + --color-components-badge-bg-green-soft: #17B26A24; + --color-components-badge-bg-orange-soft: #F7900924; + --color-components-badge-bg-red-soft: #F0443824; + --color-components-badge-bg-blue-light-soft: #0BA5EC24; + --color-components-badge-bg-gray-soft: #C8CEDA14; + + --color-components-chart-line: #5289FF; + --color-components-chart-area-1: #155AEF33; + --color-components-chart-area-2: #155AEF0A; + --color-components-chart-current-1: #5289FF; + --color-components-chart-current-2: #155AEF4D; + --color-components-chart-bg: #18181BF2; + + --color-components-actionbar-bg: #18181BCC; + --color-components-actionbar-border: #C8CEDA14; + + --color-components-dropzone-bg-alt: #18181BCC; + --color-components-dropzone-bg: #18181B66; + --color-components-dropzone-bg-accent: #155AEF24; + --color-components-dropzone-border: #C8CEDA24; + --color-components-dropzone-border-alt: #C8CEDA33; + --color-components-dropzone-border-accent: #84ABFF; + + --color-components-progress-brand-progress: #5289FF; + --color-components-progress-brand-border: #5289FF; + --color-components-progress-brand-bg: #155AEF0A; + + --color-components-progress-white-progress: #FFFFFF; + --color-components-progress-white-border: #FFFFFFF2; + --color-components-progress-white-bg: #FFFFFF03; + + --color-components-progress-gray-progress: #98A2B2; + --color-components-progress-gray-border: #98A2B2; + --color-components-progress-gray-bg: #C8CEDA05; + + --color-components-chat-input-audio-bg: #155AEF33; + --color-components-chat-input-audio-wave: #C8CEDA24; + --color-components-chat-input-bg-mask-1: #18181B0A; + --color-components-chat-input-bg-mask-2: #18181B99; + --color-components-chat-input-border: #C8CEDA33; + + --color-text-primary: #FBFBFC; + --color-text-secondary: #D9D9DE; + --color-text-tertiary: #C8CEDA99; + --color-text-quaternary: #C8CEDA66; + --color-text-destructive: #F04438; + --color-text-success: #17B26A; + --color-text-warning: #F79009; + --color-text-destructive-secondary: #F04438; + --color-text-success-secondary: #47CD89; + --color-text-warning-secondary: #FDB022; + --color-text-accent: #5289FF; + --color-text-primary-on-surface: #FFFFFFF2; + --color-text-placeholder: #C8CEDA4D; + --color-text-disabled: #C8CEDA4D; + --color-text-accent-secondary: #84ABFF; + --color-text-accent-light-mode-only: #D9D9DE; + --color-text-text-selected: #155AEF4D; + --color-text-secondary-on-surface: #FFFFFFE5; + --color-text-logo-text: #E9E9EC; + --color-text-empty-state-icon: #C8CEDA4D; + + --color-background-body: #1D1D20; + --color-background-default-subtle: #222225; + --color-background-neurtral-subtle: #1D1D20; + --color-background-sidenav-bg: #18181B80; + --color-background-default: #222225; + --color-background-soft: #18181B40; + --color-background-gradient-bg-fill-chat-bg-1: #222225; + --color-background-gradient-bg-fill-chat-bg-2: #1D1D20; + --color-background-gradient-bg-fill-chat-bubble-bg-1: #C8CEDA14; + --color-background-gradient-bg-fill-chat-bubble-bg-2: #C8CEDA05; + + --color-background-gradient-mask-gray: #18181B14; + --color-background-gradient-mask-transparent: #00000000; + --color-background-gradient-mask-input-clear-2: #393A3E00; + --color-background-gradient-mask-input-clear-1: #393A3E; + --color-background-gradient-mask-transparent-dark: #00000000; + --color-background-gradient-mask-side-panel-2: #18181BE5; + --color-background-gradient-mask-side-panel-1: #18181B0A; + + --color-background-default-burn: #1D1D20; + --color-background-overlay-fullscreen: #27272AF7; + --color-background-default-lighter: #C8CEDA0A; + --color-background-section: #18181B66; + --color-background-interaction-from-bg-1: #18181B66; + --color-background-interaction-from-bg-2: #18181B24; + --color-background-section-burn: #18181B99; + --color-background-default-dodge: #3A3A40; + --color-background-overlay: #18181BCC; + --color-background-default-dimm: #27272B; + --color-background-default-hover: #27272B; + --color-background-overlay-alt: #18181B66; + --color-background-surface-white: #FFFFFFE5; + --color-background-overlay-destructive: #F044384D; + + --color-shadow-shadow-1: #0000000D; + --color-shadow-shadow-3: #0000001A; + --color-shadow-shadow-4: #0000001F; + --color-shadow-shadow-5: #00000029; + --color-shadow-shadow-6: #00000033; + --color-shadow-shadow-7: #0000003D; + --color-shadow-shadow-8: #00000047; + --color-shadow-shadow-9: #0000005C; + --color-shadow-shadow-2: #00000014; + --color-shadow-shadow-10: #00000066; + + --color-workflow-block-border: #FFFFFF14; + --color-workflow-block-panel-bg: #27272B; + --color-workflow-block-border: #FFFFFF14; + --color-workflow-block-parma-bg: #FFFFFF0D; + --color-workflow-block-nav-bg: #1D1D20; + --color-workflow-block-nav-border-right: #FFFFFF0D; + --color-workflow-block-bg: #27272B; + + --color-workflow-canvas-workflow-dot-color: #8585AD26; + --color-workflow-canvas-workflow-bg: #1D1D20; + + --color-workflow-link-line-active: #296DFF; + --color-workflow-link-line-normal: #676F83; + --color-workflow-link-line-handle: #296DFF; + + --color-workflow-minmap-bg: #27272B; + --color-workflow-minmap-block: #C8CEDA14; + + --color-workflow-display-success-bg: #17B26A33; + --color-workflow-display-success-border-1: #17B26AE5; + --color-workflow-display-success-border-2: #17B26ACC; + --color-workflow-display-success-vignette-color: #17B26A40; + --color-workflow-display-success-bg-line-pattern: #18181BCC; + + --color-workflow-display-glass-1: #FFFFFF03; + --color-workflow-display-glass-2: #FFFFFF08; + --color-workflow-display-vignette-dark: #00000066; + --color-workflow-display-highlight: #FFFFFF1F; + --color-workflow-display-outline: #18181BF2; + --color-workflow-display-error-bg: #F0443833; + --color-workflow-display-error-bg-line-pattern: #18181BCC; + --color-workflow-display-error-border-1: #F04438E5; + --color-workflow-display-error-border-2: #F04438CC; + --color-workflow-display-error-vignette-color: #F0443840; + + --color-workflow-display-warning-bg: #F7900933; + --color-workflow-display-warning-bg-line-pattern: #18181BCC; + --color-workflow-display-warning-border-1: #F79009E5; + --color-workflow-display-warning-border-2: #F79009CC; + --color-workflow-display-warning-vignette-color: #F7900940; + + --color-workflow-display-normal-bg: #0BA5EC33; + --color-workflow-display-normal-bg-line-pattern: #18181BCC; + --color-workflow-display-normal-border-1: #0BA5ECE5; + --color-workflow-display-normal-border-2: #0BA5ECCC; + --color-workflow-display-normal-vignette-color: #0BA5EC40; + + --color-workflow-display-disabled-bg: #C8CEDA33; + --color-workflow-display-disabled-bg-line-pattern: #18181BCC; + --color-workflow-display-disabled-border-1: #C8CEDA99; + --color-workflow-display-disabled-border-2: #C8CEDA40; + --color-workflow-display-disabled-vignette-color: #C8CEDA40; + --color-workflow-display-disabled-outline: #18181BF2; + + --color-workflow-workflow-progress-bg-1: #18181B40; + --color-workflow-workflow-progress-bg-2: #18181B0A; + + --color-divider-subtle: #C8CEDA14; + --color-divider-regular: #C8CEDA24; + --color-divider-deep: #C8CEDA33; + --color-divider-burn: #18181BF2; + --color-divider-intense: #C8CEDA66; + --color-divider-soild: #3A3A40; + --color-divider-soild-alt: #747481; + + --color-state-base-hover: #C8CEDA14; + --color-state-base-active: #C8CEDA33; + --color-state-base-hover-alt: #C8CEDA24; + --color-state-base-handle: #C8CEDA4D; + --color-state-base-handle-hover: #C8CEDA80; + + --color-state-accent-hover: #155AEF24; + --color-state-accent-active: #155AEF24; + --color-state-accent-hover-alt: #155AEF40; + --color-state-accent-soild: #5289FF; + --color-state-accent-active-alt: #155AEF33; + + --color-state-destructive-hover: #F0443824; + --color-state-destructive-hover-alt: #F0443840; + --color-state-destructive-active: #F044384D; + --color-state-destructive-soild: #F97066; + --color-state-destructive-border: #F97066; + + --color-state-success-hover: #17B26A24; + --color-state-success-hover-alt: #17B26A40; + --color-state-success-active: #17B26A4D; + --color-state-success-soild: #47CD89; + + --color-state-warning-hover: #F7900924; + --color-state-warning-hover-alt: #F7900940; + --color-state-warning-active: #F790094D; + --color-state-warning-soild: #F79009; + + --color-effects-highlight: #C8CEDA14; + --color-effects-highlight-lightmode-off: #C8CEDA14; + --color-effects-image-frame: #FFFFFF; + + --color-util-colors-orange-dark-orange-dark-50: #57130A; + --color-util-colors-orange-dark-orange-dark-100: #771A0D; + --color-util-colors-orange-dark-orange-dark-200: #97180C; + --color-util-colors-orange-dark-orange-dark-300: #BC1B06; + --color-util-colors-orange-dark-orange-dark-400: #E62E05; + --color-util-colors-orange-dark-orange-dark-500: #FF4405; + --color-util-colors-orange-dark-orange-dark-600: #FF692E; + --color-util-colors-orange-dark-orange-dark-700: #FF9C66; + + --color-util-colors-orange-orange-50: #511C10; + --color-util-colors-orange-orange-100: #772917; + --color-util-colors-orange-orange-200: #932F19; + --color-util-colors-orange-orange-300: #B93815; + --color-util-colors-orange-orange-400: #E04F16; + --color-util-colors-orange-orange-500: #EF6820; + --color-util-colors-orange-orange-600: #F38744; + --color-util-colors-orange-orange-700: #F7B27A; + + --color-util-colors-pink-pink-50: #4E0D30; + --color-util-colors-pink-pink-100: #851651; + --color-util-colors-pink-pink-200: #9E165F; + --color-util-colors-pink-pink-300: #C11574; + --color-util-colors-pink-pink-400: #DD2590; + --color-util-colors-pink-pink-500: #EE46BC; + --color-util-colors-pink-pink-600: #F670C7; + --color-util-colors-pink-pink-700: #FAA7E0; + + --color-util-colors-fuchsia-fuchsia-50: #47104C; + --color-util-colors-fuchsia-fuchsia-100: #6F1877; + --color-util-colors-fuchsia-fuchsia-200: #821890; + --color-util-colors-fuchsia-fuchsia-300: #9F1AB1; + --color-util-colors-fuchsia-fuchsia-400: #BA24D5; + --color-util-colors-fuchsia-fuchsia-500: #D444F1; + --color-util-colors-fuchsia-fuchsia-600: #E478FA; + --color-util-colors-fuchsia-fuchsia-700: #EEAAFD; + + --color-util-colors-purple-purple-50: #27115F; + --color-util-colors-purple-purple-100: #3E1C96; + --color-util-colors-purple-purple-200: #4A1FB8; + --color-util-colors-purple-purple-300: #5925DC; + --color-util-colors-purple-purple-400: #6938EF; + --color-util-colors-purple-purple-500: #7A5AF8; + --color-util-colors-purple-purple-600: #9B8AFB; + --color-util-colors-purple-purple-700: #BDB4FE; + + --color-util-colors-indigo-indigo-50: #1F235B; + --color-util-colors-indigo-indigo-100: #2D3282; + --color-util-colors-indigo-indigo-200: #2D31A6; + --color-util-colors-indigo-indigo-300: #3538CD; + --color-util-colors-indigo-indigo-400: #444CE7; + --color-util-colors-indigo-indigo-500: #6172F3; + --color-util-colors-indigo-indigo-600: #8098F9; + --color-util-colors-indigo-indigo-700: #A4BCFD; + + --color-util-colors-blue-blue-50: #102A56; + --color-util-colors-blue-blue-100: #194185; + --color-util-colors-blue-blue-200: #1849A9; + --color-util-colors-blue-blue-300: #175CD3; + --color-util-colors-blue-blue-400: #1570EF; + --color-util-colors-blue-blue-500: #2E90FA; + --color-util-colors-blue-blue-600: #53B1FD; + --color-util-colors-blue-blue-700: #84CAFF; + + --color-util-colors-blue-light-blue-light-50: #062C41; + --color-util-colors-blue-light-blue-light-100: #0B4A6F; + --color-util-colors-blue-light-blue-light-200: #065986; + --color-util-colors-blue-light-blue-light-300: #026AA2; + --color-util-colors-blue-light-blue-light-400: #0086C9; + --color-util-colors-blue-light-blue-light-500: #0BA5EC; + --color-util-colors-blue-light-blue-light-600: #36BFFA; + --color-util-colors-blue-light-blue-light-700: #7CD4FD; + + --color-util-colors-gray-blue-gray-blue-50: #0D0F1C; + --color-util-colors-gray-blue-gray-blue-100: #101323; + --color-util-colors-gray-blue-gray-blue-200: #293056; + --color-util-colors-gray-blue-gray-blue-300: #363F72; + --color-util-colors-gray-blue-gray-blue-400: #3E4784; + --color-util-colors-gray-blue-gray-blue-500: #4E5BA6; + --color-util-colors-gray-blue-gray-blue-600: #717BBC; + --color-util-colors-gray-blue-gray-blue-700: #B3B8DB; + + --color-util-colors-blue-brand-blue-brand-50: #002066; + --color-util-colors-blue-brand-blue-brand-100: #00329E; + --color-util-colors-blue-brand-blue-brand-200: #003DC1; + --color-util-colors-blue-brand-blue-brand-300: #004AEB; + --color-util-colors-blue-brand-blue-brand-400: #155AEF; + --color-util-colors-blue-brand-blue-brand-500: #296DFF; + --color-util-colors-blue-brand-blue-brand-600: #5289FF; + --color-util-colors-blue-brand-blue-brand-700: #84ABFF; + + --color-util-colors-red-red-50: #55160C; + --color-util-colors-red-red-100: #7A271A; + --color-util-colors-red-red-200: #912018; + --color-util-colors-red-red-300: #B42318; + --color-util-colors-red-red-400: #D92D20; + --color-util-colors-red-red-500: #F04438; + --color-util-colors-red-red-600: #F97066; + --color-util-colors-red-red-700: #FDA29B; + + --color-util-colors-green-green-50: #053321; + --color-util-colors-green-green-100: #074D31; + --color-util-colors-green-green-200: #085D3A; + --color-util-colors-green-green-300: #067647; + --color-util-colors-green-green-400: #079455; + --color-util-colors-green-green-500: #17B26A; + --color-util-colors-green-green-600: #47CD89; + --color-util-colors-green-green-700: #75E0A7; + + --color-util-colors-warning-warning-50: #4E1D09; + --color-util-colors-warning-warning-100: #7A2E0E; + --color-util-colors-warning-warning-200: #93370D; + --color-util-colors-warning-warning-300: #B54708; + --color-util-colors-warning-warning-400: #DC6803; + --color-util-colors-warning-warning-500: #F79009; + --color-util-colors-warning-warning-600: #FDB022; + --color-util-colors-warning-warning-700: #FEC84B; + + --color-util-colors-yellow-yellow-50: #542C0D; + --color-util-colors-yellow-yellow-100: #713B12; + --color-util-colors-yellow-yellow-200: #854A0E; + --color-util-colors-yellow-yellow-300: #A15C07; + --color-util-colors-yellow-yellow-400: #CA8504; + --color-util-colors-yellow-yellow-500: #EAAA08; + --color-util-colors-yellow-yellow-600: #FAC515; + --color-util-colors-yellow-yellow-700: #FDE272; + + --color-util-colors-teal-teal-50: #0A2926; + --color-util-colors-teal-teal-100: #134E48; + --color-util-colors-teal-teal-200: #125D56; + --color-util-colors-teal-teal-300: #107569; + --color-util-colors-teal-teal-400: #0E9384; + --color-util-colors-teal-teal-500: #15B79E; + --color-util-colors-teal-teal-600: #2ED3B7; + --color-util-colors-teal-teal-700: #5FE9D0; + + --color-util-colors-cyan-cyan-50: #0D2D3A; + --color-util-colors-cyan-cyan-100: #164C63; + --color-util-colors-cyan-cyan-200: #155B75; + --color-util-colors-cyan-cyan-300: #0E7090; + --color-util-colors-cyan-cyan-400: #088AB2; + --color-util-colors-cyan-cyan-500: #06AED4; + --color-util-colors-cyan-cyan-600: #22CCEE; + --color-util-colors-cyan-cyan-700: #67E3F9; + + --color-util-colors-violet-violet-50: #2E125E; + --color-util-colors-violet-violet-100: #491C96; + --color-util-colors-violet-violet-200: #5720B7; + --color-util-colors-violet-violet-300: #6927DA; + --color-util-colors-violet-violet-400: #7839EE; + --color-util-colors-violet-violet-500: #875BF7; + --color-util-colors-violet-violet-600: #A48AFB; + --color-util-colors-violet-violet-700: #C3B5FD; + + --color-util-colors-gray-gray-50: #0C111C; + --color-util-colors-gray-gray-100: #101828; + --color-util-colors-gray-gray-200: #18222F; + --color-util-colors-gray-gray-300: #354052; + --color-util-colors-gray-gray-400: #495464; + --color-util-colors-gray-gray-500: #676F83; + --color-util-colors-gray-gray-600: #98A2B2; + --color-util-colors-gray-gray-700: #D0D5DC; + + --color-third-party-LangChain: #FFFFFF; + --color-third-party-Langfuse: #FFFFFF; +} \ No newline at end of file diff --git a/web/themes/light.css b/web/themes/light.css new file mode 100644 index 0000000000..5dd39abf88 --- /dev/null +++ b/web/themes/light.css @@ -0,0 +1,562 @@ +/* Attention: Generate by code. Don't update by hand!!! */ +html[data-theme="light"] { + --color-components-input-bg-normal: #C8CEDA40; + --color-components-input-text-placeholder: #98A2B2; + --color-components-input-bg-hover: #C8CEDA24; + --color-components-input-bg-active: #F9FAFB; + --color-components-input-border-active: #D0D5DC; + --color-components-input-border-destructive: #FDA29B; + --color-components-input-text-filled: #101828; + --color-components-input-bg-destructive: #FFFFFF; + --color-components-input-bg-disabled: #C8CEDA24; + --color-components-input-text-disabled: #D0D5DC; + --color-components-input-text-filled-disabled: #1018284D; + --color-components-input-border-hover: #D0D5DC; + --color-components-input-border-active-prompt-1: #0BA5EC; + --color-components-input-border-active-prompt-2: #155AEF; + + --color-components-kbd-bg-gray: #1018280A; + --color-components-kbd-bg-white: #FFFFFF1F; + + --color-components-tooltip-bg: #FFFFFFF2; + + --color-components-button-primary-text: #FFFFFF; + --color-components-button-primary-bg: #155AEF; + --color-components-button-primary-border: #1018280A; + --color-components-button-primary-bg-hover: #004AEB; + --color-components-button-primary-border-hover: #10182814; + --color-components-button-primary-bg-disabled: #155AEF24; + --color-components-button-primary-border-disabled: #FFFFFF00; + --color-components-button-primary-text-disabled: #FFFFFF99; + + --color-components-button-secondary-text: #354052; + --color-components-button-secondary-text-disabled: #10182840; + --color-components-button-secondary-bg: #FFFFFF; + --color-components-button-secondary-bg-hover: #F9FAFB; + --color-components-button-secondary-bg-disabled: #F9FAFB; + --color-components-button-secondary-border: #10182824; + --color-components-button-secondary-border-hover: #10182833; + --color-components-button-secondary-border-disabled: #1018280A; + + --color-components-button-tertiary-text: #354052; + --color-components-button-tertiary-text-disabled: #10182840; + --color-components-button-tertiary-bg: #F2F4F7; + --color-components-button-tertiary-bg-hover: #E9EBF0; + --color-components-button-tertiary-bg-disabled: #F9FAFB; + + --color-components-button-ghost-text: #354052; + --color-components-button-ghost-text-disabled: #10182840; + --color-components-button-ghost-bg-hover: #C8CEDA33; + + --color-components-button-destructive-primary-text: #FFFFFF; + --color-components-button-destructive-primary-text-disabled: #FFFFFF99; + --color-components-button-destructive-primary-bg: #D92D20; + --color-components-button-destructive-primary-bg-hover: #B42318; + --color-components-button-destructive-primary-bg-disabled: #FEE4E2; + --color-components-button-destructive-primary-border: #18181B0A; + --color-components-button-destructive-primary-border-hover: #18181B14; + --color-components-button-destructive-primary-border-disabled: #FFFFFF00; + + --color-components-button-destructive-secondary-text: #D92D20; + --color-components-button-destructive-secondary-text-disabled: #F0443833; + --color-components-button-destructive-secondary-bg: #FFFFFF; + --color-components-button-destructive-secondary-bg-hover: #FEF3F2; + --color-components-button-destructive-secondary-bg-disabled: #FEF3F2; + --color-components-button-destructive-secondary-border: #18181B14; + --color-components-button-destructive-secondary-border-hover: #F0443840; + --color-components-button-destructive-secondary-border-disabled: #F044380A; + + --color-components-button-destructive-tertiary-text: #D92D20; + --color-components-button-destructive-tertiary-text-disabled: #F0443833; + --color-components-button-destructive-tertiary-bg: #FEE4E2; + --color-components-button-destructive-tertiary-bg-hover: #FECDCA; + --color-components-button-destructive-tertiary-bg-disabled: #F044380A; + + --color-components-button-destructive-ghost-text: #D92D20; + --color-components-button-destructive-ghost-text-disabled: #F0443833; + --color-components-button-destructive-ghost-bg-hover: #FEE4E2; + + --color-components-button-secondary-accent-text: #155AEF; + --color-components-button-secondary-accent-text-disabled: #B2CAFF; + --color-components-button-secondary-accent-bg: #FFFFFF; + --color-components-button-secondary-accent-bg-hover: #F2F4F7; + --color-components-button-secondary-accent-bg-disabled: #F9FAFB; + --color-components-button-secondary-accent-border: #10182824; + --color-components-button-secondary-accent-border-hover: #10182824; + --color-components-button-secondary-accent-border-disabled: #1018280A; + + --color-components-checkbox-icon: #FFFFFF; + --color-components-checkbox-icon-disabled: #D0D5DC; + --color-components-checkbox-bg: #155AEF; + --color-components-checkbox-bg-hover: #004AEB; + --color-components-checkbox-bg-disabled: #F2F4F7; + --color-components-checkbox-border: #D0D5DC; + --color-components-checkbox-border-hover: #98A2B2; + --color-components-checkbox-border-disabled: #18181B0A; + --color-components-checkbox-bg-unchecked: #FFFFFF; + --color-components-checkbox-bg-unchecked-hover: #FFFFFF; + + --color-components-radio-border-checked: #155AEF; + --color-components-radio-border-checked-hover: #004AEB; + --color-components-radio-border-checked-disabled: #F2F4F7; + --color-components-radio-bg-disabled: #FFFFFF00; + --color-components-radio-border: #D0D5DC; + --color-components-radio-border-hover: #98A2B2; + --color-components-radio-border-disabled: #18181B0A; + --color-components-radio-bg: #FFFFFF00; + --color-components-radio-bg-hover: #FFFFFF00; + + --color-components-toggle-knob: #FFFFFF; + --color-components-toggle-knob-disabled: #FFFFFFF2; + --color-components-toggle-bg: #155AEF; + --color-components-toggle-bg-hover: #004AEB; + --color-components-toggle-bg-disabled: #D1E0FF; + --color-components-toggle-bg-unchecked: #E9EBF0; + --color-components-toggle-bg-unchecked-hover: #D0D5DC; + --color-components-toggle-bg-unchecked-disabled: #F2F4F7; + --color-components-toggle-knob-hover: #FFFFFF; + + --color-components-card-bg: #FCFCFD; + --color-components-card-border: #FFFFFF; + --color-components-card-bg-alt: #FFFFFF; + + --color-components-menu-item-text: #495464; + --color-components-menu-item-text-active: #18222F; + --color-components-menu-item-text-hover: #354052; + --color-components-menu-item-text-active-accent: #18222F; + + --color-components-panel-bg: #FFFFFF; + --color-components-panel-bg-blur: #FFFFFFF2; + --color-components-panel-border: #10182814; + --color-components-panel-border-subtle: #10182814; + --color-components-panel-gradient-2: #F9FAFB; + --color-components-panel-gradient-1: #FFFFFF; + --color-components-panel-bg-alt: #F9FAFB; + --color-components-panel-on-panel-item-bg: #FFFFFF; + --color-components-panel-on-panel-item-bg-hover: #F9FAFB; + --color-components-panel-on-panel-item-bg-alt: #F9FAFB; + + --color-components-main-nav-nav-button-text: #495464; + --color-components-main-nav-nav-button-text-active: #155AEF; + --color-components-main-nav-nav-button-bg: #FFFFFF00; + --color-components-main-nav-nav-button-bg-active: #FCFCFD; + --color-components-main-nav-nav-button-border: #FFFFFFF2; + --color-components-main-nav-nav-button-bg-hover: #1018280A; + + --color-components-main-nav-nav-user-border: #FFFFFF; + + --color-components-silder-knob: #FFFFFF; + --color-components-silder-knob-hover: #FFFFFF; + --color-components-silder-knob-disabled: #FFFFFFF2; + --color-components-silder-range: #296DFF; + --color-components-silder-track: #E9EBF0; + --color-components-silder-knob-border-hover: #10182833; + --color-components-silder-knob-border: #10182824; + + --color-components-segmented-control-item-active-bg: #FFFFFF; + --color-components-segmented-control-item-active-border: #FFFFFF; + --color-components-segmented-control-bg-normal: #C8CEDA33; + --color-components-segmented-control-item-active-accent-bg: #FFFFFF; + --color-components-segmented-control-item-active-accent-border: #FFFFFF; + + --color-components-option-card-option-bg: #F9FAFB; + --color-components-option-card-option-selected-bg: #FFFFFF; + --color-components-option-card-option-selected-border: #296DFF; + --color-components-option-card-option-border: #F2F4F7; + --color-components-option-card-option-bg-hover: #FFFFFF; + --color-components-option-card-option-border-hover: #D0D5DC; + + --color-components-tab-active: #155AEF; + + --color-components-badge-white-to-dark: #FFFFFF; + --color-components-badge-status-light-success-bg: #47CD89; + --color-components-badge-status-light-success-border-inner: #17B26A; + --color-components-badge-status-light-success-halo: #17B26A40; + + --color-components-badge-status-light-border-outer: #FFFFFF; + --color-components-badge-status-light-high-light: #FFFFFF4D; + --color-components-badge-status-light-warning-bg: #FDB022; + --color-components-badge-status-light-warning-border-inner: #F79009; + --color-components-badge-status-light-warning-halo: #F7900940; + + --color-components-badge-status-light-error-bg: #F97066; + --color-components-badge-status-light-error-border-inner: #F04438; + --color-components-badge-status-light-error-halo: #F0443840; + + --color-components-badge-status-light-normal-bg: #36BFFA; + --color-components-badge-status-light-normal-border-inner: #0BA5EC; + --color-components-badge-status-light-normal-halo: #0BA5EC40; + + --color-components-badge-status-light-disabled-bg: #98A2B2; + --color-components-badge-status-light-disabled-border-inner: #676F83; + --color-components-badge-status-light-disabled-halo: #1018280A; + + --color-components-badge-bg-green-soft: #17B26A14; + --color-components-badge-bg-orange-soft: #F7900914; + --color-components-badge-bg-red-soft: #F0443814; + --color-components-badge-bg-blue-light-soft: #0BA5EC14; + --color-components-badge-bg-gray-soft: #1018280A; + + --color-components-chart-line: #296DFF; + --color-components-chart-area-1: #155AEF24; + --color-components-chart-area-2: #155AEF0A; + --color-components-chart-current-1: #155AEF; + --color-components-chart-current-2: #D1E0FF; + --color-components-chart-bg: #FFFFFF; + + --color-components-actionbar-bg: #FFFFFFF2; + --color-components-actionbar-border: #1018280A; + + --color-components-dropzone-bg-alt: #F2F4F7; + --color-components-dropzone-bg: #F9FAFB; + --color-components-dropzone-bg-accent: #EFF4FF; + --color-components-dropzone-border: #10182814; + --color-components-dropzone-border-alt: #10182833; + --color-components-dropzone-border-accent: #84ABFF; + + --color-components-progress-brand-progress: #296DFF; + --color-components-progress-brand-border: #296DFF; + --color-components-progress-brand-bg: #155AEF0A; + + --color-components-progress-white-progress: #FFFFFF; + --color-components-progress-white-border: #FFFFFFF2; + --color-components-progress-white-bg: #FFFFFF03; + + --color-components-progress-gray-progress: #98A2B2; + --color-components-progress-gray-border: #98A2B2; + --color-components-progress-gray-bg: #C8CEDA05; + + --color-components-chat-input-audio-bg: #EFF4FF; + --color-components-chat-input-audio-wave: #155AEF24; + --color-components-chat-input-bg-mask-1: #FFFFFF03; + --color-components-chat-input-bg-mask-2: #F2F4F7; + --color-components-chat-input-border: #FFFFFF; + + --color-text-primary: #101828; + --color-text-secondary: #354052; + --color-text-tertiary: #676F83; + --color-text-quaternary: #1018284D; + --color-text-destructive: #D92D20; + --color-text-success: #079455; + --color-text-warning: #DC6803; + --color-text-destructive-secondary: #F04438; + --color-text-success-secondary: #17B26A; + --color-text-warning-secondary: #F79009; + --color-text-accent: #155AEF; + --color-text-primary-on-surface: #FFFFFF; + --color-text-placeholder: #98A2B2; + --color-text-disabled: #D0D5DC; + --color-text-accent-secondary: #296DFF; + --color-text-accent-light-mode-only: #155AEF; + --color-text-text-selected: #155AEF24; + --color-text-secondary-on-surface: #FFFFFFE5; + --color-text-logo-text: #18222F; + --color-text-empty-state-icon: #D0D5DC; + + --color-background-body: #F2F4F7; + --color-background-default-subtle: #FCFCFD; + --color-background-neurtral-subtle: #F9FAFB; + --color-background-sidenav-bg: #FCFCFD; + --color-background-default: #FFFFFF; + --color-background-soft: #F9FAFB; + --color-background-gradient-bg-fill-chat-bg-1: #F9FAFB; + --color-background-gradient-bg-fill-chat-bg-2: #F2F4F7; + --color-background-gradient-bg-fill-chat-bubble-bg-1: #FFFFFF; + --color-background-gradient-bg-fill-chat-bubble-bg-2: #FFFFFF99; + + --color-background-gradient-mask-gray: #C8CEDA33; + --color-background-gradient-mask-transparent: #FFFFFF00; + --color-background-gradient-mask-input-clear-2: #E9EBF000; + --color-background-gradient-mask-input-clear-1: #E9EBF0; + --color-background-gradient-mask-transparent-dark: #00000000; + --color-background-gradient-mask-side-panel-2: #1018284D; + --color-background-gradient-mask-side-panel-1: #10182805; + + --color-background-default-burn: #E9EBF0; + --color-background-overlay-fullscreen: #F9FAFBF2; + --color-background-default-lighter: #FFFFFF80; + --color-background-section: #F9FAFB; + --color-background-interaction-from-bg-1: #C8CEDA33; + --color-background-interaction-from-bg-2: #C8CEDA24; + --color-background-section-burn: #F2F4F7; + --color-background-default-dodge: #FFFFFF; + --color-background-overlay: #10182899; + --color-background-default-dimm: #E9EBF0; + --color-background-default-hover: #F9FAFB; + --color-background-overlay-alt: #10182866; + --color-background-surface-white: #FFFFFFF2; + --color-background-overlay-destructive: #F044384D; + + --color-shadow-shadow-1: #09090B08; + --color-shadow-shadow-3: #09090B0D; + --color-shadow-shadow-4: #09090B0F; + --color-shadow-shadow-5: #09090B14; + --color-shadow-shadow-6: #09090B1A; + --color-shadow-shadow-7: #09090B1F; + --color-shadow-shadow-8: #09090B24; + --color-shadow-shadow-9: #09090B2E; + --color-shadow-shadow-2: #09090B0A; + --color-shadow-shadow-10: #09090B0D; + + --color-workflow-block-border: #18181B14; + --color-workflow-block-panel-bg: #FFFFFF; + --color-workflow-block-border: #FFFFFF; + --color-workflow-block-parma-bg: #F2F4F7; + --color-workflow-block-nav-bg: #FFFFFF; + --color-workflow-block-nav-border-right: #FFFFFF; + --color-workflow-block-bg: #FCFCFD; + + --color-workflow-canvas-workflow-dot-color: #8585AD26; + --color-workflow-canvas-workflow-bg: #F2F4F7; + + --color-workflow-link-line-active: #296DFF; + --color-workflow-link-line-normal: #D0D5DC; + --color-workflow-link-line-handle: #296DFF; + + --color-workflow-minmap-bg: #E9EBF0; + --color-workflow-minmap-block: #C8CEDA4D; + + --color-workflow-display-success-bg: #ECFDF3; + --color-workflow-display-success-border-1: #17B26ACC; + --color-workflow-display-success-border-2: #17B26A80; + --color-workflow-display-success-vignette-color: #17B26A33; + --color-workflow-display-success-bg-line-pattern: #17B26A4D; + + --color-workflow-display-glass-1: #FFFFFF1F; + --color-workflow-display-glass-2: #FFFFFF80; + --color-workflow-display-vignette-dark: #0000001F; + --color-workflow-display-highlight: #FFFFFF80; + --color-workflow-display-outline: #0000000D; + --color-workflow-display-error-bg: #FEF3F2; + --color-workflow-display-error-bg-line-pattern: #F044384D; + --color-workflow-display-error-border-1: #F04438CC; + --color-workflow-display-error-border-2: #F0443880; + --color-workflow-display-error-vignette-color: #F0443833; + + --color-workflow-display-warning-bg: #FFFAEB; + --color-workflow-display-warning-bg-line-pattern: #F790094D; + --color-workflow-display-warning-border-1: #F79009CC; + --color-workflow-display-warning-border-2: #F7900980; + --color-workflow-display-warning-vignette-color: #F7900933; + + --color-workflow-display-normal-bg: #F0F9FF; + --color-workflow-display-normal-bg-line-pattern: #0BA5EC4D; + --color-workflow-display-normal-border-1: #0BA5ECCC; + --color-workflow-display-normal-border-2: #0BA5EC80; + --color-workflow-display-normal-vignette-color: #0BA5EC33; + + --color-workflow-display-disabled-bg: #F9FAFB; + --color-workflow-display-disabled-bg-line-pattern: #C8CEDA4D; + --color-workflow-display-disabled-border-1: #C8CEDA99; + --color-workflow-display-disabled-border-2: #C8CEDA66; + --color-workflow-display-disabled-vignette-color: #C8CEDA66; + --color-workflow-display-disabled-outline: #00000000; + + --color-workflow-workflow-progress-bg-1: #C8CEDA33; + --color-workflow-workflow-progress-bg-2: #C8CEDA0A; + + --color-divider-subtle: #1018280A; + --color-divider-regular: #10182814; + --color-divider-deep: #10182824; + --color-divider-burn: #1018280A; + --color-divider-intense: #1018284D; + --color-divider-soild: #D0D5DC; + --color-divider-soild-alt: #98A2B2; + + --color-state-base-hover: #C8CEDA33; + --color-state-base-active: #C8CEDA66; + --color-state-base-hover-alt: #C8CEDA66; + --color-state-base-handle: #10182833; + --color-state-base-handle-hover: #1018284D; + + --color-state-accent-hover: #EFF4FF; + --color-state-accent-active: #155AEF14; + --color-state-accent-hover-alt: #D1E0FF; + --color-state-accent-soild: #296DFF; + --color-state-accent-active-alt: #155AEF24; + + --color-state-destructive-hover: #FEF3F2; + --color-state-destructive-hover-alt: #FEE4E2; + --color-state-destructive-active: #FECDCA; + --color-state-destructive-soild: #F04438; + --color-state-destructive-border: #FDA29B; + + --color-state-success-hover: #ECFDF3; + --color-state-success-hover-alt: #DCFAE6; + --color-state-success-active: #ABEFC6; + --color-state-success-soild: #17B26A; + + --color-state-warning-hover: #FFFAEB; + --color-state-warning-hover-alt: #FEF0C7; + --color-state-warning-active: #FEDF89; + --color-state-warning-soild: #F79009; + + --color-effects-highlight: #FFFFFF; + --color-effects-highlight-lightmode-off: #FFFFFF00; + --color-effects-image-frame: #FFFFFF; + + --color-util-colors-orange-dark-orange-dark-50: #FFF4ED; + --color-util-colors-orange-dark-orange-dark-100: #FFE6D5; + --color-util-colors-orange-dark-orange-dark-200: #FFD6AE; + --color-util-colors-orange-dark-orange-dark-300: #FF9C66; + --color-util-colors-orange-dark-orange-dark-400: #FF692E; + --color-util-colors-orange-dark-orange-dark-500: #FF4405; + --color-util-colors-orange-dark-orange-dark-600: #E62E05; + --color-util-colors-orange-dark-orange-dark-700: #BC1B06; + + --color-util-colors-orange-orange-50: #FEF6EE; + --color-util-colors-orange-orange-100: #FDEAD7; + --color-util-colors-orange-orange-200: #F9DBAF; + --color-util-colors-orange-orange-300: #F7B27A; + --color-util-colors-orange-orange-400: #F38744; + --color-util-colors-orange-orange-500: #EF6820; + --color-util-colors-orange-orange-600: #E04F16; + --color-util-colors-orange-orange-700: #B93815; + + --color-util-colors-pink-pink-50: #FDF2FA; + --color-util-colors-pink-pink-100: #FCE7F6; + --color-util-colors-pink-pink-200: #FCCEEE; + --color-util-colors-pink-pink-300: #FAA7E0; + --color-util-colors-pink-pink-400: #F670C7; + --color-util-colors-pink-pink-500: #EE46BC; + --color-util-colors-pink-pink-600: #DD2590; + --color-util-colors-pink-pink-700: #C11574; + + --color-util-colors-fuchsia-fuchsia-50: #FDF4FF; + --color-util-colors-fuchsia-fuchsia-100: #FBE8FF; + --color-util-colors-fuchsia-fuchsia-200: #F6D0FE; + --color-util-colors-fuchsia-fuchsia-300: #EEAAFD; + --color-util-colors-fuchsia-fuchsia-400: #E478FA; + --color-util-colors-fuchsia-fuchsia-500: #D444F1; + --color-util-colors-fuchsia-fuchsia-600: #BA24D5; + --color-util-colors-fuchsia-fuchsia-700: #9F1AB1; + + --color-util-colors-purple-purple-50: #F4F3FF; + --color-util-colors-purple-purple-100: #EBE9FE; + --color-util-colors-purple-purple-200: #D9D6FE; + --color-util-colors-purple-purple-300: #BDB4FE; + --color-util-colors-purple-purple-400: #9B8AFB; + --color-util-colors-purple-purple-500: #7A5AF8; + --color-util-colors-purple-purple-600: #6938EF; + --color-util-colors-purple-purple-700: #5925DC; + + --color-util-colors-indigo-indigo-50: #EEF4FF; + --color-util-colors-indigo-indigo-100: #E0EAFF; + --color-util-colors-indigo-indigo-200: #C7D7FE; + --color-util-colors-indigo-indigo-300: #A4BCFD; + --color-util-colors-indigo-indigo-400: #8098F9; + --color-util-colors-indigo-indigo-500: #6172F3; + --color-util-colors-indigo-indigo-600: #444CE7; + --color-util-colors-indigo-indigo-700: #3538CD; + + --color-util-colors-blue-blue-50: #EFF8FF; + --color-util-colors-blue-blue-100: #D1E9FF; + --color-util-colors-blue-blue-200: #B2DDFF; + --color-util-colors-blue-blue-300: #84CAFF; + --color-util-colors-blue-blue-400: #53B1FD; + --color-util-colors-blue-blue-500: #2E90FA; + --color-util-colors-blue-blue-600: #1570EF; + --color-util-colors-blue-blue-700: #175CD3; + + --color-util-colors-blue-light-blue-light-50: #F0F9FF; + --color-util-colors-blue-light-blue-light-100: #E0F2FE; + --color-util-colors-blue-light-blue-light-200: #B9E6FE; + --color-util-colors-blue-light-blue-light-300: #7CD4FD; + --color-util-colors-blue-light-blue-light-400: #36BFFA; + --color-util-colors-blue-light-blue-light-500: #0BA5EC; + --color-util-colors-blue-light-blue-light-600: #0086C9; + --color-util-colors-blue-light-blue-light-700: #026AA2; + + --color-util-colors-gray-blue-gray-blue-50: #F8F9FC; + --color-util-colors-gray-blue-gray-blue-100: #EAECF5; + --color-util-colors-gray-blue-gray-blue-200: #D5D9EB; + --color-util-colors-gray-blue-gray-blue-300: #B3B8DB; + --color-util-colors-gray-blue-gray-blue-400: #717BBC; + --color-util-colors-gray-blue-gray-blue-500: #4E5BA6; + --color-util-colors-gray-blue-gray-blue-600: #3E4784; + --color-util-colors-gray-blue-gray-blue-700: #363F72; + + --color-util-colors-blue-brand-blue-brand-50: #F5F7FF; + --color-util-colors-blue-brand-blue-brand-100: #D1E0FF; + --color-util-colors-blue-brand-blue-brand-200: #B2CAFF; + --color-util-colors-blue-brand-blue-brand-300: #84ABFF; + --color-util-colors-blue-brand-blue-brand-400: #5289FF; + --color-util-colors-blue-brand-blue-brand-500: #296DFF; + --color-util-colors-blue-brand-blue-brand-600: #155AEF; + --color-util-colors-blue-brand-blue-brand-700: #004AEB; + + --color-util-colors-red-red-50: #FEF3F2; + --color-util-colors-red-red-100: #FEE4E2; + --color-util-colors-red-red-200: #FECDCA; + --color-util-colors-red-red-300: #FDA29B; + --color-util-colors-red-red-400: #F97066; + --color-util-colors-red-red-500: #F04438; + --color-util-colors-red-red-600: #D92D20; + --color-util-colors-red-red-700: #B42318; + + --color-util-colors-green-green-50: #ECFDF3; + --color-util-colors-green-green-100: #DCFAE6; + --color-util-colors-green-green-200: #ABEFC6; + --color-util-colors-green-green-300: #75E0A7; + --color-util-colors-green-green-400: #47CD89; + --color-util-colors-green-green-500: #17B26A; + --color-util-colors-green-green-600: #079455; + --color-util-colors-green-green-700: #067647; + + --color-util-colors-warning-warning-50: #FFFAEB; + --color-util-colors-warning-warning-100: #FEF0C7; + --color-util-colors-warning-warning-200: #FEDF89; + --color-util-colors-warning-warning-300: #FEC84B; + --color-util-colors-warning-warning-400: #FDB022; + --color-util-colors-warning-warning-500: #F79009; + --color-util-colors-warning-warning-600: #DC6803; + --color-util-colors-warning-warning-700: #B54708; + + --color-util-colors-yellow-yellow-50: #FEFBE8; + --color-util-colors-yellow-yellow-100: #FEF7C3; + --color-util-colors-yellow-yellow-200: #FEEE95; + --color-util-colors-yellow-yellow-300: #FDE272; + --color-util-colors-yellow-yellow-400: #FAC515; + --color-util-colors-yellow-yellow-500: #EAAA08; + --color-util-colors-yellow-yellow-600: #CA8504; + --color-util-colors-yellow-yellow-700: #A15C07; + + --color-util-colors-teal-teal-50: #F0FDF9; + --color-util-colors-teal-teal-100: #CCFBEF; + --color-util-colors-teal-teal-200: #99F6E0; + --color-util-colors-teal-teal-300: #5FE9D0; + --color-util-colors-teal-teal-400: #2ED3B7; + --color-util-colors-teal-teal-500: #15B79E; + --color-util-colors-teal-teal-600: #0E9384; + --color-util-colors-teal-teal-700: #107569; + + --color-util-colors-cyan-cyan-50: #ECFDFF; + --color-util-colors-cyan-cyan-100: #CFF9FE; + --color-util-colors-cyan-cyan-200: #A5F0FC; + --color-util-colors-cyan-cyan-300: #67E3F9; + --color-util-colors-cyan-cyan-400: #22CCEE; + --color-util-colors-cyan-cyan-500: #06AED4; + --color-util-colors-cyan-cyan-600: #088AB2; + --color-util-colors-cyan-cyan-700: #0E7090; + + --color-util-colors-violet-violet-50: #F5F3FF; + --color-util-colors-violet-violet-100: #ECE9FE; + --color-util-colors-violet-violet-200: #DDD6FE; + --color-util-colors-violet-violet-300: #C3B5FD; + --color-util-colors-violet-violet-400: #A48AFB; + --color-util-colors-violet-violet-500: #875BF7; + --color-util-colors-violet-violet-600: #7839EE; + --color-util-colors-violet-violet-700: #6927DA; + + --color-util-colors-gray-gray-50: #F9FAFB; + --color-util-colors-gray-gray-100: #F2F4F7; + --color-util-colors-gray-gray-200: #E9EBF0; + --color-util-colors-gray-gray-300: #D0D5DC; + --color-util-colors-gray-gray-400: #98A2B2; + --color-util-colors-gray-gray-500: #676F83; + --color-util-colors-gray-gray-600: #495464; + --color-util-colors-gray-gray-700: #354052; + + --color-third-party-LangChain: #1C3C3C; + --color-third-party-Langfuse: #000000; +} \ No newline at end of file diff --git a/web/themes/tailwind-theme-var-define.ts b/web/themes/tailwind-theme-var-define.ts new file mode 100644 index 0000000000..1acb728b16 --- /dev/null +++ b/web/themes/tailwind-theme-var-define.ts @@ -0,0 +1,564 @@ +/* Attention: Generate by code. Don't update by hand!!! */ +const vars = { + 'components-input-bg-normal': 'var(--color-components-input-bg-normal)', + 'components-input-text-placeholder': 'var(--color-components-input-text-placeholder)', + 'components-input-bg-hover': 'var(--color-components-input-bg-hover)', + 'components-input-bg-active': 'var(--color-components-input-bg-active)', + 'components-input-border-active': 'var(--color-components-input-border-active)', + 'components-input-border-destructive': 'var(--color-components-input-border-destructive)', + 'components-input-text-filled': 'var(--color-components-input-text-filled)', + 'components-input-bg-destructive': 'var(--color-components-input-bg-destructive)', + 'components-input-bg-disabled': 'var(--color-components-input-bg-disabled)', + 'components-input-text-disabled': 'var(--color-components-input-text-disabled)', + 'components-input-text-filled-disabled': 'var(--color-components-input-text-filled-disabled)', + 'components-input-border-hover': 'var(--color-components-input-border-hover)', + 'components-input-border-active-prompt-1': 'var(--color-components-input-border-active-prompt-1)', + 'components-input-border-active-prompt-2': 'var(--color-components-input-border-active-prompt-2)', + + 'components-kbd-bg-gray': 'var(--color-components-kbd-bg-gray)', + 'components-kbd-bg-white': 'var(--color-components-kbd-bg-white)', + + 'components-tooltip-bg': 'var(--color-components-tooltip-bg)', + + 'components-button-primary-text': 'var(--color-components-button-primary-text)', + 'components-button-primary-bg': 'var(--color-components-button-primary-bg)', + 'components-button-primary-border': 'var(--color-components-button-primary-border)', + 'components-button-primary-bg-hover': 'var(--color-components-button-primary-bg-hover)', + 'components-button-primary-border-hover': 'var(--color-components-button-primary-border-hover)', + 'components-button-primary-bg-disabled': 'var(--color-components-button-primary-bg-disabled)', + 'components-button-primary-border-disabled': 'var(--color-components-button-primary-border-disabled)', + 'components-button-primary-text-disabled': 'var(--color-components-button-primary-text-disabled)', + + 'components-button-secondary-text': 'var(--color-components-button-secondary-text)', + 'components-button-secondary-text-disabled': 'var(--color-components-button-secondary-text-disabled)', + 'components-button-secondary-bg': 'var(--color-components-button-secondary-bg)', + 'components-button-secondary-bg-hover': 'var(--color-components-button-secondary-bg-hover)', + 'components-button-secondary-bg-disabled': 'var(--color-components-button-secondary-bg-disabled)', + 'components-button-secondary-border': 'var(--color-components-button-secondary-border)', + 'components-button-secondary-border-hover': 'var(--color-components-button-secondary-border-hover)', + 'components-button-secondary-border-disabled': 'var(--color-components-button-secondary-border-disabled)', + + 'components-button-tertiary-text': 'var(--color-components-button-tertiary-text)', + 'components-button-tertiary-text-disabled': 'var(--color-components-button-tertiary-text-disabled)', + 'components-button-tertiary-bg': 'var(--color-components-button-tertiary-bg)', + 'components-button-tertiary-bg-hover': 'var(--color-components-button-tertiary-bg-hover)', + 'components-button-tertiary-bg-disabled': 'var(--color-components-button-tertiary-bg-disabled)', + + 'components-button-ghost-text': 'var(--color-components-button-ghost-text)', + 'components-button-ghost-text-disabled': 'var(--color-components-button-ghost-text-disabled)', + 'components-button-ghost-bg-hover': 'var(--color-components-button-ghost-bg-hover)', + + 'components-button-destructive-primary-text': 'var(--color-components-button-destructive-primary-text)', + 'components-button-destructive-primary-text-disabled': 'var(--color-components-button-destructive-primary-text-disabled)', + 'components-button-destructive-primary-bg': 'var(--color-components-button-destructive-primary-bg)', + 'components-button-destructive-primary-bg-hover': 'var(--color-components-button-destructive-primary-bg-hover)', + 'components-button-destructive-primary-bg-disabled': 'var(--color-components-button-destructive-primary-bg-disabled)', + 'components-button-destructive-primary-border': 'var(--color-components-button-destructive-primary-border)', + 'components-button-destructive-primary-border-hover': 'var(--color-components-button-destructive-primary-border-hover)', + 'components-button-destructive-primary-border-disabled': 'var(--color-components-button-destructive-primary-border-disabled)', + + 'components-button-destructive-secondary-text': 'var(--color-components-button-destructive-secondary-text)', + 'components-button-destructive-secondary-text-disabled': 'var(--color-components-button-destructive-secondary-text-disabled)', + 'components-button-destructive-secondary-bg': 'var(--color-components-button-destructive-secondary-bg)', + 'components-button-destructive-secondary-bg-hover': 'var(--color-components-button-destructive-secondary-bg-hover)', + 'components-button-destructive-secondary-bg-disabled': 'var(--color-components-button-destructive-secondary-bg-disabled)', + 'components-button-destructive-secondary-border': 'var(--color-components-button-destructive-secondary-border)', + 'components-button-destructive-secondary-border-hover': 'var(--color-components-button-destructive-secondary-border-hover)', + 'components-button-destructive-secondary-border-disabled': 'var(--color-components-button-destructive-secondary-border-disabled)', + + 'components-button-destructive-tertiary-text': 'var(--color-components-button-destructive-tertiary-text)', + 'components-button-destructive-tertiary-text-disabled': 'var(--color-components-button-destructive-tertiary-text-disabled)', + 'components-button-destructive-tertiary-bg': 'var(--color-components-button-destructive-tertiary-bg)', + 'components-button-destructive-tertiary-bg-hover': 'var(--color-components-button-destructive-tertiary-bg-hover)', + 'components-button-destructive-tertiary-bg-disabled': 'var(--color-components-button-destructive-tertiary-bg-disabled)', + + 'components-button-destructive-ghost-text': 'var(--color-components-button-destructive-ghost-text)', + 'components-button-destructive-ghost-text-disabled': 'var(--color-components-button-destructive-ghost-text-disabled)', + 'components-button-destructive-ghost-bg-hover': 'var(--color-components-button-destructive-ghost-bg-hover)', + + 'components-button-secondary-accent-text': 'var(--color-components-button-secondary-accent-text)', + 'components-button-secondary-accent-text-disabled': 'var(--color-components-button-secondary-accent-text-disabled)', + 'components-button-secondary-accent-bg': 'var(--color-components-button-secondary-accent-bg)', + 'components-button-secondary-accent-bg-hover': 'var(--color-components-button-secondary-accent-bg-hover)', + 'components-button-secondary-accent-bg-disabled': 'var(--color-components-button-secondary-accent-bg-disabled)', + 'components-button-secondary-accent-border': 'var(--color-components-button-secondary-accent-border)', + 'components-button-secondary-accent-border-hover': 'var(--color-components-button-secondary-accent-border-hover)', + 'components-button-secondary-accent-border-disabled': 'var(--color-components-button-secondary-accent-border-disabled)', + + 'components-checkbox-icon': 'var(--color-components-checkbox-icon)', + 'components-checkbox-icon-disabled': 'var(--color-components-checkbox-icon-disabled)', + 'components-checkbox-bg': 'var(--color-components-checkbox-bg)', + 'components-checkbox-bg-hover': 'var(--color-components-checkbox-bg-hover)', + 'components-checkbox-bg-disabled': 'var(--color-components-checkbox-bg-disabled)', + 'components-checkbox-border': 'var(--color-components-checkbox-border)', + 'components-checkbox-border-hover': 'var(--color-components-checkbox-border-hover)', + 'components-checkbox-border-disabled': 'var(--color-components-checkbox-border-disabled)', + 'components-checkbox-bg-unchecked': 'var(--color-components-checkbox-bg-unchecked)', + 'components-checkbox-bg-unchecked-hover': 'var(--color-components-checkbox-bg-unchecked-hover)', + + 'components-radio-border-checked': 'var(--color-components-radio-border-checked)', + 'components-radio-border-checked-hover': 'var(--color-components-radio-border-checked-hover)', + 'components-radio-border-checked-disabled': 'var(--color-components-radio-border-checked-disabled)', + 'components-radio-bg-disabled': 'var(--color-components-radio-bg-disabled)', + 'components-radio-border': 'var(--color-components-radio-border)', + 'components-radio-border-hover': 'var(--color-components-radio-border-hover)', + 'components-radio-border-disabled': 'var(--color-components-radio-border-disabled)', + 'components-radio-bg': 'var(--color-components-radio-bg)', + 'components-radio-bg-hover': 'var(--color-components-radio-bg-hover)', + + 'components-toggle-knob': 'var(--color-components-toggle-knob)', + 'components-toggle-knob-disabled': 'var(--color-components-toggle-knob-disabled)', + 'components-toggle-bg': 'var(--color-components-toggle-bg)', + 'components-toggle-bg-hover': 'var(--color-components-toggle-bg-hover)', + 'components-toggle-bg-disabled': 'var(--color-components-toggle-bg-disabled)', + 'components-toggle-bg-unchecked': 'var(--color-components-toggle-bg-unchecked)', + 'components-toggle-bg-unchecked-hover': 'var(--color-components-toggle-bg-unchecked-hover)', + 'components-toggle-bg-unchecked-disabled': 'var(--color-components-toggle-bg-unchecked-disabled)', + 'components-toggle-knob-hover': 'var(--color-components-toggle-knob-hover)', + + 'components-card-bg': 'var(--color-components-card-bg)', + 'components-card-border': 'var(--color-components-card-border)', + 'components-card-bg-alt': 'var(--color-components-card-bg-alt)', + + 'components-menu-item-text': 'var(--color-components-menu-item-text)', + 'components-menu-item-text-active': 'var(--color-components-menu-item-text-active)', + 'components-menu-item-text-hover': 'var(--color-components-menu-item-text-hover)', + 'components-menu-item-text-active-accent': 'var(--color-components-menu-item-text-active-accent)', + + 'components-panel-bg': 'var(--color-components-panel-bg)', + 'components-panel-bg-blur': 'var(--color-components-panel-bg-blur)', + 'components-panel-border': 'var(--color-components-panel-border)', + 'components-panel-border-subtle': 'var(--color-components-panel-border-subtle)', + 'components-panel-gradient-2': 'var(--color-components-panel-gradient-2)', + 'components-panel-gradient-1': 'var(--color-components-panel-gradient-1)', + 'components-panel-bg-alt': 'var(--color-components-panel-bg-alt)', + 'components-panel-on-panel-item-bg': 'var(--color-components-panel-on-panel-item-bg)', + 'components-panel-on-panel-item-bg-hover': 'var(--color-components-panel-on-panel-item-bg-hover)', + 'components-panel-on-panel-item-bg-alt': 'var(--color-components-panel-on-panel-item-bg-alt)', + + 'components-main-nav-nav-button-text': 'var(--color-components-main-nav-nav-button-text)', + 'components-main-nav-nav-button-text-active': 'var(--color-components-main-nav-nav-button-text-active)', + 'components-main-nav-nav-button-bg': 'var(--color-components-main-nav-nav-button-bg)', + 'components-main-nav-nav-button-bg-active': 'var(--color-components-main-nav-nav-button-bg-active)', + 'components-main-nav-nav-button-border': 'var(--color-components-main-nav-nav-button-border)', + 'components-main-nav-nav-button-bg-hover': 'var(--color-components-main-nav-nav-button-bg-hover)', + + 'components-main-nav-nav-user-border': 'var(--color-components-main-nav-nav-user-border)', + + 'components-silder-knob': 'var(--color-components-silder-knob)', + 'components-silder-knob-hover': 'var(--color-components-silder-knob-hover)', + 'components-silder-knob-disabled': 'var(--color-components-silder-knob-disabled)', + 'components-silder-range': 'var(--color-components-silder-range)', + 'components-silder-track': 'var(--color-components-silder-track)', + 'components-silder-knob-border-hover': 'var(--color-components-silder-knob-border-hover)', + 'components-silder-knob-border': 'var(--color-components-silder-knob-border)', + + 'components-segmented-control-item-active-bg': 'var(--color-components-segmented-control-item-active-bg)', + 'components-segmented-control-item-active-border': 'var(--color-components-segmented-control-item-active-border)', + 'components-segmented-control-bg-normal': 'var(--color-components-segmented-control-bg-normal)', + 'components-segmented-control-item-active-accent-bg': 'var(--color-components-segmented-control-item-active-accent-bg)', + 'components-segmented-control-item-active-accent-border': 'var(--color-components-segmented-control-item-active-accent-border)', + + 'components-option-card-option-bg': 'var(--color-components-option-card-option-bg)', + 'components-option-card-option-selected-bg': 'var(--color-components-option-card-option-selected-bg)', + 'components-option-card-option-selected-border': 'var(--color-components-option-card-option-selected-border)', + 'components-option-card-option-border': 'var(--color-components-option-card-option-border)', + 'components-option-card-option-bg-hover': 'var(--color-components-option-card-option-bg-hover)', + 'components-option-card-option-border-hover': 'var(--color-components-option-card-option-border-hover)', + + 'components-tab-active': 'var(--color-components-tab-active)', + + 'components-badge-white-to-dark': 'var(--color-components-badge-white-to-dark)', + 'components-badge-status-light-success-bg': 'var(--color-components-badge-status-light-success-bg)', + 'components-badge-status-light-success-border-inner': 'var(--color-components-badge-status-light-success-border-inner)', + 'components-badge-status-light-success-halo': 'var(--color-components-badge-status-light-success-halo)', + + 'components-badge-status-light-border-outer': 'var(--color-components-badge-status-light-border-outer)', + 'components-badge-status-light-high-light': 'var(--color-components-badge-status-light-high-light)', + 'components-badge-status-light-warning-bg': 'var(--color-components-badge-status-light-warning-bg)', + 'components-badge-status-light-warning-border-inner': 'var(--color-components-badge-status-light-warning-border-inner)', + 'components-badge-status-light-warning-halo': 'var(--color-components-badge-status-light-warning-halo)', + + 'components-badge-status-light-error-bg': 'var(--color-components-badge-status-light-error-bg)', + 'components-badge-status-light-error-border-inner': 'var(--color-components-badge-status-light-error-border-inner)', + 'components-badge-status-light-error-halo': 'var(--color-components-badge-status-light-error-halo)', + + 'components-badge-status-light-normal-bg': 'var(--color-components-badge-status-light-normal-bg)', + 'components-badge-status-light-normal-border-inner': 'var(--color-components-badge-status-light-normal-border-inner)', + 'components-badge-status-light-normal-halo': 'var(--color-components-badge-status-light-normal-halo)', + + 'components-badge-status-light-disabled-bg': 'var(--color-components-badge-status-light-disabled-bg)', + 'components-badge-status-light-disabled-border-inner': 'var(--color-components-badge-status-light-disabled-border-inner)', + 'components-badge-status-light-disabled-halo': 'var(--color-components-badge-status-light-disabled-halo)', + + 'components-badge-bg-green-soft': 'var(--color-components-badge-bg-green-soft)', + 'components-badge-bg-orange-soft': 'var(--color-components-badge-bg-orange-soft)', + 'components-badge-bg-red-soft': 'var(--color-components-badge-bg-red-soft)', + 'components-badge-bg-blue-light-soft': 'var(--color-components-badge-bg-blue-light-soft)', + 'components-badge-bg-gray-soft': 'var(--color-components-badge-bg-gray-soft)', + + 'components-chart-line': 'var(--color-components-chart-line)', + 'components-chart-area-1': 'var(--color-components-chart-area-1)', + 'components-chart-area-2': 'var(--color-components-chart-area-2)', + 'components-chart-current-1': 'var(--color-components-chart-current-1)', + 'components-chart-current-2': 'var(--color-components-chart-current-2)', + 'components-chart-bg': 'var(--color-components-chart-bg)', + + 'components-actionbar-bg': 'var(--color-components-actionbar-bg)', + 'components-actionbar-border': 'var(--color-components-actionbar-border)', + + 'components-dropzone-bg-alt': 'var(--color-components-dropzone-bg-alt)', + 'components-dropzone-bg': 'var(--color-components-dropzone-bg)', + 'components-dropzone-bg-accent': 'var(--color-components-dropzone-bg-accent)', + 'components-dropzone-border': 'var(--color-components-dropzone-border)', + 'components-dropzone-border-alt': 'var(--color-components-dropzone-border-alt)', + 'components-dropzone-border-accent': 'var(--color-components-dropzone-border-accent)', + + 'components-progress-brand-progress': 'var(--color-components-progress-brand-progress)', + 'components-progress-brand-border': 'var(--color-components-progress-brand-border)', + 'components-progress-brand-bg': 'var(--color-components-progress-brand-bg)', + + 'components-progress-white-progress': 'var(--color-components-progress-white-progress)', + 'components-progress-white-border': 'var(--color-components-progress-white-border)', + 'components-progress-white-bg': 'var(--color-components-progress-white-bg)', + + 'components-progress-gray-progress': 'var(--color-components-progress-gray-progress)', + 'components-progress-gray-border': 'var(--color-components-progress-gray-border)', + 'components-progress-gray-bg': 'var(--color-components-progress-gray-bg)', + + 'components-chat-input-audio-bg': 'var(--color-components-chat-input-audio-bg)', + 'components-chat-input-audio-wave': 'var(--color-components-chat-input-audio-wave)', + 'components-chat-input-bg-mask-1': 'var(--color-components-chat-input-bg-mask-1)', + 'components-chat-input-bg-mask-2': 'var(--color-components-chat-input-bg-mask-2)', + 'components-chat-input-border': 'var(--color-components-chat-input-border)', + + 'text-primary': 'var(--color-text-primary)', + 'text-secondary': 'var(--color-text-secondary)', + 'text-tertiary': 'var(--color-text-tertiary)', + 'text-quaternary': 'var(--color-text-quaternary)', + 'text-destructive': 'var(--color-text-destructive)', + 'text-success': 'var(--color-text-success)', + 'text-warning': 'var(--color-text-warning)', + 'text-destructive-secondary': 'var(--color-text-destructive-secondary)', + 'text-success-secondary': 'var(--color-text-success-secondary)', + 'text-warning-secondary': 'var(--color-text-warning-secondary)', + 'text-accent': 'var(--color-text-accent)', + 'text-primary-on-surface': 'var(--color-text-primary-on-surface)', + 'text-placeholder': 'var(--color-text-placeholder)', + 'text-disabled': 'var(--color-text-disabled)', + 'text-accent-secondary': 'var(--color-text-accent-secondary)', + 'text-accent-light-mode-only': 'var(--color-text-accent-light-mode-only)', + 'text-text-selected': 'var(--color-text-text-selected)', + 'text-secondary-on-surface': 'var(--color-text-secondary-on-surface)', + 'text-logo-text': 'var(--color-text-logo-text)', + 'text-empty-state-icon': 'var(--color-text-empty-state-icon)', + + 'background-body': 'var(--color-background-body)', + 'background-default-subtle': 'var(--color-background-default-subtle)', + 'background-neurtral-subtle': 'var(--color-background-neurtral-subtle)', + 'background-sidenav-bg': 'var(--color-background-sidenav-bg)', + 'background-default': 'var(--color-background-default)', + 'background-soft': 'var(--color-background-soft)', + 'background-gradient-bg-fill-chat-bg-1': 'var(--color-background-gradient-bg-fill-chat-bg-1)', + 'background-gradient-bg-fill-chat-bg-2': 'var(--color-background-gradient-bg-fill-chat-bg-2)', + 'background-gradient-bg-fill-chat-bubble-bg-1': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-1)', + 'background-gradient-bg-fill-chat-bubble-bg-2': 'var(--color-background-gradient-bg-fill-chat-bubble-bg-2)', + + 'background-gradient-mask-gray': 'var(--color-background-gradient-mask-gray)', + 'background-gradient-mask-transparent': 'var(--color-background-gradient-mask-transparent)', + 'background-gradient-mask-input-clear-2': 'var(--color-background-gradient-mask-input-clear-2)', + 'background-gradient-mask-input-clear-1': 'var(--color-background-gradient-mask-input-clear-1)', + 'background-gradient-mask-transparent-dark': 'var(--color-background-gradient-mask-transparent-dark)', + 'background-gradient-mask-side-panel-2': 'var(--color-background-gradient-mask-side-panel-2)', + 'background-gradient-mask-side-panel-1': 'var(--color-background-gradient-mask-side-panel-1)', + + 'background-default-burn': 'var(--color-background-default-burn)', + 'background-overlay-fullscreen': 'var(--color-background-overlay-fullscreen)', + 'background-default-lighter': 'var(--color-background-default-lighter)', + 'background-section': 'var(--color-background-section)', + 'background-interaction-from-bg-1': 'var(--color-background-interaction-from-bg-1)', + 'background-interaction-from-bg-2': 'var(--color-background-interaction-from-bg-2)', + 'background-section-burn': 'var(--color-background-section-burn)', + 'background-default-dodge': 'var(--color-background-default-dodge)', + 'background-overlay': 'var(--color-background-overlay)', + 'background-default-dimm': 'var(--color-background-default-dimm)', + 'background-default-hover': 'var(--color-background-default-hover)', + 'background-overlay-alt': 'var(--color-background-overlay-alt)', + 'background-surface-white': 'var(--color-background-surface-white)', + 'background-overlay-destructive': 'var(--color-background-overlay-destructive)', + + 'shadow-shadow-1': 'var(--color-shadow-shadow-1)', + 'shadow-shadow-3': 'var(--color-shadow-shadow-3)', + 'shadow-shadow-4': 'var(--color-shadow-shadow-4)', + 'shadow-shadow-5': 'var(--color-shadow-shadow-5)', + 'shadow-shadow-6': 'var(--color-shadow-shadow-6)', + 'shadow-shadow-7': 'var(--color-shadow-shadow-7)', + 'shadow-shadow-8': 'var(--color-shadow-shadow-8)', + 'shadow-shadow-9': 'var(--color-shadow-shadow-9)', + 'shadow-shadow-2': 'var(--color-shadow-shadow-2)', + 'shadow-shadow-10': 'var(--color-shadow-shadow-10)', + + 'workflow-block-border': 'var(--color-workflow-block-border)', + 'workflow-block-panel-bg': 'var(--color-workflow-block-panel-bg)', + 'workflow-block-parma-bg': 'var(--color-workflow-block-parma-bg)', + 'workflow-block-nav-bg': 'var(--color-workflow-block-nav-bg)', + 'workflow-block-nav-border-right': 'var(--color-workflow-block-nav-border-right)', + 'workflow-block-bg': 'var(--color-workflow-block-bg)', + + 'workflow-canvas-workflow-dot-color': 'var(--color-workflow-canvas-workflow-dot-color)', + 'workflow-canvas-workflow-bg': 'var(--color-workflow-canvas-workflow-bg)', + + 'workflow-link-line-active': 'var(--color-workflow-link-line-active)', + 'workflow-link-line-normal': 'var(--color-workflow-link-line-normal)', + 'workflow-link-line-handle': 'var(--color-workflow-link-line-handle)', + + 'workflow-minmap-bg': 'var(--color-workflow-minmap-bg)', + 'workflow-minmap-block': 'var(--color-workflow-minmap-block)', + + 'workflow-display-success-bg': 'var(--color-workflow-display-success-bg)', + 'workflow-display-success-border-1': 'var(--color-workflow-display-success-border-1)', + 'workflow-display-success-border-2': 'var(--color-workflow-display-success-border-2)', + 'workflow-display-success-vignette-color': 'var(--color-workflow-display-success-vignette-color)', + 'workflow-display-success-bg-line-pattern': 'var(--color-workflow-display-success-bg-line-pattern)', + + 'workflow-display-glass-1': 'var(--color-workflow-display-glass-1)', + 'workflow-display-glass-2': 'var(--color-workflow-display-glass-2)', + 'workflow-display-vignette-dark': 'var(--color-workflow-display-vignette-dark)', + 'workflow-display-highlight': 'var(--color-workflow-display-highlight)', + 'workflow-display-outline': 'var(--color-workflow-display-outline)', + 'workflow-display-error-bg': 'var(--color-workflow-display-error-bg)', + 'workflow-display-error-bg-line-pattern': 'var(--color-workflow-display-error-bg-line-pattern)', + 'workflow-display-error-border-1': 'var(--color-workflow-display-error-border-1)', + 'workflow-display-error-border-2': 'var(--color-workflow-display-error-border-2)', + 'workflow-display-error-vignette-color': 'var(--color-workflow-display-error-vignette-color)', + + 'workflow-display-warning-bg': 'var(--color-workflow-display-warning-bg)', + 'workflow-display-warning-bg-line-pattern': 'var(--color-workflow-display-warning-bg-line-pattern)', + 'workflow-display-warning-border-1': 'var(--color-workflow-display-warning-border-1)', + 'workflow-display-warning-border-2': 'var(--color-workflow-display-warning-border-2)', + 'workflow-display-warning-vignette-color': 'var(--color-workflow-display-warning-vignette-color)', + + 'workflow-display-normal-bg': 'var(--color-workflow-display-normal-bg)', + 'workflow-display-normal-bg-line-pattern': 'var(--color-workflow-display-normal-bg-line-pattern)', + 'workflow-display-normal-border-1': 'var(--color-workflow-display-normal-border-1)', + 'workflow-display-normal-border-2': 'var(--color-workflow-display-normal-border-2)', + 'workflow-display-normal-vignette-color': 'var(--color-workflow-display-normal-vignette-color)', + + 'workflow-display-disabled-bg': 'var(--color-workflow-display-disabled-bg)', + 'workflow-display-disabled-bg-line-pattern': 'var(--color-workflow-display-disabled-bg-line-pattern)', + 'workflow-display-disabled-border-1': 'var(--color-workflow-display-disabled-border-1)', + 'workflow-display-disabled-border-2': 'var(--color-workflow-display-disabled-border-2)', + 'workflow-display-disabled-vignette-color': 'var(--color-workflow-display-disabled-vignette-color)', + 'workflow-display-disabled-outline': 'var(--color-workflow-display-disabled-outline)', + + 'workflow-workflow-progress-bg-1': 'var(--color-workflow-workflow-progress-bg-1)', + 'workflow-workflow-progress-bg-2': 'var(--color-workflow-workflow-progress-bg-2)', + + 'divider-subtle': 'var(--color-divider-subtle)', + 'divider-regular': 'var(--color-divider-regular)', + 'divider-deep': 'var(--color-divider-deep)', + 'divider-burn': 'var(--color-divider-burn)', + 'divider-intense': 'var(--color-divider-intense)', + 'divider-soild': 'var(--color-divider-soild)', + 'divider-soild-alt': 'var(--color-divider-soild-alt)', + + 'state-base-hover': 'var(--color-state-base-hover)', + 'state-base-active': 'var(--color-state-base-active)', + 'state-base-hover-alt': 'var(--color-state-base-hover-alt)', + 'state-base-handle': 'var(--color-state-base-handle)', + 'state-base-handle-hover': 'var(--color-state-base-handle-hover)', + + 'state-accent-hover': 'var(--color-state-accent-hover)', + 'state-accent-active': 'var(--color-state-accent-active)', + 'state-accent-hover-alt': 'var(--color-state-accent-hover-alt)', + 'state-accent-soild': 'var(--color-state-accent-soild)', + 'state-accent-active-alt': 'var(--color-state-accent-active-alt)', + + 'state-destructive-hover': 'var(--color-state-destructive-hover)', + 'state-destructive-hover-alt': 'var(--color-state-destructive-hover-alt)', + 'state-destructive-active': 'var(--color-state-destructive-active)', + 'state-destructive-soild': 'var(--color-state-destructive-soild)', + 'state-destructive-border': 'var(--color-state-destructive-border)', + + 'state-success-hover': 'var(--color-state-success-hover)', + 'state-success-hover-alt': 'var(--color-state-success-hover-alt)', + 'state-success-active': 'var(--color-state-success-active)', + 'state-success-soild': 'var(--color-state-success-soild)', + + 'state-warning-hover': 'var(--color-state-warning-hover)', + 'state-warning-hover-alt': 'var(--color-state-warning-hover-alt)', + 'state-warning-active': 'var(--color-state-warning-active)', + 'state-warning-soild': 'var(--color-state-warning-soild)', + + 'effects-highlight': 'var(--color-effects-highlight)', + 'effects-highlight-lightmode-off': 'var(--color-effects-highlight-lightmode-off)', + 'effects-image-frame': 'var(--color-effects-image-frame)', + + 'util-colors-orange-dark-orange-dark-50': 'var(--color-util-colors-orange-dark-orange-dark-50)', + 'util-colors-orange-dark-orange-dark-100': 'var(--color-util-colors-orange-dark-orange-dark-100)', + 'util-colors-orange-dark-orange-dark-200': 'var(--color-util-colors-orange-dark-orange-dark-200)', + 'util-colors-orange-dark-orange-dark-300': 'var(--color-util-colors-orange-dark-orange-dark-300)', + 'util-colors-orange-dark-orange-dark-400': 'var(--color-util-colors-orange-dark-orange-dark-400)', + 'util-colors-orange-dark-orange-dark-500': 'var(--color-util-colors-orange-dark-orange-dark-500)', + 'util-colors-orange-dark-orange-dark-600': 'var(--color-util-colors-orange-dark-orange-dark-600)', + 'util-colors-orange-dark-orange-dark-700': 'var(--color-util-colors-orange-dark-orange-dark-700)', + + 'util-colors-orange-orange-50': 'var(--color-util-colors-orange-orange-50)', + 'util-colors-orange-orange-100': 'var(--color-util-colors-orange-orange-100)', + 'util-colors-orange-orange-200': 'var(--color-util-colors-orange-orange-200)', + 'util-colors-orange-orange-300': 'var(--color-util-colors-orange-orange-300)', + 'util-colors-orange-orange-400': 'var(--color-util-colors-orange-orange-400)', + 'util-colors-orange-orange-500': 'var(--color-util-colors-orange-orange-500)', + 'util-colors-orange-orange-600': 'var(--color-util-colors-orange-orange-600)', + 'util-colors-orange-orange-700': 'var(--color-util-colors-orange-orange-700)', + + 'util-colors-pink-pink-50': 'var(--color-util-colors-pink-pink-50)', + 'util-colors-pink-pink-100': 'var(--color-util-colors-pink-pink-100)', + 'util-colors-pink-pink-200': 'var(--color-util-colors-pink-pink-200)', + 'util-colors-pink-pink-300': 'var(--color-util-colors-pink-pink-300)', + 'util-colors-pink-pink-400': 'var(--color-util-colors-pink-pink-400)', + 'util-colors-pink-pink-500': 'var(--color-util-colors-pink-pink-500)', + 'util-colors-pink-pink-600': 'var(--color-util-colors-pink-pink-600)', + 'util-colors-pink-pink-700': 'var(--color-util-colors-pink-pink-700)', + + 'util-colors-fuchsia-fuchsia-50': 'var(--color-util-colors-fuchsia-fuchsia-50)', + 'util-colors-fuchsia-fuchsia-100': 'var(--color-util-colors-fuchsia-fuchsia-100)', + 'util-colors-fuchsia-fuchsia-200': 'var(--color-util-colors-fuchsia-fuchsia-200)', + 'util-colors-fuchsia-fuchsia-300': 'var(--color-util-colors-fuchsia-fuchsia-300)', + 'util-colors-fuchsia-fuchsia-400': 'var(--color-util-colors-fuchsia-fuchsia-400)', + 'util-colors-fuchsia-fuchsia-500': 'var(--color-util-colors-fuchsia-fuchsia-500)', + 'util-colors-fuchsia-fuchsia-600': 'var(--color-util-colors-fuchsia-fuchsia-600)', + 'util-colors-fuchsia-fuchsia-700': 'var(--color-util-colors-fuchsia-fuchsia-700)', + + 'util-colors-purple-purple-50': 'var(--color-util-colors-purple-purple-50)', + 'util-colors-purple-purple-100': 'var(--color-util-colors-purple-purple-100)', + 'util-colors-purple-purple-200': 'var(--color-util-colors-purple-purple-200)', + 'util-colors-purple-purple-300': 'var(--color-util-colors-purple-purple-300)', + 'util-colors-purple-purple-400': 'var(--color-util-colors-purple-purple-400)', + 'util-colors-purple-purple-500': 'var(--color-util-colors-purple-purple-500)', + 'util-colors-purple-purple-600': 'var(--color-util-colors-purple-purple-600)', + 'util-colors-purple-purple-700': 'var(--color-util-colors-purple-purple-700)', + + 'util-colors-indigo-indigo-50': 'var(--color-util-colors-indigo-indigo-50)', + 'util-colors-indigo-indigo-100': 'var(--color-util-colors-indigo-indigo-100)', + 'util-colors-indigo-indigo-200': 'var(--color-util-colors-indigo-indigo-200)', + 'util-colors-indigo-indigo-300': 'var(--color-util-colors-indigo-indigo-300)', + 'util-colors-indigo-indigo-400': 'var(--color-util-colors-indigo-indigo-400)', + 'util-colors-indigo-indigo-500': 'var(--color-util-colors-indigo-indigo-500)', + 'util-colors-indigo-indigo-600': 'var(--color-util-colors-indigo-indigo-600)', + 'util-colors-indigo-indigo-700': 'var(--color-util-colors-indigo-indigo-700)', + + 'util-colors-blue-blue-50': 'var(--color-util-colors-blue-blue-50)', + 'util-colors-blue-blue-100': 'var(--color-util-colors-blue-blue-100)', + 'util-colors-blue-blue-200': 'var(--color-util-colors-blue-blue-200)', + 'util-colors-blue-blue-300': 'var(--color-util-colors-blue-blue-300)', + 'util-colors-blue-blue-400': 'var(--color-util-colors-blue-blue-400)', + 'util-colors-blue-blue-500': 'var(--color-util-colors-blue-blue-500)', + 'util-colors-blue-blue-600': 'var(--color-util-colors-blue-blue-600)', + 'util-colors-blue-blue-700': 'var(--color-util-colors-blue-blue-700)', + + 'util-colors-blue-light-blue-light-50': 'var(--color-util-colors-blue-light-blue-light-50)', + 'util-colors-blue-light-blue-light-100': 'var(--color-util-colors-blue-light-blue-light-100)', + 'util-colors-blue-light-blue-light-200': 'var(--color-util-colors-blue-light-blue-light-200)', + 'util-colors-blue-light-blue-light-300': 'var(--color-util-colors-blue-light-blue-light-300)', + 'util-colors-blue-light-blue-light-400': 'var(--color-util-colors-blue-light-blue-light-400)', + 'util-colors-blue-light-blue-light-500': 'var(--color-util-colors-blue-light-blue-light-500)', + 'util-colors-blue-light-blue-light-600': 'var(--color-util-colors-blue-light-blue-light-600)', + 'util-colors-blue-light-blue-light-700': 'var(--color-util-colors-blue-light-blue-light-700)', + + 'util-colors-gray-blue-gray-blue-50': 'var(--color-util-colors-gray-blue-gray-blue-50)', + 'util-colors-gray-blue-gray-blue-100': 'var(--color-util-colors-gray-blue-gray-blue-100)', + 'util-colors-gray-blue-gray-blue-200': 'var(--color-util-colors-gray-blue-gray-blue-200)', + 'util-colors-gray-blue-gray-blue-300': 'var(--color-util-colors-gray-blue-gray-blue-300)', + 'util-colors-gray-blue-gray-blue-400': 'var(--color-util-colors-gray-blue-gray-blue-400)', + 'util-colors-gray-blue-gray-blue-500': 'var(--color-util-colors-gray-blue-gray-blue-500)', + 'util-colors-gray-blue-gray-blue-600': 'var(--color-util-colors-gray-blue-gray-blue-600)', + 'util-colors-gray-blue-gray-blue-700': 'var(--color-util-colors-gray-blue-gray-blue-700)', + + 'util-colors-blue-brand-blue-brand-50': 'var(--color-util-colors-blue-brand-blue-brand-50)', + 'util-colors-blue-brand-blue-brand-100': 'var(--color-util-colors-blue-brand-blue-brand-100)', + 'util-colors-blue-brand-blue-brand-200': 'var(--color-util-colors-blue-brand-blue-brand-200)', + 'util-colors-blue-brand-blue-brand-300': 'var(--color-util-colors-blue-brand-blue-brand-300)', + 'util-colors-blue-brand-blue-brand-400': 'var(--color-util-colors-blue-brand-blue-brand-400)', + 'util-colors-blue-brand-blue-brand-500': 'var(--color-util-colors-blue-brand-blue-brand-500)', + 'util-colors-blue-brand-blue-brand-600': 'var(--color-util-colors-blue-brand-blue-brand-600)', + 'util-colors-blue-brand-blue-brand-700': 'var(--color-util-colors-blue-brand-blue-brand-700)', + + 'util-colors-red-red-50': 'var(--color-util-colors-red-red-50)', + 'util-colors-red-red-100': 'var(--color-util-colors-red-red-100)', + 'util-colors-red-red-200': 'var(--color-util-colors-red-red-200)', + 'util-colors-red-red-300': 'var(--color-util-colors-red-red-300)', + 'util-colors-red-red-400': 'var(--color-util-colors-red-red-400)', + 'util-colors-red-red-500': 'var(--color-util-colors-red-red-500)', + 'util-colors-red-red-600': 'var(--color-util-colors-red-red-600)', + 'util-colors-red-red-700': 'var(--color-util-colors-red-red-700)', + + 'util-colors-green-green-50': 'var(--color-util-colors-green-green-50)', + 'util-colors-green-green-100': 'var(--color-util-colors-green-green-100)', + 'util-colors-green-green-200': 'var(--color-util-colors-green-green-200)', + 'util-colors-green-green-300': 'var(--color-util-colors-green-green-300)', + 'util-colors-green-green-400': 'var(--color-util-colors-green-green-400)', + 'util-colors-green-green-500': 'var(--color-util-colors-green-green-500)', + 'util-colors-green-green-600': 'var(--color-util-colors-green-green-600)', + 'util-colors-green-green-700': 'var(--color-util-colors-green-green-700)', + + 'util-colors-warning-warning-50': 'var(--color-util-colors-warning-warning-50)', + 'util-colors-warning-warning-100': 'var(--color-util-colors-warning-warning-100)', + 'util-colors-warning-warning-200': 'var(--color-util-colors-warning-warning-200)', + 'util-colors-warning-warning-300': 'var(--color-util-colors-warning-warning-300)', + 'util-colors-warning-warning-400': 'var(--color-util-colors-warning-warning-400)', + 'util-colors-warning-warning-500': 'var(--color-util-colors-warning-warning-500)', + 'util-colors-warning-warning-600': 'var(--color-util-colors-warning-warning-600)', + 'util-colors-warning-warning-700': 'var(--color-util-colors-warning-warning-700)', + + 'util-colors-yellow-yellow-50': 'var(--color-util-colors-yellow-yellow-50)', + 'util-colors-yellow-yellow-100': 'var(--color-util-colors-yellow-yellow-100)', + 'util-colors-yellow-yellow-200': 'var(--color-util-colors-yellow-yellow-200)', + 'util-colors-yellow-yellow-300': 'var(--color-util-colors-yellow-yellow-300)', + 'util-colors-yellow-yellow-400': 'var(--color-util-colors-yellow-yellow-400)', + 'util-colors-yellow-yellow-500': 'var(--color-util-colors-yellow-yellow-500)', + 'util-colors-yellow-yellow-600': 'var(--color-util-colors-yellow-yellow-600)', + 'util-colors-yellow-yellow-700': 'var(--color-util-colors-yellow-yellow-700)', + + 'util-colors-teal-teal-50': 'var(--color-util-colors-teal-teal-50)', + 'util-colors-teal-teal-100': 'var(--color-util-colors-teal-teal-100)', + 'util-colors-teal-teal-200': 'var(--color-util-colors-teal-teal-200)', + 'util-colors-teal-teal-300': 'var(--color-util-colors-teal-teal-300)', + 'util-colors-teal-teal-400': 'var(--color-util-colors-teal-teal-400)', + 'util-colors-teal-teal-500': 'var(--color-util-colors-teal-teal-500)', + 'util-colors-teal-teal-600': 'var(--color-util-colors-teal-teal-600)', + 'util-colors-teal-teal-700': 'var(--color-util-colors-teal-teal-700)', + + 'util-colors-cyan-cyan-50': 'var(--color-util-colors-cyan-cyan-50)', + 'util-colors-cyan-cyan-100': 'var(--color-util-colors-cyan-cyan-100)', + 'util-colors-cyan-cyan-200': 'var(--color-util-colors-cyan-cyan-200)', + 'util-colors-cyan-cyan-300': 'var(--color-util-colors-cyan-cyan-300)', + 'util-colors-cyan-cyan-400': 'var(--color-util-colors-cyan-cyan-400)', + 'util-colors-cyan-cyan-500': 'var(--color-util-colors-cyan-cyan-500)', + 'util-colors-cyan-cyan-600': 'var(--color-util-colors-cyan-cyan-600)', + 'util-colors-cyan-cyan-700': 'var(--color-util-colors-cyan-cyan-700)', + + 'util-colors-violet-violet-50': 'var(--color-util-colors-violet-violet-50)', + 'util-colors-violet-violet-100': 'var(--color-util-colors-violet-violet-100)', + 'util-colors-violet-violet-200': 'var(--color-util-colors-violet-violet-200)', + 'util-colors-violet-violet-300': 'var(--color-util-colors-violet-violet-300)', + 'util-colors-violet-violet-400': 'var(--color-util-colors-violet-violet-400)', + 'util-colors-violet-violet-500': 'var(--color-util-colors-violet-violet-500)', + 'util-colors-violet-violet-600': 'var(--color-util-colors-violet-violet-600)', + 'util-colors-violet-violet-700': 'var(--color-util-colors-violet-violet-700)', + + 'util-colors-gray-gray-50': 'var(--color-util-colors-gray-gray-50)', + 'util-colors-gray-gray-100': 'var(--color-util-colors-gray-gray-100)', + 'util-colors-gray-gray-200': 'var(--color-util-colors-gray-gray-200)', + 'util-colors-gray-gray-300': 'var(--color-util-colors-gray-gray-300)', + 'util-colors-gray-gray-400': 'var(--color-util-colors-gray-gray-400)', + 'util-colors-gray-gray-500': 'var(--color-util-colors-gray-gray-500)', + 'util-colors-gray-gray-600': 'var(--color-util-colors-gray-gray-600)', + 'util-colors-gray-gray-700': 'var(--color-util-colors-gray-gray-700)', + + 'third-party-LangChain': 'var(--color-third-party-LangChain)', + 'third-party-Langfuse': 'var(--color-third-party-Langfuse)', + +} + +export default vars diff --git a/web/types/app.ts b/web/types/app.ts index 294d2980a8..ed73e2f5f7 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -160,6 +160,7 @@ export type ModelConfig = { enabled: boolean voice?: string language?: string + autoPlay?: TtsAutoPlay } retriever_resource: { enabled: boolean @@ -349,6 +350,11 @@ export enum TransferMethod { remote_url = 'remote_url', } +export enum TtsAutoPlay { + enabled = 'enabled', + disabled = 'disabled', +} + export const ALLOW_FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'webp', 'gif'] export type VisionSettings = { diff --git a/web/utils/classnames.ts b/web/utils/classnames.ts new file mode 100644 index 0000000000..6ce2284954 --- /dev/null +++ b/web/utils/classnames.ts @@ -0,0 +1,8 @@ +import { twMerge } from 'tailwind-merge' +import cn from 'classnames' + +const classNames = (...cls: cn.ArgumentArray) => { + return twMerge(cn(cls)) +} + +export default classNames diff --git a/web/yarn.lock b/web/yarn.lock index 393e81cf97..57e59d05b4 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - "@alloc/quick-lru@^5.2.0": version "5.2.0" resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz" @@ -73,23 +68,23 @@ yaml-eslint-parser "^1.1.0" "@babel/code-frame@^7.0.0": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== + version "7.21.4" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== dependencies: - "@babel/highlight" "^7.22.5" + "@babel/highlight" "^7.18.6" -"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== dependencies: - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.18.6" chalk "^2.0.0" js-tokens "^4.0.0" @@ -99,18 +94,11 @@ integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.21.5", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1": - version "7.23.7" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz" - integrity sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA== + version "7.22.3" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz" + integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.23.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" - integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== - dependencies: - regenerator-runtime "^0.14.0" + regenerator-runtime "^0.13.11" "@braintree/sanitize-url@^6.0.1": version "6.0.4" @@ -119,19 +107,19 @@ "@dagrejs/dagre@^1.1.2": version "1.1.2" - resolved "https://registry.yarnpkg.com/@dagrejs/dagre/-/dagre-1.1.2.tgz#5ec339979447091f48d2144deed8c70dfadae374" + resolved "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.2.tgz" integrity sha512-F09dphqvHsbe/6C2t2unbmpr5q41BNPEfJCdn8Z7aEBpVSy/zFQ/b4SWsweQjWNsYMDvE2ffNUN8X0CeFsEGNw== dependencies: "@dagrejs/graphlib" "2.2.2" "@dagrejs/graphlib@2.2.2": version "2.2.2" - resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.2.tgz#74154d5cb880a23b4fae71034a09b4b5aef06feb" + resolved "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.2.tgz" integrity sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg== "@emnapi/runtime@^0.45.0": version "0.45.0" - resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz#e754de04c683263f34fd0c7f32adfe718bbe4ddd" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-0.45.0.tgz#e754de04c683263f34fd0c7f32adfe718bbe4ddd" integrity sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w== dependencies: tslib "^2.4.0" @@ -149,18 +137,18 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0": - version "4.6.2" - resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz" - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + version "4.5.1" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== "@eslint/eslintrc@^2.0.1": - version "2.1.0" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz" - integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A== + version "2.0.3" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" + espree "^9.5.2" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -178,14 +166,7 @@ resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz" integrity sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw== -"@floating-ui/core@^1.0.0": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" - integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== - dependencies: - "@floating-ui/utils" "^0.2.0" - -"@floating-ui/core@^1.1.0": +"@floating-ui/core@^1.1.0", "@floating-ui/core@^1.4.1": version "1.4.1" resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz" integrity sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ== @@ -199,27 +180,27 @@ dependencies: "@floating-ui/core" "^1.1.0" -"@floating-ui/dom@^1.0.0": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" - integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== +"@floating-ui/dom@^1.5.1": + version "1.5.1" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz" + integrity sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw== dependencies: - "@floating-ui/core" "^1.0.0" - "@floating-ui/utils" "^0.2.0" + "@floating-ui/core" "^1.4.1" + "@floating-ui/utils" "^0.1.1" -"@floating-ui/react-dom@^2.0.2": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" - integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== +"@floating-ui/react-dom@^2.0.1": + version "2.0.2" + resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz" + integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== dependencies: - "@floating-ui/dom" "^1.0.0" + "@floating-ui/dom" "^1.5.1" "@floating-ui/react@^0.25.2": - version "0.25.3" - resolved "https://registry.npmjs.org/@floating-ui/react/-/react-0.25.3.tgz" - integrity sha512-Ti3ClVZIUqZq1OCkfbhsBA8u3m8jJ0h9gAInFwdrLaa+yTAZx3bFH8YR+/wQwPmRrpgJJ3cRhCfx4puz0PqVIA== + version "0.25.2" + resolved "https://registry.npmjs.org/@floating-ui/react/-/react-0.25.2.tgz" + integrity sha512-3e10G9LFOgl32/SMWLBOwT7oVCtB+d5zBsU2GxTSVOvRgZexwno5MlYbc0BaXr+TR5EEGpqe9tg9OUbjlrVRnQ== dependencies: - "@floating-ui/react-dom" "^2.0.2" + "@floating-ui/react-dom" "^2.0.1" "@floating-ui/utils" "^0.1.1" tabbable "^6.0.1" @@ -228,11 +209,6 @@ resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz" integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw== -"@floating-ui/utils@^0.2.0": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" - integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== - "@formatjs/intl-localematcher@^0.5.4": version "0.5.4" resolved "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz" @@ -241,9 +217,9 @@ tslib "^2.4.0" "@headlessui/react@^1.7.13": - version "1.7.17" - resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz" - integrity sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow== + version "1.7.15" + resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz" + integrity sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw== dependencies: client-only "^0.0.1" @@ -254,7 +230,7 @@ "@hookform/resolvers@^3.3.4": version "3.3.4" - resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b" + resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz" integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ== "@humanwhocodes/config-array@^0.11.8": @@ -278,152 +254,164 @@ "@img/sharp-darwin-arm64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz#0a52a82c2169112794dac2c71bfba9e90f7c5bd1" integrity sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w== optionalDependencies: "@img/sharp-libvips-darwin-arm64" "1.0.1" "@img/sharp-darwin-x64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz#982e26bb9d38a81f75915c4032539aed621d1c21" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz#982e26bb9d38a81f75915c4032539aed621d1c21" integrity sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg== optionalDependencies: "@img/sharp-libvips-darwin-x64" "1.0.1" "@img/sharp-libvips-darwin-arm64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz#81e83ffc2c497b3100e2f253766490f8fad479cd" integrity sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw== "@img/sharp-libvips-darwin-x64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz#fc1fcd9d78a178819eefe2c1a1662067a83ab1d6" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz#fc1fcd9d78a178819eefe2c1a1662067a83ab1d6" integrity sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog== "@img/sharp-libvips-linux-arm64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz#26eb8c556a9b0db95f343fc444abc3effb67ebcf" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz#26eb8c556a9b0db95f343fc444abc3effb67ebcf" integrity sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA== "@img/sharp-libvips-linux-arm@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz#2a377b959ff7dd6528deee486c25461296a4fa8b" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz#2a377b959ff7dd6528deee486c25461296a4fa8b" integrity sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ== "@img/sharp-libvips-linux-s390x@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz#af28ac9ba929204467ecdf843330d791e9421e10" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz#af28ac9ba929204467ecdf843330d791e9421e10" integrity sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ== "@img/sharp-libvips-linux-x64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz#4273d182aa51912e655e1214ea47983d7c1f7f8d" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz#4273d182aa51912e655e1214ea47983d7c1f7f8d" integrity sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw== "@img/sharp-libvips-linuxmusl-arm64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz#d150c92151cea2e8d120ad168b9c358d09c77ce8" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz#d150c92151cea2e8d120ad168b9c358d09c77ce8" integrity sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg== "@img/sharp-libvips-linuxmusl-x64@1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz#e297c1a4252c670d93b0f9e51fca40a7a5b6acfd" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz#e297c1a4252c670d93b0f9e51fca40a7a5b6acfd" integrity sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw== "@img/sharp-linux-arm64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz#af3409f801a9bee1d11d0c7e971dcd6180f80022" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz#af3409f801a9bee1d11d0c7e971dcd6180f80022" integrity sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew== optionalDependencies: "@img/sharp-libvips-linux-arm64" "1.0.1" "@img/sharp-linux-arm@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz#181f7466e6ac074042a38bfb679eb82505e17083" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz#181f7466e6ac074042a38bfb679eb82505e17083" integrity sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA== optionalDependencies: "@img/sharp-libvips-linux-arm" "1.0.1" "@img/sharp-linux-s390x@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz#9c171f49211f96fba84410b3e237b301286fa00f" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz#9c171f49211f96fba84410b3e237b301286fa00f" integrity sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA== optionalDependencies: "@img/sharp-libvips-linux-s390x" "1.0.1" "@img/sharp-linux-x64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz#b956dfc092adc58c2bf0fae2077e6f01a8b2d5d7" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz#b956dfc092adc58c2bf0fae2077e6f01a8b2d5d7" integrity sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A== optionalDependencies: "@img/sharp-libvips-linux-x64" "1.0.1" "@img/sharp-linuxmusl-arm64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz#10e0ec5a79d1234c6a71df44c9f3b0bef0bc0f15" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz#10e0ec5a79d1234c6a71df44c9f3b0bef0bc0f15" integrity sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA== optionalDependencies: "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" "@img/sharp-linuxmusl-x64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz#29e0030c24aa27c38201b1fc84e3d172899fcbe0" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz#29e0030c24aa27c38201b1fc84e3d172899fcbe0" integrity sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A== optionalDependencies: "@img/sharp-libvips-linuxmusl-x64" "1.0.1" "@img/sharp-wasm32@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz#38d7c740a22de83a60ad1e6bcfce17462b0d4230" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz#38d7c740a22de83a60ad1e6bcfce17462b0d4230" integrity sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ== dependencies: "@emnapi/runtime" "^0.45.0" "@img/sharp-win32-ia32@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz#09456314e223f68e5417c283b45c399635c16202" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz#09456314e223f68e5417c283b45c399635c16202" integrity sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g== "@img/sharp-win32-x64@0.33.2": version "0.33.2" - resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz#148e96dfd6e68747da41a311b9ee4559bb1b1471" + resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz" integrity sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg== -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: - "@jridgewell/set-array" "^1.0.1" + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + version "3.1.0" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.22" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz" - integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" "@lexical/clipboard@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.16.0.tgz#3ae0d87a56bd3518de077e45b0c1bbba2f356193" + resolved "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.16.0.tgz" integrity sha512-eYMJ6jCXpWBVC05Mu9HLMysrBbfi++xFfsm+Yo7A6kYGrqYUhpXqjJkYnw1xdZYL3bV73Oe4ByVJuq42GU+Mqw== dependencies: "@lexical/html" "0.16.0" @@ -434,7 +422,7 @@ "@lexical/code@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.16.0.tgz#225030342e3c361e5541c750033323007a947880" + resolved "https://registry.npmjs.org/@lexical/code/-/code-0.16.0.tgz" integrity sha512-1EKCBSFV745UI2zn5v75sKcvVdmd+y2JtZhw8CItiQkRnBLv4l4d/RZYy+cKOuXJGsoBrKtxXn5sl7HebwQbPw== dependencies: "@lexical/utils" "0.16.0" @@ -443,7 +431,7 @@ "@lexical/devtools-core@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/devtools-core/-/devtools-core-0.16.0.tgz#326c8e2995ce6e6e9e1fc4654ee2affbecdbd46d" + resolved "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.16.0.tgz" integrity sha512-Jt8p0J0UoMHf3UMh3VdyrXbLLwpEZuMqihTmbPRpwo+YQ6NGQU35QgwY2K0DpPAThpxL/Cm7uaFqGOy8Kjrhqw== dependencies: "@lexical/html" "0.16.0" @@ -455,14 +443,14 @@ "@lexical/dragon@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.16.0.tgz#de083903701af2bb5264309b565d613c3eec06a0" + resolved "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.16.0.tgz" integrity sha512-Yr29SFZzOPs+S6UrEZaXnnso1fJGVfZOXVJQZbyzlspqJpSHXVH7InOXYHWN6JSWQ8Hs/vU3ksJXwqz+0TCp2g== dependencies: lexical "0.16.0" "@lexical/hashtag@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.16.0.tgz#ea0187060a114678753adaf0a15aad59d4f49a71" + resolved "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.16.0.tgz" integrity sha512-2EdAvxYVYqb0nv6vgxCRgE8ip7yez5p0y0oeUyxmdbcfZdA+Jl90gYH3VdevmZ5Bk3wE0/fIqiLD+Bb5smqjCQ== dependencies: "@lexical/utils" "0.16.0" @@ -470,7 +458,7 @@ "@lexical/history@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.16.0.tgz#f83f2e331957208c5c8186d98f2f84681d936cec" + resolved "https://registry.npmjs.org/@lexical/history/-/history-0.16.0.tgz" integrity sha512-xwFxgDZGviyGEqHmgt6A6gPhsyU/yzlKRk9TBUVByba3khuTknlJ1a80H5jb+OYcrpiElml7iVuGYt+oC7atCA== dependencies: "@lexical/utils" "0.16.0" @@ -478,7 +466,7 @@ "@lexical/html@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/html/-/html-0.16.0.tgz#98477ed0dee4c7d910608f4e4de3fbd5eeecdffe" + resolved "https://registry.npmjs.org/@lexical/html/-/html-0.16.0.tgz" integrity sha512-okxn3q/1qkUpCZNEFRI39XeJj4YRjb6prm3WqZgP4d39DI1W24feeTZJjYRCW+dc3NInwFaolU3pNA2MGkjRtg== dependencies: "@lexical/selection" "0.16.0" @@ -487,7 +475,7 @@ "@lexical/link@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.16.0.tgz#f137ab3071206ed3c3a8b8a302ed66b084399ed1" + resolved "https://registry.npmjs.org/@lexical/link/-/link-0.16.0.tgz" integrity sha512-ppvJSh/XGqlzbeymOiwcXJcUcrqgQqTK2QXTBAZq7JThtb0WsJxYd2CSLSN+Ycu23prnwqOqILcU0+34+gAVFw== dependencies: "@lexical/utils" "0.16.0" @@ -495,7 +483,7 @@ "@lexical/list@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.16.0.tgz#ed97733633492e89c68ad51a1d455b63ce5aa1c0" + resolved "https://registry.npmjs.org/@lexical/list/-/list-0.16.0.tgz" integrity sha512-nBx/DMM7nCgnOzo1JyNnVaIrk/Xi5wIPNi8jixrEV6w9Om2K6dHutn/79Xzp2dQlNGSLHEDjky6N2RyFgmXh0g== dependencies: "@lexical/utils" "0.16.0" @@ -503,7 +491,7 @@ "@lexical/mark@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.16.0.tgz#e87d92845c8bd231ef47106c5d44e7e10d2a3934" + resolved "https://registry.npmjs.org/@lexical/mark/-/mark-0.16.0.tgz" integrity sha512-WMR4nqygSgIQ6Vdr5WAzohxBGjH+m44dBNTbWTGZGVlRvPzvBT6tieCoxFqpceIq/ko67HGTCNoFj2cMKVwgIA== dependencies: "@lexical/utils" "0.16.0" @@ -511,7 +499,7 @@ "@lexical/markdown@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.16.0.tgz#fd2d2759d9d5554d9899c3e1fb30a868bfa162a2" + resolved "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.16.0.tgz" integrity sha512-7HQLFrBbpY68mcq4A6C1qIGmjgA+fAByditi2WRe7tD2eoIKb/B5baQAnDKis0J+m5kTaCBmdlT6csSzyOPzeQ== dependencies: "@lexical/code" "0.16.0" @@ -524,21 +512,21 @@ "@lexical/offset@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.16.0.tgz#bb3bc695ed403db0795f095330c68cdc5cbbec4b" + resolved "https://registry.npmjs.org/@lexical/offset/-/offset-0.16.0.tgz" integrity sha512-4TqPEC2qA7sgO8Tm65nOWnhJ8dkl22oeuGv9sUB+nhaiRZnw3R45mDelg23r56CWE8itZnvueE7TKvV+F3OXtQ== dependencies: lexical "0.16.0" "@lexical/overflow@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.16.0.tgz#31b791f7f7005ea4b160f3ae8083a2b3de05cfdc" + resolved "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.16.0.tgz" integrity sha512-a7gtIRxleEuMN9dj2yO4CdezBBfIr9Mq+m7G5z62+xy7VL7cfMfF+xWjy3EmDYDXS4vOQgAXAUgO4oKz2AKGhQ== dependencies: lexical "0.16.0" "@lexical/plain-text@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.16.0.tgz#b903bfb59fb6629ded24194e1bef451df3383393" + resolved "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.16.0.tgz" integrity sha512-BK7/GSOZUHRJTbNPkpb9a/xN9z+FBCdunTsZhnOY8pQ7IKws3kuMO2Tk1zXfTd882ZNAxFdDKNdLYDSeufrKpw== dependencies: "@lexical/clipboard" "0.16.0" @@ -548,7 +536,7 @@ "@lexical/react@^0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.16.0.tgz#0bd3ae63ceb5ad8b77e8c0e8ba7df1a0369462f0" + resolved "https://registry.npmjs.org/@lexical/react/-/react-0.16.0.tgz" integrity sha512-WKFQbI0/m1YkLjL5t90YLJwjGcl5QRe6mkfm3ljQuL7Ioj3F92ZN/J2gHFVJ9iC8/lJs6Zzw6oFjiP8hQxJf9Q== dependencies: "@lexical/clipboard" "0.16.0" @@ -574,7 +562,7 @@ "@lexical/rich-text@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.16.0.tgz#5b9ea6ceb1ea034fa7adf1770bd7fa6af1571d1d" + resolved "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.16.0.tgz" integrity sha512-AGTD6yJZ+kj2TNah1r7/6vyufs6fZANeSvv9x5eG+WjV4uyUJYkd1qR8C5gFZHdkyr+bhAcsAXvS039VzAxRrQ== dependencies: "@lexical/clipboard" "0.16.0" @@ -584,14 +572,14 @@ "@lexical/selection@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.16.0.tgz#8e09edb1e555e79c646a0105beab58ac21fc7158" + resolved "https://registry.npmjs.org/@lexical/selection/-/selection-0.16.0.tgz" integrity sha512-trT9gQVJ2j6AwAe7tHJ30SRuxCpV6yR9LFtggxphHsXSvJYnoHC0CXh1TF2jHl8Gd5OsdWseexGLBE4Y0V3gwQ== dependencies: lexical "0.16.0" "@lexical/table@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.16.0.tgz#68592afbb0f9c0d9bf42bebaae626b8129fc470d" + resolved "https://registry.npmjs.org/@lexical/table/-/table-0.16.0.tgz" integrity sha512-A66K779kxdr0yH2RwT2itsMnkzyFLFNPXyiWGLobCH8ON4QPuBouZvjbRHBe8Pe64yJ0c1bRDxSbTqUi9Wt3Gg== dependencies: "@lexical/utils" "0.16.0" @@ -599,14 +587,14 @@ "@lexical/text@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.16.0.tgz#fc4789591f8aaa4a33bc1814280bc8725fd036a9" + resolved "https://registry.npmjs.org/@lexical/text/-/text-0.16.0.tgz" integrity sha512-9ilaOhuNIIGHKC8g8j3K/mEvJ09af9B6RKbm3GNoRcf/WNHD4dEFWNTEvgo/3zCzAS8EUBI6UINmfQQWlMjdIQ== dependencies: lexical "0.16.0" "@lexical/utils@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.16.0.tgz#6ad5785c53347aed5b39c980240c09b21c4a7469" + resolved "https://registry.npmjs.org/@lexical/utils/-/utils-0.16.0.tgz" integrity sha512-GWmFEmd7o3GHqJBaEwzuZQbfTNI3Gg8ReGuHMHABgrkhZ8j2NggoRBlxsQLG0f7BewfTMVwbye22yBPq78775w== dependencies: "@lexical/list" "0.16.0" @@ -616,7 +604,7 @@ "@lexical/yjs@0.16.0": version "0.16.0" - resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.16.0.tgz#e27bec25c12e90f7768b980da08f2d2d9919d25b" + resolved "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.16.0.tgz" integrity sha512-YIJr87DfAXTwoVHDjR7cci//hr4r/a61Nn95eo2JNwbTqQo65Gp8rwJivqVxNfvKZmRdwHTKgvdEDoBmI/tGog== dependencies: "@lexical/offset" "0.16.0" @@ -677,66 +665,66 @@ "@next/env@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz#5546813dc4f809884a37d257b254a5ce1b0248d7" + resolved "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz" integrity sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg== -"@next/eslint-plugin-next@14.0.4": - version "14.0.4" - resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.4.tgz" - integrity sha512-U3qMNHmEZoVmHA0j/57nRfi3AscXNvkOnxDmle/69Jz/G0o/gWjXTDdlgILZdrxQ0Lw/jv2mPW8PGy0EGIHXhQ== +"@next/eslint-plugin-next@14.1.0": + version "14.1.0" + resolved "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.1.0.tgz" + integrity sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q== dependencies: - glob "7.1.7" + glob "10.3.10" "@next/mdx@^14.0.4": - version "14.0.4" - resolved "https://registry.npmjs.org/@next/mdx/-/mdx-14.0.4.tgz" - integrity sha512-w0b+A2LRdlqqTIzmaeqPOaafid2cYYYjETA+G+3ZFwkNbBQjvZp57P1waOexF3MGHzcCEoXEnhYpAc+FO6S0Rg== + version "14.1.0" + resolved "https://registry.npmjs.org/@next/mdx/-/mdx-14.1.0.tgz" + integrity sha512-YLYsViq91+H8+3oCtK1iuMWdeN14K70Hy6/tYScY+nfo5bQ84A/A+vA6UdNC9MkbWQ/373hQubx2p4JvUjlb2Q== dependencies: source-map "^0.7.0" "@next/swc-darwin-arm64@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz#da9f04c34a3d5f0b8401ed745768420e4a604036" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz#da9f04c34a3d5f0b8401ed745768420e4a604036" integrity sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg== "@next/swc-darwin-x64@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz#46dedb29ec5503bf171a72a3ecb8aac6e738e9d6" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz#46dedb29ec5503bf171a72a3ecb8aac6e738e9d6" integrity sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg== "@next/swc-linux-arm64-gnu@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz#c9697ab9eb422bd1d7ffd0eb0779cc2aefa9d4a1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz#c9697ab9eb422bd1d7ffd0eb0779cc2aefa9d4a1" integrity sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ== "@next/swc-linux-arm64-musl@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz#cbbceb2008571c743b5a310a488d2e166d200a75" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz#cbbceb2008571c743b5a310a488d2e166d200a75" integrity sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A== "@next/swc-linux-x64-gnu@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz#d79184223f857bacffb92f643cb2943a43632568" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz#d79184223f857bacffb92f643cb2943a43632568" integrity sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q== "@next/swc-linux-x64-musl@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz#6b6c3e5ac02ca5e63394d280ec8ee607491902df" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz#6b6c3e5ac02ca5e63394d280ec8ee607491902df" integrity sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ== "@next/swc-win32-arm64-msvc@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz#dbad3906e870dba84c5883d9d4c4838472e0697f" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz#dbad3906e870dba84c5883d9d4c4838472e0697f" integrity sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A== "@next/swc-win32-ia32-msvc@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz#6074529b91ba49132922ce89a2e16d25d2ec235d" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz#6074529b91ba49132922ce89a2e16d25d2ec235d" integrity sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag== "@next/swc-win32-x64-msvc@14.2.4": version "14.2.4" - resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz#e65a1c6539a671f97bb86d5183d6e3a1733c29c7" + resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz" integrity sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg== "@nodelib/fs.scandir@2.1.5": @@ -760,21 +748,26 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/utils@^2.3.1": - version "2.4.2" - resolved "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz" - integrity sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw== + version "2.4.1" + resolved "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz" + integrity sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w== dependencies: cross-spawn "^7.0.3" - fast-glob "^3.3.0" + fast-glob "^3.2.12" is-glob "^4.0.3" open "^9.1.0" picocolors "^1.0.0" - tslib "^2.6.0" + tslib "^2.5.0" "@reactflow/background@11.3.13": version "11.3.13" - resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.13.tgz#a29bcdce01b5e881a330067bfd08c58c12fc7266" + resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.13.tgz" integrity sha512-hkvpVEhgvfTDyCvdlitw4ioKCYLaaiRXnuEG+1QM3Np+7N1DiWF1XOv5I8AFyNoJL07yXEkbECUTsHvkBvcG5A== dependencies: "@reactflow/core" "11.11.3" @@ -783,7 +776,7 @@ "@reactflow/controls@11.2.13": version "11.2.13" - resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.2.13.tgz#a05d86b82fc49e8ed0ca35d04c838a45f90f4f15" + resolved "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.13.tgz" integrity sha512-3xgEg6ALIVkAQCS4NiBjb7ad8Cb3D8CtA7Vvl4Hf5Ar2PIVs6FOaeft9s2iDZGtsWP35ECDYId1rIFVhQL8r+A== dependencies: "@reactflow/core" "11.11.3" @@ -792,7 +785,7 @@ "@reactflow/core@11.11.3": version "11.11.3" - resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.11.3.tgz#2cdc0c684931918125d505bec3cb94f115b87747" + resolved "https://registry.npmjs.org/@reactflow/core/-/core-11.11.3.tgz" integrity sha512-+adHdUa7fJSEM93fWfjQwyWXeI92a1eLKwWbIstoCakHpL8UjzwhEh6sn+mN2h/59MlVI7Ehr1iGTt3MsfcIFA== dependencies: "@types/d3" "^7.4.0" @@ -807,7 +800,7 @@ "@reactflow/minimap@11.7.13": version "11.7.13" - resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.7.13.tgz#99396175065a1e2d058b8639883d13c71777764f" + resolved "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.13.tgz" integrity sha512-m2MvdiGSyOu44LEcERDEl1Aj6x//UQRWo3HEAejNU4HQTlJnYrSN8tgrYF8TxC1+c/9UdyzQY5VYgrTwW4QWdg== dependencies: "@reactflow/core" "11.11.3" @@ -820,7 +813,7 @@ "@reactflow/node-resizer@2.2.13": version "2.2.13" - resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.2.13.tgz#83faf6e2977f40b528bf100c0da634e08f8fb51a" + resolved "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.13.tgz" integrity sha512-X7ceQ2s3jFLgbkg03n2RYr4hm3jTVrzkW2W/8ANv/SZfuVmF8XJxlERuD8Eka5voKqLda0ywIZGAbw9GoHLfUQ== dependencies: "@reactflow/core" "11.11.3" @@ -831,7 +824,7 @@ "@reactflow/node-toolbar@1.3.13": version "1.3.13" - resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.3.13.tgz#f15a2e6ed89287a33c4305d3bd7f3991b85db4af" + resolved "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.13.tgz" integrity sha512-aknvNICO10uWdthFSpgD6ctY/CTBeJUMV9co8T9Ilugr08Nb89IQ4uD0dPmr031ewMQxixtYIkw+sSDDzd2aaQ== dependencies: "@reactflow/core" "11.11.3" @@ -840,7 +833,7 @@ "@remixicon/react@^4.2.0": version "4.2.0" - resolved "https://registry.yarnpkg.com/@remixicon/react/-/react-4.2.0.tgz#9093ea394e288ee9a88d0613bd38739c728f02ac" + resolved "https://registry.npmjs.org/@remixicon/react/-/react-4.2.0.tgz" integrity sha512-eGhKpZ88OU0qkcY9pJu6khBmItDV82nU130E6C68yc+FbljueHlUYy/4CrJsmf860RIDMay2Rpzl27OSJ81miw== "@rgrove/parse-xml@^4.1.0": @@ -849,82 +842,82 @@ integrity sha512-pBiltENdy8SfI0AeR1e5TRpS9/9Gl0eiOEt6ful2jQfzsgvZYWqsKiBWaOCLdocQuk0wS7KOHI37n0C1pnKqTw== "@rushstack/eslint-patch@^1.3.3": - version "1.6.1" - resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz" - integrity sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw== + version "1.7.2" + resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz" + integrity sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA== -"@sentry-internal/tracing@7.60.1": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.60.1.tgz" - integrity sha512-2vM+3/ddzmoBfi92OOD9FFTHXf0HdQhKtNM26+/RsmkKnTid+/inbvA7nKi+Qa7ExcnlC6eclEHQEg+0X3yDkQ== +"@sentry-internal/tracing@7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.54.0.tgz" + integrity sha512-JsyhZ0wWZ+VqbHJg+azqRGdYJDkcI5R9+pnkO6SzbzxrRewqMAIwzkpPee3oI7vG99uhMEkOkMjHu0nQGwkOQw== dependencies: - "@sentry/core" "7.60.1" - "@sentry/types" "7.60.1" - "@sentry/utils" "7.60.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry/core" "7.54.0" + "@sentry/types" "7.54.0" + "@sentry/utils" "7.54.0" + tslib "^1.9.3" -"@sentry/browser@7.60.1": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/browser/-/browser-7.60.1.tgz" - integrity sha512-opZQee3S0c459LXt8YGpwOM/qiTlzluHEEnfW2q+D2yVCWh8iegsDX3kbRiv4i/mtQu9yPhM9M761KDnc/0eZw== +"@sentry/browser@7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/browser/-/browser-7.54.0.tgz" + integrity sha512-EvLAw03N9WE2m1CMl2/1YMeIs1icw9IEOVJhWmf3uJEysNJOFWXu6ZzdtHEz1E6DiJYhc1HzDya0ExZeJxNARA== dependencies: - "@sentry-internal/tracing" "7.60.1" - "@sentry/core" "7.60.1" - "@sentry/replay" "7.60.1" - "@sentry/types" "7.60.1" - "@sentry/utils" "7.60.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry-internal/tracing" "7.54.0" + "@sentry/core" "7.54.0" + "@sentry/replay" "7.54.0" + "@sentry/types" "7.54.0" + "@sentry/utils" "7.54.0" + tslib "^1.9.3" -"@sentry/core@7.60.1": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/core/-/core-7.60.1.tgz" - integrity sha512-yr/0VFYWOJyXj+F2nifkRYxXskotsNnDggUnFOZZN2ZgTG94IzRFsOZQ6RslHJ8nrYPTBNO74reU0C0GB++xRw== +"@sentry/core@7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/core/-/core-7.54.0.tgz" + integrity sha512-MAn0E2EwgNn1pFQn4qxhU+1kz6edullWg6VE5wCmtpXWOVw6sILBUsQpeIG5djBKMcneJCdOlz5jeqcKPrLvZQ== dependencies: - "@sentry/types" "7.60.1" - "@sentry/utils" "7.60.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry/types" "7.54.0" + "@sentry/utils" "7.54.0" + tslib "^1.9.3" "@sentry/react@^7.54.0": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/react/-/react-7.60.1.tgz" - integrity sha512-977wb5gp7SHv9kHPs1HZtL60slt2WBFY9/YJI9Av7BjjJ/A89OhtBwbVhIcKXZ4hwHQVWuOiFCJdMrIfZXpFPA== + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/react/-/react-7.54.0.tgz" + integrity sha512-qUbwmRRpTh05m2rbC8A2zAFQYsoHhwIpxT5UXxh0P64ZlA3cSg1/DmTTgwnd1l+7gzKrc31UikXQ4y0YDbMNKg== dependencies: - "@sentry/browser" "7.60.1" - "@sentry/types" "7.60.1" - "@sentry/utils" "7.60.1" + "@sentry/browser" "7.54.0" + "@sentry/types" "7.54.0" + "@sentry/utils" "7.54.0" hoist-non-react-statics "^3.3.2" - tslib "^2.4.1 || ^1.9.3" + tslib "^1.9.3" -"@sentry/replay@7.60.1": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/replay/-/replay-7.60.1.tgz" - integrity sha512-WHQxEpJbHICs12L17LGgS/ql91yn9wJDH/hgb+1H90HaasjoR54ofWCKul29OvYV0snTWuHd6xauwtzyv9tzvg== +"@sentry/replay@7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/replay/-/replay-7.54.0.tgz" + integrity sha512-C0F0568ybphzGmKGe23duB6n5wJcgM7WLYhoeqW3o2bHeqpj1dGPSka/K3s9KzGaAgzn1zeOUYXJsOs+T/XdsA== dependencies: - "@sentry/core" "7.60.1" - "@sentry/types" "7.60.1" - "@sentry/utils" "7.60.1" + "@sentry/core" "7.54.0" + "@sentry/types" "7.54.0" + "@sentry/utils" "7.54.0" -"@sentry/types@7.60.1": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/types/-/types-7.60.1.tgz" - integrity sha512-8lKKSCOhZ953cWxwnfZwoR3ZFFlZG4P3PQFTaFt/u4LxLh/0zYbdtgvtUqXRURjMCi5P6ddeE9Uw9FGnTJCsTw== +"@sentry/types@7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/types/-/types-7.54.0.tgz" + integrity sha512-D+i9xogBeawvQi2r0NOrM7zYcUaPuijeME4O9eOTrDF20tj71hWtJLilK+KTGLYFtpGg1h+9bPaz7OHEIyVopg== -"@sentry/utils@7.60.1", "@sentry/utils@^7.54.0": - version "7.60.1" - resolved "https://registry.npmjs.org/@sentry/utils/-/utils-7.60.1.tgz" - integrity sha512-ik+5sKGBx4DWuvf6UUKPSafaDiASxP+Xvjg3C9ppop2I/JWxP1FfZ5g22n5ZmPmNahD6clTSoTWly8qyDUlUOw== +"@sentry/utils@7.54.0", "@sentry/utils@^7.54.0": + version "7.54.0" + resolved "https://registry.npmjs.org/@sentry/utils/-/utils-7.54.0.tgz" + integrity sha512-3Yf5KlKjIcYLddOexSt2ovu2TWlR4Fi7M+aCK8yUTzwNzf/xwFSWOstHlD/WiDy9HvfhWAOB/ukNTuAeJmtasw== dependencies: - "@sentry/types" "7.60.1" - tslib "^2.4.1 || ^1.9.3" + "@sentry/types" "7.54.0" + tslib "^1.9.3" "@swc/counter@^0.1.3": version "0.1.3" - resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== "@swc/helpers@0.5.5": version "0.5.5" - resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz#12689df71bfc9b21c4f4ca00ae55f2f16c8b77c0" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz" integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A== dependencies: "@swc/counter" "^0.1.3" @@ -1197,11 +1190,11 @@ integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg== "@types/hast@^2.0.0": - version "2.3.5" - resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.5.tgz" - integrity sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg== + version "2.3.4" + resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== dependencies: - "@types/unist" "^2" + "@types/unist" "*" "@types/js-cookie@^2.x.x": version "2.2.7" @@ -1229,28 +1222,28 @@ integrity sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA== "@types/katex@^0.16.0": - version "0.16.2" - resolved "https://registry.npmjs.org/@types/katex/-/katex-0.16.2.tgz" - integrity sha512-dHsSjSlU/EWEEbeNADr3FtZZOAXPkFPUO457QCnoNqcZQXNqNEu/svQd0Nritvd3wNff4vvC/f4e6xgX3Llt8A== + version "0.16.0" + resolved "https://registry.npmjs.org/@types/katex/-/katex-0.16.0.tgz" + integrity sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw== "@types/lodash-es@^4.17.7": - version "4.17.8" - resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.8.tgz" - integrity sha512-euY3XQcZmIzSy7YH5+Unb3b2X12Wtk54YWINBvvGQ5SmMvwb11JQskGsfkH/5HXK77Kr8GF0wkVDIxzAisWtog== + version "4.17.7" + resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz" + integrity sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ== dependencies: "@types/lodash" "*" "@types/lodash@*": - version "4.14.196" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz" - integrity sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ== + version "4.14.195" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz" + integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== "@types/mdast@^3.0.0": - version "3.0.12" - resolved "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz" - integrity sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg== + version "3.0.11" + resolved "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz" + integrity sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw== dependencies: - "@types/unist" "^2" + "@types/unist" "*" "@types/mdx@^2.0.0": version "2.0.5" @@ -1296,7 +1289,7 @@ "@types/react-dom@~18.2.0": version "18.2.25" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.25.tgz#2946a30081f53e7c8d585eb138277245caedc521" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.25.tgz" integrity sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA== dependencies: "@types/react" "*" @@ -1332,7 +1325,7 @@ "@types/react@*", "@types/react@>=16", "@types/react@~18.2.0": version "18.2.79" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.79.tgz#c40efb4f255711f554d47b449f796d1c7756d865" + resolved "https://registry.npmjs.org/@types/react/-/react-18.2.79.tgz" integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w== dependencies: "@types/prop-types" "*" @@ -1353,15 +1346,10 @@ resolved "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.1.tgz" integrity sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ== -"@types/unist@^2": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc" - integrity sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA== - -"@types/unist@^2.0.0", "@types/unist@^2.0.2": - version "2.0.7" - resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.7.tgz" - integrity sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g== +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== "@types/uuid@^9.0.8": version "9.0.8" @@ -1369,87 +1357,87 @@ integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA== "@typescript-eslint/eslint-plugin@^5.53.0": - version "5.62.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz" + integrity sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/type-utils" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" - graphemer "^1.4.0" + grapheme-splitter "^1.0.4" ignore "^5.2.0" natural-compare-lite "^1.4.0" semver "^7.3.7" tsutils "^3.21.0" "@typescript-eslint/parser@^5.4.2 || ^6.0.0", "@typescript-eslint/parser@^5.53.0": - version "5.62.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz" + integrity sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ== dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== +"@typescript-eslint/scope-manager@5.59.9": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz" + integrity sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== +"@typescript-eslint/type-utils@5.59.9": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz" + integrity sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q== dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@typescript-eslint/typescript-estree" "5.59.9" + "@typescript-eslint/utils" "5.59.9" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@5.59.9": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz" + integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw== -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== +"@typescript-eslint/typescript-estree@5.59.9": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz" + integrity sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/visitor-keys" "5.59.9" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.53.0": - version "5.62.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== +"@typescript-eslint/utils@5.59.9", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.53.0": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.9.tgz" + integrity sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/scope-manager" "5.59.9" + "@typescript-eslint/types" "5.59.9" + "@typescript-eslint/typescript-estree" "5.59.9" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== +"@typescript-eslint/visitor-keys@5.59.9": + version "5.59.9" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz" + integrity sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q== dependencies: - "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/types" "5.59.9" eslint-visitor-keys "^3.3.0" "@vue/compiler-core@3.4.25": @@ -1481,15 +1469,10 @@ acorn-jsx@^5.0.0, acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.0.0, acorn@^8.5.0: - version "8.10.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -acorn@^8.9.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" - integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== +acorn@^8.0.0, acorn@^8.5.0, acorn@^8.8.0: + version "8.8.2" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== aggregate-error@^3.0.0: version "3.1.0" @@ -1505,9 +1488,9 @@ ahooks-v3-count@^1.0.0: integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ== ahooks@^3.7.5: - version "3.7.8" - resolved "https://registry.npmjs.org/ahooks/-/ahooks-3.7.8.tgz" - integrity sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA== + version "3.7.7" + resolved "https://registry.npmjs.org/ahooks/-/ahooks-3.7.7.tgz" + integrity sha512-5e5WlPq81Y84UnTLOKIQeq2cJw4aa7yj8fR2Nb/oMmXPrWMjIMCbPS1o+fpxSfCaNA3AzOnnMc8AehWRZltkJQ== dependencies: "@babel/runtime" "^7.21.0" "@types/js-cookie" "^2.x.x" @@ -1561,7 +1544,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.0.0: +ansi-styles@^6.0.0, ansi-styles@^6.1.0: version "6.2.1" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== @@ -1589,12 +1572,12 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== +aria-query@^5.1.3: + version "5.1.3" + resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== dependencies: - dequal "^2.0.3" + deep-equal "^2.0.5" array-buffer-byte-length@^1.0.0: version "1.0.0" @@ -1604,15 +1587,7 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - -array-includes@^3.1.6, array-includes@^3.1.7: +array-includes@^3.1.5, array-includes@^3.1.6, array-includes@^3.1.7: version "3.1.7" resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz" integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== @@ -1639,7 +1614,7 @@ array.prototype.findlastindex@^1.2.3: es-shim-unscopables "^1.0.0" get-intrinsic "^1.2.1" -array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: +array.prototype.flat@^1.3.2: version "1.3.2" resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz" integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== @@ -1660,15 +1635,15 @@ array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: es-shim-unscopables "^1.0.0" array.prototype.tosorted@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz" - integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ== + version "1.1.2" + resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz" + integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.1" arraybuffer.prototype.slice@^1.0.2: version "1.0.2" @@ -1683,24 +1658,10 @@ arraybuffer.prototype.slice@^1.0.2: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - -ast-types-flow@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" - integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz" + integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== astral-regex@^2.0.0: version "2.0.0" @@ -1743,24 +1704,17 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" +axe-core@^4.6.2: + version "4.7.2" + resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz" + integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== -axe-core@=4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" - integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== - -axobject-query@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" - integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== +axobject-query@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz" + integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== dependencies: - dequal "^2.0.3" + deep-equal "^2.0.5" bail@^2.0.0: version "2.0.2" @@ -1802,6 +1756,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -1810,12 +1771,12 @@ braces@^3.0.2, braces@~3.0.2: fill-range "^7.1.1" browserslist@^4.21.5: - version "4.22.3" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz" - integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== + version "4.23.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== dependencies: - caniuse-lite "^1.0.30001580" - electron-to-chromium "^1.4.648" + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" node-releases "^2.0.14" update-browserslist-db "^1.0.13" @@ -1854,17 +1815,6 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" -call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -1875,22 +1825,17 @@ camelcase-css@^2.0.1: resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001580: - version "1.0.30001581" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz" - integrity sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ== - -caniuse-lite@^1.0.30001579: - version "1.0.30001636" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78" - integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg== +caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587: + version "1.0.30001620" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz" + integrity sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew== ccount@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@4.1.1: +chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== @@ -1912,14 +1857,6 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.1: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - character-entities-html4@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz" @@ -1977,7 +1914,7 @@ ci-info@^3.6.1: class-variance-authority@^0.7.0: version "0.7.0" - resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz" integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== dependencies: clsx "2.0.0" @@ -2039,7 +1976,7 @@ client-only@0.0.1, client-only@^0.0.1: clsx@2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== code-inspector-core@0.13.0: @@ -2169,7 +2106,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2499,37 +2436,10 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - dayjs@^1.11.7, dayjs@^1.9.1: - version "1.11.9" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz" - integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== + version "1.11.8" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz" + integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== debug@^3.2.7: version "3.2.7" @@ -2552,6 +2462,30 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" +deep-equal@^2.0.5: + version "2.2.1" + resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz" + integrity sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.0" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" @@ -2584,15 +2518,6 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - define-lazy-prop@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz" @@ -2614,7 +2539,7 @@ delaunator@5: dependencies: robust-predicates "^3.0.0" -dequal@^2.0.0, dequal@^2.0.3: +dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -2709,17 +2634,17 @@ echarts-for-react@^3.0.2: size-sensor "^1.0.1" echarts@^5.4.1: - version "5.4.3" - resolved "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz" - integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA== + version "5.4.2" + resolved "https://registry.npmjs.org/echarts/-/echarts-5.4.2.tgz" + integrity sha512-2W3vw3oI2tWJdyAz+b8DuWS0nfXtSDqlDmqgin/lfzbkB01cuMEN66KWBlmur3YMp5nEDEEt5s23pllnAzB4EA== dependencies: tslib "2.3.0" - zrender "5.4.4" + zrender "5.4.3" -electron-to-chromium@^1.4.648: - version "1.4.650" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.650.tgz" - integrity sha512-sYSQhJCJa4aGA1wYol5cMQgekDBlbVfTRavlGZVr3WZpDdOPcp6a6xUnFfrt8TqZhsBYYbDxJZCjGfHuGupCRQ== +electron-to-chromium@^1.4.668: + version "1.4.775" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.775.tgz" + integrity sha512-JpOfl1aNAiZ88wFzjPczTLwYIoPIsij8S9/XQH9lqMpiJOf23kxea68B8wje4f68t4rOIq4Bh+vP4I65njiJBw== elkjs@^0.8.2: version "0.8.2" @@ -2742,9 +2667,9 @@ emoji-regex@^9.2.2: integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== enhanced-resolve@^5.12.0: - version "5.15.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + version "5.16.1" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz" + integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -2806,69 +2731,20 @@ es-abstract@^1.20.4, es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.3: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" + call-bind "^1.0.2" + get-intrinsic "^1.1.3" has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" - -es-errors@^1.2.1, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" es-iterator-helpers@^1.0.12: version "1.0.15" @@ -2890,33 +2766,6 @@ es-iterator-helpers@^1.0.12: iterator.prototype "^1.1.2" safe-array-concat "^1.0.1" -es-iterator-helpers@^1.0.15: - version "1.0.19" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" - integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.3" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.2" - safe-array-concat "^1.1.2" - -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - es-set-tostringtag@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz" @@ -2926,15 +2775,6 @@ es-set-tostringtag@^2.0.1: has "^1.0.3" has-tostringtag "^1.0.0" -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz" @@ -2951,10 +2791,10 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== escape-string-regexp@^1.0.5: version "1.0.5" @@ -2972,11 +2812,11 @@ escape-string-regexp@^5.0.0: integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== eslint-config-next@^14.0.4: - version "14.0.4" - resolved "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.4.tgz" - integrity sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ== + version "14.1.0" + resolved "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.1.0.tgz" + integrity sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg== dependencies: - "@next/eslint-plugin-next" "14.0.4" + "@next/eslint-plugin-next" "14.1.0" "@rushstack/eslint-patch" "^1.3.3" "@typescript-eslint/parser" "^5.4.2 || ^6.0.0" eslint-import-resolver-node "^0.3.6" @@ -3070,42 +2910,42 @@ eslint-plugin-import@^2.27.5, eslint-plugin-import@^2.28.1: tsconfig-paths "^3.15.0" eslint-plugin-jest@^27.2.1: - version "27.2.3" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz" - integrity sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ== + version "27.2.1" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz" + integrity sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg== dependencies: "@typescript-eslint/utils" "^5.10.0" eslint-plugin-jsonc@^2.6.0: - version "2.9.0" - resolved "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.9.0.tgz" - integrity sha512-RK+LeONVukbLwT2+t7/OY54NJRccTXh/QbnXzPuTLpFMVZhPuq1C9E07+qWenGx7rrQl0kAalAWl7EmB+RjpGA== + version "2.8.0" + resolved "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.8.0.tgz" + integrity sha512-K4VsnztnNwpm+V49CcCu5laq8VjclJpuhfI9LFkOrOyK+BKdQHMzkWo43B4X4rYaVrChm4U9kw/tTU5RHh5Wtg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" jsonc-eslint-parser "^2.0.4" natural-compare "^1.4.0" eslint-plugin-jsx-a11y@^6.7.1: - version "6.8.0" - resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz" - integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + version "6.7.1" + resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz" + integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== dependencies: - "@babel/runtime" "^7.23.2" - aria-query "^5.3.0" - array-includes "^3.1.7" - array.prototype.flatmap "^1.3.2" - ast-types-flow "^0.0.8" - axe-core "=4.7.0" - axobject-query "^3.2.1" + "@babel/runtime" "^7.20.7" + aria-query "^5.1.3" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + ast-types-flow "^0.0.7" + axe-core "^4.6.2" + axobject-query "^3.1.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - es-iterator-helpers "^1.0.15" - hasown "^2.0.0" - jsx-ast-utils "^3.3.5" - language-tags "^1.0.9" + has "^1.0.3" + jsx-ast-utils "^3.3.3" + language-tags "=1.0.5" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + semver "^6.3.0" eslint-plugin-markdown@^3.0.0: version "3.0.0" @@ -3139,9 +2979,9 @@ eslint-plugin-promise@^6.1.1: integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== "eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": - version "4.6.0" - resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz" - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + version "5.0.0-canary-7118f5dd7-20230705" + resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz" + integrity sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw== eslint-plugin-react@^7.33.2: version "7.33.2" @@ -3195,9 +3035,9 @@ eslint-plugin-unused-imports@^2.0.0: eslint-rule-composer "^0.3.0" eslint-plugin-vue@^9.9.0: - version "9.15.1" - resolved "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz" - integrity sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A== + version "9.14.1" + resolved "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz" + integrity sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw== dependencies: "@eslint-community/eslint-utils" "^4.3.0" natural-compare "^1.4.0" @@ -3208,9 +3048,9 @@ eslint-plugin-vue@^9.9.0: xml-name-validator "^4.0.0" eslint-plugin-yml@^1.5.0: - version "1.8.0" - resolved "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.8.0.tgz" - integrity sha512-fgBiJvXD0P2IN7SARDJ2J7mx8t0bLdG6Zcig4ufOqW5hOvSiFxeUyc2g5I1uIm8AExbo26NNYCcTGZT0MXTsyg== + version "1.7.0" + resolved "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.7.0.tgz" + integrity sha512-qq61FQJk+qIgWl0R06bec7UQQEIBrUH22jS+MroTbFUKu+3/iVlGRpZd8mjpOAm/+H/WEDFwy4x/+kKgVGbsWw== dependencies: debug "^4.3.2" lodash "^4.17.21" @@ -3231,9 +3071,9 @@ eslint-scope@^5.1.1: estraverse "^4.1.1" eslint-scope@^7.1.1: - version "7.2.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz" - integrity sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA== + version "7.2.0" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -3313,12 +3153,12 @@ eslint@^8.36.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.0.0, espree@^9.3.1, espree@^9.5.0, espree@^9.6.0: - version "9.6.1" - resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^9.0.0, espree@^9.3.1, espree@^9.5.0, espree@^9.5.2: + version "9.5.2" + resolved "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: - acorn "^8.9.0" + acorn "^8.8.0" acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" @@ -3417,9 +3257,9 @@ execa@^5.0.0: strip-final-newline "^2.0.0" execa@^7.0.0, execa@^7.1.1: - version "7.2.0" - resolved "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + version "7.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz" + integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== dependencies: cross-spawn "^7.0.3" get-stream "^6.0.1" @@ -3441,7 +3281,18 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.11, fast-glob@^3.2.12: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.1" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== @@ -3526,6 +3377,14 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + format@^0.2.0: version "0.2.2" resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz" @@ -3542,9 +3401,9 @@ fs.realpath@^1.0.0: integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" @@ -3576,17 +3435,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" -get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -3600,19 +3448,10 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - get-tsconfig@^4.5.0: - version "4.6.2" - resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz" - integrity sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg== + version "4.6.0" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz" + integrity sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg== dependencies: resolve-pkg-maps "^1.0.0" @@ -3630,6 +3469,17 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@10.3.10: + version "10.3.10" + resolved "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + glob@7.1.6: version "7.1.6" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" @@ -3642,15 +3492,15 @@ glob@7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.7, glob@^7.1.3: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -3681,13 +3531,13 @@ globby@^11.1.0: slash "^3.0.0" globby@^13.1.3: - version "13.2.2" - resolved "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + version "13.1.4" + resolved "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz" + integrity sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g== dependencies: dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" + fast-glob "^3.2.11" + ignore "^5.2.0" merge2 "^1.4.1" slash "^4.0.0" @@ -3708,11 +3558,6 @@ grapheme-splitter@^1.0.4: resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" @@ -3728,30 +3573,18 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz" + integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== dependencies: - get-intrinsic "^1.1.1" - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" + get-intrinsic "^1.2.2" has-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" @@ -3764,13 +3597,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - has@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" @@ -3785,13 +3611,6 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -hasown@^2.0.1, hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - hast-util-from-dom@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz" @@ -3987,7 +3806,7 @@ iconv-lite@0.6: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ignore@^5.0.5, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.0.5, ignore@^5.1.1, ignore@^5.2.0: version "5.2.4" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -3998,9 +3817,9 @@ immer@^9.0.19: integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== immutable@^4.0.0: - version "4.3.1" - resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.1.tgz" - integrity sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A== + version "4.3.0" + resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz" + integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" @@ -4038,7 +3857,7 @@ inline-style-parser@0.1.1: resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== -internal-slot@^1.0.3, internal-slot@^1.0.5: +internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -4047,15 +3866,6 @@ internal-slot@^1.0.3, internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - "internmap@1 - 2": version "2.0.3" resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" @@ -4097,6 +3907,14 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz" @@ -4106,14 +3924,6 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -4170,20 +3980,13 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.9.0: +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: version "2.13.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: hasown "^2.0.0" -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" @@ -4264,7 +4067,7 @@ is-inside-container@^1.0.0: dependencies: is-docker "^3.0.0" -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== @@ -4274,11 +4077,6 @@ is-negative-zero@^2.0.2: resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - is-number-object@^1.0.4: version "1.0.7" resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" @@ -4316,7 +4114,7 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.1: +is-set@^2.0.1, is-set@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== @@ -4328,13 +4126,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" @@ -4366,13 +4157,6 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: dependencies: which-typed-array "^1.1.11" -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" @@ -4421,10 +4205,19 @@ iterator.prototype@^1.1.2: reflect.getprototypeof "^1.0.4" set-function-name "^2.0.1" -jiti@^1.18.2: - version "1.19.1" - resolved "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz" - integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.21.0: + version "1.21.6" + resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" + integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== js-audio-recorder@^1.0.7: version "1.0.7" @@ -4442,9 +4235,9 @@ js-cookie@^3.0.1: integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== js-sdsl@^4.1.4: - version "4.4.2" - resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz" - integrity sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w== + version "4.4.0" + resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -4500,15 +4293,13 @@ jsonc-eslint-parser@^2.0.4, jsonc-eslint-parser@^2.1.0: espree "^9.0.0" semver "^7.3.5" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: - version "3.3.5" - resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" + integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" + array-includes "^3.1.5" + object.assign "^4.1.3" katex@^0.16.0, katex@^0.16.10: version "0.16.10" @@ -4534,17 +4325,17 @@ lamejs@^1.2.1: dependencies: use-strict "1.0.1" -language-subtag-registry@^0.3.20: - version "0.3.23" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" - integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== +language-subtag-registry@~0.3.2: + version "0.3.22" + resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" - integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== +language-tags@=1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz" + integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== dependencies: - language-subtag-registry "^0.3.20" + language-subtag-registry "~0.3.2" layout-base@^1.0.0: version "1.0.2" @@ -4566,7 +4357,7 @@ levn@^0.4.1: lexical@0.16.0, lexical@^0.16.0: version "0.16.0" - resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.16.0.tgz#0515d4003cbfba5a5e0e3e50f32f65076a6b89e2" + resolved "https://registry.npmjs.org/lexical/-/lexical-0.16.0.tgz" integrity sha512-Skn45Qhriazq4fpAtwnAB11U//GKc4vjzx54xsV3TkDLDvWpbL4Z9TNRwRoN3g7w8AkWnqjeOSODKkrjgfRSrg== lilconfig@2.1.0, lilconfig@^2.0.5, lilconfig@^2.1.0: @@ -4580,9 +4371,9 @@ lines-and-columns@^1.1.6: integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== lint-staged@^13.2.2: - version "13.2.3" - resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.3.tgz" - integrity sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg== + version "13.2.2" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.2.tgz" + integrity sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA== dependencies: chalk "5.2.0" cli-truncate "^3.1.0" @@ -4693,6 +4484,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +"lru-cache@^9.1.1 || ^10.0.0": + version "10.2.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + markdown-extensions@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz" @@ -5377,18 +5173,30 @@ min-indent@^1.0.0: resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + mkdirp@^0.5.6: version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" @@ -5444,7 +5252,7 @@ next-nprogress-bar@^2.3.8: next@^14.1.1: version "14.2.4" - resolved "https://registry.npmjs.org/next/-/next-14.2.4.tgz#ef66c39c71e2d8ad0a3caa0383c8933f4663e4d1" + resolved "https://registry.npmjs.org/next/-/next-14.2.4.tgz" integrity sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ== dependencies: "@next/env" "14.2.4" @@ -5536,12 +5344,20 @@ object-inspect@^1.12.3, object-inspect@^1.13.1, object-inspect@^1.9.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4: +object.assign@^4.1.3, object.assign@^4.1.4: version "4.1.4" resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -5551,33 +5367,14 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - object.entries@^1.1.6: - version "1.1.7" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz" - integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== + version "1.1.6" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz" + integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== dependencies: call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -object.entries@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" + define-properties "^1.1.4" + es-abstract "^1.20.4" object.fromentries@^2.0.6, object.fromentries@^2.0.7: version "2.0.7" @@ -5599,12 +5396,12 @@ object.groupby@^1.0.1: get-intrinsic "^1.2.1" object.hasown@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz" - integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== + version "1.1.3" + resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz" + integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== dependencies: - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" object.values@^1.1.6, object.values@^1.1.7: version "1.1.7" @@ -5647,16 +5444,16 @@ open@^9.1.0: is-wsl "^2.2.0" optionator@^0.9.1: - version "0.9.3" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + version "0.9.1" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" + word-wrap "^1.2.3" p-limit@^2.2.0: version "2.3.0" @@ -5778,6 +5575,14 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" @@ -5792,10 +5597,10 @@ periscopic@^3.0.0: estree-walker "^3.0.0" is-reference "^3.0.0" -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -5812,10 +5617,15 @@ pify@^2.3.0: resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== +pinyin-pro@^3.23.0: + version "3.23.0" + resolved "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.23.0.tgz" + integrity sha512-YDwKw31PPxsr1RQzDMmHuv4Z3exaTHrVQNdVgolyhoIrsRuM3QhsoAtzYPXIaVxb5MyWCSIiEbkwvXMfy1imNA== + pirates@^4.0.1: - version "4.0.6" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + version "4.0.5" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pluralize@^8.0.0: version "8.0.0" @@ -5831,11 +5641,6 @@ portfinder@^1.0.28: debug "^3.2.7" mkdirp "^0.5.6" -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - postcss-import@^15.1.0: version "15.1.0" resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz" @@ -5956,9 +5761,9 @@ queue-microtask@^1.2.2: integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== rc-input@~1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/rc-input/-/rc-input-1.3.5.tgz" - integrity sha512-SPPwbTJa5ACHNoDdGZF/70AOqqm1Rir3WleuFBKq+nFby1zvpnzvWsHJgzWOr6uJ0GNt8dTMzBrmVGQJkTXqqQ== + version "1.3.6" + resolved "https://registry.npmjs.org/rc-input/-/rc-input-1.3.6.tgz" + integrity sha512-/HjTaKi8/Ts4zNbYaB5oWCquxFyFQO4Co1MnMgoCeGJlpe7k8Eir2HN0a0F9IHDmmo+GYiGgPpz7w/d/krzsJA== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" @@ -6002,7 +5807,7 @@ react-18-input-autosize@^3.0.0: react-dom@~18.2.0: version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" @@ -6016,9 +5821,9 @@ react-error-boundary@^3.1.4: "@babel/runtime" "^7.12.5" react-error-boundary@^4.0.2: - version "4.0.10" - resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.10.tgz" - integrity sha512-pvVKdi77j2OoPHo+p3rorgE43OjDWiqFkaqkJz8sJKK6uf/u8xtzuaVfj5qJ2JnDLIgF1De3zY5AJDijp+LVPA== + version "4.0.9" + resolved "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.9.tgz" + integrity sha512-f6DcHVdTDZmc9ixmRmuLDZpkdghYR/HKZdUzMLHD58s4cR2C4R6y4ktYztCosM6pyeK4/C8IofwqxgID25W6kw== dependencies: "@babel/runtime" "^7.12.5" @@ -6031,7 +5836,7 @@ react-headless-pagination@^1.1.4: react-hook-form@^7.51.4: version "7.51.4" - resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz#c3a47aeb22b699c45de9fc12b58763606cb52f0c" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz" integrity sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA== react-i18next@^12.2.0: @@ -6094,9 +5899,9 @@ react-papaparse@^4.1.0: papaparse "^5.3.1" react-slider@^2.0.4: - version "2.0.6" - resolved "https://registry.npmjs.org/react-slider/-/react-slider-2.0.6.tgz" - integrity sha512-gJxG1HwmuMTJ+oWIRCmVWvgwotNCbByTwRkFZC6U4MBsHqJBmxwbYRJUmxy4Tke1ef8r9jfXjgkmY/uHOCEvbA== + version "2.0.5" + resolved "https://registry.npmjs.org/react-slider/-/react-slider-2.0.5.tgz" + integrity sha512-MU5gaK1yYCKnbDDN3CMiVcgkKZwMvdqK2xUEW7fFU37NAzRgS1FZbF9N7vP08E3XXNVhiuZnwVzUa3PYQAZIMg== dependencies: prop-types "^15.8.1" @@ -6142,14 +5947,14 @@ react-window@^1.8.9: react@~18.2.0: version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" reactflow@^11.11.3: version "11.11.3" - resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.3.tgz#5e8d8b395bd443c6d10d7cef2101866ed185a1e0" + resolved "https://registry.npmjs.org/reactflow/-/reactflow-11.11.3.tgz" integrity sha512-wusd1Xpn1wgsSEv7UIa4NNraCwH9syBtubBy4xVNXg3b+CDKM+sFaF3hnMx0tr0et4km9urIDdNvwm34QiZong== dependencies: "@reactflow/background" "11.3.13" @@ -6218,17 +6023,17 @@ refractor@^3.6.0: parse-entities "^2.0.0" prismjs "~1.27.0" -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regexp-tree@^0.1.24, regexp-tree@~0.1.1: version "0.1.27" resolved "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.1: +regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -6237,16 +6042,6 @@ regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.1: define-properties "^1.2.0" set-function-name "^2.0.0" -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - regexpp@^3.0.0: version "3.2.0" resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" @@ -6342,16 +6137,7 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.22.1, resolve@^1.22.2: - version "1.22.2" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== - dependencies: - is-core-module "^2.11.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -resolve@^1.22.4: +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -6361,11 +6147,11 @@ resolve@^1.22.4: supports-preserve-symlinks-flag "^1.0.0" resolve@^2.0.0-next.4: - version "2.0.0-next.4" - resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz" - integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + version "2.0.0-next.5" + resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -6433,22 +6219,12 @@ sade@^1.7.3: mri "^1.1.0" safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + version "1.1.0" + resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz" + integrity sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - isarray "^2.0.5" - -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" + call-bind "^1.0.5" + get-intrinsic "^1.2.2" has-symbols "^1.0.3" isarray "^2.0.5" @@ -6461,15 +6237,6 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - safe-regex@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz" @@ -6483,9 +6250,9 @@ safe-regex@^2.1.1: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass@^1.61.0: - version "1.64.1" - resolved "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz" - integrity sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ== + version "1.62.1" + resolved "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz" + integrity sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -6508,15 +6275,15 @@ screenfull@^5.0.0: resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.0.0, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + version "7.6.0" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" @@ -6526,26 +6293,15 @@ server-only@^0.0.1: integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA== set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + version "1.2.0" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz" + integrity sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w== dependencies: define-data-property "^1.1.1" - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.4" + get-intrinsic "^1.2.2" gopd "^1.0.1" - has-property-descriptors "^1.0.2" + has-property-descriptors "^1.0.1" set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" @@ -6611,6 +6367,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" @@ -6715,6 +6476,13 @@ state-local@^1.0.6: resolved "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz" integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" @@ -6725,6 +6493,15 @@ string-argv@^0.3.1: resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -6734,7 +6511,7 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.0: +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -6744,17 +6521,18 @@ string-width@^5.0.0: strip-ansi "^7.0.1" string.prototype.matchall@^4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== + version "4.0.10" + resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz" + integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" + set-function-name "^2.0.0" side-channel "^1.0.4" string.prototype.trim@^1.2.8: @@ -6766,16 +6544,6 @@ string.prototype.trim@^1.2.8: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - string.prototype.trimend@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz" @@ -6785,15 +6553,6 @@ string.prototype.trimend@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - string.prototype.trimstart@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz" @@ -6803,15 +6562,6 @@ string.prototype.trimstart@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - stringify-entities@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz" @@ -6820,6 +6570,13 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6881,9 +6638,9 @@ stylis@^4.1.3: integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== sucrase@^3.32.0: - version "3.34.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz" - integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + version "3.32.0" + resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz" + integrity sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ== dependencies: "@jridgewell/gen-mapping" "^0.3.2" commander "^4.0.0" @@ -6913,9 +6670,9 @@ supports-preserve-symlinks-flag@^1.0.0: integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== swr@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz" - integrity sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ== + version "2.1.5" + resolved "https://registry.npmjs.org/swr/-/swr-2.1.5.tgz" + integrity sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw== dependencies: use-sync-external-store "^1.2.0" @@ -6932,20 +6689,25 @@ tabbable@^6.0.1: resolved "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== -tailwindcss@^3.3.3: - version "3.3.3" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz" - integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== +tailwind-merge@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz#1345209dc1f484f15159c9180610130587703042" + integrity sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A== + +tailwindcss@^3.4.4: + version "3.4.4" + resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz#351d932273e6abfa75ce7d226b5bf3a6cb257c05" + integrity sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" chokidar "^3.5.3" didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.2.12" + fast-glob "^3.3.0" glob-parent "^6.0.2" is-glob "^4.0.3" - jiti "^1.18.2" + jiti "^1.21.0" lilconfig "^2.1.0" micromatch "^4.0.5" normalize-path "^3.0.0" @@ -7051,15 +6813,15 @@ tslib@2.3.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tslib@^1.8.1: +tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1, "tslib@^2.4.1 || ^1.9.3", tslib@^2.5.0, tslib@^2.6.0: - version "2.6.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz" - integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== +tslib@^2.1.0, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.5.0: + version "2.5.3" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== tsutils@^3.21.0: version "3.21.0" @@ -7104,15 +6866,6 @@ typed-array-buffer@^1.0.0: get-intrinsic "^1.2.1" is-typed-array "^1.1.10" -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - typed-array-byte-length@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz" @@ -7123,17 +6876,6 @@ typed-array-byte-length@^1.0.0: has-proto "^1.0.1" is-typed-array "^1.1.10" -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - typed-array-byte-offset@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz" @@ -7145,18 +6887,6 @@ typed-array-byte-offset@^1.0.0: has-proto "^1.0.1" is-typed-array "^1.1.10" -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" @@ -7166,18 +6896,6 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - typescript@4.9.5: version "4.9.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" @@ -7290,12 +7008,12 @@ untildify@^4.0.0: integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + version "1.0.16" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz" + integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" uri-js@^4.2.2: version "4.4.1" @@ -7386,9 +7104,9 @@ void-elements@3.1.0: integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== vue-eslint-parser@^9.3.0: - version "9.3.1" - resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz" - integrity sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g== + version "9.3.0" + resolved "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.0.tgz" + integrity sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ== dependencies: debug "^4.3.4" eslint-scope "^7.1.1" @@ -7465,17 +7183,6 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" @@ -7483,6 +7190,20 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +word-wrap@^1.2.3: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" @@ -7501,6 +7222,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -7542,13 +7272,13 @@ yocto-queue@^0.1.0: zod@^3.23.6: version "3.23.6" - resolved "https://registry.npmjs.org/zod/-/zod-3.23.6.tgz#c08a977e2255dab1fdba933651584a05fcbf19e1" + resolved "https://registry.npmjs.org/zod/-/zod-3.23.6.tgz" integrity sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA== -zrender@5.4.4: - version "5.4.4" - resolved "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz" - integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== +zrender@5.4.3: + version "5.4.3" + resolved "https://registry.npmjs.org/zrender/-/zrender-5.4.3.tgz" + integrity sha512-DRUM4ZLnoaT0PBVvGBDO9oWIDBKFdAVieNWxWwK0niYzJCMwGchRk21/hsE+RKkIveH3XHCyvXcJDkgLVvfizQ== dependencies: tslib "2.3.0" @@ -7558,9 +7288,9 @@ zundo@^2.1.0: integrity sha512-IMhYXDZWbyGu/p3rQb1d3orhCfAyi9hGkx6N579ZtO7mWrzvBdNyGEcxciv1jtIYPKBqLSAgzKqjLguau09f9g== zustand@^4.4.1, zustand@^4.5.2: - version "4.5.2" - resolved "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz" - integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g== + version "4.5.4" + resolved "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz" + integrity sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg== dependencies: use-sync-external-store "1.2.0"