headless_lms_models/
chatbot_conversations.rs

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