headless_lms_server/controllers/cms/
ai_suggestions.rs1use headless_lms_models::application_task_default_language_models::{self, ApplicationTask};
3use headless_lms_models::cms_ai::ParagraphSuggestionAction;
4use utoipa::{OpenApi, ToSchema};
5
6use crate::prelude::*;
7
8#[derive(Debug, Serialize, Deserialize, ToSchema)]
9
10pub struct ParagraphSuggestionMeta {
11 pub tone: Option<String>,
12 pub language: Option<String>,
13 pub setting_type: Option<String>,
14}
15
16#[derive(Debug, Serialize, Deserialize, ToSchema)]
17
18pub struct ParagraphSuggestionContext {
19 pub page_id: Option<Uuid>,
20 pub course_id: Option<Uuid>,
21 pub locale: Option<String>,
22}
23
24#[derive(Debug, Serialize, Deserialize, ToSchema)]
25
26pub struct ParagraphSuggestionRequest {
27 pub action: ParagraphSuggestionAction,
28 pub content: String,
29 pub is_html: bool,
30 pub meta: Option<ParagraphSuggestionMeta>,
31 pub context: Option<ParagraphSuggestionContext>,
32}
33
34#[derive(Serialize, Deserialize, ToSchema)]
35
36pub struct ParagraphSuggestionResponse {
37 pub suggestions: Vec<String>,
38}
39
40#[derive(OpenApi)]
41#[openapi(paths(suggest_paragraph))]
42pub(crate) struct CmsAiSuggestionsApiDoc;
43
44#[instrument(skip(pool, app_conf))]
52#[utoipa::path(
53 post,
54 path = "/paragraph",
55 operation_id = "requestParagraphSuggestions",
56 tag = "cms_ai_suggestions",
57 request_body = ParagraphSuggestionRequest,
58 responses(
59 (status = 200, description = "Generated paragraph suggestions", body = ParagraphSuggestionResponse)
60 )
61)]
62async fn suggest_paragraph(
63 pool: web::Data<PgPool>,
64 app_conf: web::Data<ApplicationConfiguration>,
65 user: AuthUser,
66 payload: web::Json<ParagraphSuggestionRequest>,
67) -> ControllerResult<web::Json<ParagraphSuggestionResponse>> {
68 let mut conn = pool.acquire().await?;
69
70 if payload.content.trim().is_empty() {
72 return Err(ControllerError::new(
73 ControllerErrorType::BadRequest,
74 "Paragraph content must not be empty.".to_string(),
75 None,
76 ));
77 }
78
79 let token = if let Some(ParagraphSuggestionContext {
82 page_id: Some(page_id),
83 ..
84 }) = &payload.context
85 {
86 authorize(&mut conn, Act::Edit, Some(user.id), Res::Page(*page_id)).await?
87 } else {
88 authorize(&mut conn, Act::Teach, Some(user.id), Res::AnyCourse).await?
89 };
90
91 let task_lm = application_task_default_language_models::get_for_task(
92 &mut conn,
93 ApplicationTask::CmsParagraphSuggestion,
94 )
95 .await?;
96
97 let meta = payload.meta.as_ref();
98 let generator_input = headless_lms_chatbot::cms_ai_suggestion::CmsParagraphSuggestionInput {
99 action: payload.action,
100 content: payload.content.clone(),
101 is_html: payload.is_html,
102 meta_tone: meta.and_then(|m| m.tone.clone()),
103 meta_language: meta.and_then(|m| m.language.clone()),
104 meta_setting_type: meta.and_then(|m| m.setting_type.clone()),
105 };
106
107 drop(conn);
109
110 let suggestions = headless_lms_chatbot::cms_ai_suggestion::generate_paragraph_suggestions(
111 &app_conf,
112 task_lm,
113 &generator_input,
114 )
115 .await?;
116
117 token.authorized_ok(web::Json(ParagraphSuggestionResponse { suggestions }))
118}
119
120pub fn _add_routes(cfg: &mut ServiceConfig) {
128 cfg.route("/paragraph", web::post().to(suggest_paragraph));
129}