feat: implement knowledge tag management functionality

This commit is contained in:
CaptainB 2025-10-11 15:45:05 +08:00 committed by 刘瑞斌
parent 56d32c1b71
commit c47c70afb0
26 changed files with 2146 additions and 82 deletions

View File

@ -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"

View File

@ -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

107
apps/knowledge/api/tag.py Normal file
View File

@ -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

View File

@ -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')},
),
]

View File

@ -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):
"""

View File

@ -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

View File

@ -0,0 +1,250 @@
# coding=utf-8
"""
@project: maxkb
@AuthorAI Assistant
@file tag.py
@date2025/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

View File

@ -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()),

View File

@ -2,3 +2,4 @@ from .document import *
from .knowledge import *
from .paragraph import *
from .problem import *
from .tag import *

View File

@ -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]

138
apps/knowledge/views/tag.py Normal file
View File

@ -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())

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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',

View File

@ -93,6 +93,19 @@ export default {
import: '开始导入',
preview: '生成预览',
},
tag: {
label: '标签管理',
key: '标签',
value: '标签值',
add: '添加标签',
setting: '标签设置',
create: '创建标签',
edit: '编辑标签',
deleteConfirm: '是否删除标签: ',
deleteTip: '删除后使用该标签的资源将会删除该标签,请谨慎操作!',
requiredMessage1: '请输入标签',
requiredMessage2: '请输入标签值',
},
table: {
name: '文件名称',
char_length: '字符数',

View File

@ -96,6 +96,19 @@ export default {
import: '開始導入',
preview: '生成預覽',
},
tag: {
label: '標籤管理',
key: '標籤',
value: '標籤值',
add: '添加標籤',
setting: '標籤設置',
create: '創建標籤',
edit: '編輯標籤',
deleteConfirm: '是否刪除標籤: ',
deleteTip: '刪除後使用該標籤的資源將會刪除該標籤,請謹慎操作!',
requiredMessage1: '請輸入標籤',
requiredMessage2: '請輸入標籤值',
},
table: {
name: '文件名稱',
char_length: '字符數',

View File

@ -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>

View File

@ -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}, // idknowledgeID
} = 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>

View File

@ -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}, // idknowledgeID
} = 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>

View File

@ -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}, // idknowledgeID
} = 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>

View File

@ -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}, // idknowledgeID
} = 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) => {
// keyvalue 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>

View File

@ -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()
})