use actix_web::{HttpResponse, Result, web}; use service::error::AppError; use session::Session; use uuid::Uuid; use crate::ApiResponse; use crate::error::ApiError; use super::types::{ConversationListQuery, ConversationResponse, CreateConversationParams}; fn get_user_id(session: &Session) -> Result { session .user() .ok_or_else(|| ApiError::from(AppError::Unauthorized)) } #[utoipa::path( post, path = "/api/ai/conversations", operation_id = "ai_conversation_create", request_body = CreateConversationParams, responses( (status = 200, description = "Conversation created", body = ApiResponse), (status = 401, description = "Unauthorized"), ), tag = "AI Chat" )] pub async fn conversation_create( service: web::Data, session: Session, params: web::Json, ) -> Result { let user_id = get_user_id(&session)?; let model = params.model.clone().unwrap_or_else(|| "gpt-4".to_string()); let conversation = service .create_conversation( user_id, params.project_id, params.title.clone(), model, params.model_config.clone(), params.access_visibility.clone(), params.can_ask.clone(), params.model_uid, params.model_name.clone(), ) .await?; let resp = ConversationResponse::from(conversation); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations", operation_id = "ai_conversation_list", params( ("project_id" = Option, Query, description = "Filter by project"), ("q" = Option, Query, description = "Search query (title)"), ), responses( (status = 200, description = "List of conversations", body = ApiResponse>), (status = 401, description = "Unauthorized"), ), tag = "AI Chat" )] pub async fn conversation_list( service: web::Data, session: Session, query: web::Query, ) -> Result { let user_id = get_user_id(&session)?; let convs = service .list_conversations(user_id, query.project_id, 50, query.q.clone()) .await?; let resp: Vec = convs.into_iter().map(ConversationResponse::from).collect(); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( get, path = "/api/ai/conversations/{conversation_id}", operation_id = "ai_conversation_get", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ), responses( (status = 200, description = "Get conversation", body = ApiResponse), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn conversation_get( service: web::Data, session: Session, path: web::Path, ) -> Result { let user_id = get_user_id(&session)?; let conversation_id = path.into_inner(); let c = service .find_conversation_owned(conversation_id, user_id) .await?; let resp = ConversationResponse::from(c); Ok(ApiResponse::ok(resp).to_response()) } #[utoipa::path( patch, path = "/api/ai/conversations/{conversation_id}", operation_id = "ai_conversation_update", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ), request_body = super::types::UpdateConversationParams, responses( (status = 200, description = "Conversation updated"), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn conversation_update( 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(); service .update_conversation( conversation_id, user_id, params.title.clone(), params.model.clone(), params.model_config.clone(), params.status.clone(), params.access_visibility.clone(), params.can_ask.clone(), params.model_uid, params.model_name.clone(), ) .await?; Ok(crate::api_success()) } #[utoipa::path( delete, path = "/api/ai/conversations/{conversation_id}", operation_id = "ai_conversation_delete", params( ("conversation_id" = Uuid, Path, description = "Conversation ID"), ), responses( (status = 200, description = "Conversation deleted"), (status = 404, description = "Not found"), ), tag = "AI Chat" )] pub async fn conversation_delete( service: web::Data, session: Session, path: web::Path, ) -> Result { let user_id = get_user_id(&session)?; let conversation_id = path.into_inner(); service .delete_conversation(conversation_id, user_id) .await?; Ok(crate::api_success()) }