headless_lms_models/
chatbot_conversations.rs

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