diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index 394f36c3ff..c100f53078 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -1,19 +1,21 @@ from flask import request -from flask_restful import marshal, reqparse +from flask_restful import marshal, marshal_with, reqparse from werkzeug.exceptions import Forbidden, NotFound import services.dataset_service from controllers.service_api import api from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError -from controllers.service_api.wraps import DatasetApiResource +from controllers.service_api.wraps import DatasetApiResource, validate_dataset_token from core.model_runtime.entities.model_entities import ModelType from core.plugin.entities.plugin import ModelProviderID from core.provider_manager import ProviderManager from fields.dataset_fields import dataset_detail_fields +from fields.tag_fields import tag_fields from libs.login import current_user from models.dataset import Dataset, DatasetPermissionEnum from services.dataset_service import DatasetPermissionService, DatasetService from services.entities.knowledge_entities.knowledge_entities import RetrievalModel +from services.tag_service import TagService def _validate_name(name): @@ -320,5 +322,134 @@ class DatasetApi(DatasetApiResource): raise DatasetInUseError() +class DatasetTagsApi(DatasetApiResource): + @validate_dataset_token + @marshal_with(tag_fields) + def get(self, _, dataset_id): + """Get all knowledge type tags.""" + tags = TagService.get_tags("knowledge", current_user.current_tenant_id) + + return tags, 200 + + @validate_dataset_token + def post(self, _, dataset_id): + """Add a knowledge type tag.""" + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument( + "name", + nullable=False, + required=True, + help="Name must be between 1 to 50 characters.", + type=DatasetTagsApi._validate_tag_name, + ) + + args = parser.parse_args() + args["type"] = "knowledge" + tag = TagService.save_tags(args) + + response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0} + + return response, 200 + + @validate_dataset_token + def patch(self, _, dataset_id): + if not (current_user.is_editor or current_user.is_dataset_editor): + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument( + "name", + nullable=False, + required=True, + help="Name must be between 1 to 50 characters.", + type=DatasetTagsApi._validate_tag_name, + ) + parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str) + args = parser.parse_args() + tag = TagService.update_tags(args, args.get("tag_id")) + + binding_count = TagService.get_tag_binding_count(args.get("tag_id")) + + response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": binding_count} + + return response, 200 + + @validate_dataset_token + def delete(self, _, dataset_id): + """Delete a knowledge type tag.""" + if not current_user.is_editor: + raise Forbidden() + parser = reqparse.RequestParser() + parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str) + args = parser.parse_args() + TagService.delete_tag(args.get("tag_id")) + + return 204 + + @staticmethod + def _validate_tag_name(name): + if not name or len(name) < 1 or len(name) > 50: + raise ValueError("Name must be between 1 to 50 characters.") + return name + + +class DatasetTagBindingApi(DatasetApiResource): + @validate_dataset_token + def post(self, _, dataset_id): + # 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() + parser.add_argument( + "tag_ids", type=list, nullable=False, required=True, location="json", help="Tag IDs is required." + ) + parser.add_argument( + "target_id", type=str, nullable=False, required=True, location="json", help="Target Dataset ID is required." + ) + + args = parser.parse_args() + args["type"] = "knowledge" + TagService.save_tag_binding(args) + + return 204 + + +class DatasetTagUnbindingApi(DatasetApiResource): + @validate_dataset_token + def post(self, _, dataset_id): + # 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() + parser.add_argument("tag_id", type=str, nullable=False, required=True, help="Tag ID is required.") + parser.add_argument("target_id", type=str, nullable=False, required=True, help="Target ID is required.") + + args = parser.parse_args() + args["type"] = "knowledge" + TagService.delete_tag_binding(args) + + return 204 + + +class DatasetTagsBindingStatusApi(DatasetApiResource): + @validate_dataset_token + def get(self, _, *args, **kwargs): + """Get all knowledge type tags.""" + dataset_id = kwargs.get("dataset_id") + tags = TagService.get_tags_by_target_id("knowledge", current_user.current_tenant_id, str(dataset_id)) + tags_list = [{"id": tag.id, "name": tag.name} for tag in tags] + response = {"data": tags_list, "total": len(tags)} + return response, 200 + + api.add_resource(DatasetListApi, "/datasets") api.add_resource(DatasetApi, "/datasets/") +api.add_resource(DatasetTagsApi, "/datasets/tags") +api.add_resource(DatasetTagBindingApi, "/datasets/tags/binding") +api.add_resource(DatasetTagUnbindingApi, "/datasets/tags/unbinding") +api.add_resource(DatasetTagsBindingStatusApi, "/datasets//tags") diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 21cb861f87..be748e8dd1 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -44,6 +44,17 @@ class TagService: results = [tag_binding.target_id for tag_binding in tag_bindings] return results + @staticmethod + def get_tag_by_tag_name(tag_type: str, current_tenant_id: str, tag_name: str) -> list: + tags = ( + db.session.query(Tag) + .filter(Tag.name == tag_name, Tag.tenant_id == current_tenant_id, Tag.type == tag_type) + .all() + ) + if not tags: + return [] + return tags + @staticmethod def get_tags_by_target_id(tag_type: str, current_tenant_id: str, target_id: str) -> list: tags = ( @@ -62,6 +73,8 @@ class TagService: @staticmethod def save_tags(args: dict) -> Tag: + if TagService.get_tag_by_tag_name(args["type"], current_user.current_tenant_id, args["name"]): + raise ValueError("Tag name already exists") tag = Tag( id=str(uuid.uuid4()), name=args["name"], @@ -75,6 +88,8 @@ class TagService: @staticmethod def update_tags(args: dict, tag_id: str) -> Tag: + if TagService.get_tag_by_tag_name(args["type"], current_user.current_tenant_id, args["name"]): + raise ValueError("Tag name already exists") tag = db.session.query(Tag).filter(Tag.id == tag_id).first() if not tag: raise NotFound("Tag not found") diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index 1a0979b412..4d00b7b2b5 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -2349,6 +2349,316 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi
+Okay, I will translate the Chinese text in your document while keeping all formatting and code content unchanged. + + + + + ### Request Body + + + (text) New tag name, required, maximum length 50 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + + +
+ + + + + ### Request Body + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + + + + +
+ + + + + ### Request Body + + + (text) Modified tag name, required, maximum length 50 + + + (text) Tag ID, required + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + +
+ + + + + + ### Request Body + + + (text) Tag ID, required + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + + + + +
+ + + + + ### Request Body + + + (list) List of Tag IDs, required + + + (text) Dataset ID, required + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + +
+ + + + + ### Request Body + + + (text) Tag ID, required + + + (text) Dataset ID, required + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + + +
+ + + + + ### Path + + + (text) Dataset ID + + + + + /tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets//tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + + + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + + + + + +
+ diff --git a/web/app/(commonLayout)/datasets/template/template.ja.mdx b/web/app/(commonLayout)/datasets/template/template.ja.mdx index f194c8012e..b9fab19948 100644 --- a/web/app/(commonLayout)/datasets/template/template.ja.mdx +++ b/web/app/(commonLayout)/datasets/template/template.ja.mdx @@ -2001,6 +2001,313 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi +
+ + + + ### Request Body + + + (text) 新しいタグ名、必須、最大長50文字 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + + +
+ + + + + ### Request Body + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + + + + +
+ + + + + ### Request Body + + + (text) 変更後のタグ名、必須、最大長50文字 + + + (text) タグID、必須 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + +
+ + + + + + ### Request Body + + + (text) タグID、必須 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + + + + +
+ + + + + ### Request Body + + + (list) タグIDリスト、必須 + + + (text) ナレッジベースID、必須 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + +
+ + + + + ### Request Body + + + (text) タグID、必須 + + + (text) ナレッジベースID、必須 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + + +
+ + + + + ### Path + + + (text) ナレッジベースID + + + + + /tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets//tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + + + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + + + + +
diff --git a/web/app/(commonLayout)/datasets/template/template.zh.mdx b/web/app/(commonLayout)/datasets/template/template.zh.mdx index 95bc4d6aa3..b10f22002a 100644 --- a/web/app/(commonLayout)/datasets/template/template.zh.mdx +++ b/web/app/(commonLayout)/datasets/template/template.zh.mdx @@ -2391,6 +2391,314 @@ import { Row, Col, Properties, Property, Heading, SubProperty, PropertyInstructi +
+ + + + + ### Request Body + + + (text) 新标签名称,必填,最大长度为50 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag1"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "testtag1", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + + +
+ + + + + ### Request Body + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' + ``` + + + ```json {{ title: 'Response' }} + [ + { + "id": "39d6934c-ed36-463d-b4a7-377fa1503dc0", + "name": "testtag1", + "type": "knowledge", + "binding_count": "0" + }, + ... + ] + ``` + + + + +
+ + + + + ### Request Body + + + (text) 修改后的标签名称,必填,最大长度为50 + + + (text) 标签ID,必填 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request PATCH '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "testtag2", "tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eddb66c2-04a1-4e3a-8cb2-75abd01e12a6", + "name": "tag-renamed", + "type": "knowledge", + "binding_count": 0 + } + ``` + + + + +
+ + + + + + ### Request Body + + + (text) 标签ID,必填 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/datasets/tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "e1a0a3db-ee34-4e04-842a-81555d5316fd"}' + ``` + + + ```json {{ title: 'Response' }} + + {"result": "success"} + + ``` + + + + +
+ + + + + ### Request Body + + + (list) 标签ID列表,必填 + + + (text) 知识库ID,必填 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/binding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_ids": ["65cc29be-d072-4e26-adf4-2f727644da29","1e5348f3-d3ff-42b8-a1b7-0a86d518001a"], "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + +
+ + + + + ### Request Body + + + (text) 标签ID,必填 + + + (text) 知识库ID,必填 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/tags/unbinding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"tag_id": "1e5348f3-d3ff-42b8-a1b7-0a86d518001a", "target_id": "a932ea9f-fae1-4b2c-9b65-71c56e2cacd6"}' + ``` + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + + + +
+ + + + + ### Path + + + (text) 知识库ID + + + + + /tags' \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n`} + > + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets//tags' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + + + ```json {{ title: 'Response' }} + { + "data": + [ + {"id": "4a601f4f-f8a2-4166-ae7c-58c3b252a524", + "name": "123" + }, + ... + ], + "total": 3 + } + ``` + + + + +