Files
pit-router/app/routes/chat.py

383 lines
11 KiB
Python
Raw Normal View History

"""
聊天路由 - 聊天会话管理 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/<session_id>', 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/<session_id>/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/<session_id>/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/<session_id>/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/<session_id>', 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