"""Message API routes""" import json import uuid from datetime import datetime, timezone from flask import Blueprint, request, g from backend import db from backend.models import Conversation, Message from backend.utils.helpers import ok, err, message_to_dict from backend.services.chat import ChatService bp = Blueprint("messages", __name__) # ChatService will be injected during registration _chat_service = None def init_chat_service(client): """Initialize chat service with LLM client""" global _chat_service _chat_service = ChatService(client) def _get_conv(conv_id): """Get conversation with ownership check.""" conv = db.session.get(Conversation, conv_id) if not conv or conv.user_id != g.current_user.id: return None return conv @bp.route("/api/conversations//messages", methods=["GET", "POST"]) def message_list(conv_id): """List or create messages""" conv = _get_conv(conv_id) if not conv: return err(404, "conversation not found") if request.method == "GET": cursor = request.args.get("cursor") limit = min(int(request.args.get("limit", 50)), 100) q = Message.query.filter_by(conversation_id=conv_id) if cursor: q = q.filter(Message.created_at < ( db.session.query(Message.created_at).filter_by(id=cursor).scalar() or datetime.now(timezone.utc))) rows = q.order_by(Message.created_at.asc()).limit(limit + 1).all() items = [message_to_dict(r) for r in rows[:limit]] return ok({ "items": items, "next_cursor": items[-1]["id"] if len(rows) > limit else None, "has_more": len(rows) > limit, }) # POST - create message and get AI response d = request.json or {} text = (d.get("text") or "").strip() attachments = d.get("attachments") # [{"name": "a.py", "extension": "py", "content": "..."}] if not text and not attachments: return err(400, "text or attachments is required") # Build content JSON structure content_json = {"text": text} if attachments: content_json["attachments"] = attachments user_msg = Message( id=str(uuid.uuid4()), conversation_id=conv_id, role="user", content=json.dumps(content_json, ensure_ascii=False), ) db.session.add(user_msg) db.session.commit() tools_enabled = d.get("tools_enabled", True) project_id = d.get("project_id") or conv.project_id return _chat_service.stream_response(conv, tools_enabled, project_id) @bp.route("/api/conversations//messages/", methods=["DELETE"]) def delete_message(conv_id, msg_id): """Delete a message""" conv = _get_conv(conv_id) if not conv: return err(404, "conversation not found") if msg_id.startswith("temp_"): return ok(message="deleted") msg = db.session.get(Message, msg_id) if not msg or msg.conversation_id != conv_id: return err(404, "message not found") db.session.delete(msg) db.session.commit() return ok(message="deleted") @bp.route("/api/conversations//regenerate/", methods=["POST"]) def regenerate_message(conv_id, msg_id): """Regenerate an assistant message""" conv = _get_conv(conv_id) if not conv: return err(404, "conversation not found") # 获取要重新生成的消息 msg = db.session.get(Message, msg_id) if not msg or msg.conversation_id != conv_id: return err(404, "message not found") if msg.role != "assistant": return err(400, "can only regenerate assistant messages") # 删除该消息及其后面的所有消息 Message.query.filter( Message.conversation_id == conv_id, Message.created_at >= msg.created_at ).delete(synchronize_session=False) db.session.commit() # 获取工具启用状态 d = request.json or {} tools_enabled = d.get("tools_enabled", True) project_id = d.get("project_id") or conv.project_id # 流式重新生成 return _chat_service.stream_response(conv, tools_enabled, project_id)