""" 聊天路由 - 聊天会话管理 API 提供 REST API 用于管理聊天会话(补充 WebSocket 功能) """ from flask import Blueprint, request, jsonify from flask_jwt_extended import jwt_required, get_jwt_identity from datetime import datetime from app.models import User, Session, Message, Bot, Agent from app.extensions import db from app.services.bot_service import BotService chat_bp = Blueprint('chat', __name__) @chat_bp.route('/sessions', methods=['GET']) @jwt_required() def get_chat_sessions(): """ 获取聊天会话列表 Query params: - status: str - 会话状态过滤(active/paused/closed) - bot_id: str - 按 Bot 过滤 - limit: int - 返回数量限制(默认 20) - offset: int - 分页偏移(默认 0) """ user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 # 获取查询参数 status = request.args.get('status') bot_id = request.args.get('bot_id') limit = min(int(request.args.get('limit', 20)), 100) # 最大 100 offset = int(request.args.get('offset', 0)) # 构建查询 query = Session.query.filter_by(user_id=user_id) # 只查询聊天会话(有 bot_id 的) query = query.filter(Session.bot_id.isnot(None)) if status: query = query.filter_by(status=status) if bot_id: query = query.filter_by(bot_id=bot_id) # 按最后活跃时间倒序 query = query.order_by(Session.last_active_at.desc()) # 分页 total = query.count() sessions = query.offset(offset).limit(limit).all() # 构建返回数据 result = [] for session in sessions: session_data = session.to_dict() # 添加 Bot 信息 if session.bot_id: bot = BotService.get_bot_by_id(session.bot_id) if bot: session_data['bot'] = { 'id': bot.id, 'name': bot.name, 'display_name': bot.display_name, 'avatar': bot.avatar, 'status': bot.status } # 添加最后一条消息预览 last_message = Message.query.filter_by(session_id=session.id)\ .order_by(Message.created_at.desc())\ .first() if last_message: session_data['last_message'] = { 'id': last_message.id, 'content': last_message.content[:100] if last_message.content else '', 'sender_type': last_message.sender_type, 'created_at': last_message.created_at.isoformat() } result.append(session_data) return jsonify({ 'sessions': result, 'total': total, 'limit': limit, 'offset': offset }), 200 @chat_bp.route('/sessions', methods=['POST']) @jwt_required() def create_chat_session(): """ 创建聊天会话(HTTP 方式,WebSocket 的替代方案) Request body: - bot_id: str - 机器人 ID(必填) - title: str - 会话标题(可选) """ user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 data = request.get_json() if not data or 'bot_id' not in data: return jsonify({'error': 'bot_id is required'}), 400 bot_id = data['bot_id'] bot = BotService.get_bot_by_id(bot_id) if not bot: return jsonify({'error': 'Bot not found'}), 404 # 权限检查 if not BotService.check_permission(user, bot, 'use'): return jsonify({'error': 'Permission denied'}), 403 # 检查 Agent if not bot.agent_id: return jsonify({'error': 'Bot has no agent bound'}), 400 agent = Agent.query.get(bot.agent_id) if not agent: return jsonify({'error': 'Agent not found'}), 404 # 创建会话 title = data.get('title', f'Chat with {bot.display_name or bot.name}') session = Session( user_id=user_id, bot_id=bot.id, primary_agent_id=agent.id, title=title, channel_type='pit-bot', status='active', created_at=datetime.utcnow(), last_active_at=datetime.utcnow() ) db.session.add(session) db.session.commit() return jsonify({ 'session': { **session.to_dict(), 'bot': bot.to_dict() }, 'message': 'Chat session created' }), 201 @chat_bp.route('/sessions/', methods=['GET']) @jwt_required() def get_chat_session(session_id): """获取聊天会话详情""" user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 session = Session.query.get(session_id) if not session: return jsonify({'error': 'Session not found'}), 404 # 权限检查 if session.user_id != user_id: return jsonify({'error': 'Permission denied'}), 403 # 验证是聊天会话 if not session.bot_id: return jsonify({'error': 'Not a chat session'}), 400 session_data = session.to_dict() # 添加 Bot 信息 bot = BotService.get_bot_by_id(session.bot_id) if bot: session_data['bot'] = bot.to_dict() return jsonify({'session': session_data}), 200 @chat_bp.route('/sessions//messages', methods=['GET']) @jwt_required() def get_chat_messages(session_id): """ 获取聊天消息历史 Query params: - limit: int - 返回数量限制(默认 50,最大 100) - before: str - 获取此 ID 之前的消息(分页) """ user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 session = Session.query.get(session_id) if not session: return jsonify({'error': 'Session not found'}), 404 # 权限检查 if session.user_id != user_id: return jsonify({'error': 'Permission denied'}), 403 # 获取查询参数 limit = min(int(request.args.get('limit', 50)), 100) before_id = request.args.get('before') # 构建查询 query = Message.query.filter_by(session_id=session_id) if before_id: before_msg = Message.query.get(before_id) if before_msg: query = query.filter(Message.created_at < before_msg.created_at) # 按时间倒序,取最近的消息 messages = query.order_by(Message.created_at.desc()).limit(limit).all() # 翻转顺序(从旧到新) messages = list(reversed(messages)) return jsonify({ 'messages': [m.to_dict() for m in messages], 'count': len(messages), 'has_more': len(messages) == limit }), 200 @chat_bp.route('/sessions//messages', methods=['POST']) @jwt_required() def send_chat_message(session_id): """ 发送消息(HTTP 方式,WebSocket 的替代方案) Request body: - content: str - 消息内容(必填) - reply_to: str - 回复的消息 ID(可选) - content_type: str - 内容类型(默认 markdown) """ user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 session = Session.query.get(session_id) if not session: return jsonify({'error': 'Session not found'}), 404 # 权限检查 if session.user_id != user_id: return jsonify({'error': 'Permission denied'}), 403 # 检查会话状态 if session.status == 'closed': return jsonify({'error': 'Session is closed'}), 400 data = request.get_json() if not data or 'content' not in data: return jsonify({'error': 'content is required'}), 400 content = data['content'].strip() if not content: return jsonify({'error': 'content cannot be empty'}), 400 # 获取 Bot 信息用于 sender_name bot = None sender_name = user.nickname or user.username if session.bot_id: bot = BotService.get_bot_by_id(session.bot_id) # 这里不需要修改 sender_name,因为这是用户发送的消息 # 创建消息 message = Message( session_id=session_id, sender_type='user', sender_id=user_id, sender_name=sender_name, bot_id=session.bot_id, message_type='text', content=content, content_type=data.get('content_type', 'markdown'), reply_to=data.get('reply_to'), status='sent', ack_status='pending', created_at=datetime.utcnow() ) db.session.add(message) # 更新会话 session.message_count += 1 session.last_active_at = datetime.utcnow() session.updated_at = datetime.utcnow() db.session.commit() return jsonify({ 'message': message.to_dict(), 'session_id': session_id }), 201 @chat_bp.route('/sessions//read', methods=['PUT']) @jwt_required() def mark_session_read(session_id): """标记会话消息已读""" user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 session = Session.query.get(session_id) if not session: return jsonify({'error': 'Session not found'}), 404 # 权限检查 if session.user_id != user_id: return jsonify({'error': 'Permission denied'}), 403 # 将所有未读消息标记为已读 messages = Message.query.filter_by( session_id=session_id, status='delivered' ).all() for msg in messages: msg.status = 'read' # 重置会话未读数 session.unread_count = 0 db.session.commit() return jsonify({ 'session_id': session_id, 'marked_read': len(messages) }), 200 @chat_bp.route('/sessions/', methods=['DELETE']) @jwt_required() def close_chat_session(session_id): """关闭聊天会话""" user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 session = Session.query.get(session_id) if not session: return jsonify({'error': 'Session not found'}), 404 # 权限检查 if session.user_id != user_id: return jsonify({'error': 'Permission denied'}), 403 # 关闭会话 session.status = 'closed' session.updated_at = datetime.utcnow() db.session.commit() return jsonify({ 'session_id': session_id, 'status': 'closed', 'message': 'Chat session closed' }), 200