from rest_framework import viewsets, status, permissions from rest_framework.decorators import action from rest_framework.response import Response from django.utils import timezone from .models import Meeting, Participant, Message, MeetingMinutes from .serializers import ( MeetingSerializer, ParticipantSerializer, MessageSerializer, InboxSerializer ) from django.contrib.auth import get_user_model from django.shortcuts import get_object_or_404 import uuid User = get_user_model() class MeetingViewSet(viewsets.ModelViewSet): """会议室视图集""" queryset = Meeting.objects.all() serializer_class = MeetingSerializer permission_classes = [] # 临时开放所有权限 def get_queryset(self): # 简单返回所有会议 return Meeting.objects.all().order_by('-created_at') def create(self, request, *args, **kwargs): """创建会议""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) # 临时:使用第一个用户作为 host from django.contrib.auth import get_user_model User = get_user_model() host = User.objects.first() meeting = serializer.save(host=host) # 创建主持人参会记录 Participant.objects.create( meeting=meeting, user=host, agent_type='human', nickname=host.username, is_host=True ) return Response(serializer.data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['post']) def start(self, request, pk=None): """开始会议""" meeting = self.get_object() # 临时:不检查主持人权限(开发环境) # if meeting.host != request.user: # return Response( # {'error': '只有主持人可以开始会议'}, # status=status.HTTP_403_FORBIDDEN # ) meeting.status = 'active' meeting.started_at = timezone.now() meeting.save() return Response({'status': '会议已开始'}) @action(detail=True, methods=['post']) def end(self, request, pk=None): """结束会议""" meeting = self.get_object() # 临时:不检查主持人权限(开发环境) # if meeting.host != request.user: # return Response( # {'error': '只有主持人可以结束会议'}, # status=status.HTTP_403_FORBIDDEN # ) meeting.status = 'ended' meeting.ended_at = timezone.now() meeting.save() return Response({'status': '会议已结束'}) @action(detail=True, methods=['post']) def join(self, request, pk=None): """加入会议""" meeting = self.get_object() if meeting.status == 'ended': return Response( {'error': '会议已结束'}, status=status.HTTP_400_BAD_REQUEST ) invite_code = request.data.get('invite_code') if invite_code != meeting.invite_code: return Response( {'error': '邀请码错误'}, status=status.HTTP_400_BAD_REQUEST ) # 检查是否已加入 existing = Participant.objects.filter( meeting=meeting, user=request.user, left_at__isnull=True ).first() if existing: return Response(ParticipantSerializer(existing).data) # 创建参会记录 participant = Participant.objects.create( meeting=meeting, user=request.user, agent_type='human', nickname=request.user.username ) return Response(ParticipantSerializer(participant).data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['get']) def participants(self, request, pk=None): """获取参会者列表""" meeting = self.get_object() participants = meeting.participants.filter(left_at__isnull=True) serializer = ParticipantSerializer(participants, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def mention_agent(self, request, pk=None): """@Agent 功能 - 发送消息给特定 Agent""" meeting = self.get_object() target_agent_id = request.data.get('target_agent_id') content = request.data.get('content') sender_agent_id = request.data.get('sender_agent_id') sender_name = request.data.get('sender_name', 'User') sender_emoji = request.data.get('sender_emoji', '👤') if not target_agent_id or not content: return Response( {'error': '缺少 target_agent_id 或 content'}, status=status.HTTP_400_BAD_REQUEST ) # 找到目标 Agent target_participant = Participant.objects.filter( meeting=meeting, agent_id=target_agent_id, left_at__isnull=True ).first() if not target_participant: return Response( {'error': f'未找到 Agent: {target_agent_id}'}, status=status.HTTP_404_NOT_FOUND ) # 获取或创建发送者 sender_participant = Participant.objects.filter( meeting=meeting, agent_id=sender_agent_id, left_at__isnull=True ).first() if not sender_participant and sender_agent_id: sender_participant = Participant.objects.create( meeting=meeting, agent_type='openclaw', agent_id=sender_agent_id, agent_name=sender_name, agent_emoji=sender_emoji, nickname=f"{sender_emoji} {sender_name}" ) if not sender_participant: # 人类发送的,用主持人 sender_participant = Participant.objects.filter( meeting=meeting, is_host=True ).first() # 创建消息,标记为需要回复 message = Message.objects.create( meeting=meeting, sender=sender_participant, content=f"@{target_participant.nickname} {content}", is_broadcast=False, # 只发给目标 Agent requires_response=True ) return Response(MessageSerializer(message).data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['get']) def messages(self, request, pk=None): """获取消息(人类轮询)""" meeting = self.get_object() last_id = request.query_params.get('last_id', 0) messages = meeting.messages.filter(id__gt=last_id).select_related('sender') serializer = MessageSerializer(messages, many=True) return Response({'messages': serializer.data}) @action(detail=True, methods=['post']) def send_message(self, request, pk=None): """发送消息""" meeting = self.get_object() # 获取或创建参会者(临时:使用第一个参会者或创建) participant = Participant.objects.filter( meeting=meeting, left_at__isnull=True ).first() if not participant: # 创建默认参会者 host = meeting.host participant = Participant.objects.create( meeting=meeting, user=host, agent_type='human', nickname=host.username ) content = request.data.get('content') if not content: return Response( {'error': '消息内容不能为空'}, status=status.HTTP_400_BAD_REQUEST ) message = Message.objects.create( meeting=meeting, sender=participant, content=content, is_broadcast=request.data.get('is_broadcast', True), requires_response=request.data.get('requires_response', False) ) return Response(MessageSerializer(message).data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['get']) def inbox(self, request, pk=None): """Agent 查阅信箱(自动加入会议如果还没加入)""" meeting = self.get_object() agent_id = request.query_params.get('agent_id') agent_name = request.query_params.get('agent_name', 'Agent') agent_emoji = request.query_params.get('agent_emoji', '🤖') if not agent_id: return Response( {'error': '缺少 agent_id 参数'}, status=status.HTTP_400_BAD_REQUEST ) # 找到或创建这个 Agent 的参会记录 participant = Participant.objects.filter( meeting=meeting, agent_id=agent_id, left_at__isnull=True ).first() if not participant: # Agent 首次访问,自动加入会议 participant = Participant.objects.create( meeting=meeting, agent_type='openclaw', agent_id=agent_id, agent_name=agent_name, agent_emoji=agent_emoji, nickname=f"{agent_emoji} {agent_name}" ) # 获取发给这个 Agent 的消息(未读) messages = Message.objects.filter( meeting=meeting ).exclude( read_by=participant ) # 标记为已读 participant.read_messages.add(*messages) serializer = MessageSerializer(messages, many=True) return Response({ 'unread_count': messages.count(), 'messages': serializer.data, 'participant': ParticipantSerializer(participant).data }) @action(detail=True, methods=['post']) def agent_reply(self, request, pk=None): """Agent 回复消息""" meeting = self.get_object() agent_id = request.data.get('agent_id') agent_name = request.data.get('agent_name', 'Agent') agent_emoji = request.data.get('agent_emoji', '🤖') content = request.data.get('content') in_reply_to = request.data.get('in_reply_to') if not agent_id: return Response( {'error': '缺少 agent_id 参数'}, status=status.HTTP_400_BAD_REQUEST ) if not content: return Response( {'error': '消息内容不能为空'}, status=status.HTTP_400_BAD_REQUEST ) # 找到或创建 Agent 参会记录 participant = Participant.objects.filter( meeting=meeting, agent_id=agent_id, left_at__isnull=True ).first() if not participant: participant = Participant.objects.create( meeting=meeting, agent_type='openclaw', agent_id=agent_id, agent_name=agent_name, agent_emoji=agent_emoji, nickname=f"{agent_emoji} {agent_name}" ) # 创建回复消息 message = Message.objects.create( meeting=meeting, sender=participant, content=content, is_broadcast=request.data.get('is_broadcast', True), requires_response=request.data.get('requires_response', False), in_reply_to_id=in_reply_to ) return Response(MessageSerializer(message).data, status=status.HTTP_201_CREATED) @action(detail=True, methods=['get'], url_path='minutes') def minutes(self, request, pk=None): """生成会议纪要""" meeting = self.get_object() try: from .utils import generate_meeting_minutes, export_minutes_to_markdown minutes = generate_meeting_minutes(str(meeting.id)) output_format = request.query_params.get('output', 'json') if output_format == 'markdown': md_content = export_minutes_to_markdown(minutes) return Response({'markdown': md_content}) else: return Response(minutes) except Exception as e: return Response( {'error': f'生成纪要失败:{str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) class ParticipantViewSet(viewsets.ModelViewSet): """参会者视图集""" queryset = Participant.objects.all() serializer_class = ParticipantSerializer permission_classes = [permissions.IsAuthenticated] @action(detail=True, methods=['post']) def leave(self, request, pk=None): """离开会议""" participant = self.get_object() if participant.user != request.user: return Response( {'error': '无权操作'}, status=status.HTTP_403_FORBIDDEN ) participant.left_at = timezone.now() participant.save() return Response({'status': '已离开会议'})