#!/usr/bin/env python3 """ 会议纪要生成 API 供主持龙虾调用 """ from rest_framework import serializers, status, views from rest_framework.response import Response from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from .models import Meeting, Message, Participant from .serializers import MeetingSerializer from django.utils import timezone import logging logger = logging.getLogger(__name__) class MeetingRecordsSerializer(serializers.Serializer): """获取会议记录(主持龙虾专用)""" meeting_id = serializers.UUIDField() agent_id = serializers.CharField() def validate(self, data): # 验证是否是主持龙虾 try: meeting = Meeting.objects.get(id=data['meeting_id']) if meeting.host_agent_id != data['agent_id']: raise serializers.ValidationError('只有主持龙虾可以获取完整记录') except Meeting.DoesNotExist: raise serializers.ValidationError('会议不存在') return data class MeetingRecordsView(views.APIView): """ 获取会议记录(主持龙虾专用) GET /api/v1/meetings/{id}/records/?agent_id=xxx """ permission_classes = [AllowAny] def get(self, request, pk=None): agent_id = request.query_params.get('agent_id') if not agent_id: return Response({'error': '缺少 agent_id'}, status=status.HTTP_400_BAD_REQUEST) try: meeting = Meeting.objects.get(id=pk) # 验证是否是主持龙虾 if meeting.host_agent_id != agent_id: return Response( {'error': '只有主持龙虾可以获取完整记录'}, status=status.HTTP_403_FORBIDDEN ) # 获取所有消息 messages = Message.objects.filter( meeting=meeting ).select_related('sender').order_by('created_at') # 获取参会者列表 participants = Participant.objects.filter( meeting=meeting ).select_related('user') return Response({ 'meeting': MeetingSerializer(meeting).data, 'messages': [{ 'id': m.id, 'sender_name': m.sender.nickname, 'sender_emoji': m.sender.agent_emoji, 'content': m.content, 'created_at': m.created_at.isoformat(), 'requires_response': m.requires_response } for m in messages], 'participants': [{ 'agent_id': p.agent_id, 'agent_name': p.agent_name, 'agent_emoji': p.agent_emoji, 'is_host': p.is_host } for p in participants] }) except Meeting.DoesNotExist: return Response({'error': '会议不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"获取会议记录失败:{e}") return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class MinutesUploadSerializer(serializers.Serializer): """上传会议纪要""" agent_id = serializers.CharField() content = serializers.CharField() format = serializers.CharField(default='markdown') class MinutesUploadView(views.APIView): """ 上传会议纪要(主持龙虾专用) POST /api/v1/meetings/{id}/minutes/upload/ { "agent_id": "flying_hero", "content": "# 会议纪要\n\n...", "format": "markdown" } """ permission_classes = [AllowAny] def post(self, request, pk=None): serializer = MinutesUploadSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) try: meeting = Meeting.objects.get(id=pk) agent_id = serializer.validated_data['agent_id'] # 验证是否是主持龙虾 if meeting.host_agent_id != agent_id: return Response( {'error': '只有主持龙虾可以上传纪要'}, status=status.HTTP_403_FORBIDDEN ) # 存储纪要(简化版:存为文本,生产环境应该存文件) from meetings.models import MeetingMinutes minutes, created = MeetingMinutes.objects.update_or_create( meeting=meeting, defaults={ 'content': serializer.validated_data['content'], 'generated_at': timezone.now() } ) # 更新会议状态 meeting.minutes_generated = True meeting.minutes_uploaded_at = timezone.now() meeting.save() logger.info(f"✅ 会议纪要已上传:{meeting.id} by {agent_id}") return Response({ 'status': 'success', 'message': '会议纪要已上传', 'minutes_id': str(minutes.id) }) except Meeting.DoesNotExist: return Response({'error': '会议不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"上传会议纪要失败:{e}") return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class MeetingEndNotifyView(views.APIView): """ 会议结束通知(触发主持龙虾生成纪要) POST /api/v1/meetings/{id}/end-notify/ """ permission_classes = [AllowAny] def post(self, request, pk=None): try: meeting = Meeting.objects.get(id=pk) if meeting.status != 'ended': return Response( {'error': '会议尚未结束'}, status=status.HTTP_400_BAD_REQUEST ) # 如果已有纪要,跳过 if meeting.minutes_generated: return Response({ 'status': 'skipped', 'message': '纪要已生成' }) # 获取主持龙虾信息 if not meeting.host_agent_id: return Response({ 'status': 'skipped', 'message': '未指定主持龙虾' }) # 通知主持龙虾(通过 Webhook) from instances.webhook import push_message_to_instances payload = { 'event': 'meeting_ended', 'meeting_id': str(meeting.id), 'topic': meeting.topic, 'host_agent_id': meeting.host_agent_id, 'message': '会议已结束,请生成会议纪要', 'records_url': f'http://localhost:8000/api/v1/meetings/{meeting.id}/records/', 'upload_url': f'http://localhost:8000/api/v1/meetings/{meeting.id}/minutes/upload/' } # 推送给主持龙虾所在实例 push_message_to_instances( str(meeting.id), payload, target_agent_ids=[meeting.host_agent_id] ) logger.info(f"📬 会议结束通知已发送:{meeting.id} -> {meeting.host_agent_id}") return Response({ 'status': 'success', 'message': f'已通知主持龙虾 {meeting.host_agent_id}' }) except Meeting.DoesNotExist: return Response({'error': '会议不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"发送结束通知失败:{e}") return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)