from flask_restx import fields, marshal_with, reqparse from flask_restx.inputs import int_range from sqlalchemy.orm import Session from werkzeug.exceptions import NotFound from controllers.web import web_ns from controllers.web.error import NotChatAppError from controllers.web.wraps import WebApiResource from core.app.entities.app_invoke_entities import InvokeFrom from extensions.ext_database import db from fields.conversation_fields import conversation_infinite_scroll_pagination_fields, simple_conversation_fields from libs.helper import uuid_value from models.model import AppMode from services.conversation_service import ConversationService from services.errors.conversation import ConversationNotExistsError, LastConversationNotExistsError from services.web_conversation_service import WebConversationService @web_ns.route("/conversations") class ConversationListApi(WebApiResource): @web_ns.doc("Get Conversation List") @web_ns.doc(description="Retrieve paginated list of conversations for a chat application.") @web_ns.doc( params={ "last_id": {"description": "Last conversation ID for pagination", "type": "string", "required": False}, "limit": { "description": "Number of conversations to return (1-100)", "type": "integer", "required": False, "default": 20, }, "pinned": { "description": "Filter by pinned status", "type": "string", "enum": ["true", "false"], "required": False, }, "sort_by": { "description": "Sort order", "type": "string", "enum": ["created_at", "-created_at", "updated_at", "-updated_at"], "required": False, "default": "-updated_at", }, } ) @web_ns.doc( responses={ 200: "Success", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "App Not Found or Not a Chat App", 500: "Internal Server Error", } ) @marshal_with(conversation_infinite_scroll_pagination_fields) def get(self, app_model, end_user): app_mode = AppMode.value_of(app_model.mode) if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: raise NotChatAppError() parser = reqparse.RequestParser() parser.add_argument("last_id", type=uuid_value, location="args") parser.add_argument("limit", type=int_range(1, 100), required=False, default=20, location="args") parser.add_argument("pinned", type=str, choices=["true", "false", None], location="args") parser.add_argument( "sort_by", type=str, choices=["created_at", "-created_at", "updated_at", "-updated_at"], required=False, default="-updated_at", location="args", ) args = parser.parse_args() pinned = None if "pinned" in args and args["pinned"] is not None: pinned = args["pinned"] == "true" try: with Session(db.engine) as session: return WebConversationService.pagination_by_last_id( session=session, app_model=app_model, user=end_user, last_id=args["last_id"], limit=args["limit"], invoke_from=InvokeFrom.WEB_APP, pinned=pinned, sort_by=args["sort_by"], ) except LastConversationNotExistsError: raise NotFound("Last Conversation Not Exists.") @web_ns.route("/conversations/") class ConversationApi(WebApiResource): delete_response_fields = { "result": fields.String, } @web_ns.doc("Delete Conversation") @web_ns.doc(description="Delete a specific conversation.") @web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}}) @web_ns.doc( responses={ 204: "Conversation deleted successfully", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Conversation Not Found or Not a Chat App", 500: "Internal Server Error", } ) @marshal_with(delete_response_fields) def delete(self, app_model, end_user, c_id): app_mode = AppMode.value_of(app_model.mode) if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: raise NotChatAppError() conversation_id = str(c_id) try: ConversationService.delete(app_model, conversation_id, end_user) except ConversationNotExistsError: raise NotFound("Conversation Not Exists.") return {"result": "success"}, 204 @web_ns.route("/conversations//name") class ConversationRenameApi(WebApiResource): @web_ns.doc("Rename Conversation") @web_ns.doc(description="Rename a specific conversation with a custom name or auto-generate one.") @web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}}) @web_ns.doc( params={ "name": {"description": "New conversation name", "type": "string", "required": False}, "auto_generate": { "description": "Auto-generate conversation name", "type": "boolean", "required": False, "default": False, }, } ) @web_ns.doc( responses={ 200: "Conversation renamed successfully", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Conversation Not Found or Not a Chat App", 500: "Internal Server Error", } ) @marshal_with(simple_conversation_fields) def post(self, app_model, end_user, c_id): app_mode = AppMode.value_of(app_model.mode) if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: raise NotChatAppError() conversation_id = str(c_id) parser = reqparse.RequestParser() parser.add_argument("name", type=str, required=False, location="json") parser.add_argument("auto_generate", type=bool, required=False, default=False, location="json") args = parser.parse_args() try: return ConversationService.rename(app_model, conversation_id, end_user, args["name"], args["auto_generate"]) except ConversationNotExistsError: raise NotFound("Conversation Not Exists.") @web_ns.route("/conversations//pin") class ConversationPinApi(WebApiResource): pin_response_fields = { "result": fields.String, } @web_ns.doc("Pin Conversation") @web_ns.doc(description="Pin a specific conversation to keep it at the top of the list.") @web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}}) @web_ns.doc( responses={ 200: "Conversation pinned successfully", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Conversation Not Found or Not a Chat App", 500: "Internal Server Error", } ) @marshal_with(pin_response_fields) def patch(self, app_model, end_user, c_id): app_mode = AppMode.value_of(app_model.mode) if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: raise NotChatAppError() conversation_id = str(c_id) try: WebConversationService.pin(app_model, conversation_id, end_user) except ConversationNotExistsError: raise NotFound("Conversation Not Exists.") return {"result": "success"} @web_ns.route("/conversations//unpin") class ConversationUnPinApi(WebApiResource): unpin_response_fields = { "result": fields.String, } @web_ns.doc("Unpin Conversation") @web_ns.doc(description="Unpin a specific conversation to remove it from the top of the list.") @web_ns.doc(params={"c_id": {"description": "Conversation UUID", "type": "string", "required": True}}) @web_ns.doc( responses={ 200: "Conversation unpinned successfully", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Conversation Not Found or Not a Chat App", 500: "Internal Server Error", } ) @marshal_with(unpin_response_fields) def patch(self, app_model, end_user, c_id): app_mode = AppMode.value_of(app_model.mode) if app_mode not in {AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT}: raise NotChatAppError() conversation_id = str(c_id) WebConversationService.unpin(app_model, conversation_id, end_user) return {"result": "success"}