Skip to main content

headless_lms_models/
chatbot_conversations.rs

1use futures::future::OptionFuture;
2use utoipa::ToSchema;
3
4use crate::{
5    chatbot_conversation_messages::ChatbotConversationMessage,
6    chatbot_conversation_messages_citations::ChatbotConversationMessageCitation,
7    chatbot_conversation_suggested_messages::ChatbotConversationSuggestedMessage, prelude::*,
8};
9
10#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
11
12pub struct ChatbotConversation {
13    pub id: Uuid,
14    pub created_at: DateTime<Utc>,
15    pub updated_at: DateTime<Utc>,
16    pub deleted_at: Option<DateTime<Utc>>,
17    pub course_id: Uuid,
18    pub user_id: Uuid,
19    pub chatbot_configuration_id: Uuid,
20}
21
22#[derive(Serialize, Deserialize, PartialEq, Clone, ToSchema)]
23
24/** Should contain all information required to display the chatbot to the user. */
25pub struct ChatbotConversationInfo {
26    pub current_conversation: Option<ChatbotConversation>,
27    pub current_conversation_messages: Option<Vec<ChatbotConversationMessage>>,
28    pub current_conversation_message_citations: Option<Vec<ChatbotConversationMessageCitation>>,
29    pub chatbot_name: String,
30    pub course_name: String,
31    pub hide_citations: bool,
32    pub suggested_messages: Option<Vec<ChatbotConversationSuggestedMessage>>,
33}
34
35pub async fn insert(
36    conn: &mut PgConnection,
37    input: ChatbotConversation,
38) -> ModelResult<ChatbotConversation> {
39    let res = sqlx::query_as!(
40        ChatbotConversation,
41        r#"
42INSERT INTO chatbot_conversations (course_id, user_id, chatbot_configuration_id)
43VALUES ($1, $2, $3)
44RETURNING *
45        "#,
46        input.course_id,
47        input.user_id,
48        input.chatbot_configuration_id
49    )
50    .fetch_one(conn)
51    .await?;
52    Ok(res)
53}
54
55pub async fn get_latest_conversation_for_user(
56    conn: &mut PgConnection,
57    user_id: Uuid,
58    chatbot_configuration_id: Uuid,
59) -> ModelResult<ChatbotConversation> {
60    let res = sqlx::query_as!(
61        ChatbotConversation,
62        r#"
63SELECT *
64FROM chatbot_conversations
65WHERE user_id = $1
66  AND chatbot_configuration_id = $2
67  AND deleted_at IS NULL
68ORDER BY created_at DESC
69LIMIT 1
70        "#,
71        user_id,
72        chatbot_configuration_id
73    )
74    .fetch_one(conn)
75    .await?;
76    Ok(res)
77}
78
79/// Gets the current conversation for the user, if any. Also inlcudes information about the chatbot so that the chatbot ui can be rendered using the information.
80pub async fn get_current_conversation_info(
81    tx: &mut PgConnection,
82    user_id: Uuid,
83    chatbot_configuration_id: Uuid,
84) -> ModelResult<ChatbotConversationInfo> {
85    let chatbot_configuration =
86        crate::chatbot_configurations::get_by_id(tx, chatbot_configuration_id).await?;
87    let course = crate::courses::get_course(tx, chatbot_configuration.course_id).await?;
88    let current_conversation =
89        get_latest_conversation_for_user(tx, user_id, chatbot_configuration_id)
90            .await
91            .optional()?;
92    // the messages are sorted by response_order_number
93    let current_conversation_messages = OptionFuture::from(
94        current_conversation
95            .clone()
96            .map(|c| crate::chatbot_conversation_messages::get_by_conversation_id(tx, c.id)),
97    )
98    .await
99    .transpose()?;
100
101    let current_conversation_message_citations =
102        OptionFuture::from(current_conversation.clone().map(|c| {
103            crate::chatbot_conversation_messages_citations::get_by_conversation_id(tx, c.id)
104        }))
105        .await
106        .transpose()?;
107
108    let suggested_messages = if chatbot_configuration.suggest_next_messages
109        && let Some(ccm) = &current_conversation_messages
110        && let Some(last_ccm) = ccm.last()
111    {
112        let sm = crate::chatbot_conversation_suggested_messages::get_by_conversation_message_id(
113            tx,
114            last_ccm.id.to_owned(),
115        )
116        .await?;
117        // return an empty vec if there are not yet any suggested messages
118        Some(sm)
119    } else {
120        None
121    };
122
123    Ok(ChatbotConversationInfo {
124        current_conversation,
125        current_conversation_messages,
126        current_conversation_message_citations,
127        suggested_messages,
128        // Don't want to expose everything from the chatbot configuration to the user because it contains private information like the prompt.
129        chatbot_name: chatbot_configuration.chatbot_name,
130        course_name: course.name,
131        hide_citations: chatbot_configuration.hide_citations,
132    })
133}