mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:12:51 +00:00
feat: implement knowledge tag management functionality
This commit is contained in:
parent
56d32c1b71
commit
c47c70afb0
|
|
@ -39,8 +39,11 @@ class Group(Enum):
|
|||
SYSTEM_RES_KNOWLEDGE = "SYSTEM_RESOURCE_KNOWLEDGE"
|
||||
KNOWLEDGE_HIT_TEST = "KNOWLEDGE_HIT_TEST"
|
||||
KNOWLEDGE_DOCUMENT = "KNOWLEDGE_DOCUMENT"
|
||||
KNOWLEDGE_TAG = "KNOWLEDGE_TAG"
|
||||
SYSTEM_KNOWLEDGE_DOCUMENT = "SYSTEM_KNOWLEDGE_DOCUMENT"
|
||||
SYSTEM_RES_KNOWLEDGE_DOCUMENT = "SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT"
|
||||
SYSTEM_RES_KNOWLEDGE_TAG = "SYSTEM_RES_KNOWLEDGE_TAG"
|
||||
SYSTEM_KNOWLEDGE_TAG = "SYSTEM_KNOWLEDGE_TAG"
|
||||
|
||||
KNOWLEDGE_PROBLEM = "KNOWLEDGE_PROBLEM"
|
||||
SYSTEM_KNOWLEDGE_PROBLEM = "SYSTEM_KNOWLEDGE_PROBLEM"
|
||||
|
|
@ -696,6 +699,28 @@ class PermissionConstants(Enum):
|
|||
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],
|
||||
parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
|
||||
)
|
||||
KNOWLEDGE_TAG_READ = Permission(
|
||||
group=Group.KNOWLEDGE_TAG, operate=Operate.READ,
|
||||
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],
|
||||
parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
|
||||
)
|
||||
KNOWLEDGE_TAG_CREATE = Permission(
|
||||
group=Group.KNOWLEDGE_TAG, operate=Operate.CREATE,
|
||||
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],
|
||||
parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
|
||||
)
|
||||
KNOWLEDGE_TAG_EDIT = Permission(
|
||||
group=Group.KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],
|
||||
parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
|
||||
)
|
||||
KNOWLEDGE_TAG_DELETE = Permission(
|
||||
group=Group.KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||
resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],
|
||||
parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]
|
||||
)
|
||||
APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission(
|
||||
group=Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ,
|
||||
role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],
|
||||
|
|
@ -1199,6 +1224,22 @@ class PermissionConstants(Enum):
|
|||
group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
SHARED_KNOWLEDGE_TAG_READ = Permission(
|
||||
group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
SHARED_KNOWLEDGE_TAG_CREATE = Permission(
|
||||
group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
SHARED_KNOWLEDGE_TAG_EDIT = Permission(
|
||||
group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
SHARED_KNOWLEDGE_TAG_DELETE = Permission(
|
||||
group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
SHARED_KNOWLEDGE_PROBLEM_READ = Permission(
|
||||
group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
|
|
@ -1427,6 +1468,22 @@ class PermissionConstants(Enum):
|
|||
group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
RESOURCE_KNOWLEDGE_TAG_READ = Permission(
|
||||
group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
RESOURCE_KNOWLEDGE_TAG_CREATE = Permission(
|
||||
group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
RESOURCE_KNOWLEDGE_TAG_EDIT = Permission(
|
||||
group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
RESOURCE_KNOWLEDGE_TAG_DELETE = Permission(
|
||||
group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
)
|
||||
RESOURCE_KNOWLEDGE_CHAT_USER_READ = Permission(
|
||||
group=Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN],
|
||||
parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == "EE"
|
||||
|
|
|
|||
|
|
@ -535,3 +535,39 @@ class DocumentDownloadSourceAPI(APIMixin):
|
|||
@staticmethod
|
||||
def get_response():
|
||||
return DefaultResultSerializer
|
||||
|
||||
|
||||
class DocumentTagsAPI(APIMixin):
|
||||
@staticmethod
|
||||
def get_parameters():
|
||||
return [
|
||||
OpenApiParameter(
|
||||
name="workspace_id",
|
||||
description="工作空间id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="knowledge_id",
|
||||
description="知识库id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="document_id",
|
||||
description="文档id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_request():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_response():
|
||||
return DefaultResultSerializer
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter
|
||||
|
||||
from common.mixins.api_mixin import APIMixin
|
||||
from common.result import DefaultResultSerializer
|
||||
from knowledge.serializers.tag import TagCreateSerializer, TagEditSerializer
|
||||
|
||||
|
||||
class TagCreateAPI(APIMixin):
|
||||
@staticmethod
|
||||
def get_parameters():
|
||||
return [
|
||||
OpenApiParameter(
|
||||
name="workspace_id",
|
||||
description="工作空间id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="knowledge_id",
|
||||
description="知识库id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_request():
|
||||
return TagCreateSerializer
|
||||
|
||||
@staticmethod
|
||||
def get_response():
|
||||
return DefaultResultSerializer
|
||||
|
||||
|
||||
class TagDeleteAPI(APIMixin):
|
||||
@staticmethod
|
||||
def get_parameters():
|
||||
return [
|
||||
OpenApiParameter(
|
||||
name="workspace_id",
|
||||
description="工作空间id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="knowledge_id",
|
||||
description="知识库id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="tag_id",
|
||||
description="标签id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_request():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_response():
|
||||
return DefaultResultSerializer
|
||||
|
||||
|
||||
class TagEditAPI(APIMixin):
|
||||
@staticmethod
|
||||
def get_parameters():
|
||||
return [
|
||||
OpenApiParameter(
|
||||
name="workspace_id",
|
||||
description="工作空间id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="knowledge_id",
|
||||
description="知识库id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="tag_id",
|
||||
description="标签id",
|
||||
type=OpenApiTypes.STR,
|
||||
location='path',
|
||||
required=True,
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_request():
|
||||
return TagEditSerializer
|
||||
|
||||
@staticmethod
|
||||
def get_response():
|
||||
return DefaultResultSerializer
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Generated by Django 5.2.7 on 2025-10-11 07:38
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid_utils.compat
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('knowledge', '0002_alter_file_source_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||
('key', models.CharField(db_index=True, max_length=64, verbose_name='标签键')),
|
||||
('value', models.CharField(db_index=True, max_length=128, verbose_name='标签值')),
|
||||
('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'tag',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DocumentTag',
|
||||
fields=[
|
||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||
('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document', verbose_name='文档')),
|
||||
('tag', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.tag', verbose_name='标签')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'document_tag',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tag',
|
||||
index=models.Index(fields=['knowledge', 'key'], name='tag_knowled_cba590_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='tag',
|
||||
unique_together={('knowledge', 'key', 'value')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='documenttag',
|
||||
unique_together={('document', 'tag')},
|
||||
),
|
||||
]
|
||||
|
|
@ -162,6 +162,35 @@ class Document(AppModelMixin):
|
|||
class Meta:
|
||||
db_table = "document"
|
||||
|
||||
class Tag(AppModelMixin):
|
||||
"""
|
||||
标签表 - 存储标签的key-value定义
|
||||
"""
|
||||
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
|
||||
knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name="知识库", db_constraint=False)
|
||||
key = models.CharField(max_length=64, verbose_name="标签键", db_index=True)
|
||||
value = models.CharField(max_length=128, verbose_name="标签值", db_index=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "tag"
|
||||
unique_together = [['knowledge', 'key', 'value']] # 在同一知识库内key-value组合唯一
|
||||
indexes = [
|
||||
models.Index(fields=['knowledge', 'key']),
|
||||
]
|
||||
|
||||
|
||||
class DocumentTag(AppModelMixin):
|
||||
"""
|
||||
文档标签关联表
|
||||
"""
|
||||
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id")
|
||||
document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, verbose_name="文档", db_constraint=False)
|
||||
tag = models.ForeignKey(Tag, on_delete=models.DO_NOTHING, verbose_name="标签", db_constraint=False)
|
||||
|
||||
class Meta:
|
||||
db_table = "document_tag"
|
||||
unique_together = [['document', 'tag']] # 文档和标签的组合唯一
|
||||
|
||||
|
||||
class Paragraph(AppModelMixin):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import json
|
|||
import os
|
||||
import re
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from functools import reduce
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Dict, List
|
||||
|
|
@ -15,8 +16,8 @@ from django.core import validators
|
|||
from django.db import transaction, models
|
||||
from django.db.models import QuerySet, Func, F, Value
|
||||
from django.db.models.aggregates import Max
|
||||
from django.db.models.fields.json import KeyTextTransform
|
||||
from django.db.models.functions import Substr, Reverse, Coalesce, Cast
|
||||
from django.db.models.functions import Substr, Reverse
|
||||
from django.db.models.query_utils import Q
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import gettext_lazy as _, gettext, get_language, to_locale
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
||||
|
|
@ -47,7 +48,7 @@ from common.utils.fork import Fork
|
|||
from common.utils.logger import maxkb_logger
|
||||
from common.utils.split_model import get_split_model, flat_map
|
||||
from knowledge.models import Knowledge, Paragraph, Problem, Document, KnowledgeType, ProblemParagraphMapping, State, \
|
||||
TaskType, File, FileSourceType
|
||||
TaskType, File, FileSourceType, Tag, DocumentTag
|
||||
from knowledge.serializers.common import ProblemParagraphManage, BatchSerializer, \
|
||||
get_embedding_model_id_by_knowledge_id, MetaSerializer, write_image, zip_dir
|
||||
from knowledge.serializers.paragraph import ParagraphSerializers, ParagraphInstanceSerializer, \
|
||||
|
|
@ -1286,6 +1287,35 @@ class DocumentSerializers(serializers.Serializer):
|
|||
except AlreadyQueued as e:
|
||||
pass
|
||||
|
||||
def batch_add_tag(self, instance: Dict, with_valid=True):
|
||||
if with_valid:
|
||||
self.is_valid(raise_exception=True)
|
||||
document_id_list = instance.get("document_ids")
|
||||
tag_id_list = instance.get("tag_ids")
|
||||
# 批量查询已存在的标签关联关系
|
||||
existing_relations = {
|
||||
(str(doc_id), str(tag_id))
|
||||
for doc_id, tag_id in QuerySet(DocumentTag).filter(
|
||||
document_id__in=document_id_list,
|
||||
tag_id__in=tag_id_list
|
||||
).values_list('document_id', 'tag_id')
|
||||
}
|
||||
|
||||
# 批量创建不存在的关联关系
|
||||
new_relations = [
|
||||
DocumentTag(
|
||||
id=uuid.uuid7(),
|
||||
document_id=document_id,
|
||||
tag_id=tag_id,
|
||||
)
|
||||
for document_id in document_id_list
|
||||
for tag_id in tag_id_list
|
||||
if (document_id, tag_id) not in existing_relations
|
||||
]
|
||||
|
||||
if new_relations:
|
||||
QuerySet(DocumentTag).bulk_create(new_relations)
|
||||
|
||||
class BatchGenerateRelated(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
|
|
@ -1328,10 +1358,149 @@ class DocumentSerializers(serializers.Serializer):
|
|||
QuerySet(Document).filter(id__in=document_id_list))()
|
||||
try:
|
||||
for document_id in document_id_list:
|
||||
generate_related_by_document_id.delay(document_id, model_id, model_params_setting, prompt, state_list)
|
||||
generate_related_by_document_id.delay(
|
||||
document_id, model_id, model_params_setting, prompt, state_list
|
||||
)
|
||||
except AlreadyQueued as e:
|
||||
pass
|
||||
|
||||
class Tags(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
document_id = serializers.UUIDField(required=True, label=_('document id'))
|
||||
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value'))
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
if not QuerySet(Document).filter(
|
||||
id=self.data.get('document_id'),
|
||||
knowledge_id=self.data.get('knowledge_id')
|
||||
).exists():
|
||||
raise AppApiException(500, _('Document id does not exist'))
|
||||
|
||||
def list(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
|
||||
tag_ids = QuerySet(DocumentTag).filter(
|
||||
document_id=self.data.get('document_id')
|
||||
).values_list('tag_id', flat=True)
|
||||
|
||||
if self.data.get('name'):
|
||||
tag_ids = QuerySet(Tag).filter(
|
||||
knowledge_id=self.data.get('knowledge_id'),
|
||||
id__in=tag_ids,
|
||||
).filter(
|
||||
Q(key__icontains=self.data.get('name')) | Q(value__icontains=self.data.get('name'))
|
||||
).values_list('id', flat=True)
|
||||
|
||||
# 获取所有标签,按创建时间排序保持稳定顺序
|
||||
tags = QuerySet(Tag).filter(
|
||||
knowledge_id=self.data.get('knowledge_id'),
|
||||
id__in=tag_ids
|
||||
).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')
|
||||
|
||||
# 按key分组
|
||||
grouped_tags = defaultdict(list)
|
||||
for tag in tags:
|
||||
grouped_tags[tag['key']].append({
|
||||
'id': tag['id'],
|
||||
'value': tag['value'],
|
||||
'create_time': tag['create_time'],
|
||||
'update_time': tag['update_time']
|
||||
})
|
||||
|
||||
# 转换为期望的格式,保持key的顺序
|
||||
result = []
|
||||
# 按key排序以确保结果顺序一致
|
||||
for key in sorted(grouped_tags.keys()):
|
||||
values = grouped_tags[key]
|
||||
# 按创建时间对values进行排序
|
||||
values.sort(key=lambda x: x['create_time'])
|
||||
result.append({
|
||||
'key': key,
|
||||
'values': values,
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
class AddTags(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
document_id = serializers.UUIDField(required=True, label=_('document id'))
|
||||
tag_ids = serializers.ListField(
|
||||
required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id'))
|
||||
)
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
if not QuerySet(Document).filter(
|
||||
id=self.data.get('document_id'),
|
||||
knowledge_id=self.data.get('knowledge_id')
|
||||
).exists():
|
||||
raise AppApiException(500, _('Document id does not exist'))
|
||||
|
||||
def add_tags(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
document_id = self.data.get('document_id')
|
||||
tag_ids = self.data.get('tag_ids')
|
||||
existing_tag_ids = set(
|
||||
QuerySet(DocumentTag).filter(
|
||||
document_id=document_id, tag_id__in=tag_ids
|
||||
).values_list('tag_id', flat=True)
|
||||
)
|
||||
new_tags = [
|
||||
DocumentTag(
|
||||
id=uuid.uuid7(),
|
||||
document_id=document_id,
|
||||
tag_id=tag_id
|
||||
) for tag_id in tag_ids if tag_id not in existing_tag_ids
|
||||
]
|
||||
if new_tags:
|
||||
QuerySet(DocumentTag).bulk_create(new_tags)
|
||||
|
||||
class DeleteTags(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))
|
||||
document_id = serializers.UUIDField(required=True, label=_('document id'))
|
||||
tag_ids = serializers.ListField(
|
||||
required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id'))
|
||||
)
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
if not QuerySet(Document).filter(
|
||||
id=self.data.get('document_id'),
|
||||
knowledge_id=self.data.get('knowledge_id')
|
||||
).exists():
|
||||
raise AppApiException(500, _('Document id does not exist'))
|
||||
|
||||
def delete_tags(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
document_id = self.data.get('document_id')
|
||||
tag_ids = self.data.get('tag_ids')
|
||||
QuerySet(DocumentTag).filter(
|
||||
document_id=document_id,
|
||||
tag_id__in=tag_ids
|
||||
).delete()
|
||||
|
||||
|
||||
class FileBufferHandle:
|
||||
buffer = None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,250 @@
|
|||
# coding=utf-8
|
||||
"""
|
||||
@project: maxkb
|
||||
@Author:AI Assistant
|
||||
@file: tag.py
|
||||
@date:2025/10/13
|
||||
@desc: 标签系统相关序列化器
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from typing import Dict
|
||||
|
||||
import uuid_utils.compat as uuid
|
||||
from django.db import transaction
|
||||
from django.db.models import QuerySet
|
||||
from django.db.models.query_utils import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.exception.app_exception import AppApiException
|
||||
from knowledge.models import Tag, Knowledge, DocumentTag
|
||||
|
||||
|
||||
class TagModelSerializer(serializers.ModelSerializer):
|
||||
"""标签模型序列化器"""
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ['id', 'knowledge_id', 'key', 'value', 'create_time', 'update_time']
|
||||
read_only_fields = ['id', 'create_time', 'update_time']
|
||||
|
||||
|
||||
class TagCreateSerializer(serializers.Serializer):
|
||||
"""创建标签序列化器"""
|
||||
key = serializers.CharField(required=True, max_length=64, label=_('Tag Key'))
|
||||
value = serializers.CharField(required=True, max_length=128, label=_('Tag Value'))
|
||||
|
||||
|
||||
class TagEditSerializer(serializers.Serializer):
|
||||
key = serializers.CharField(required=False, max_length=64, label=_('Tag Key'))
|
||||
value = serializers.CharField(required=False, max_length=128, label=_('Tag Value'))
|
||||
|
||||
|
||||
class TagSerializers(serializers.Serializer):
|
||||
class Create(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))
|
||||
tags = serializers.ListField(required=True, label=_('Tags'), child=TagCreateSerializer())
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
|
||||
def insert(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
|
||||
knowledge_id = self.data.get('knowledge_id')
|
||||
|
||||
# 获取数据库中已存在的key-value组合
|
||||
existing_tags = set(
|
||||
QuerySet(Tag).filter(knowledge_id=knowledge_id)
|
||||
.values_list('key', 'value', named=False)
|
||||
)
|
||||
|
||||
# 过滤掉已存在的标签
|
||||
tag_objects = []
|
||||
for tag_data in self.data.get('tags', []):
|
||||
key = tag_data.get('key')
|
||||
value = tag_data.get('value')
|
||||
|
||||
# 检查key-value组合是否已存在
|
||||
if (key, value) not in existing_tags:
|
||||
tag = Tag(
|
||||
id=uuid.uuid7(),
|
||||
knowledge_id=knowledge_id,
|
||||
key=key,
|
||||
value=value
|
||||
)
|
||||
tag_objects.append(tag)
|
||||
# 将新标签添加到已存在集合中,避免本次批量插入中的重复
|
||||
existing_tags.add((key, value))
|
||||
|
||||
# 批量插入未重复的标签
|
||||
if tag_objects:
|
||||
Tag.objects.bulk_create(tag_objects)
|
||||
|
||||
class Operate(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))
|
||||
tag_id = serializers.UUIDField(required=True, label=_('Tag ID'))
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
|
||||
@transaction.atomic
|
||||
def edit(self, instance: Dict):
|
||||
self.is_valid(raise_exception=True)
|
||||
tag = QuerySet(Tag).get(id=self.data.get('tag_id'))
|
||||
if tag is None:
|
||||
raise AppApiException(500, _('Tag id does not exist'))
|
||||
|
||||
# 如果key发生变化,更新所有相同key的标签
|
||||
if instance.get('key') and instance.get('key') != tag.key:
|
||||
old_key = tag.key
|
||||
new_key = instance.get('key')
|
||||
|
||||
# 检查新key是否已存在于同一个knowledge中
|
||||
existing_key_exists = QuerySet(Tag).filter(
|
||||
knowledge_id=tag.knowledge_id,
|
||||
key=new_key
|
||||
).exists()
|
||||
|
||||
if existing_key_exists:
|
||||
raise AppApiException(500, _('Tag key already exists'))
|
||||
|
||||
# 批量更新所有具有相同old_key的标签
|
||||
QuerySet(Tag).filter(
|
||||
knowledge_id=tag.knowledge_id,
|
||||
key=old_key
|
||||
).update(key=new_key)
|
||||
|
||||
# 如果只是value变化,只更新当前标签
|
||||
if instance.get('value') and instance.get('value') != tag.value:
|
||||
# 检查新key是否已存在于同一个knowledge中
|
||||
existing_value_exists = QuerySet(Tag).filter(
|
||||
knowledge_id=tag.knowledge_id,
|
||||
key=instance.get('key'),
|
||||
value=instance.get('value')
|
||||
).exists()
|
||||
|
||||
if existing_value_exists:
|
||||
raise AppApiException(500, _('Tag value already exists'))
|
||||
QuerySet(Tag).filter(
|
||||
id=tag.id
|
||||
).update(value=instance.get('value'))
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, delete_type: str):
|
||||
self.is_valid(raise_exception=True)
|
||||
if delete_type == 'key':
|
||||
# 删除同一knowledge_id下相同key的所有标签
|
||||
tag = QuerySet(Tag).get(id=self.data.get('tag_id'))
|
||||
if tag is None:
|
||||
raise AppApiException(500, _('Tag id does not exist'))
|
||||
QuerySet(Tag).filter(
|
||||
knowledge_id=tag.knowledge_id,
|
||||
key=tag.key
|
||||
).delete()
|
||||
QuerySet(DocumentTag).filter(tag_id=tag.id).delete()
|
||||
else:
|
||||
# 仅删除当前标签
|
||||
QuerySet(Tag).filter(id=self.data.get('tag_id')).delete()
|
||||
QuerySet(DocumentTag).filter(tag_id=self.data.get('tag_id')).delete()
|
||||
|
||||
class BatchDelete(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))
|
||||
tag_ids = serializers.ListField(required=True, label=_('Tag IDs'), child=serializers.UUIDField())
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
|
||||
@transaction.atomic
|
||||
def batch_delete(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
tag_ids = self.data.get('tag_ids', [])
|
||||
if not tag_ids:
|
||||
return
|
||||
|
||||
# 获取要删除的标签的key
|
||||
tags_to_delete = QuerySet(Tag).filter(id__in=tag_ids)
|
||||
keys_to_delete = set(tags_to_delete.values_list('key', flat=True))
|
||||
|
||||
# 删除具有相同key的所有标签
|
||||
QuerySet(Tag).filter(
|
||||
knowledge_id=self.data.get('knowledge_id'),
|
||||
key__in=keys_to_delete
|
||||
).delete()
|
||||
|
||||
# 删除关联的DocumentTag
|
||||
QuerySet(DocumentTag).filter(tag_id__in=tag_ids).delete()
|
||||
|
||||
class Query(serializers.Serializer):
|
||||
workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))
|
||||
knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))
|
||||
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value'))
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
super().is_valid(raise_exception=True)
|
||||
workspace_id = self.data.get('workspace_id')
|
||||
query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))
|
||||
if workspace_id and workspace_id != 'None':
|
||||
query_set = query_set.filter(workspace_id=workspace_id)
|
||||
if not query_set.exists():
|
||||
raise AppApiException(500, _('Knowledge id does not exist'))
|
||||
|
||||
def list(self):
|
||||
self.is_valid(raise_exception=True)
|
||||
if self.data.get('name'):
|
||||
name = self.data.get('name')
|
||||
tags = QuerySet(Tag).filter(
|
||||
knowledge_id=self.data.get('knowledge_id')
|
||||
).filter(
|
||||
Q(key__icontains=name) | Q(value__icontains=name)
|
||||
).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')
|
||||
else:
|
||||
# 获取所有标签,按创建时间排序保持稳定顺序
|
||||
tags = QuerySet(Tag).filter(
|
||||
knowledge_id=self.data.get('knowledge_id')
|
||||
).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')
|
||||
|
||||
# 按key分组
|
||||
grouped_tags = defaultdict(list)
|
||||
for tag in tags:
|
||||
grouped_tags[tag['key']].append({
|
||||
'id': tag['id'],
|
||||
'value': tag['value'],
|
||||
'create_time': tag['create_time'],
|
||||
'update_time': tag['update_time']
|
||||
})
|
||||
|
||||
# 转换为期望的格式,保持key的顺序
|
||||
result = []
|
||||
# 按key排序以确保结果顺序一致
|
||||
for key in sorted(grouped_tags.keys()):
|
||||
values = grouped_tags[key]
|
||||
# 按创建时间对values进行排序
|
||||
values.sort(key=lambda x: x['create_time'])
|
||||
result.append({
|
||||
'key': key,
|
||||
'values': values,
|
||||
})
|
||||
|
||||
return result
|
||||
|
|
@ -19,6 +19,10 @@ urlpatterns = [
|
|||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/hit_test', views.KnowledgeView.HitTest.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/export', views.KnowledgeView.Export.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/export_zip', views.KnowledgeView.ExportZip.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags', views.KnowledgeTagView.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/batch_delete', views.KnowledgeTagView.BatchDelete.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/<str:tag_id>', views.KnowledgeTagView.Operate.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/<str:tag_id>/<str:delete_type>', views.KnowledgeTagView.Delete.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document', views.DocumentView.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/split', views.DocumentView.Split.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/split_pattern', views.DocumentView.SplitPattern.as_view()),
|
||||
|
|
@ -32,6 +36,7 @@ urlpatterns = [
|
|||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/table', views.TableDocumentView.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_hit_handling', views.DocumentView.BatchEditHitHandling.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_cancel_task', views.DocumentView.BatchCancelTask.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_add_tag', views.DocumentView.BatchAddTag.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/migrate/<str:target_knowledge_id>', views.DocumentView.Migrate.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>', views.DocumentView.Operate.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/sync', views.DocumentView.SyncWeb.as_view()),
|
||||
|
|
@ -40,6 +45,8 @@ urlpatterns = [
|
|||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/export', views.DocumentView.Export.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/export_zip', views.DocumentView.ExportZip.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/download_source_file', views.DocumentView.DownloadSourceFile.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/tags', views.DocumentView.Tags.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/tags/batch_delete', views.DocumentView.Tags.BatchDelete.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph', views.ParagraphView.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/batch_delete', views.ParagraphView.BatchDelete.as_view()),
|
||||
path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/batch_generate_related', views.ParagraphView.BatchGenerateRelated.as_view()),
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ from .document import *
|
|||
from .knowledge import *
|
||||
from .paragraph import *
|
||||
from .problem import *
|
||||
from .tag import *
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from knowledge.api.document import DocumentSplitAPI, DocumentBatchAPI, DocumentB
|
|||
DocumentReadAPI, DocumentEditAPI, DocumentDeleteAPI, TableDocumentCreateAPI, QaDocumentCreateAPI, \
|
||||
WebDocumentCreateAPI, CancelTaskAPI, BatchCancelTaskAPI, SyncWebAPI, RefreshAPI, BatchEditHitHandlingAPI, \
|
||||
DocumentTreeReadAPI, DocumentSplitPatternAPI, BatchRefreshAPI, BatchGenerateRelatedAPI, TemplateExportAPI, \
|
||||
DocumentExportAPI, DocumentMigrateAPI, DocumentDownloadSourceAPI
|
||||
DocumentExportAPI, DocumentMigrateAPI, DocumentDownloadSourceAPI, DocumentTagsAPI
|
||||
from knowledge.serializers.common import get_knowledge_operation_object
|
||||
from knowledge.serializers.document import DocumentSerializers
|
||||
from knowledge.views.common import get_knowledge_document_operation_object, get_document_operation_object_batch, \
|
||||
|
|
@ -506,6 +506,37 @@ class DocumentView(APIView):
|
|||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}
|
||||
).batch_refresh(request.data))
|
||||
|
||||
class BatchAddTag(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
methods=['POST'],
|
||||
summary=_('Batch add tags to documents'),
|
||||
operation_id=_('Batch add tags to documents'), # type: ignore
|
||||
request=DocumentTagsAPI.get_request(),
|
||||
parameters=DocumentTagsAPI.get_parameters(),
|
||||
responses=DocumentTagsAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Documentation')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),
|
||||
PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
ViewPermission([RoleConstants.USER.get_workspace_role()],
|
||||
[PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),
|
||||
)
|
||||
@log(
|
||||
menu='document', operate="Batch add tags to documents",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(
|
||||
get_knowledge_operation_object(keywords.get('knowledge_id')),
|
||||
get_document_operation_object_batch(r.data.get('document_ids'))
|
||||
),
|
||||
)
|
||||
def post(self, request: Request, workspace_id: str, knowledge_id: str):
|
||||
return result.success(DocumentSerializers.Batch(
|
||||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}
|
||||
).batch_add_tag(request.data))
|
||||
|
||||
class BatchGenerateRelated(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
|
|
@ -655,6 +686,86 @@ class DocumentView(APIView):
|
|||
'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id
|
||||
}).download_source_file()
|
||||
|
||||
class Tags(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_('Get document tags'),
|
||||
description=_('Get document tags'),
|
||||
operation_id=_('Get document tags'), # type: ignore
|
||||
parameters=DocumentTagsAPI.get_parameters(),
|
||||
responses=DocumentTagsAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Documentation')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_knowledge_permission(),
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
ViewPermission([RoleConstants.USER.get_workspace_role()],
|
||||
[PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),
|
||||
)
|
||||
def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):
|
||||
return result.success(DocumentSerializers.Tags(data={
|
||||
'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id,
|
||||
'name': request.query_params.get('name')
|
||||
}).list())
|
||||
|
||||
@extend_schema(
|
||||
summary=_('Add document tags'),
|
||||
description=_('Add document tags'),
|
||||
operation_id=_('Add document tags'), # type: ignore
|
||||
parameters=DocumentTagsAPI.get_parameters(),
|
||||
responses=DocumentTagsAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Documentation')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_knowledge_permission(),
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
ViewPermission([RoleConstants.USER.get_workspace_role()],
|
||||
[PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),
|
||||
)
|
||||
def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):
|
||||
return result.success(DocumentSerializers.AddTags(data={
|
||||
'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id,
|
||||
'tag_ids': request.data
|
||||
}).add_tags())
|
||||
|
||||
class BatchDelete(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_('Delete document tags'),
|
||||
description=_('Delete document tags'),
|
||||
operation_id=_('Delete document tags'), # type: ignore
|
||||
parameters=DocumentTagsAPI.get_parameters(),
|
||||
request=DocumentTagsAPI.get_request(),
|
||||
responses=DocumentTagsAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Documentation')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_knowledge_permission(),
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission_workspace_manage_role(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
ViewPermission([RoleConstants.USER.get_workspace_role()],
|
||||
[PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],
|
||||
CompareConstants.AND),
|
||||
)
|
||||
@log(
|
||||
menu='document', operate="Delete document tags",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(
|
||||
get_knowledge_operation_object(keywords.get('knowledge_id')),
|
||||
get_document_operation_object(keywords.get('document_id'))
|
||||
),
|
||||
)
|
||||
def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):
|
||||
return result.success(DocumentSerializers.DeleteTags(data={
|
||||
'workspace_id': workspace_id,
|
||||
'knowledge_id': knowledge_id,
|
||||
'document_id': document_id,
|
||||
'tag_ids': request.data
|
||||
}).delete_tags())
|
||||
|
||||
class Migrate(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.auth import TokenAuth
|
||||
from common.auth.authentication import has_permissions
|
||||
from common.constants.permission_constants import PermissionConstants, RoleConstants
|
||||
from common.log.log import log
|
||||
from common.result import result
|
||||
from knowledge.api.tag import TagCreateAPI, TagDeleteAPI, TagEditAPI
|
||||
from knowledge.serializers.common import get_knowledge_operation_object
|
||||
from knowledge.serializers.tag import TagSerializers
|
||||
|
||||
|
||||
class KnowledgeTagView(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_("Create Knowledge Tag"),
|
||||
description=_("Create a new knowledge tag"),
|
||||
parameters=TagCreateAPI.get_parameters(),
|
||||
request=TagCreateAPI.get_request(),
|
||||
responses=TagCreateAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Tag')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_CREATE.get_workspace_permission(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role()
|
||||
)
|
||||
@log(
|
||||
menu='tag', operate="Create a knowledge tag",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))
|
||||
)
|
||||
def post(self, request: Request, workspace_id: str, knowledge_id: str):
|
||||
return result.success(TagSerializers.Create(
|
||||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tags': request.data}
|
||||
).insert())
|
||||
|
||||
@extend_schema(
|
||||
summary=_("Get Knowledge Tag"),
|
||||
description=_("Get knowledge tag"),
|
||||
parameters=TagCreateAPI.get_parameters(),
|
||||
request=TagCreateAPI.get_request(),
|
||||
responses=TagCreateAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Tag')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role()
|
||||
)
|
||||
@log(
|
||||
menu='tag', operate="Create a knowledge tag",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))
|
||||
)
|
||||
def get(self, request: Request, workspace_id: str, knowledge_id: str):
|
||||
return result.success(TagSerializers.Query(data={
|
||||
'workspace_id': workspace_id,
|
||||
'knowledge_id': knowledge_id,
|
||||
'name': request.query_params.get('name')
|
||||
}).list())
|
||||
|
||||
class Operate(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_("Update Knowledge Tag"),
|
||||
description=_("Update a knowledge tag"),
|
||||
parameters=TagEditAPI.get_parameters(),
|
||||
request=TagEditAPI.get_request(),
|
||||
responses=TagEditAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Tag')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_EDIT.get_workspace_permission(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role()
|
||||
)
|
||||
@log(
|
||||
menu='tag', operate="Update a knowledge tag",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))
|
||||
)
|
||||
def put(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str):
|
||||
return result.success(TagSerializers.Operate(
|
||||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id}
|
||||
).edit(request.data))
|
||||
|
||||
class Delete(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_("Delete Knowledge Tag"),
|
||||
description=_("Delete a knowledge tag"),
|
||||
parameters=TagDeleteAPI.get_parameters(),
|
||||
request=TagDeleteAPI.get_request(),
|
||||
responses=TagDeleteAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Tag')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role()
|
||||
)
|
||||
@log(
|
||||
menu='tag', operate="Delete a knowledge tag",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))
|
||||
)
|
||||
def delete(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str, delete_type: str):
|
||||
return result.success(TagSerializers.Operate(
|
||||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id}
|
||||
).delete(delete_type))
|
||||
|
||||
class BatchDelete(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
@extend_schema(
|
||||
summary=_("Batch Delete Knowledge Tag"),
|
||||
description=_("Batch Delete a knowledge tag"),
|
||||
parameters=TagDeleteAPI.get_parameters(),
|
||||
request=TagDeleteAPI.get_request(),
|
||||
responses=TagDeleteAPI.get_response(),
|
||||
tags=[_('Knowledge Base/Tag')] # type: ignore
|
||||
)
|
||||
@has_permissions(
|
||||
PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission(),
|
||||
RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),
|
||||
RoleConstants.USER.get_workspace_role()
|
||||
)
|
||||
@log(
|
||||
menu='tag', operate="Batch Delete knowledge tag",
|
||||
get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))
|
||||
)
|
||||
def put(self, request: Request, workspace_id: str, knowledge_id: str):
|
||||
return result.success(TagSerializers.BatchDelete(
|
||||
data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_ids': request.data}
|
||||
).batch_delete())
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put, exportExcel, exportFile } from '@/request/index'
|
||||
import { del, exportExcel, exportFile, get, post, put } from '@/request/index'
|
||||
import type { Ref } from 'vue'
|
||||
import type { KeyValue } from '@/api/type/common'
|
||||
import type { pageRequest } from '@/api/type/common'
|
||||
import type { KeyValue, pageRequest } from '@/api/type/common'
|
||||
|
||||
import useStore from '@/stores'
|
||||
const prefix: any = { _value: '/workspace/' }
|
||||
|
||||
const prefix: any = {_value: '/workspace/'}
|
||||
Object.defineProperty(prefix, 'value', {
|
||||
get: function () {
|
||||
const { user } = useStore()
|
||||
const {user} = useStore()
|
||||
return this._value + user.getWorkspaceId() + '/knowledge'
|
||||
},
|
||||
})
|
||||
|
|
@ -18,7 +18,7 @@ Object.defineProperty(prefix, 'value', {
|
|||
* @param 参数 knowledge_id,
|
||||
* param {
|
||||
" name": "string",
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
|
|
@ -32,9 +32,9 @@ const getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise
|
|||
* 文档分页列表
|
||||
* @param 参数 knowledge_id,
|
||||
* param {
|
||||
"name": "string",
|
||||
folder_id: "string",
|
||||
}
|
||||
"name": "string",
|
||||
folder_id: "string",
|
||||
}
|
||||
*/
|
||||
|
||||
const getDocumentPage: (
|
||||
|
|
@ -69,9 +69,9 @@ const getDocumentDetail: (
|
|||
* @param 参数
|
||||
* knowledge_id, document_id,
|
||||
* {
|
||||
"name": "string",
|
||||
"is_active": true,
|
||||
"meta": {}
|
||||
"name": "string",
|
||||
"is_active": true,
|
||||
"meta": {}
|
||||
}
|
||||
*/
|
||||
const putDocument: (
|
||||
|
|
@ -99,11 +99,11 @@ const delDocument: (
|
|||
* 批量取消文档任务
|
||||
* @param 参数 knowledge_id,
|
||||
*{
|
||||
"id_list": [
|
||||
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||
],
|
||||
"type": 0
|
||||
}
|
||||
"id_list": [
|
||||
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||
],
|
||||
"type": 0
|
||||
}
|
||||
*/
|
||||
|
||||
const putBatchCancelTask: (
|
||||
|
|
@ -192,10 +192,10 @@ const exportDocumentZip: (
|
|||
* @param 参数
|
||||
* knowledge_id, document_id,
|
||||
* {
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putDocumentRefresh: (
|
||||
knowledge_id: string,
|
||||
|
|
@ -205,7 +205,7 @@ const putDocumentRefresh: (
|
|||
) => Promise<Result<any>> = (knowledge_id, document_id, state_list, loading) => {
|
||||
return put(
|
||||
`${prefix.value}/${knowledge_id}/document/${document_id}/refresh`,
|
||||
{ state_list },
|
||||
{state_list},
|
||||
undefined,
|
||||
loading,
|
||||
)
|
||||
|
|
@ -232,23 +232,23 @@ const putDocumentSync: (
|
|||
/**
|
||||
* 创建批量文档
|
||||
* @param 参数
|
||||
{
|
||||
"name": "string",
|
||||
"paragraphs": [
|
||||
{
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
],
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"source_file_id": string
|
||||
}
|
||||
{
|
||||
"name": "string",
|
||||
"paragraphs": [
|
||||
{
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
],
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"source_file_id": string
|
||||
}
|
||||
*/
|
||||
const putMulDocument: (
|
||||
knowledge_id: string,
|
||||
|
|
@ -268,8 +268,8 @@ const putMulDocument: (
|
|||
* 批量删除文档
|
||||
* @param 参数 knowledge_id,
|
||||
* {
|
||||
"id_list": [String]
|
||||
}
|
||||
"id_list": [String]
|
||||
}
|
||||
*/
|
||||
const delMulDocument: (
|
||||
knowledge_id: string,
|
||||
|
|
@ -278,7 +278,7 @@ const delMulDocument: (
|
|||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return put(
|
||||
`${prefix.value}/${knowledge_id}/document/batch_delete`,
|
||||
{ id_list: data },
|
||||
{id_list: data},
|
||||
undefined,
|
||||
loading,
|
||||
)
|
||||
|
|
@ -287,16 +287,16 @@ const delMulDocument: (
|
|||
/**
|
||||
* 批量关联
|
||||
* @param 参数 knowledge_id,
|
||||
{
|
||||
"document_id_list": [
|
||||
"string"
|
||||
],
|
||||
"model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"prompt": "string",
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
{
|
||||
"document_id_list": [
|
||||
"string"
|
||||
],
|
||||
"model_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||
"prompt": "string",
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putBatchGenerateRelated: (
|
||||
knowledge_id: string,
|
||||
|
|
@ -336,14 +336,14 @@ const putBatchEditHitHandling: (
|
|||
* 批量刷新文档向量库
|
||||
* @param knowledge_id 知识库id
|
||||
* @param data
|
||||
{
|
||||
"id_list": [
|
||||
"string"
|
||||
],
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
{
|
||||
"id_list": [
|
||||
"string"
|
||||
],
|
||||
"state_list": [
|
||||
"string"
|
||||
]
|
||||
}
|
||||
* @param loading
|
||||
* @returns
|
||||
*/
|
||||
|
|
@ -355,7 +355,7 @@ const putBatchRefresh: (
|
|||
) => Promise<Result<boolean>> = (knowledge_id, data, stateList, loading) => {
|
||||
return put(
|
||||
`${prefix.value}/${knowledge_id}/document/batch_refresh`,
|
||||
{ id_list: data, state_list: stateList },
|
||||
{id_list: data, state_list: stateList},
|
||||
undefined,
|
||||
loading,
|
||||
)
|
||||
|
|
@ -372,7 +372,7 @@ const putMulSyncDocument: (
|
|||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return put(
|
||||
`${prefix.value}/${knowledge_id}/document/batch_sync`,
|
||||
{ id_list: data },
|
||||
{id_list: data},
|
||||
undefined,
|
||||
loading,
|
||||
)
|
||||
|
|
@ -462,7 +462,7 @@ const exportQATemplate: (fileName: string, type: string, loading?: Ref<boolean>)
|
|||
type,
|
||||
loading,
|
||||
) => {
|
||||
return exportExcel(fileName, `/workspace/knowledge/document/template/export`, { type }, loading)
|
||||
return exportExcel(fileName, `/workspace/knowledge/document/template/export`, {type}, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -477,7 +477,7 @@ const exportTableTemplate: (fileName: string, type: string, loading?: Ref<boolea
|
|||
return exportExcel(
|
||||
fileName,
|
||||
`/workspace/knowledge/document/table_template/export`,
|
||||
{ type },
|
||||
{type},
|
||||
loading,
|
||||
)
|
||||
}
|
||||
|
|
@ -550,7 +550,7 @@ const putMulLarkSyncDocument: (
|
|||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return put(`${prefix.value}/lark/${knowledge_id}/_batch`, { id_list: data }, undefined, loading)
|
||||
return put(`${prefix.value}/lark/${knowledge_id}/_batch`, {id_list: data}, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -564,6 +564,41 @@ const importLarkDocument: (
|
|||
return post(`${prefix.value}/lark/${knowledge_id}/import`, data, null, loading)
|
||||
}
|
||||
|
||||
const getDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
params: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {
|
||||
return get(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {
|
||||
return post(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)
|
||||
}
|
||||
|
||||
const postMulDocumentTags: (
|
||||
knowledge_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return post(`${prefix.value}/${knowledge_id}/document/batch_add_tag`, data, null, loading)
|
||||
}
|
||||
|
||||
const delMulDocumentTag: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
tags: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {
|
||||
return put(`${prefix.value}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getDocumentList,
|
||||
getDocumentPage,
|
||||
|
|
@ -595,4 +630,8 @@ export default {
|
|||
putLarkDocumentSync,
|
||||
putMulLarkSyncDocument,
|
||||
importLarkDocument,
|
||||
getDocumentTags,
|
||||
postDocumentTags,
|
||||
postMulDocumentTags,
|
||||
delMulDocumentTag
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,6 +255,49 @@ const putLarkKnowledge: (
|
|||
return put(`${prefix.value}/lark/${knowledge_id}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
|
||||
const getTags: (knowledge_id: string, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
params,
|
||||
loading,
|
||||
) => {
|
||||
return get(`${prefix.value}/${knowledge_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postTags: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return post(`${prefix.value}/${knowledge_id}/tags`, tags, null, loading)
|
||||
}
|
||||
|
||||
const putTag: (knowledge_id: string, tag_id: string, tag: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
tag,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix.value}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)
|
||||
}
|
||||
|
||||
const delTag: (knowledge_id: string, tag_id: string, type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
type,
|
||||
loading,
|
||||
) => {
|
||||
return del(`${prefix.value}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)
|
||||
}
|
||||
|
||||
const delMulTag: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix.value}/${knowledge_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getKnowledgeList,
|
||||
getKnowledgeListPage,
|
||||
|
|
@ -271,5 +314,10 @@ export default {
|
|||
getKnowledgeModel,
|
||||
postWebKnowledge,
|
||||
postLarkKnowledge,
|
||||
putLarkKnowledge
|
||||
putLarkKnowledge,
|
||||
getTags,
|
||||
postTags,
|
||||
putTag,
|
||||
delTag,
|
||||
delMulTag
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,6 +523,40 @@ const importLarkDocument: (
|
|||
return post(`${prefix}/lark/${knowledge_id}/import`, data, null, loading)
|
||||
}
|
||||
|
||||
const getDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
params: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {
|
||||
return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {
|
||||
return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)
|
||||
}
|
||||
|
||||
const postMulDocumentTags: (
|
||||
knowledge_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading)
|
||||
}
|
||||
|
||||
const delMulDocumentTag: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
tags: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {
|
||||
return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getDocumentList,
|
||||
|
|
@ -555,4 +589,8 @@ export default {
|
|||
putLarkDocumentSync,
|
||||
putMulLarkSyncDocument,
|
||||
importLarkDocument,
|
||||
getDocumentTags,
|
||||
postDocumentTags,
|
||||
postMulDocumentTags,
|
||||
delMulDocumentTag
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,49 @@ const putLarkKnowledge: (
|
|||
return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
const getTags: (knowledge_id: string, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
params,
|
||||
loading,
|
||||
) => {
|
||||
return get(`${prefix}/${knowledge_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postTags: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading)
|
||||
}
|
||||
|
||||
const putTag: (knowledge_id: string, tag_id: string, tag: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
tag,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)
|
||||
}
|
||||
|
||||
|
||||
const delTag: (knowledge_id: string, tag_id: string, type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
type,
|
||||
loading,
|
||||
) => {
|
||||
return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)
|
||||
}
|
||||
|
||||
const delMulTag: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getKnowledgeList,
|
||||
|
|
@ -219,7 +262,12 @@ export default {
|
|||
putKnowledgeHitTest,
|
||||
putSyncWebKnowledge,
|
||||
getKnowledgeModel,
|
||||
putLarkKnowledge
|
||||
putLarkKnowledge,
|
||||
getTags,
|
||||
postTags,
|
||||
putTag,
|
||||
delTag,
|
||||
delMulTag
|
||||
} as {
|
||||
[key: string]: any
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,6 +524,41 @@ const importLarkDocument: (
|
|||
}
|
||||
|
||||
|
||||
const getDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
params: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {
|
||||
return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postDocumentTags: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {
|
||||
return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)
|
||||
}
|
||||
|
||||
const postMulDocumentTags: (
|
||||
knowledge_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {
|
||||
return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading)
|
||||
}
|
||||
|
||||
const delMulDocumentTag: (
|
||||
knowledge_id: string,
|
||||
document_id: string,
|
||||
tags: any,
|
||||
loading?: Ref<boolean>,
|
||||
) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {
|
||||
return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getDocumentList,
|
||||
getDocumentPage,
|
||||
|
|
@ -555,4 +590,8 @@ export default {
|
|||
putLarkDocumentSync,
|
||||
putMulLarkSyncDocument,
|
||||
importLarkDocument,
|
||||
getDocumentTags,
|
||||
postDocumentTags,
|
||||
postMulDocumentTags,
|
||||
delMulDocumentTag
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,6 +249,47 @@ const putLarkKnowledge: (
|
|||
return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
const getTags: (knowledge_id: string, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
params,
|
||||
loading,
|
||||
) => {
|
||||
return get(`${prefix}/${knowledge_id}/tags`, params, loading)
|
||||
}
|
||||
|
||||
const postTags: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading)
|
||||
}
|
||||
|
||||
const putTag: (knowledge_id: string, tag_id: string, tag: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
tag,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)
|
||||
}
|
||||
|
||||
const delTag: (knowledge_id: string, tag_id: string, type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tag_id,
|
||||
type,
|
||||
loading,
|
||||
) => {
|
||||
return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)
|
||||
}
|
||||
|
||||
const delMulTag: (knowledge_id: string, tags: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
knowledge_id,
|
||||
tags,
|
||||
loading,
|
||||
) => {
|
||||
return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getKnowledgeList,
|
||||
|
|
@ -266,7 +307,12 @@ export default {
|
|||
getKnowledgeModel,
|
||||
postWebKnowledge,
|
||||
postLarkKnowledge,
|
||||
putLarkKnowledge
|
||||
putLarkKnowledge,
|
||||
getTags,
|
||||
postTags,
|
||||
putTag,
|
||||
delTag,
|
||||
delMulTag
|
||||
} as {
|
||||
[key: string]: any
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,19 @@ export default {
|
|||
import: 'Start Import',
|
||||
preview: 'Apply',
|
||||
},
|
||||
tag: {
|
||||
label: 'Tag Management',
|
||||
key: 'Tag',
|
||||
value: 'Value',
|
||||
add: 'Add Tag',
|
||||
setting: 'Tag Settings',
|
||||
create: 'Create Tag',
|
||||
edit: 'Edit Tag',
|
||||
deleteConfirm: 'Confirm delete tag: ',
|
||||
deleteTip: 'After deletion, resources using this tag will have the tag removed. Please proceed with caution!',
|
||||
requiredMessage1: 'Please enter a tag',
|
||||
requiredMessage2: 'Please enter a value',
|
||||
},
|
||||
table: {
|
||||
name: 'Document Name',
|
||||
char_length: 'Character',
|
||||
|
|
|
|||
|
|
@ -93,6 +93,19 @@ export default {
|
|||
import: '开始导入',
|
||||
preview: '生成预览',
|
||||
},
|
||||
tag: {
|
||||
label: '标签管理',
|
||||
key: '标签',
|
||||
value: '标签值',
|
||||
add: '添加标签',
|
||||
setting: '标签设置',
|
||||
create: '创建标签',
|
||||
edit: '编辑标签',
|
||||
deleteConfirm: '是否删除标签: ',
|
||||
deleteTip: '删除后使用该标签的资源将会删除该标签,请谨慎操作!',
|
||||
requiredMessage1: '请输入标签',
|
||||
requiredMessage2: '请输入标签值',
|
||||
},
|
||||
table: {
|
||||
name: '文件名称',
|
||||
char_length: '字符数',
|
||||
|
|
|
|||
|
|
@ -96,6 +96,19 @@ export default {
|
|||
import: '開始導入',
|
||||
preview: '生成預覽',
|
||||
},
|
||||
tag: {
|
||||
label: '標籤管理',
|
||||
key: '標籤',
|
||||
value: '標籤值',
|
||||
add: '添加標籤',
|
||||
setting: '標籤設置',
|
||||
create: '創建標籤',
|
||||
edit: '編輯標籤',
|
||||
deleteConfirm: '是否刪除標籤: ',
|
||||
deleteTip: '刪除後使用該標籤的資源將會刪除該標籤,請謹慎操作!',
|
||||
requiredMessage1: '請輸入標籤',
|
||||
requiredMessage2: '請輸入標籤值',
|
||||
},
|
||||
table: {
|
||||
name: '文件名稱',
|
||||
char_length: '字符數',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('views.document.tag.add')"
|
||||
:before-close="close"
|
||||
>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="{ tags }"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
@submit.prevent
|
||||
>
|
||||
<div class="flex-between flex" v-for="(tag, index) in tags" :key="tag">
|
||||
<el-form-item :label="index === 0? $t('views.document.tag.key') : ''"
|
||||
:prop="`tags.${index}.key`"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage1'), trigger: 'blur' }">
|
||||
<el-select v-model="tag.key" @change="tagKeyChange(tag)" filterable>
|
||||
<el-option v-for="op in keyOptions" :key="op" :value="op.key"
|
||||
:label="op.key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="index === 0? $t('views.document.tag.value') : ''"
|
||||
:prop="`tags.${index}.value`"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage2'), trigger: 'blur' }">
|
||||
<el-select v-model="tag.value" filterable>
|
||||
<el-option v-for="op in tag.valueOptions" :key="op" :value="op.id"
|
||||
:label="op.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
|
||||
<div class="mt-20">
|
||||
<el-button link type="primary" @click="add">
|
||||
<AppIcon iconName="app-add-outlined" class="mr-4"/>
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit">{{ $t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import AppIcon from "@/components/app-icon/AppIcon.vue";
|
||||
|
||||
const emit = defineEmits(['addTags'])
|
||||
const props = defineProps({
|
||||
knowledgeTags: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const FormRef = ref()
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const tags = ref<Array<any>>([])
|
||||
const keyOptions = ref()
|
||||
|
||||
const add = () => {
|
||||
tags.value.push({})
|
||||
}
|
||||
|
||||
function tagKeyChange(tag: any) {
|
||||
const currentKeyOption = keyOptions.value.find((op: any) => op.key === tag.key)
|
||||
tag.valueOptions = currentKeyOption ? currentKeyOption.values : []
|
||||
tag.value = null
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
FormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) return
|
||||
emit('addTags', tags.value.map(tag => tag.value))
|
||||
})
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
tags.value = [{}]
|
||||
keyOptions.value = props.knowledgeTags
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({open, close})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('views.document.tag.create')"
|
||||
:before-close="close"
|
||||
>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="{ tags }"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
@submit.prevent
|
||||
>
|
||||
<div class="flex-between" v-for="(tag, index) in tags" :key="tag">
|
||||
<el-form-item :label="index === 0? $t('views.document.tag.key') : ''"
|
||||
:prop="`tags.${index}.key`"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage1'), trigger: 'blur' }">
|
||||
<el-input v-model="tag.key" :disabled="currentTagKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="index === 0? $t('views.document.tag.value') : ''"
|
||||
:prop="`tags.${index}.value`"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage2'), trigger: 'blur' }">
|
||||
<el-input v-model="tag.value"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
|
||||
<div class="mt-20">
|
||||
<el-button link type="primary" @click="add">
|
||||
<AppIcon iconName="app-add-outlined" class="mr-4"/>
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit">{{ $t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import AppIcon from "@/components/app-icon/AppIcon.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { loadSharedApi } from "@/utils/dynamics-api/shared-api.ts";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}, // id为knowledgeID
|
||||
} = route as any
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
|
||||
const FormRef = ref()
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const currentTagKey = ref(null)
|
||||
const tags = ref<Array<any>>([])
|
||||
|
||||
const add = () => {
|
||||
if (currentTagKey.value) {
|
||||
tags.value.push({key: currentTagKey.value})
|
||||
} else {
|
||||
tags.value.push({})
|
||||
}
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
FormRef.value.validate((valid: boolean) => {
|
||||
if (!valid) return
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.postTags(id, tags.value, loading)
|
||||
.then((res: any) => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const open = (row?: any) => {
|
||||
const currentRow = cloneDeep(row)
|
||||
dialogVisible.value = true
|
||||
currentTagKey.value = currentRow ? currentRow.key : null
|
||||
tags.value = currentRow ? [{...{key: currentRow.key}}] : [{}]
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({open, close})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('views.document.tag.edit')"
|
||||
:before-close="close"
|
||||
>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="标签" v-if="isEditKey"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage1'), trigger: 'blur' }"
|
||||
prop="key">
|
||||
<el-input v-model="form.key"></el-input>
|
||||
</el-form-item>
|
||||
<div v-else class="flex-between">
|
||||
<el-form-item label="标签" prop="key"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage1'), trigger: 'blur' }">
|
||||
<el-input v-model="form.key" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签值" prop="value"
|
||||
style="width: 50%"
|
||||
:rules="{ required: true, message: $t('views.document.tag.requiredMessage2'), trigger: 'blur' }">
|
||||
<el-input v-model="form.value"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit">{{ $t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from "vue-router";
|
||||
import { loadSharedApi } from "@/utils/dynamics-api/shared-api.ts";
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}, // id为knowledgeID
|
||||
} = route as any
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
const FormRef = ref()
|
||||
const isEditKey = ref(false)
|
||||
const form = ref({
|
||||
id: '',
|
||||
key: '',
|
||||
value: ''
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const submit = () => {
|
||||
FormRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.putTag(id, form.value.id, form.value, loading)
|
||||
.then((res: any) => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const open = (row: any, isKey: boolean) => {
|
||||
dialogVisible.value = true
|
||||
form.value.id = row.id
|
||||
form.value.key = row.key
|
||||
form.value.value = row.value
|
||||
isEditKey.value = isKey
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({open, close})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
<template>
|
||||
<el-drawer v-model="debugVisible" size="60%" :append-to-body="true">
|
||||
<template #header>
|
||||
<h4>{{ $t('views.document.tag.label') }}</h4>
|
||||
</template>
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<el-button type="primary" @click="openCreateTagDialog()">{{
|
||||
$t('views.document.tag.create')
|
||||
}}
|
||||
</el-button>
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="batchDelete">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:span-method="spanMethod"
|
||||
v-loading="loading"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55"/>
|
||||
<el-table-column :label="$t('views.document.tag.key')">
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
{{ row.key }}
|
||||
<div>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-add-outlined" class="mr-4"
|
||||
@click="openCreateTagDialog(row)"/>
|
||||
</el-button>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-edit" class="mr-4" @click="editTagKey(row)"/>
|
||||
</el-button>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-delete" class="mr-4" @click="delTag(row)"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('views.document.tag.value')">
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
{{ row.value }}
|
||||
<div>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-edit" class="mr-4" @click="editTagValue(row)"/>
|
||||
</el-button>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-delete" class="mr-4" @click="delTagValue(row)"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<CreateTagDialog ref="createTagDialogRef" @refresh="getList"/>
|
||||
<EditTagDialog ref="editTagDialogRef" @refresh="getList"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { loadSharedApi } from "@/utils/dynamics-api/shared-api.ts"
|
||||
import CreateTagDialog from "./CreateTagDialog.vue"
|
||||
import { MsgConfirm } from "@/utils/message.ts";
|
||||
import { t } from "@/locales";
|
||||
import EditTagDialog from "@/views/document/component/EditTagDialog.vue";
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}, // id为knowledgeID
|
||||
} = route as any
|
||||
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const debugVisible = ref(false)
|
||||
const filterText = ref('')
|
||||
const tags = ref<Array<any>>([])
|
||||
|
||||
// 将原始数据转换为表格数据
|
||||
const tableData = computed(() => {
|
||||
const result: any[] = []
|
||||
tags.value.forEach((tag: any) => {
|
||||
if (tag.values && tag.values.length > 0) {
|
||||
tag.values.forEach((value: any, index: number) => {
|
||||
result.push({
|
||||
id: value.id,
|
||||
key: tag.key,
|
||||
value: value.value,
|
||||
keyIndex: index // 用于判断是否为第一行
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
// 合并单元格方法
|
||||
const spanMethod = ({row, column, rowIndex, columnIndex}: any) => {
|
||||
if (columnIndex === 0 || columnIndex === 1) { // key列 (由于添加了选择列,索引变为1)
|
||||
if (row.keyIndex === 0) {
|
||||
// 计算当前key有多少个值
|
||||
const sameKeyCount = tableData.value.filter(item => item.key === row.key).length
|
||||
return {
|
||||
rowspan: sameKeyCount,
|
||||
colspan: 1
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const multipleSelection = ref<any[]>([])
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
|
||||
const createTagDialogRef = ref()
|
||||
|
||||
function openCreateTagDialog(row?: any) {
|
||||
createTagDialogRef.value?.open(row)
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
MsgConfirm(t('views.document.tag.deleteConfirm'), t('views.document.tag.deleteTip'), {
|
||||
confirmButtonText: t('common.delete'),
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
.then(() => {
|
||||
const tagsToDelete = multipleSelection.value.map(item => item.id)
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.delMulTag(id, tagsToDelete)
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const editTagDialogRef = ref()
|
||||
|
||||
function editTagKey(row: any) {
|
||||
editTagDialogRef.value?.open(row, true)
|
||||
}
|
||||
|
||||
function delTag(row: any) {
|
||||
MsgConfirm(t('views.document.tag.deleteConfirm') + row.key, t('views.document.tag.deleteTip'), {
|
||||
confirmButtonText: t('common.delete'),
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
.then(() => {
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.delTag(id, row.id, 'key')
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function editTagValue(row: any) {
|
||||
editTagDialogRef.value?.open(row, false)
|
||||
}
|
||||
|
||||
function delTagValue(row: any) {
|
||||
MsgConfirm(t('views.document.tag.deleteConfirm') + row.key + '-' + row.value, t('views.document.tag.deleteTip'), {
|
||||
confirmButtonText: t('common.delete'),
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
.then(() => {
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.delTag(id, row.id, 'one')
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
...(filterText.value && {name: filterText.value}),
|
||||
}
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.getTags(id, params, loading)
|
||||
.then((res: any) => {
|
||||
tags.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
debugVisible.value = true
|
||||
getList()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
<template>
|
||||
<el-drawer v-model="debugVisible" size="60%" :append-to-body="true">
|
||||
<template #header>
|
||||
<h4>{{ $t('views.document.tag.setting') }}</h4>
|
||||
</template>
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<el-button type="primary" @click="openAddTagDialog()">
|
||||
{{ $t('views.document.tag.add') }}
|
||||
</el-button>
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="batchDelete">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:span-method="spanMethod"
|
||||
v-loading="loading"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55"/>
|
||||
<el-table-column :label="$t('views.document.tag.key')">
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
{{ row.key }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('views.document.tag.value')">
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
{{ row.value }}
|
||||
<div>
|
||||
<el-button link>
|
||||
<AppIcon iconName="app-delete" class="mr-4" @click="delTagValue(row)"/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
<AddTagDialog ref="addTagDialogRef" @addTags="addTags" :knowledge-tags="knowledgeTags"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { loadSharedApi } from "@/utils/dynamics-api/shared-api.ts"
|
||||
import { MsgConfirm } from "@/utils/message.ts";
|
||||
import { t } from "@/locales";
|
||||
import AddTagDialog from "@/views/document/component/AddTagDialog.vue";
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const props = defineProps({
|
||||
knowledgeTags: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: {id}, // id为knowledgeID
|
||||
} = route as any
|
||||
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
|
||||
const document_id = ref('')
|
||||
const loading = ref(false)
|
||||
const debugVisible = ref(false)
|
||||
const filterText = ref('')
|
||||
const tags = ref<Array<any>>([])
|
||||
|
||||
// 将原始数据转换为表格数据
|
||||
const tableData = computed(() => {
|
||||
const result: any[] = []
|
||||
tags.value.forEach((tag: any) => {
|
||||
if (tag.values && tag.values.length > 0) {
|
||||
tag.values.forEach((value: any, index: number) => {
|
||||
result.push({
|
||||
id: value.id,
|
||||
key: tag.key,
|
||||
value: value.value,
|
||||
keyIndex: index // 用于判断是否为第一行
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
// 合并单元格方法
|
||||
const spanMethod = ({row, column, rowIndex, columnIndex}: any) => {
|
||||
if (columnIndex === 0 || columnIndex === 1) { // key列 (由于添加了选择列,索引变为1)
|
||||
if (row.keyIndex === 0) {
|
||||
// 计算当前key有多少个值
|
||||
const sameKeyCount = tableData.value.filter(item => item.key === row.key).length
|
||||
return {
|
||||
rowspan: sameKeyCount,
|
||||
colspan: 1
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const multipleSelection = ref<any[]>([])
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
|
||||
function batchDelete() {
|
||||
MsgConfirm(t('views.document.tag.deleteConfirm'), t('views.document.tag.deleteTip'), {
|
||||
confirmButtonText: t('common.delete'),
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
.then(() => {
|
||||
const tagsToDelete = multipleSelection.value.reduce((acc, item) => {
|
||||
// 找出当前选中项的key对应的所有value id
|
||||
const sameKeyItems = tableData.value.filter(data => data.key === item.key)
|
||||
const sameKeyIds = sameKeyItems.map(data => data.id)
|
||||
return [...acc, ...sameKeyIds]
|
||||
}, [] as string[])
|
||||
|
||||
loadSharedApi({type: 'document', systemType: apiType.value})
|
||||
.delMulDocumentTag(id, document_id.value, tagsToDelete, loading)
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function delTagValue(row: any) {
|
||||
MsgConfirm(t('views.document.tag.deleteConfirm') + row.key + '-' + row.value, t('views.document.tag.deleteTip'), {
|
||||
confirmButtonText: t('common.delete'),
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
.then(() => {
|
||||
loadSharedApi({type: 'document', systemType: apiType.value})
|
||||
.delMulDocumentTag(id, document_id.value, [row.id], loading)
|
||||
.then(() => {
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
...(filterText.value && {name: filterText.value}),
|
||||
}
|
||||
loadSharedApi({type: 'document', systemType: apiType.value})
|
||||
.getDocumentTags(id, document_id.value, params, loading)
|
||||
.then((res: any) => {
|
||||
tags.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const addTagDialogRef = ref()
|
||||
|
||||
function openAddTagDialog() {
|
||||
addTagDialogRef.value?.open()
|
||||
}
|
||||
|
||||
function addTags(tags: any) {
|
||||
loadSharedApi({type: 'document', systemType: apiType.value})
|
||||
.postDocumentTags(id, document_id.value, tags, loading)
|
||||
.then(() => {
|
||||
addTagDialogRef.value?.close()
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const open = (doc: any) => {
|
||||
debugVisible.value = true
|
||||
document_id.value = doc.id
|
||||
|
||||
getList()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -57,6 +57,13 @@
|
|||
>
|
||||
{{ $t('common.setting') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="openAddTagDialog()"
|
||||
:disabled="multipleSelection.length === 0"
|
||||
v-if="permissionPrecise.doc_edit(id)"
|
||||
>
|
||||
{{ $t('views.document.tag.add') }}
|
||||
</el-button>
|
||||
<el-dropdown v-if="MoreFilledPermission0(id)">
|
||||
<el-button class="ml-12 mr-12">
|
||||
<AppIcon iconName="app-more"></AppIcon>
|
||||
|
|
@ -96,15 +103,19 @@
|
|||
</el-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
<el-button @click="openTagDrawer" class="ml-12">
|
||||
{{ $t('views.document.tag.label') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<app-table
|
||||
ref="multipleTableRef"
|
||||
|
|
@ -448,6 +459,11 @@
|
|||
></AppIcon>
|
||||
{{ $t('views.document.generateQuestion.title') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="openTagSettingDrawer(row)"
|
||||
>
|
||||
{{ $t('views.document.tag.setting') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="openknowledgeDialog(row)"
|
||||
v-if="permissionPrecise.doc_migrate(id)"
|
||||
|
|
@ -645,6 +661,9 @@
|
|||
:workspaceId="knowledgeDetail?.workspace_id"
|
||||
/>
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="getList" :apiType="apiType" />
|
||||
<TagDrawer ref="tagDrawerRef"/>
|
||||
<TagSettingDrawer ref="tagSettingDrawerRef" :knowledge-tags="knowledgeTags"/>
|
||||
<AddTagDialog ref="addTagDialogRef" @addTags="addTags" :knowledge-tags="knowledgeTags"/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -666,6 +685,9 @@ import { TaskType, State } from '@/utils/status'
|
|||
import { t } from '@/locales'
|
||||
import permissionMap from '@/permission'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import TagDrawer from "./component/TagDrawer.vue";
|
||||
import TagSettingDrawer from "./component/TagSettingDrawer.vue";
|
||||
import AddTagDialog from "@/views/document/component/AddTagDialog.vue";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
|
@ -1181,6 +1203,43 @@ function openGenerateDialog(row?: any) {
|
|||
GenerateRelatedDialogRef.value.open(arr, 'document')
|
||||
}
|
||||
|
||||
const tagDrawerRef = ref()
|
||||
function openTagDrawer() {
|
||||
tagDrawerRef.value.open()
|
||||
}
|
||||
|
||||
const tagSettingDrawerRef = ref()
|
||||
function openTagSettingDrawer(doc: any) {
|
||||
tagSettingDrawerRef.value.open(doc)
|
||||
}
|
||||
|
||||
const addTagDialogRef = ref()
|
||||
|
||||
function openAddTagDialog() {
|
||||
addTagDialogRef.value?.open()
|
||||
}
|
||||
|
||||
function addTags(tags: any) {
|
||||
const arr: string[] = multipleSelection.value.map((v) => v.id)
|
||||
|
||||
loadSharedApi({type: 'document', systemType: apiType.value})
|
||||
.postMulDocumentTags(id, {tag_ids: tags, document_ids: arr}, loading)
|
||||
.then(() => {
|
||||
addTagDialogRef.value?.close()
|
||||
getList()
|
||||
clearSelection()
|
||||
})
|
||||
}
|
||||
|
||||
const knowledgeTags = ref<any[]>([])
|
||||
function getTags() {
|
||||
loadSharedApi({type: 'knowledge', systemType: apiType.value})
|
||||
.getTags(id, {}, loading)
|
||||
.then((res: any) => {
|
||||
knowledgeTags.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
if (beforePagination.value) {
|
||||
|
|
@ -1191,6 +1250,7 @@ onMounted(() => {
|
|||
filterMethod.value = beforeSearch.value['filterMethod']
|
||||
}
|
||||
getList()
|
||||
getTags()
|
||||
// 初始化定时任务
|
||||
initInterval()
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue