conversation_id显示用户对话
This commit is contained in:
parent
c0bba14ee8
commit
9153594375
@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-21 04:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user_management', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='chathistory',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, default=dict, help_text="\n {\n 'model_id': 'xxx',\n 'dataset_id_list': ['id1', 'id2', ...],\n 'dataset_external_id_list': ['ext1', 'ext2', ...],\n 'primary_knowledge_base': 'id1'\n }\n "),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='chathistory',
|
||||
index=models.Index(fields=['conversation_id', 'is_deleted'], name='chat_histor_convers_89bc43_idx'),
|
||||
),
|
||||
]
|
@ -1,3 +1,4 @@
|
||||
from itertools import count
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
@ -286,13 +287,23 @@ class ChatHistory(models.Model):
|
||||
]
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
# 保留与主知识库的关联
|
||||
knowledge_base = models.ForeignKey('KnowledgeBase', on_delete=models.CASCADE)
|
||||
# 用于标识知识库组合的对话
|
||||
conversation_id = models.CharField(max_length=100, db_index=True)
|
||||
parent_id = models.CharField(max_length=100, null=True, blank=True)
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
|
||||
content = models.TextField()
|
||||
tokens = models.IntegerField(default=0, help_text="消息token数")
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
# 扩展metadata字段,用于存储知识库组合信息
|
||||
metadata = models.JSONField(default=dict, blank=True, help_text="""
|
||||
{
|
||||
'model_id': 'xxx',
|
||||
'dataset_id_list': ['id1', 'id2', ...],
|
||||
'dataset_external_id_list': ['ext1', 'ext2', ...],
|
||||
'primary_knowledge_base': 'id1'
|
||||
}
|
||||
""")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
|
||||
@ -302,6 +313,8 @@ class ChatHistory(models.Model):
|
||||
indexes = [
|
||||
models.Index(fields=['conversation_id', 'created_at']),
|
||||
models.Index(fields=['user', 'created_at']),
|
||||
# 添加新的索引以支持知识库组合查询
|
||||
models.Index(fields=['conversation_id', 'is_deleted']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
@ -315,11 +328,69 @@ class ChatHistory(models.Model):
|
||||
is_deleted=False
|
||||
).order_by('created_at')
|
||||
|
||||
@classmethod
|
||||
def get_conversations_by_knowledge_bases(cls, dataset_ids, user):
|
||||
"""根据知识库组合获取对话历史"""
|
||||
# 对知识库ID列表排序以确保一致性
|
||||
sorted_kb_ids = sorted(dataset_ids)
|
||||
conversation_id = str(uuid.uuid5(
|
||||
uuid.NAMESPACE_DNS,
|
||||
'-'.join(sorted_kb_ids)
|
||||
))
|
||||
|
||||
return cls.objects.filter(
|
||||
conversation_id=conversation_id,
|
||||
user=user,
|
||||
is_deleted=False
|
||||
).order_by('created_at')
|
||||
|
||||
@classmethod
|
||||
def get_knowledge_base_combinations(cls, user):
|
||||
"""获取用户的所有知识库组合"""
|
||||
return cls.objects.filter(
|
||||
user=user,
|
||||
is_deleted=False
|
||||
).values('conversation_id').annotate(
|
||||
last_message=max('created_at'),
|
||||
message_count=count('id')
|
||||
).values(
|
||||
'conversation_id',
|
||||
'last_message',
|
||||
'message_count',
|
||||
'metadata'
|
||||
).order_by('-last_message')
|
||||
|
||||
def get_knowledge_bases(self):
|
||||
"""获取此消息关联的所有知识库"""
|
||||
if self.metadata and 'dataset_id_list' in self.metadata:
|
||||
return KnowledgeBase.objects.filter(
|
||||
id__in=self.metadata['dataset_id_list']
|
||||
)
|
||||
return KnowledgeBase.objects.filter(id=self.knowledge_base.id)
|
||||
|
||||
def soft_delete(self):
|
||||
"""软删除消息"""
|
||||
self.is_deleted = True
|
||||
self.save()
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
return {
|
||||
'id': str(self.id),
|
||||
'conversation_id': self.conversation_id,
|
||||
'role': self.role,
|
||||
'content': self.content,
|
||||
'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'metadata': self.metadata,
|
||||
'knowledge_bases': [
|
||||
{
|
||||
'id': str(kb.id),
|
||||
'name': kb.name,
|
||||
'type': kb.type
|
||||
} for kb in self.get_knowledge_bases()
|
||||
]
|
||||
}
|
||||
|
||||
class UserProfile(models.Model):
|
||||
"""用户档案模型"""
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
||||
|
@ -26,7 +26,7 @@ import os
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.http import Http404
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.db import IntegrityError
|
||||
from channels.exceptions import ChannelFull
|
||||
from django.conf import settings
|
||||
@ -93,79 +93,73 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
|
||||
def list(self, request):
|
||||
"""获取聊天记录列表"""
|
||||
"""获取对话列表概览"""
|
||||
try:
|
||||
# 获取查询参数
|
||||
dataset_id = request.query_params.get('dataset_id')
|
||||
page = int(request.query_params.get('page', 1))
|
||||
page_size = int(request.query_params.get('page_size', 10))
|
||||
|
||||
query = self.get_queryset()
|
||||
# 获取所有对话的概览
|
||||
latest_chats = self.get_queryset().values(
|
||||
'conversation_id'
|
||||
).annotate(
|
||||
latest_id=Max('id'),
|
||||
message_count=Count('id'),
|
||||
last_message=Max('created_at')
|
||||
).order_by('-last_message')
|
||||
|
||||
if dataset_id:
|
||||
# 获取特定知识库的完整对话历史
|
||||
records = query.filter(
|
||||
knowledge_base__id=dataset_id
|
||||
).order_by('created_at')
|
||||
# 计算分页
|
||||
total = latest_chats.count()
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
chats = latest_chats[start:end]
|
||||
|
||||
conversation = {
|
||||
'dataset_id': dataset_id,
|
||||
'dataset_name': records.first().knowledge_base.name if records.exists() else None,
|
||||
'messages': [{
|
||||
'id': record.id,
|
||||
'role': record.role,
|
||||
'content': record.content,
|
||||
'created_at': record.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
} for record in records]
|
||||
results = []
|
||||
for chat in chats:
|
||||
# 获取最新消息记录
|
||||
latest_record = ChatHistory.objects.get(id=chat['latest_id'])
|
||||
|
||||
# 从metadata中获取完整的知识库信息
|
||||
dataset_info = []
|
||||
if latest_record.metadata:
|
||||
dataset_id_list = latest_record.metadata.get('dataset_id_list', [])
|
||||
dataset_names = latest_record.metadata.get('dataset_names', [])
|
||||
|
||||
# 如果有知识库ID列表
|
||||
if dataset_id_list:
|
||||
# 如果同时有名称列表且长度匹配
|
||||
if dataset_names and len(dataset_names) == len(dataset_id_list):
|
||||
dataset_info = [{
|
||||
'id': str(id),
|
||||
'name': name
|
||||
} for id, name in zip(dataset_id_list, dataset_names)]
|
||||
else:
|
||||
# 如果没有名称列表,则只返回ID
|
||||
datasets = KnowledgeBase.objects.filter(id__in=dataset_id_list)
|
||||
dataset_info = [{
|
||||
'id': str(ds.id),
|
||||
'name': ds.name
|
||||
} for ds in datasets]
|
||||
|
||||
results.append({
|
||||
'conversation_id': chat['conversation_id'],
|
||||
'message_count': chat['message_count'],
|
||||
'last_message': latest_record.content,
|
||||
'last_time': chat['last_message'].strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'dataset_id_list': [ds['id'] for ds in dataset_info], # 添加完整的知识库ID列表
|
||||
'datasets': dataset_info # 包含ID和名称的完整信息
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': {
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'results': results
|
||||
}
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': conversation
|
||||
})
|
||||
else:
|
||||
# 获取所有对话的概览
|
||||
latest_chats = query.values(
|
||||
'conversation_id',
|
||||
'knowledge_base__id',
|
||||
'knowledge_base__name'
|
||||
).annotate(
|
||||
latest_id=Max('id'),
|
||||
message_count=Count('id'),
|
||||
last_message=Max('created_at')
|
||||
).order_by('-last_message')
|
||||
|
||||
# 计算分页
|
||||
total = latest_chats.count()
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
|
||||
# 获取分页数据
|
||||
chats = latest_chats[start:end]
|
||||
|
||||
results = []
|
||||
for chat in chats:
|
||||
latest_record = ChatHistory.objects.get(id=chat['latest_id'])
|
||||
results.append({
|
||||
'conversation_id': chat['conversation_id'],
|
||||
'dataset_id': str(chat['knowledge_base__id']),
|
||||
'dataset_name': chat['knowledge_base__name'],
|
||||
'message_count': chat['message_count'],
|
||||
'last_message': latest_record.content,
|
||||
'last_time': chat['last_message'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': {
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'results': results
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取聊天记录失败: {str(e)}")
|
||||
@ -176,6 +170,119 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def conversation_detail(self, request):
|
||||
"""获取特定对话的详细信息"""
|
||||
try:
|
||||
conversation_id = request.query_params.get('conversation_id')
|
||||
if not conversation_id:
|
||||
return Response({
|
||||
'code': 400,
|
||||
'message': '缺少conversation_id参数',
|
||||
'data': None
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 获取对话历史
|
||||
messages = self.get_queryset().filter(
|
||||
conversation_id=conversation_id
|
||||
).order_by('created_at')
|
||||
|
||||
if not messages.exists():
|
||||
return Response({
|
||||
'code': 404,
|
||||
'message': '对话不存在',
|
||||
'data': None
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 获取知识库信息
|
||||
first_message = messages.first()
|
||||
dataset_info = []
|
||||
if first_message and first_message.metadata:
|
||||
if 'dataset_id_list' in first_message.metadata:
|
||||
datasets = KnowledgeBase.objects.filter(
|
||||
id__in=first_message.metadata['dataset_id_list']
|
||||
)
|
||||
dataset_info = [{
|
||||
'id': str(ds.id),
|
||||
'name': ds.name,
|
||||
'type': ds.type
|
||||
} for ds in datasets]
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': {
|
||||
'conversation_id': conversation_id,
|
||||
'datasets': dataset_info,
|
||||
'messages': [{
|
||||
'id': str(msg.id),
|
||||
'role': msg.role,
|
||||
'content': msg.content,
|
||||
'created_at': msg.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
} for msg in messages]
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取对话详情失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': f'获取对话详情失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def available_datasets(self, request):
|
||||
"""获取用户可访问的知识库列表"""
|
||||
try:
|
||||
user = request.user
|
||||
|
||||
# 获取用户有权限访问的知识库
|
||||
accessible_datasets = []
|
||||
|
||||
# 1. 获取用户通过权限表直接授权的知识库
|
||||
permission_datasets = KnowledgeBase.objects.filter(
|
||||
id__in=KBPermissionModel.objects.filter(
|
||||
user=user,
|
||||
can_read=True,
|
||||
status='active'
|
||||
).values_list('knowledge_base_id', flat=True)
|
||||
)
|
||||
|
||||
# 2. 获取用户根据角色可以访问的知识库
|
||||
role_datasets = KnowledgeBase.objects.filter(
|
||||
Q(type='member', department=user.department) | # 成员级知识库
|
||||
Q(type='leader', department=user.department) | # 部门级知识库,组长可访问
|
||||
Q(type='admin') # 管理级知识库,管理员可访问
|
||||
).exclude(
|
||||
Q(type='private') & ~Q(user_id=str(user.id)) # 排除不属于自己的私人知识库
|
||||
)
|
||||
|
||||
# 3. 合并并去重
|
||||
accessible_datasets = list(set(list(permission_datasets) + list(role_datasets)))
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': [{
|
||||
'id': str(ds.id),
|
||||
'name': ds.name,
|
||||
'type': ds.type,
|
||||
'department': ds.department,
|
||||
'description': ds.desc
|
||||
} for ds in accessible_datasets]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取可用知识库列表失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': f'获取可用知识库列表失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
def create(self, request):
|
||||
"""创建聊天记录"""
|
||||
try:
|
||||
@ -192,9 +299,23 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
# 检查知识库ID:支持dataset_id或dataset_id_list格式
|
||||
dataset_ids = []
|
||||
if 'dataset_id' in data:
|
||||
dataset_ids.append(data['dataset_id'])
|
||||
elif 'dataset_id_list' in data and isinstance(data['dataset_id_list'], list):
|
||||
dataset_ids = data['dataset_id_list']
|
||||
dataset_id = data['dataset_id']
|
||||
# 直接使用标准UUID格式
|
||||
dataset_ids.append(str(dataset_id))
|
||||
elif 'dataset_id_list' in data and isinstance(data['dataset_id_list'], (list, str)):
|
||||
# 处理可能的字符串格式
|
||||
if isinstance(data['dataset_id_list'], str):
|
||||
try:
|
||||
# 尝试解析JSON字符串
|
||||
dataset_list = json.loads(data['dataset_id_list'])
|
||||
if isinstance(dataset_list, list):
|
||||
dataset_ids = [str(id) for id in dataset_list]
|
||||
except json.JSONDecodeError:
|
||||
# 如果解析失败,可能是单个ID
|
||||
dataset_ids = [str(data['dataset_id_list'])]
|
||||
else:
|
||||
# 如果已经是列表,直接使用标准UUID格式
|
||||
dataset_ids = [str(id) for id in data['dataset_id_list']]
|
||||
else:
|
||||
return Response({
|
||||
'code': 400,
|
||||
@ -212,9 +333,9 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
# 验证所有知识库并收集external_ids
|
||||
external_id_list = []
|
||||
user = request.user
|
||||
primary_knowledge_base = None # 主知识库,用于关联聊天记录
|
||||
knowledge_bases = [] # 存储所有知识库对象
|
||||
|
||||
for idx, kb_id in enumerate(dataset_ids):
|
||||
for kb_id in dataset_ids:
|
||||
try:
|
||||
knowledge_base = KnowledgeBase.objects.filter(id=kb_id).first()
|
||||
if not knowledge_base:
|
||||
@ -224,9 +345,7 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
'data': None
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 保存第一个知识库作为主知识库
|
||||
if idx == 0:
|
||||
primary_knowledge_base = knowledge_base
|
||||
knowledge_bases.append(knowledge_base)
|
||||
|
||||
# 检查知识库权限
|
||||
can_read = False
|
||||
@ -242,7 +361,6 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
if permission:
|
||||
can_read = True
|
||||
else:
|
||||
# 使用_can_read方法判断
|
||||
can_read = self._can_read(
|
||||
type=knowledge_base.type,
|
||||
user=user,
|
||||
@ -279,7 +397,20 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 获取或创建对话ID
|
||||
conversation_id = data.get('conversation_id', str(uuid.uuid4()))
|
||||
conversation_id = data.get('conversation_id')
|
||||
|
||||
# 如果没有提供 conversation_id,根据知识库组合生成新的ID
|
||||
if not conversation_id:
|
||||
# 对知识库ID列表排序以确保相同组合生成相同的hash
|
||||
sorted_kb_ids = sorted(dataset_ids)
|
||||
# 使用知识库ID组合生成唯一的conversation_id
|
||||
conversation_id = str(uuid.uuid5(
|
||||
uuid.NAMESPACE_DNS,
|
||||
'-'.join(sorted_kb_ids)
|
||||
))
|
||||
logger.info(f"为知识库组合 {sorted_kb_ids} 生成新的conversation_id: {conversation_id}")
|
||||
else:
|
||||
logger.info(f"使用现有conversation_id: {conversation_id}")
|
||||
|
||||
# 调用外部API获取答案 (传递多个knowledge base的external_id)
|
||||
answer = self._get_answer_from_external_api(
|
||||
@ -294,41 +425,44 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 创建用户问题记录 (关联到主知识库)
|
||||
# 准备完整的metadata
|
||||
metadata = {
|
||||
'model_id': data.get('model_id', '58c5deb4-f2e2-11ef-9a1b-0242ac120009'),
|
||||
'dataset_id_list': [str(id) for id in dataset_ids],
|
||||
'dataset_external_id_list': [str(id) for id in external_id_list],
|
||||
'dataset_names': [kb.name for kb in knowledge_bases] # 添加知识库名称列表
|
||||
}
|
||||
|
||||
# 创建用户问题记录
|
||||
question_record = ChatHistory.objects.create(
|
||||
user=request.user,
|
||||
knowledge_base=primary_knowledge_base,
|
||||
conversation_id=conversation_id,
|
||||
knowledge_base=knowledge_bases[0], # 仍然需要一个主知识库,使用第一个
|
||||
conversation_id=str(conversation_id),
|
||||
role='user',
|
||||
content=data['question'],
|
||||
metadata={
|
||||
'model_id': data.get('model_id', '58c5deb4-f2e2-11ef-9a1b-0242ac120009'),
|
||||
'dataset_id_list': dataset_ids
|
||||
}
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# 创建AI回答记录
|
||||
answer_record = ChatHistory.objects.create(
|
||||
user=request.user,
|
||||
knowledge_base=primary_knowledge_base,
|
||||
conversation_id=conversation_id,
|
||||
knowledge_base=knowledge_bases[0], # 仍然需要一个主知识库,使用第一个
|
||||
conversation_id=str(conversation_id),
|
||||
parent_id=str(question_record.id),
|
||||
role='assistant',
|
||||
content=answer,
|
||||
metadata={
|
||||
'model_id': data.get('model_id', '58c5deb4-f2e2-11ef-9a1b-0242ac120009'),
|
||||
'dataset_id_list': dataset_ids
|
||||
}
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# 返回完整的响应
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '创建成功',
|
||||
'data': {
|
||||
'id': answer_record.id,
|
||||
'conversation_id': conversation_id,
|
||||
'dataset_id': str(primary_knowledge_base.id),
|
||||
'dataset_name': primary_knowledge_base.name,
|
||||
'id': str(answer_record.id),
|
||||
'conversation_id': str(conversation_id),
|
||||
'dataset_id_list': [str(id) for id in dataset_ids],
|
||||
'dataset_names': [kb.name for kb in knowledge_bases], # 返回所有知识库名称
|
||||
'role': 'assistant',
|
||||
'content': answer_record.content,
|
||||
'created_at': answer_record.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
@ -342,7 +476,7 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
'code': 500,
|
||||
'message': f'创建聊天记录失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
}, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
def _get_answer_from_external_api(self, dataset_external_id_list, question):
|
||||
"""调用外部API获取AI回答"""
|
||||
@ -354,7 +488,7 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
|
||||
# 第一个API调用创建聊天
|
||||
chat_request_data = {
|
||||
"id": "d333dee2-b3c2-11ef-af2c-a4bb6dafa942",
|
||||
"id": "65031f4d-c86d-430e-8089-d8ff2731a837",
|
||||
"model_id": "58c5deb4-f2e2-11ef-9a1b-0242ac120009",
|
||||
"dataset_id_list": dataset_external_ids,
|
||||
"multiple_rounds_dialogue": False,
|
||||
@ -617,6 +751,210 @@ class ChatHistoryViewSet(viewsets.ModelViewSet):
|
||||
'message': f'搜索失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def export(self, request):
|
||||
"""导出聊天记录为Excel文件"""
|
||||
try:
|
||||
# 获取查询参数
|
||||
conversation_id = request.query_params.get('conversation_id')
|
||||
dataset_id = request.query_params.get('dataset_id')
|
||||
history_days = request.query_params.get('history_days', '7') # 默认导出最近7天
|
||||
|
||||
# 至少需要一个筛选条件
|
||||
if not conversation_id and not dataset_id:
|
||||
return Response({
|
||||
'code': 400,
|
||||
'message': '需要提供conversation_id或dataset_id参数',
|
||||
'data': None
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 验证权限
|
||||
user = request.user
|
||||
if dataset_id:
|
||||
knowledge_base = KnowledgeBase.objects.filter(id=dataset_id).first()
|
||||
if not knowledge_base:
|
||||
return Response({
|
||||
'code': 404,
|
||||
'message': '知识库不存在',
|
||||
'data': None
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 检查是否有读取权限
|
||||
can_read = False
|
||||
permission = KBPermissionModel.objects.filter(
|
||||
knowledge_base=knowledge_base,
|
||||
user=user,
|
||||
can_read=True,
|
||||
status='active'
|
||||
).first()
|
||||
|
||||
if permission:
|
||||
can_read = True
|
||||
else:
|
||||
can_read = self._can_read(
|
||||
type=knowledge_base.type,
|
||||
user=user,
|
||||
department=knowledge_base.department,
|
||||
group=knowledge_base.group,
|
||||
creator_id=knowledge_base.user_id
|
||||
)
|
||||
|
||||
if not can_read:
|
||||
return Response({
|
||||
'code': 403,
|
||||
'message': '无权访问该知识库',
|
||||
'data': None
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# 查询确认有聊天记录存在
|
||||
query = self.get_queryset()
|
||||
if conversation_id:
|
||||
records = query.filter(conversation_id=conversation_id)
|
||||
elif dataset_id:
|
||||
records = query.filter(knowledge_base__id=dataset_id)
|
||||
|
||||
if not records.exists():
|
||||
return Response({
|
||||
'code': 404,
|
||||
'message': '未找到相关对话记录',
|
||||
'data': None
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 调用外部API导出Excel文件 - 使用GET请求
|
||||
application_id = "65031f4d-c86d-430e-8089-d8ff2731a837" # 固定值
|
||||
export_url = f"{settings.API_BASE_URL}/api/application/{application_id}/chat/export?history_day={history_days}"
|
||||
|
||||
logger.info(f"发送导出请求:{export_url}")
|
||||
|
||||
export_response = requests.get(
|
||||
url=export_url,
|
||||
timeout=60,
|
||||
stream=True # 使用流式传输处理大文件
|
||||
)
|
||||
|
||||
# 检查响应状态
|
||||
if export_response.status_code != 200:
|
||||
logger.error(f"导出API调用失败: {export_response.status_code}, {export_response.text}")
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': '导出失败,外部服务返回错误',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 创建响应对象并设置文件下载头
|
||||
response = HttpResponse(
|
||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
response['Content-Disposition'] = 'attachment; filename="data.xlsx"'
|
||||
|
||||
# 将API响应内容写入响应对象
|
||||
for chunk in export_response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
response.write(chunk)
|
||||
|
||||
logger.info("导出成功完成")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"导出聊天记录失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': f'导出聊天记录失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def chat_list(self, request):
|
||||
"""获取对话列表"""
|
||||
try:
|
||||
# 获取查询参数
|
||||
history_days = request.query_params.get('history_days', '7') # 默认7天
|
||||
|
||||
# 构建API请求
|
||||
application_id = "65031f4d-c86d-430e-8089-d8ff2731a837"
|
||||
api_url = f"{settings.API_BASE_URL}/api/application/{application_id}/chat"
|
||||
|
||||
# 添加查询参数
|
||||
params = {
|
||||
'history_day': history_days
|
||||
}
|
||||
|
||||
logger.info(f"发送获取对话列表请求:{api_url}")
|
||||
|
||||
# 调用外部API
|
||||
response = requests.get(
|
||||
url=api_url,
|
||||
params=params,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"获取对话列表失败: {response.status_code}, {response.text}")
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': '获取对话列表失败,外部服务返回错误',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 解析响应数据
|
||||
try:
|
||||
result = response.json()
|
||||
|
||||
if result.get('code') != 200:
|
||||
logger.error(f"外部API返回错误: {result}")
|
||||
return Response({
|
||||
'code': result.get('code', 500),
|
||||
'message': result.get('message', '获取对话列表失败'),
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
# 处理返回的数据
|
||||
chat_list = result.get('data', [])
|
||||
|
||||
# 格式化返回数据
|
||||
formatted_chats = []
|
||||
for chat in chat_list:
|
||||
formatted_chat = {
|
||||
'id': chat['id'],
|
||||
'chat_id': chat['chat_id'],
|
||||
'abstract': chat['abstract'],
|
||||
'message_count': chat['chat_record_count'],
|
||||
'created_at': datetime.fromisoformat(chat['create_time'].replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'updated_at': datetime.fromisoformat(chat['update_time'].replace('Z', '+00:00')).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'star_count': chat['star_num'],
|
||||
'trample_count': chat['trample_num'],
|
||||
'mark_sum': chat['mark_sum'],
|
||||
'is_deleted': chat['is_deleted']
|
||||
}
|
||||
formatted_chats.append(formatted_chat)
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'message': '获取成功',
|
||||
'data': {
|
||||
'total': len(formatted_chats),
|
||||
'results': formatted_chats
|
||||
}
|
||||
})
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"解析响应数据失败: {str(e)}")
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': '解析响应数据失败',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取对话列表失败: {str(e)}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response({
|
||||
'code': 500,
|
||||
'message': f'获取对话列表失败: {str(e)}',
|
||||
'data': None
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
def _highlight_keyword(self, text, keyword):
|
||||
"""高亮关键词"""
|
||||
|
Loading…
Reference in New Issue
Block a user