feat: application chat log (#3230)
This commit is contained in:
parent
ab256b980d
commit
87ab2178bf
135
apps/application/api/application_chat.py
Normal file
135
apps/application/api/application_chat.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat.py
|
||||||
|
@date:2025/6/10 13:54
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_chat import ApplicationChatQuerySerializers, \
|
||||||
|
ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
from common.result import ResultSerializer, ResultPageSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatListResponseSerializers(ResultSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationChatResponseSerializers(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatPageResponseSerializers(ResultPageSerializer):
|
||||||
|
def get_data(self):
|
||||||
|
return ApplicationChatResponseSerializers(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatQuerySerializers
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
), OpenApiParameter(
|
||||||
|
name="start_time",
|
||||||
|
description="start Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="end_time",
|
||||||
|
description="end Time",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="abstract",
|
||||||
|
description="summary",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="min_star",
|
||||||
|
description=_("Minimum number of likes"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="min_trample",
|
||||||
|
description=_("Minimum number of clicks"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="comparer",
|
||||||
|
description=_("Comparator"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationChatListResponseSerializers
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatQueryPageAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatQueryAPI.get_request()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
*ApplicationChatQueryAPI.get_parameters(),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="current_page",
|
||||||
|
description=_("Current page"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="page_size",
|
||||||
|
description=_("Page size"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return ApplicationChatPageResponseSerializers
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatExportAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordExportRequest
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return ApplicationChatQueryAPI.get_parameters()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return None
|
||||||
180
apps/application/api/application_chat_record.py
Normal file
180
apps/application/api/application_chat_record.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat_record.py
|
||||||
|
@date:2025/6/10 15:19
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from application.serializers.application_chat_record import ApplicationChatRecordAddKnowledgeSerializer, \
|
||||||
|
ApplicationChatRecordImproveInstanceSerializer
|
||||||
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_id",
|
||||||
|
description=_("Chat ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="order_asc",
|
||||||
|
description=_("Is it in order"),
|
||||||
|
type=OpenApiTypes.BOOL,
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordPageQueryAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [*ApplicationChatRecordQueryAPI.get_parameters(),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="current_page",
|
||||||
|
description=_("Current page"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="page_size",
|
||||||
|
description=_("Page size"),
|
||||||
|
type=OpenApiTypes.INT,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImproveParagraphAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordImproveInstanceSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_id",
|
||||||
|
description=_("Chat ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="chat_record_id",
|
||||||
|
description=_("Chat Record ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="knowledge_id",
|
||||||
|
description=_("Knowledge ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="document_id",
|
||||||
|
description=_("Document ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
class Operate(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [*ApplicationChatRecordImproveParagraphAPI.get_parameters(), OpenApiParameter(
|
||||||
|
name="paragraph_id",
|
||||||
|
description=_("Paragraph ID"),
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordAddKnowledgeAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return ApplicationChatRecordAddKnowledgeSerializer
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_response():
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_parameters():
|
||||||
|
return [
|
||||||
|
OpenApiParameter(
|
||||||
|
name="workspace_id",
|
||||||
|
description="工作空间id",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="application_id",
|
||||||
|
description="Application ID",
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
location='path',
|
||||||
|
required=True,
|
||||||
|
)]
|
||||||
225
apps/application/serializers/application_chat.py
Normal file
225
apps/application/serializers/application_chat.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat.py
|
||||||
|
@date:2025/6/10 11:06
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import openpyxl
|
||||||
|
import pytz
|
||||||
|
from django.core import validators
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import QuerySet, Q
|
||||||
|
from django.http import StreamingHttpResponse
|
||||||
|
from django.utils.translation import gettext_lazy as _, gettext
|
||||||
|
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from application.models import Chat
|
||||||
|
from common.db.search import get_dynamics_model, native_search, native_page_search
|
||||||
|
from common.utils.common import get_file_content
|
||||||
|
from maxkb.conf import PROJECT_DIR
|
||||||
|
from maxkb.settings import TIME_ZONE
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatResponseSerializers(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField(required=True, label=_("chat id"))
|
||||||
|
abstract = serializers.CharField(required=True, label=_("summary"))
|
||||||
|
chat_user_id = serializers.UUIDField(required=True, label=_("Chat User ID"))
|
||||||
|
chat_user_type = serializers.CharField(required=True, label=_("Chat User Type"))
|
||||||
|
is_deleted = serializers.BooleanField(required=True, label=_("Is delete"))
|
||||||
|
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
|
||||||
|
chat_record_count = serializers.IntegerField(required=True, label=_("Number of conversations"))
|
||||||
|
star_num = serializers.IntegerField(required=True, label=_("Number of Likes"))
|
||||||
|
trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs"))
|
||||||
|
mark_sum = serializers.IntegerField(required=True, label=_("Number of tags"))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordExportRequest(serializers.Serializer):
|
||||||
|
select_ids = serializers.ListField(required=True, label=_("Chat ID List"),
|
||||||
|
child=serializers.UUIDField(required=True, label=_("Chat ID")))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatQuerySerializers(serializers.Serializer):
|
||||||
|
abstract = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("summary"))
|
||||||
|
start_time = serializers.DateField(format='%Y-%m-%d', label=_("Start time"))
|
||||||
|
end_time = serializers.DateField(format='%Y-%m-%d', label=_("End time"))
|
||||||
|
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
|
||||||
|
min_star = serializers.IntegerField(required=False, min_value=0,
|
||||||
|
label=_("Minimum number of likes"))
|
||||||
|
min_trample = serializers.IntegerField(required=False, min_value=0,
|
||||||
|
label=_("Minimum number of clicks"))
|
||||||
|
comparer = serializers.CharField(required=False, label=_("Comparator"), validators=[
|
||||||
|
validators.RegexValidator(regex=re.compile("^and|or$"),
|
||||||
|
message=_("Only supports and|or"), code=500)
|
||||||
|
])
|
||||||
|
|
||||||
|
def get_end_time(self):
|
||||||
|
return datetime.datetime.combine(
|
||||||
|
datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d'),
|
||||||
|
datetime.datetime.max.time())
|
||||||
|
|
||||||
|
def get_start_time(self):
|
||||||
|
return self.data.get('start_time')
|
||||||
|
|
||||||
|
def get_query_set(self, select_ids=None):
|
||||||
|
end_time = self.get_end_time()
|
||||||
|
start_time = self.get_start_time()
|
||||||
|
query_set = QuerySet(model=get_dynamics_model(
|
||||||
|
{'application_chat.application_id': models.CharField(),
|
||||||
|
'application_chat.abstract': models.CharField(),
|
||||||
|
"star_num": models.IntegerField(),
|
||||||
|
'trample_num': models.IntegerField(),
|
||||||
|
'comparer': models.CharField(),
|
||||||
|
'application_chat.update_time': models.DateTimeField(),
|
||||||
|
'application_chat.id': models.UUIDField(), }))
|
||||||
|
|
||||||
|
base_query_dict = {'application_chat.application_id': self.data.get("application_id"),
|
||||||
|
'application_chat.update_time__gte': start_time,
|
||||||
|
'application_chat.update_time__lte': end_time,
|
||||||
|
}
|
||||||
|
if 'abstract' in self.data and self.data.get('abstract') is not None:
|
||||||
|
base_query_dict['application_chat.abstract__icontains'] = self.data.get('abstract')
|
||||||
|
|
||||||
|
if select_ids is not None and len(select_ids) > 0:
|
||||||
|
base_query_dict['application_chat.id__in'] = select_ids
|
||||||
|
base_condition = Q(**base_query_dict)
|
||||||
|
min_star_query = None
|
||||||
|
min_trample_query = None
|
||||||
|
if 'min_star' in self.data and self.data.get('min_star') is not None:
|
||||||
|
min_star_query = Q(star_num__gte=self.data.get('min_star'))
|
||||||
|
if 'min_trample' in self.data and self.data.get('min_trample') is not None:
|
||||||
|
min_trample_query = Q(trample_num__gte=self.data.get('min_trample'))
|
||||||
|
if min_star_query is not None and min_trample_query is not None:
|
||||||
|
if self.data.get(
|
||||||
|
'comparer') is not None and self.data.get('comparer') == 'or':
|
||||||
|
condition = base_condition & (min_star_query | min_trample_query)
|
||||||
|
else:
|
||||||
|
condition = base_condition & (min_star_query & min_trample_query)
|
||||||
|
elif min_star_query is not None:
|
||||||
|
condition = base_condition & min_star_query
|
||||||
|
elif min_trample_query is not None:
|
||||||
|
condition = base_condition & min_trample_query
|
||||||
|
else:
|
||||||
|
condition = base_condition
|
||||||
|
inner_queryset = QuerySet(Chat).filter(application_id=self.data.get("application_id"))
|
||||||
|
if 'abstract' in self.data and self.data.get('abstract') is not None:
|
||||||
|
inner_queryset = inner_queryset.filter(abstract__icontains=self.data.get('abstract'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'inner_queryset': inner_queryset,
|
||||||
|
'default_queryset': query_set.filter(condition).order_by("-application_chat.update_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
def list(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
return native_search(self.get_query_set(), select_string=get_file_content(
|
||||||
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_application_chat.sql')),
|
||||||
|
with_table_name=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def paragraph_list_to_string(paragraph_list):
|
||||||
|
return "\n**********\n".join(
|
||||||
|
[f"{paragraph.get('title')}:\n{paragraph.get('content')}" for paragraph in
|
||||||
|
paragraph_list] if paragraph_list is not None else '')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_row(row: Dict):
|
||||||
|
details = row.get('details')
|
||||||
|
padding_problem_text = ' '.join(node.get("answer", "") for key, node in details.items() if
|
||||||
|
node.get("type") == 'question-node')
|
||||||
|
search_dataset_node_list = [(key, node) for key, node in details.items() if
|
||||||
|
node.get("type") == 'search-dataset-node' or node.get(
|
||||||
|
"step_type") == 'search_step']
|
||||||
|
reference_paragraph_len = '\n'.join([str(len(node.get('paragraph_list',
|
||||||
|
[]))) if key == 'search_step' else node.get(
|
||||||
|
'name') + ':' + str(
|
||||||
|
len(node.get('paragraph_list', [])) if node.get('paragraph_list', []) is not None else '0') for
|
||||||
|
key, node in search_dataset_node_list])
|
||||||
|
reference_paragraph = '\n----------\n'.join(
|
||||||
|
[ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list',
|
||||||
|
[])) if key == 'search_step' else node.get(
|
||||||
|
'name') + ':\n' + ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list',
|
||||||
|
[])) for
|
||||||
|
key, node in search_dataset_node_list])
|
||||||
|
improve_paragraph_list = row.get('improve_paragraph_list')
|
||||||
|
vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'}
|
||||||
|
return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text,
|
||||||
|
row.get('answer_text'), vote_status_map.get(row.get('vote_status')), reference_paragraph_len,
|
||||||
|
reference_paragraph,
|
||||||
|
"\n".join([
|
||||||
|
f"{improve_paragraph_list[index].get('title')}\n{improve_paragraph_list[index].get('content')}"
|
||||||
|
for index in range(len(improve_paragraph_list))]),
|
||||||
|
row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'),
|
||||||
|
str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
)]
|
||||||
|
|
||||||
|
def export(self, data, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
ApplicationChatRecordExportRequest(data=data).is_valid(raise_exception=True)
|
||||||
|
data_list = native_search(self.get_query_set(data.get('select_ids')),
|
||||||
|
select_string=get_file_content(
|
||||||
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
|
'export_application_chat.sql')),
|
||||||
|
with_table_name=False)
|
||||||
|
|
||||||
|
batch_size = 500
|
||||||
|
|
||||||
|
def stream_response():
|
||||||
|
workbook = openpyxl.Workbook()
|
||||||
|
worksheet = workbook.active
|
||||||
|
worksheet.title = 'Sheet1'
|
||||||
|
|
||||||
|
headers = [gettext('Conversation ID'), gettext('summary'), gettext('User Questions'),
|
||||||
|
gettext('Problem after optimization'),
|
||||||
|
gettext('answer'), gettext('User feedback'),
|
||||||
|
gettext('Reference segment number'),
|
||||||
|
gettext('Section title + content'),
|
||||||
|
gettext('Annotation'), gettext('Consuming tokens'),
|
||||||
|
gettext('Time consumed (s)'),
|
||||||
|
gettext('Question Time')]
|
||||||
|
for col_idx, header in enumerate(headers, 1):
|
||||||
|
cell = worksheet.cell(row=1, column=col_idx)
|
||||||
|
cell.value = header
|
||||||
|
|
||||||
|
for i in range(0, len(data_list), batch_size):
|
||||||
|
batch_data = data_list[i:i + batch_size]
|
||||||
|
|
||||||
|
for row_idx, row in enumerate(batch_data, start=i + 2):
|
||||||
|
for col_idx, value in enumerate(self.to_row(row), 1):
|
||||||
|
cell = worksheet.cell(row=row_idx, column=col_idx)
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
eastern = pytz.timezone(TIME_ZONE)
|
||||||
|
c = datetime.timezone(eastern._utcoffset)
|
||||||
|
value = value.astimezone(c)
|
||||||
|
cell.value = value
|
||||||
|
|
||||||
|
output = BytesIO()
|
||||||
|
workbook.save(output)
|
||||||
|
output.seek(0)
|
||||||
|
yield output.getvalue()
|
||||||
|
output.close()
|
||||||
|
workbook.close()
|
||||||
|
|
||||||
|
response = StreamingHttpResponse(stream_response(),
|
||||||
|
content_type='application/vnd.open.xmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="data.xlsx"'
|
||||||
|
return response
|
||||||
|
|
||||||
|
def page(self, current_page: int, page_size: int, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
return native_page_search(current_page, page_size, self.get_query_set(), select_string=get_file_content(
|
||||||
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'list_application_chat.sql')),
|
||||||
|
with_table_name=False)
|
||||||
306
apps/application/serializers/application_chat_record.py
Normal file
306
apps/application/serializers/application_chat_record.py
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat_record.py
|
||||||
|
@date:2025/6/10 15:10
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.utils.formatting import lazy_format
|
||||||
|
|
||||||
|
from application.models import ChatRecord
|
||||||
|
from common.db.search import page_search
|
||||||
|
from common.exception.app_exception import AppApiException
|
||||||
|
from common.utils.common import post
|
||||||
|
from knowledge.models import Paragraph, Document, Problem, ProblemParagraphMapping
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _, gettext
|
||||||
|
|
||||||
|
from knowledge.serializers.common import get_embedding_model_id_by_knowledge_id, update_document_char_length
|
||||||
|
from knowledge.serializers.paragraph import ParagraphSerializers
|
||||||
|
from knowledge.task.embedding import embedding_by_paragraph, embedding_by_paragraph_list
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRecordSerializerModel(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ChatRecord
|
||||||
|
fields = ['id', 'chat_id', 'vote_status', 'problem_text', 'answer_text',
|
||||||
|
'message_tokens', 'answer_tokens', 'const', 'improve_paragraph_id_list', 'run_time', 'index',
|
||||||
|
'answer_text_list',
|
||||||
|
'create_time', 'update_time']
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordQuerySerializers(serializers.Serializer):
|
||||||
|
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
|
||||||
|
chat_id = serializers.UUIDField(required=True, label=_("Chat ID"))
|
||||||
|
order_asc = serializers.BooleanField(required=False, allow_null=True, label=_("Is it in order"))
|
||||||
|
|
||||||
|
def list(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id'))
|
||||||
|
order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get(
|
||||||
|
'order_asc') else '-create_time'
|
||||||
|
return [ChatRecordSerializerModel(chat_record).data for chat_record in
|
||||||
|
QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by)]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_chat_record(chat_record):
|
||||||
|
knowledge_list = []
|
||||||
|
paragraph_list = []
|
||||||
|
|
||||||
|
if 'search_step' in chat_record.details and chat_record.details.get('search_step').get(
|
||||||
|
'paragraph_list') is not None:
|
||||||
|
paragraph_list = chat_record.details.get('search_step').get(
|
||||||
|
'paragraph_list')
|
||||||
|
knowledge_list = [{'id': dataset_id, 'name': name} for dataset_id, name in reduce(lambda x, y: {**x, **y},
|
||||||
|
[{row.get(
|
||||||
|
'knowledge_id'): row.get(
|
||||||
|
"knowledge_name")} for
|
||||||
|
row in
|
||||||
|
paragraph_list],
|
||||||
|
{}).items()]
|
||||||
|
if len(chat_record.improve_paragraph_id_list) > 0:
|
||||||
|
paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list)
|
||||||
|
if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list):
|
||||||
|
paragraph_model_id_list = [str(p.id) for p in paragraph_model_list]
|
||||||
|
chat_record.improve_paragraph_id_list = list(
|
||||||
|
filter(lambda p_id: paragraph_model_id_list.__contains__(p_id),
|
||||||
|
chat_record.improve_paragraph_id_list))
|
||||||
|
chat_record.save()
|
||||||
|
|
||||||
|
return {
|
||||||
|
**ChatRecordSerializerModel(chat_record).data,
|
||||||
|
'padding_problem_text': chat_record.details.get('problem_padding').get(
|
||||||
|
'padding_problem_text') if 'problem_padding' in chat_record.details else None,
|
||||||
|
'knowledge_list': knowledge_list,
|
||||||
|
'paragraph_list': paragraph_list,
|
||||||
|
'execution_details': [chat_record.details[key] for key in chat_record.details]
|
||||||
|
}
|
||||||
|
|
||||||
|
def page(self, current_page: int, page_size: int, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
order_by = '-create_time' if self.data.get('order_asc') is None or self.data.get(
|
||||||
|
'order_asc') else 'create_time'
|
||||||
|
page = page_search(current_page, page_size,
|
||||||
|
QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by),
|
||||||
|
post_records_handler=lambda chat_record: self.reset_chat_record(chat_record))
|
||||||
|
return page
|
||||||
|
|
||||||
|
|
||||||
|
class ParagraphModel(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Paragraph
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRecordImproveSerializer(serializers.Serializer):
|
||||||
|
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
||||||
|
|
||||||
|
chat_record_id = serializers.UUIDField(required=True,
|
||||||
|
label=_("Conversation record id"))
|
||||||
|
|
||||||
|
def get(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
chat_record_id = self.data.get('chat_record_id')
|
||||||
|
chat_id = self.data.get('chat_id')
|
||||||
|
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
|
||||||
|
if chat_record is None:
|
||||||
|
raise AppApiException(500, gettext('Conversation record does not exist'))
|
||||||
|
if chat_record.improve_paragraph_id_list is None or len(chat_record.improve_paragraph_id_list) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list)
|
||||||
|
if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list):
|
||||||
|
paragraph_model_id_list = [str(p.id) for p in paragraph_model_list]
|
||||||
|
chat_record.improve_paragraph_id_list = list(
|
||||||
|
filter(lambda p_id: paragraph_model_id_list.__contains__(p_id),
|
||||||
|
chat_record.improve_paragraph_id_list))
|
||||||
|
chat_record.save()
|
||||||
|
return [ParagraphModel(p).data for p in paragraph_model_list]
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImproveInstanceSerializer(serializers.Serializer):
|
||||||
|
title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
|
||||||
|
label=_("Section title"))
|
||||||
|
content = serializers.CharField(required=True, label=_("Paragraph content"))
|
||||||
|
|
||||||
|
problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
|
||||||
|
label=_("question"))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordAddKnowledgeSerializer(serializers.Serializer):
|
||||||
|
knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id"))
|
||||||
|
document_id = serializers.UUIDField(required=True, label=_("Document id"))
|
||||||
|
chat_ids = serializers.ListSerializer(child=serializers.UUIDField(), required=True,
|
||||||
|
label=_("Conversation ID"))
|
||||||
|
|
||||||
|
def is_valid(self, *, raise_exception=False):
|
||||||
|
super().is_valid(raise_exception=True)
|
||||||
|
if not Document.objects.filter(id=self.data['document_id'], knowledge_id=self.data['knowledge_id']).exists():
|
||||||
|
raise AppApiException(500, gettext("The document id is incorrect"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post_embedding_paragraph(paragraph_ids, knowledge_id):
|
||||||
|
model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)
|
||||||
|
embedding_by_paragraph_list(paragraph_ids, model_id)
|
||||||
|
|
||||||
|
@post(post_function=post_embedding_paragraph)
|
||||||
|
@transaction.atomic
|
||||||
|
def post_improve(self, instance: Dict):
|
||||||
|
ApplicationChatRecordAddKnowledgeSerializer(data=instance).is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
chat_ids = instance['chat_ids']
|
||||||
|
document_id = instance['document_id']
|
||||||
|
knowledge_id = instance['knowledge_id']
|
||||||
|
|
||||||
|
# 获取所有聊天记录
|
||||||
|
chat_record_list = list(ChatRecord.objects.filter(chat_id__in=chat_ids))
|
||||||
|
if len(chat_record_list) < len(chat_ids):
|
||||||
|
raise AppApiException(500, gettext("Conversation records that do not exist"))
|
||||||
|
|
||||||
|
# 批量创建段落和问题映射
|
||||||
|
paragraphs = []
|
||||||
|
paragraph_ids = []
|
||||||
|
problem_paragraph_mappings = []
|
||||||
|
for chat_record in chat_record_list:
|
||||||
|
paragraph = Paragraph(
|
||||||
|
id=uuid.uuid1(),
|
||||||
|
document_id=document_id,
|
||||||
|
content=chat_record.answer_text,
|
||||||
|
knowledge_id=knowledge_id,
|
||||||
|
title=chat_record.problem_text
|
||||||
|
)
|
||||||
|
problem, _ = Problem.objects.get_or_create(content=chat_record.problem_text, knowledge_id=knowledge_id)
|
||||||
|
problem_paragraph_mapping = ProblemParagraphMapping(
|
||||||
|
id=uuid.uuid1(),
|
||||||
|
knowledge_id=knowledge_id,
|
||||||
|
document_id=document_id,
|
||||||
|
problem_id=problem.id,
|
||||||
|
paragraph_id=paragraph.id
|
||||||
|
)
|
||||||
|
paragraphs.append(paragraph)
|
||||||
|
paragraph_ids.append(paragraph.id)
|
||||||
|
problem_paragraph_mappings.append(problem_paragraph_mapping)
|
||||||
|
chat_record.improve_paragraph_id_list.append(paragraph.id)
|
||||||
|
|
||||||
|
# 批量保存段落和问题映射
|
||||||
|
Paragraph.objects.bulk_create(paragraphs)
|
||||||
|
ProblemParagraphMapping.objects.bulk_create(problem_paragraph_mappings)
|
||||||
|
|
||||||
|
# 批量保存聊天记录
|
||||||
|
ChatRecord.objects.bulk_update(chat_record_list, ['improve_paragraph_id_list'])
|
||||||
|
update_document_char_length(document_id)
|
||||||
|
|
||||||
|
return paragraph_ids, knowledge_id
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImproveSerializer(serializers.Serializer):
|
||||||
|
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
||||||
|
|
||||||
|
chat_record_id = serializers.UUIDField(required=True,
|
||||||
|
label=_("Conversation record id"))
|
||||||
|
|
||||||
|
knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id"))
|
||||||
|
|
||||||
|
document_id = serializers.UUIDField(required=True, label=_("Document id"))
|
||||||
|
|
||||||
|
def is_valid(self, *, raise_exception=False):
|
||||||
|
super().is_valid(raise_exception=True)
|
||||||
|
if not QuerySet(Document).filter(id=self.data.get('document_id'),
|
||||||
|
knowledge_id=self.data.get('knowledge_id')).exists():
|
||||||
|
raise AppApiException(500, gettext("The document id is incorrect"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post_embedding_paragraph(chat_record, paragraph_id, knowledge_id):
|
||||||
|
model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)
|
||||||
|
# 发送向量化事件
|
||||||
|
embedding_by_paragraph(paragraph_id, model_id)
|
||||||
|
return chat_record
|
||||||
|
|
||||||
|
@post(post_function=post_embedding_paragraph)
|
||||||
|
@transaction.atomic
|
||||||
|
def improve(self, instance: Dict, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
ApplicationChatRecordImproveInstanceSerializer(data=instance).is_valid(raise_exception=True)
|
||||||
|
chat_record_id = self.data.get('chat_record_id')
|
||||||
|
chat_id = self.data.get('chat_id')
|
||||||
|
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
|
||||||
|
if chat_record is None:
|
||||||
|
raise AppApiException(500, gettext('Conversation record does not exist'))
|
||||||
|
|
||||||
|
document_id = self.data.get("document_id")
|
||||||
|
knowledge_id = self.data.get("knowledge_id")
|
||||||
|
paragraph = Paragraph(id=uuid.uuid1(),
|
||||||
|
document_id=document_id,
|
||||||
|
content=instance.get("content"),
|
||||||
|
knowledge_id=knowledge_id,
|
||||||
|
title=instance.get("title") if 'title' in instance else '')
|
||||||
|
problem_text = instance.get('problem_text') if instance.get(
|
||||||
|
'problem_text') is not None else chat_record.problem_text
|
||||||
|
problem, _ = QuerySet(Problem).get_or_create(content=problem_text, knowledge_id=knowledge_id)
|
||||||
|
problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid1(), knowledge_id=knowledge_id,
|
||||||
|
document_id=document_id,
|
||||||
|
problem_id=problem.id,
|
||||||
|
paragraph_id=paragraph.id)
|
||||||
|
# 插入段落
|
||||||
|
paragraph.save()
|
||||||
|
# 插入关联问题
|
||||||
|
problem_paragraph_mapping.save()
|
||||||
|
chat_record.improve_paragraph_id_list.append(paragraph.id)
|
||||||
|
update_document_char_length(document_id)
|
||||||
|
# 添加标注
|
||||||
|
chat_record.save()
|
||||||
|
return ChatRecordSerializerModel(chat_record).data, paragraph.id, knowledge_id
|
||||||
|
|
||||||
|
class Operate(serializers.Serializer):
|
||||||
|
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
||||||
|
|
||||||
|
chat_record_id = serializers.UUIDField(required=True,
|
||||||
|
label=_("Conversation record id"))
|
||||||
|
|
||||||
|
knowledge_id = serializers.UUIDField(required=True, label=_("Knowledge base id"))
|
||||||
|
|
||||||
|
document_id = serializers.UUIDField(required=True, label=_("Document id"))
|
||||||
|
|
||||||
|
paragraph_id = serializers.UUIDField(required=True, label=_("Paragraph id"))
|
||||||
|
|
||||||
|
workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
|
||||||
|
|
||||||
|
def delete(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
workspace_id = self.data.get('workspace_id')
|
||||||
|
chat_record_id = self.data.get('chat_record_id')
|
||||||
|
chat_id = self.data.get('chat_id')
|
||||||
|
knowledge_id = self.data.get('knowledge_id')
|
||||||
|
document_id = self.data.get('document_id')
|
||||||
|
paragraph_id = self.data.get('paragraph_id')
|
||||||
|
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
|
||||||
|
if chat_record is None:
|
||||||
|
raise AppApiException(500, gettext('Conversation record does not exist'))
|
||||||
|
if not chat_record.improve_paragraph_id_list.__contains__(uuid.UUID(paragraph_id)):
|
||||||
|
message = lazy_format(
|
||||||
|
gettext(
|
||||||
|
'The paragraph id is wrong. The current conversation record does not exist. [{paragraph_id}] paragraph id'),
|
||||||
|
paragraph_id=paragraph_id)
|
||||||
|
raise AppApiException(500, message.__str__())
|
||||||
|
chat_record.improve_paragraph_id_list = [row for row in chat_record.improve_paragraph_id_list if
|
||||||
|
str(row) != paragraph_id]
|
||||||
|
chat_record.save()
|
||||||
|
o = ParagraphSerializers.Operate(
|
||||||
|
data={"workspace_id": workspace_id, "knowledge_id": knowledge_id, 'document_id': document_id,
|
||||||
|
"paragraph_id": paragraph_id})
|
||||||
|
o.is_valid(raise_exception=True)
|
||||||
|
o.delete()
|
||||||
|
return True
|
||||||
40
apps/application/sql/export_application_chat.sql
Normal file
40
apps/application/sql/export_application_chat.sql
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
SELECT
|
||||||
|
application_chat."id" as chat_id,
|
||||||
|
application_chat.abstract as abstract,
|
||||||
|
application_chat_record_temp.problem_text as problem_text,
|
||||||
|
application_chat_record_temp.answer_text as answer_text,
|
||||||
|
application_chat_record_temp.message_tokens as message_tokens,
|
||||||
|
application_chat_record_temp.answer_tokens as answer_tokens,
|
||||||
|
application_chat_record_temp.run_time as run_time,
|
||||||
|
application_chat_record_temp.details::JSON as details,
|
||||||
|
application_chat_record_temp."index" as "index",
|
||||||
|
application_chat_record_temp.improve_paragraph_list as improve_paragraph_list,
|
||||||
|
application_chat_record_temp.vote_status as vote_status,
|
||||||
|
application_chat_record_temp.create_time as create_time
|
||||||
|
FROM
|
||||||
|
application_chat application_chat
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT COUNT
|
||||||
|
( "id" ) AS chat_record_count,
|
||||||
|
SUM ( CASE WHEN "vote_status" = '0' THEN 1 ELSE 0 END ) AS star_num,
|
||||||
|
SUM ( CASE WHEN "vote_status" = '1' THEN 1 ELSE 0 END ) AS trample_num,
|
||||||
|
SUM ( CASE WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN 0 ELSE array_length( application_chat_record.improve_paragraph_id_list, 1 ) END ) AS mark_sum,
|
||||||
|
chat_id
|
||||||
|
FROM
|
||||||
|
application_chat_record
|
||||||
|
WHERE chat_id IN (
|
||||||
|
SELECT id FROM application_chat ${inner_queryset})
|
||||||
|
GROUP BY
|
||||||
|
application_chat_record.chat_id
|
||||||
|
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
*,
|
||||||
|
CASE
|
||||||
|
WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN
|
||||||
|
'{}' ELSE ( SELECT ARRAY_AGG ( row_to_json ( paragraph ) ) FROM paragraph WHERE "id" = ANY ( application_chat_record.improve_paragraph_id_list ) )
|
||||||
|
END as improve_paragraph_list
|
||||||
|
FROM
|
||||||
|
application_chat_record application_chat_record
|
||||||
|
) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id"
|
||||||
|
${default_queryset}
|
||||||
19
apps/application/sql/list_application_chat.sql
Normal file
19
apps/application/sql/list_application_chat.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
application_chat application_chat
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT COUNT
|
||||||
|
( "id" ) AS chat_record_count,
|
||||||
|
SUM ( CASE WHEN "vote_status" = '0' THEN 1 ELSE 0 END ) AS star_num,
|
||||||
|
SUM ( CASE WHEN "vote_status" = '1' THEN 1 ELSE 0 END ) AS trample_num,
|
||||||
|
SUM ( CASE WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN 0 ELSE array_length( application_chat_record.improve_paragraph_id_list, 1 ) END ) AS mark_sum,
|
||||||
|
chat_id
|
||||||
|
FROM
|
||||||
|
application_chat_record
|
||||||
|
WHERE chat_id IN (
|
||||||
|
SELECT id FROM application_chat ${inner_queryset})
|
||||||
|
GROUP BY
|
||||||
|
application_chat_record.chat_id
|
||||||
|
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
|
||||||
|
${default_queryset}
|
||||||
@ -23,6 +23,29 @@ urlpatterns = [
|
|||||||
views.ApplicationVersionView.as_view()),
|
views.ApplicationVersionView.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/access_token',
|
path('workspace/<str:workspace_id>/application/<str:application_id>/access_token',
|
||||||
views.AccessToken.as_view()),
|
views.AccessToken.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/application/<str:application_id>/add_knowledge',
|
||||||
|
views.ApplicationChatRecordAddKnowledge.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat',
|
||||||
|
views.ApplicationChat.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/export',
|
||||||
|
views.ApplicationChat.Export.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<int:current_page>/<int:page_size>',
|
||||||
|
views.ApplicationChat.Page.as_view()),
|
||||||
|
path(
|
||||||
|
'workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record',
|
||||||
|
views.ApplicationChatRecord.as_view()),
|
||||||
|
path(
|
||||||
|
'workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<int:current_page>/<int:page_size>',
|
||||||
|
views.ApplicationChatRecord.Page.as_view()),
|
||||||
|
path(
|
||||||
|
'workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/improve',
|
||||||
|
views.ApplicationChatRecordImprove.as_view()),
|
||||||
|
path(
|
||||||
|
'workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/improve',
|
||||||
|
views.ApplicationChatRecordImproveParagraph.as_view()),
|
||||||
|
path(
|
||||||
|
'workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/improve',
|
||||||
|
views.ApplicationChatRecordImproveParagraph.Operate.as_view()),
|
||||||
path(
|
path(
|
||||||
'workspace/<str:workspace_id>/application/<str:application_id>/work_flow_version/<int:current_page>/<int:page_size>',
|
'workspace/<str:workspace_id>/application/<str:application_id>/work_flow_version/<int:current_page>/<int:page_size>',
|
||||||
views.ApplicationVersionView.Page.as_view()),
|
views.ApplicationVersionView.Page.as_view()),
|
||||||
|
|||||||
@ -10,4 +10,6 @@ from .application_api_key import *
|
|||||||
from .application import *
|
from .application import *
|
||||||
from .application_version import *
|
from .application_version import *
|
||||||
from .application_access_token import *
|
from .application_access_token import *
|
||||||
from .application_stats import *
|
from .application_stats import *
|
||||||
|
from .application_chat import *
|
||||||
|
from .application_chat_record import *
|
||||||
|
|||||||
80
apps/application/views/application_chat.py
Normal file
80
apps/application/views/application_chat.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat.py
|
||||||
|
@date:2025/6/10 11:00
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
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 application.api.application_chat import ApplicationChatQueryAPI, ApplicationChatQueryPageAPI, \
|
||||||
|
ApplicationChatExportAPI
|
||||||
|
from application.serializers.application_chat import ApplicationChatQuerySerializers
|
||||||
|
from common.auth import TokenAuth
|
||||||
|
from common.auth.authentication import has_permissions
|
||||||
|
from common.constants.permission_constants import PermissionConstants
|
||||||
|
from common.result import result
|
||||||
|
from common.utils.common import query_params_to_single_dict
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChat(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
description=_("Get the conversation list"),
|
||||||
|
summary=_("Get the conversation list"),
|
||||||
|
operation_id=_("Get the conversation list"), # type: ignore
|
||||||
|
request=ApplicationChatQueryAPI.get_request(),
|
||||||
|
parameters=ApplicationChatQueryAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatQueryAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def get(self, request: Request, workspace_id: str, application_id: str):
|
||||||
|
return result.success(ApplicationChatQuerySerializers(
|
||||||
|
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
|
||||||
|
}).list())
|
||||||
|
|
||||||
|
class Page(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
description=_("Get the conversation list by page"),
|
||||||
|
summary=_("Get the conversation list by page"),
|
||||||
|
operation_id=_("Get the conversation list by page"), # type: ignore
|
||||||
|
request=ApplicationChatQueryPageAPI.get_request(),
|
||||||
|
parameters=ApplicationChatQueryPageAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatQueryPageAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int):
|
||||||
|
return result.success(ApplicationChatQuerySerializers(
|
||||||
|
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
|
||||||
|
}).page(current_page=current_page,
|
||||||
|
page_size=page_size))
|
||||||
|
|
||||||
|
class Export(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
description=_("Export conversation"),
|
||||||
|
summary=_("Export conversation"),
|
||||||
|
operation_id=_("Export conversation"), # type: ignore
|
||||||
|
request=ApplicationChatExportAPI.get_request(),
|
||||||
|
parameters=ApplicationChatExportAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatExportAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_EXPORT.get_workspace_application_permission())
|
||||||
|
def post(self, request: Request, workspace_id: str, application_id: str):
|
||||||
|
return ApplicationChatQuerySerializers(
|
||||||
|
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
|
||||||
|
}).export(request.data)
|
||||||
150
apps/application/views/application_chat_record.py
Normal file
150
apps/application/views/application_chat_record.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_chat_record.py
|
||||||
|
@date:2025/6/10 15:08
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
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 application.api.application_chat_record import ApplicationChatRecordQueryAPI, \
|
||||||
|
ApplicationChatRecordImproveParagraphAPI, ApplicationChatRecordAddKnowledgeAPI
|
||||||
|
from application.serializers.application_chat_record import ApplicationChatRecordQuerySerializers, \
|
||||||
|
ApplicationChatRecordImproveSerializer, ChatRecordImproveSerializer, ApplicationChatRecordAddKnowledgeSerializer
|
||||||
|
from common import result
|
||||||
|
from common.auth import TokenAuth
|
||||||
|
from common.auth.authentication import has_permissions
|
||||||
|
from common.constants.permission_constants import PermissionConstants
|
||||||
|
from common.utils.common import query_params_to_single_dict
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecord(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
description=_("Get the conversation list"),
|
||||||
|
summary=_("Get the conversation list"),
|
||||||
|
operation_id=_("Get the conversation list"), # type: ignore
|
||||||
|
request=ApplicationChatRecordQueryAPI.get_request(),
|
||||||
|
parameters=ApplicationChatRecordQueryAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordQueryAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str):
|
||||||
|
return result.success(ApplicationChatRecordQuerySerializers(
|
||||||
|
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
|
||||||
|
'chat_id': chat_id
|
||||||
|
}).list())
|
||||||
|
|
||||||
|
class Page(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
description=_("Get the conversation record list by page"),
|
||||||
|
summary=_("Get the conversation record list by page"),
|
||||||
|
operation_id=_("Get the conversation record list by page"), # type: ignore
|
||||||
|
request=ApplicationChatRecordQueryAPI.get_request(),
|
||||||
|
parameters=ApplicationChatRecordQueryAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordQueryAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, current_page: int,
|
||||||
|
page_size: int):
|
||||||
|
return result.success(ApplicationChatRecordQuerySerializers(
|
||||||
|
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
|
||||||
|
'chat_id': chat_id}).page(
|
||||||
|
current_page=current_page,
|
||||||
|
page_size=page_size))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordAddKnowledge(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
description=_("Add to Knowledge Base"),
|
||||||
|
summary=_("Add to Knowledge Base"),
|
||||||
|
operation_id=_("Add to Knowledge Base"), # type: ignore
|
||||||
|
request=ApplicationChatRecordAddKnowledgeAPI.get_request(),
|
||||||
|
parameters=ApplicationChatRecordAddKnowledgeAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordAddKnowledgeAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def post(self, request: Request, workspace_id: str, application_id: str):
|
||||||
|
return result.success(ApplicationChatRecordAddKnowledgeSerializer().post_improve(request.data))
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImprove(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['GET'],
|
||||||
|
description=_("Get the list of marked paragraphs"),
|
||||||
|
summary=_("Get the list of marked paragraphs"),
|
||||||
|
operation_id=_("Get the list of marked paragraphs"), # type: ignore
|
||||||
|
request=ApplicationChatRecordQueryAPI.get_request(),
|
||||||
|
parameters=ApplicationChatRecordQueryAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordQueryAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG.get_workspace_application_permission())
|
||||||
|
def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str):
|
||||||
|
return result.success(ChatRecordImproveSerializer(
|
||||||
|
data={'chat_id': chat_id, 'chat_record_id': chat_record_id}).get())
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationChatRecordImproveParagraph(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['PUT'],
|
||||||
|
description=_("Annotation"),
|
||||||
|
summary=_("Annotation"),
|
||||||
|
operation_id=_("Annotation"), # type: ignore
|
||||||
|
request=ApplicationChatRecordImproveParagraphAPI.get_request(),
|
||||||
|
parameters=ApplicationChatRecordImproveParagraphAPI.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordImproveParagraphAPI.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission())
|
||||||
|
def put(self, request: Request,
|
||||||
|
workspace_id: str,
|
||||||
|
application_id: str,
|
||||||
|
chat_id: str,
|
||||||
|
chat_record_id: str,
|
||||||
|
knowledge_id: str,
|
||||||
|
document_id: str):
|
||||||
|
return result.success(ApplicationChatRecordImproveSerializer(
|
||||||
|
data={'chat_id': chat_id, 'chat_record_id': chat_record_id,
|
||||||
|
'knowledge_id': knowledge_id, 'document_id': document_id}).improve(request.data))
|
||||||
|
|
||||||
|
class Operate(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['DELETE'],
|
||||||
|
description=_("Delete a Annotation"),
|
||||||
|
summary=_("Delete a Annotation"),
|
||||||
|
operation_id=_("Delete a Annotation"), # type: ignore
|
||||||
|
request=ApplicationChatRecordImproveParagraphAPI.Operate.get_request(),
|
||||||
|
parameters=ApplicationChatRecordImproveParagraphAPI.Operate.get_parameters(),
|
||||||
|
responses=ApplicationChatRecordImproveParagraphAPI.Operate.get_response(),
|
||||||
|
tags=[_("Application/Conversation Log")] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission())
|
||||||
|
def delete(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str,
|
||||||
|
knowledge_id: str,
|
||||||
|
document_id: str, paragraph_id: str):
|
||||||
|
return result.success(ApplicationChatRecordImproveSerializer.Operate(
|
||||||
|
data={'chat_id': chat_id, 'chat_record_id': chat_record_id, 'workspace_id': workspace_id,
|
||||||
|
'knowledge_id': knowledge_id, 'document_id': document_id,
|
||||||
|
'paragraph_id': paragraph_id}).delete())
|
||||||
@ -61,6 +61,7 @@ class Group(Enum):
|
|||||||
OTHER = "OTHER"
|
OTHER = "OTHER"
|
||||||
OVERVIEW = "OVERVIEW"
|
OVERVIEW = "OVERVIEW"
|
||||||
APPLICATION_ACCESS = "APPLICATION_ACCESS"
|
APPLICATION_ACCESS = "APPLICATION_ACCESS"
|
||||||
|
APPLICATION_CHAT_LOG = "APPLICATION_CHAT_LOG"
|
||||||
|
|
||||||
|
|
||||||
class SystemGroup(Enum):
|
class SystemGroup(Enum):
|
||||||
@ -124,6 +125,8 @@ class Operate(Enum):
|
|||||||
MIGRATE = "READ+MIGRATE" # 迁移
|
MIGRATE = "READ+MIGRATE" # 迁移
|
||||||
RELATE = "READ+RELATE" # 关联
|
RELATE = "READ+RELATE" # 关联
|
||||||
USER_GROUP = "READ+USER_GROUP" # 用户组
|
USER_GROUP = "READ+USER_GROUP" # 用户组
|
||||||
|
ANNOTATION = "READ+ANNOTATION" # 标注
|
||||||
|
CLEAR_POLICY = "READ+CLEAR_POLICY"
|
||||||
|
|
||||||
|
|
||||||
class RoleGroup(Enum):
|
class RoleGroup(Enum):
|
||||||
@ -237,6 +240,8 @@ Permission_Label = {
|
|||||||
Operate.VECTOR.value: _("Vector"),
|
Operate.VECTOR.value: _("Vector"),
|
||||||
Operate.MIGRATE.value: _("Migrate"),
|
Operate.MIGRATE.value: _("Migrate"),
|
||||||
Operate.RELATE.value: _("Relate"),
|
Operate.RELATE.value: _("Relate"),
|
||||||
|
Operate.ANNOTATION.value: _("Annotation"),
|
||||||
|
Operate.CLEAR_POLICY.value: _("Clear Policy"),
|
||||||
Group.LOGIN_AUTH.value: _("Login Auth"),
|
Group.LOGIN_AUTH.value: _("Login Auth"),
|
||||||
Group.DISPLAY_SETTINGS.value: _("Display Settings"),
|
Group.DISPLAY_SETTINGS.value: _("Display Settings"),
|
||||||
Group.SYSTEM_API_KEY.value: _("System API Key"),
|
Group.SYSTEM_API_KEY.value: _("System API Key"),
|
||||||
@ -257,7 +262,8 @@ Permission_Label = {
|
|||||||
Group.SYSTEM_RES_KNOWLEDGE_PROBLEM.value: _("Problem"),
|
Group.SYSTEM_RES_KNOWLEDGE_PROBLEM.value: _("Problem"),
|
||||||
Group.WORKSPACE_USER_GROUP.value: _("User Group"),
|
Group.WORKSPACE_USER_GROUP.value: _("User Group"),
|
||||||
Group.WORKSPACE_CHAT_USER.value: _("Chat User"),
|
Group.WORKSPACE_CHAT_USER.value: _("Chat User"),
|
||||||
Group.WORKSPACE_WORKSPACE: _("Workspace"),
|
Group.WORKSPACE_WORKSPACE.value: _("Workspace"),
|
||||||
|
Group.APPLICATION_CHAT_LOG.value: _("Dialogue log")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,6 +655,30 @@ class PermissionConstants(Enum):
|
|||||||
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
||||||
label=_('Public settings')
|
label=_('Public settings')
|
||||||
)
|
)
|
||||||
|
APPLICATION_CHAT_LOG = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.READ,
|
||||||
|
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
|
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
||||||
|
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
||||||
|
label=_('Dialogue log'))
|
||||||
|
|
||||||
|
APPLICATION_CHAT_LOG_ANNOTATION = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.ANNOTATION,
|
||||||
|
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
|
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
||||||
|
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
||||||
|
label=_('Dialogue log'))
|
||||||
|
|
||||||
|
APPLICATION_CHAT_LOG_EXPORT = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.EXPORT,
|
||||||
|
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
|
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
||||||
|
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
||||||
|
label=_('Dialogue log'))
|
||||||
|
|
||||||
|
APPLICATION_CHAT_LOG_CLEAR_POLICY = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.CLEAR_POLICY,
|
||||||
|
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
|
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
||||||
|
resource_permission_group_list=[ResourcePermissionGroup.VIEW],
|
||||||
|
label=_('Dialogue log'))
|
||||||
|
|
||||||
APPLICATION_ACCESS_READ = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.READ,
|
APPLICATION_ACCESS_READ = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.READ,
|
||||||
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
role_list=[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],
|
||||||
|
|||||||
@ -54,6 +54,7 @@ pymupdf = "1.26.0"
|
|||||||
pypdf = "5.5.0"
|
pypdf = "5.5.0"
|
||||||
gunicorn = "23.0.0"
|
gunicorn = "23.0.0"
|
||||||
python-daemon = "3.1.2"
|
python-daemon = "3.1.2"
|
||||||
|
pytz = "^2025.2"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user