headless_lms_server/controllers/course_material/
chatbot.rs1use actix_web::http::header::ContentType;
2use chrono::Utc;
3
4use headless_lms_chatbot::azure_chatbot::{ChatbotUserContext, send_chat_request_and_parse_stream};
5use headless_lms_chatbot::llm_utils::estimate_tokens;
6use headless_lms_models::chatbot_conversation_messages::MessageRole;
7use headless_lms_models::chatbot_conversations::{
8 self, ChatbotConversation, ChatbotConversationInfo,
9};
10use headless_lms_models::{chatbot_configurations, courses};
11
12use crate::prelude::*;
13
14#[instrument(skip(pool))]
20async fn get_default_chatbot_configuration_for_course(
21 pool: web::Data<PgPool>,
22 course_id: web::Path<Uuid>,
23) -> ControllerResult<web::Json<Option<Uuid>>> {
24 let token = skip_authorize();
25
26 let mut conn = pool.acquire().await?;
27 let chatbot_configurations =
28 models::chatbot_configurations::get_for_course(&mut conn, *course_id).await?;
29
30 let res = chatbot_configurations
31 .into_iter()
32 .filter(|c| c.enabled_to_students)
33 .find(|c| c.default_chatbot)
34 .map(|c| c.id);
35
36 token.authorized_ok(web::Json(res))
37}
38
39#[instrument(skip(pool, app_conf))]
45async fn send_message(
46 pool: web::Data<PgPool>,
47 params: web::Path<(Uuid, Uuid)>,
48 user: AuthUser,
49 app_conf: web::Data<ApplicationConfiguration>,
50 payload: web::Json<String>,
51) -> ControllerResult<HttpResponse> {
52 let message = payload.into_inner();
53 let chatbot_configuration_id = params.0;
54 let conversation_id = params.1;
55 let mut conn = pool.acquire().await?;
56 let mut tx: sqlx::Transaction<Postgres> = conn.begin().await?;
57 let course_id = chatbot_configurations::get_by_id(&mut tx, chatbot_configuration_id)
58 .await?
59 .course_id;
60 let course_name = courses::get_course(&mut tx, course_id).await?.name;
61 let chatbot_user = ChatbotUserContext {
62 user_id: user.id.to_owned(),
63 course_id,
64 course_name,
65 };
66 let token = skip_authorize();
67
68 let response_stream = send_chat_request_and_parse_stream(
69 &mut tx,
70 pool.get_ref().clone(),
72 &app_conf,
73 chatbot_configuration_id,
74 conversation_id,
75 &message,
76 chatbot_user,
77 )
78 .await?;
79
80 tx.commit().await?;
81
82 token.authorized_ok(
83 HttpResponse::Ok()
84 .content_type(ContentType::json())
85 .streaming(response_stream),
86 )
87}
88
89#[instrument(skip(pool))]
95async fn new_conversation(
96 pool: web::Data<PgPool>,
97 user: AuthUser,
98 params: web::Path<Uuid>,
99) -> ControllerResult<web::Json<ChatbotConversation>> {
100 let token = skip_authorize();
101
102 let mut conn = pool.acquire().await?;
103 let mut tx = conn.begin().await?;
104
105 let configuration = models::chatbot_configurations::get_by_id(&mut tx, *params).await?;
106
107 let conversation = models::chatbot_conversations::insert(
108 &mut tx,
109 ChatbotConversation {
110 id: Uuid::new_v4(),
111 created_at: Utc::now(),
112 updated_at: Utc::now(),
113 deleted_at: None,
114 course_id: configuration.course_id,
115 user_id: user.id,
116 chatbot_configuration_id: configuration.id,
117 },
118 )
119 .await?;
120
121 let _first_message = models::chatbot_conversation_messages::insert(
122 &mut tx,
123 models::chatbot_conversation_messages::ChatbotConversationMessage {
124 id: Uuid::new_v4(),
125 created_at: Utc::now(),
126 updated_at: Utc::now(),
127 deleted_at: None,
128 conversation_id: conversation.id,
129 message: Some(configuration.initial_message.clone()),
130 message_role: MessageRole::Assistant,
131 message_is_complete: true,
132 used_tokens: estimate_tokens(&configuration.initial_message),
133 order_number: 0,
134 tool_output: None,
135 tool_call_fields: vec![],
136 },
137 )
138 .await?;
139
140 tx.commit().await?;
141
142 token.authorized_ok(web::Json(conversation))
143}
144
145#[instrument(skip(pool))]
151async fn current_conversation_info(
152 pool: web::Data<PgPool>,
153 user: AuthUser,
154 params: web::Path<Uuid>,
155) -> ControllerResult<web::Json<ChatbotConversationInfo>> {
156 let token = skip_authorize();
157
158 let mut conn = pool.acquire().await?;
159 let chatbot_configuration =
160 models::chatbot_configurations::get_by_id(&mut conn, *params).await?;
161 let res = chatbot_conversations::get_current_conversation_info(
162 &mut conn,
163 user.id,
164 chatbot_configuration.id,
165 )
166 .await?;
167
168 token.authorized_ok(web::Json(res))
169}
170
171pub fn _add_routes(cfg: &mut ServiceConfig) {
179 cfg.route(
180 "/{chatbot_configuration_id}/conversations/{conversation_id}/send-message",
181 web::post().to(send_message),
182 )
183 .route(
184 "/{chatbot_configuration_id}/conversations/current",
185 web::get().to(current_conversation_info),
186 )
187 .route(
188 "/{chatbot_configuration_id}/conversations/new",
189 web::post().to(new_conversation),
190 )
191 .route(
192 "/default-for-course/{course_id}",
193 web::get().to(get_default_chatbot_configuration_for_course),
194 );
195}