use crate::error::ApiError; use crate::ApiResponse; use actix_web::{web, HttpResponse, Result}; use session::Session; use service::error::AppError; use uuid::Uuid; use super::types::{CreateMessageParams, EditMessageParams, MessageListQuery, MessageResponse}; fn get_user_id(session: &Session) -> Result { session.user().ok_or_else(|| ApiError::from(AppError::Unauthorized)) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}/messages", operation_id = "ai_message_list", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("limit" = Option, Query, description = "Max messages"), ), responses( (status = 200, description = "List messages", body = ApiResponse>), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn message_list( service: web::Data, session: Session, path: web::Path, query: web::Query, ) -> Result { let user_id = get_user_id(&session)?; let conversation_id = path.into_inner(); let limit = query.limit.unwrap_or(50) as u64; let msgs = service .list_messages(conversation_id, user_id, limit) .await?; let resp: Vec = msgs .into_iter() .map(MessageResponse::from) .collect(); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( post, path = "/api/ai/conversations/{conversation_id}/messages", operation_id = "ai_message_create", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ), request_body = CreateMessageParams, responses( (status = 200, description = "Message created", body = ApiResponse), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn message_create( service: web::Data, session: Session, path: web::Path, params: web::Json, ) -> Result { let user_id = get_user_id(&session)?; let conversation_id = path.into_inner(); let msg = service .create_message( conversation_id, user_id, params.parent_message_id, params.content.role.clone(), params.content.content.clone(), params.model.clone(), params.is_fork_origin.unwrap_or(false), params.metadata.clone(), params.room_id, ) .await?; let resp = MessageResponse::from(msg); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}", operation_id = "ai_message_get", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), responses( (status = 200, description = "Get message", body = ApiResponse), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn message_get( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); let msg = service .get_message(conversation_id, user_id, message_id) .await?; let resp = MessageResponse::from(msg); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( post, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/stop", operation_id = "ai_message_stop", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), responses( (status = 200, description = "Message stopped"), ), tag = "AI Chat" )] pub async fn message_stop( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); service .stop_message(conversation_id, user_id, message_id) .await?; Ok(crate::api_success()) } #[utoipa::path( post, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/resend", operation_id = "ai_message_resend", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), responses( (status = 200, description = "Resend message", body = ApiResponse), ), tag = "AI Chat" )] pub async fn message_resend( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); let new_msg = service .resend_message(conversation_id, user_id, message_id) .await?; let resp = MessageResponse::from(new_msg); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/children", operation_id = "ai_message_children", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Parent message ID"), ), responses( (status = 200, description = "List child messages", body = ApiResponse>), ), tag = "AI Chat" )] pub async fn message_children( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, parent_message_id) = path.into_inner(); let msgs = service .list_child_messages(conversation_id, user_id, parent_message_id) .await?; let resp: Vec = msgs .into_iter() .map(MessageResponse::from) .collect(); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/stream", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), responses( (status = 200, description = "SSE stream"), ), tag = "AI Chat" )] pub async fn message_stream( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); // Verify user owns the conversation let conv = service .find_conversation_owned(conversation_id, user_id) .await?; let model = conv.model; let response = actix_web::HttpResponse::Ok() .content_type("text/event-stream") .insert_header(("Cache-Control", "no-cache")) .insert_header(("X-Accel-Buffering", "no")) .streaming(super::super::stream::create_chat_sse_stream( service.get_ref().clone(), conversation_id, message_id, model, user_id, )); Ok(response.into()) } #[utoipa::path( post, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/edit", operation_id = "ai_message_edit", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID to edit"), ), request_body = EditMessageParams, responses( (status = 200, description = "Message edited, new version created", body = ApiResponse), ), tag = "AI Chat" )] pub async fn message_edit( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, params: web::Json, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); let new_msg = service .edit_message(conversation_id, user_id, message_id, params.content.clone()) .await?; let resp = MessageResponse::from(new_msg); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/versions", operation_id = "ai_message_versions", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), responses( (status = 200, description = "List message versions", body = ApiResponse>), ), tag = "AI Chat" )] pub async fn message_versions( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); let versions = service .list_message_versions(conversation_id, user_id, message_id) .await?; let resp: Vec = versions .into_iter() .map(MessageResponse::from) .collect(); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( post, path = "/api/ai/conversations/{conversation_id}/messages/{message_id}/switch-version", operation_id = "ai_message_switch_version", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ("message_id" = Uuid, Path, description = "Message ID"), ), request_body = super::types::SwitchVersionParams, responses( (status = 200, description = "Version switched", body = ApiResponse), ), tag = "AI Chat" )] pub async fn message_switch_version( service: web::Data, session: Session, path: web::Path<(Uuid, Uuid)>, params: web::Json, ) -> Result { let user_id = get_user_id(&session)?; let (conversation_id, message_id) = path.into_inner(); let msg = service .switch_message_version(conversation_id, user_id, message_id, params.version_number) .await?; let resp = MessageResponse::from(msg); Ok(ApiResponse::ok(resp).to_response()) }