Skip to main content

headless_lms_models/
chatbot_conversation_messages.rs

1use utoipa::ToSchema;
2
3use crate::{
4    chatbot_conversation_message_messages::{self, ChatbotConversationMessageMessage},
5    chatbot_conversation_message_reasoning::{self, ChatbotConversationMessageReasoning},
6    chatbot_conversation_message_tool_calls::{self, ChatbotConversationMessageToolCall},
7    chatbot_conversation_message_tool_outputs::{self, ChatbotConversationMessageToolOutput},
8    prelude::*,
9};
10
11#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
12pub struct ChatbotConversationMessageRow {
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 conversation_id: Uuid,
18    pub order_number: i32,
19}
20
21#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
22#[serde(untagged)]
23pub enum Message {
24    Text(ChatbotConversationMessageMessage),
25    ToolCall(ChatbotConversationMessageToolCall),
26    ToolOutput(ChatbotConversationMessageToolOutput),
27    Reasoning(ChatbotConversationMessageReasoning),
28}
29
30#[derive(Clone, PartialEq, Deserialize, Serialize, Debug, ToSchema)]
31pub struct ChatbotConversationMessage {
32    pub id: Uuid,
33    pub created_at: DateTime<Utc>,
34    pub updated_at: DateTime<Utc>,
35    pub deleted_at: Option<DateTime<Utc>>,
36    pub conversation_id: Uuid,
37    pub order_number: i32,
38    pub message: Message,
39}
40
41impl Default for ChatbotConversationMessage {
42    fn default() -> Self {
43        Self {
44            id: Uuid::nil(),
45            created_at: Default::default(),
46            updated_at: Default::default(),
47            deleted_at: None,
48            conversation_id: Uuid::nil(),
49            order_number: Default::default(),
50            message: Message::Text(ChatbotConversationMessageMessage::default()),
51        }
52    }
53}
54
55impl ChatbotConversationMessage {
56    pub fn from_row(r: ChatbotConversationMessageRow, m: Message) -> Self {
57        ChatbotConversationMessage {
58            id: r.id,
59            created_at: r.created_at,
60            updated_at: r.updated_at,
61            deleted_at: r.deleted_at,
62            conversation_id: r.conversation_id,
63            order_number: r.order_number,
64            message: m,
65        }
66    }
67}
68
69pub async fn insert(
70    conn: &mut PgConnection,
71    input: ChatbotConversationMessage,
72) -> ModelResult<ChatbotConversationMessage> {
73    let mut tx = conn.begin().await?;
74    let msg = sqlx::query_as!(
75        ChatbotConversationMessageRow,
76        r#"
77INSERT INTO chatbot_conversation_messages (conversation_id, order_number)
78VALUES (
79    $1,
80    COALESCE((
81      SELECT order_number
82      FROM chatbot_conversation_messages
83      WHERE conversation_id = $1
84        AND deleted_at IS NULL
85      ORDER BY order_number DESC
86      LIMIT 1
87    ), 0) + 1
88  )
89RETURNING *
90        "#,
91        input.conversation_id,
92    )
93    .fetch_one(&mut *tx)
94    .await?;
95
96    let inner = match input.message {
97        Message::Text(message) => {
98            let res =
99                chatbot_conversation_message_messages::insert(&mut tx, message, msg.id).await?;
100            Message::Text(res)
101        }
102        Message::ToolCall(tool_call) => {
103            let res =
104                chatbot_conversation_message_tool_calls::insert(&mut tx, tool_call, msg.id).await?;
105            Message::ToolCall(res)
106        }
107        Message::ToolOutput(tool_output) => {
108            let res =
109                chatbot_conversation_message_tool_outputs::insert(&mut tx, tool_output, msg.id)
110                    .await?;
111            Message::ToolOutput(res)
112        }
113        Message::Reasoning(reasoning) => {
114            let res =
115                chatbot_conversation_message_reasoning::insert(&mut tx, reasoning, msg.id).await?;
116            Message::Reasoning(res)
117        }
118    };
119
120    let res = ChatbotConversationMessage::from_row(msg, inner);
121    tx.commit().await?;
122    Ok(res)
123}
124
125// todo
126pub async fn insert_for_conversation_user_and_configuration(
127    conn: &mut PgConnection,
128    input: ChatbotConversationMessage,
129    user_id: Uuid,
130    chatbot_configuration_id: Uuid,
131) -> ModelResult<ChatbotConversationMessage> {
132    let mut tx = conn.begin().await?;
133
134    sqlx::query!(
135        r#"
136SELECT id
137FROM chatbot_conversations
138WHERE id = $1
139  AND user_id = $2
140  AND chatbot_configuration_id = $3
141  AND deleted_at IS NULL
142        "#,
143        input.conversation_id,
144        user_id,
145        chatbot_configuration_id
146    )
147    .fetch_one(&mut *tx)
148    .await?;
149
150    let msg = sqlx::query_as!(
151        ChatbotConversationMessageRow,
152        r#"
153INSERT INTO chatbot_conversation_messages (
154    conversation_id,
155    order_number
156)
157VALUES (
158    $1,
159    COALESCE((
160      SELECT order_number
161      FROM chatbot_conversation_messages
162      WHERE conversation_id = $1
163        AND deleted_at IS NULL
164      ORDER BY order_number DESC
165      LIMIT 1
166    ), 0) + 1
167)
168RETURNING *
169        "#,
170        input.conversation_id,
171    )
172    .fetch_one(&mut *tx)
173    .await?;
174
175    let inner = match input.message {
176        Message::Text(message) => {
177            let res =
178                chatbot_conversation_message_messages::insert(&mut tx, message, msg.id).await?;
179            Message::Text(res)
180        }
181        Message::ToolCall(tool_call) => {
182            let res =
183                chatbot_conversation_message_tool_calls::insert(&mut tx, tool_call, msg.id).await?;
184            Message::ToolCall(res)
185        }
186        Message::ToolOutput(tool_output) => {
187            let res =
188                chatbot_conversation_message_tool_outputs::insert(&mut tx, tool_output, msg.id)
189                    .await?;
190            Message::ToolOutput(res)
191        }
192        Message::Reasoning(reasoning) => {
193            let res =
194                chatbot_conversation_message_reasoning::insert(&mut tx, reasoning, msg.id).await?;
195            Message::Reasoning(res)
196        }
197    };
198
199    let res = ChatbotConversationMessage::from_row(msg, inner);
200    tx.commit().await?;
201    Ok(res)
202}
203
204pub async fn get_by_conversation_id(
205    conn: &mut PgConnection,
206    conversation_id: Uuid,
207) -> ModelResult<Vec<ChatbotConversationMessage>> {
208    let mut tx = conn.begin().await?;
209    let mut msgs: Vec<ChatbotConversationMessageRow> = sqlx::query_as!(
210        ChatbotConversationMessageRow,
211        r#"
212SELECT *
213FROM chatbot_conversation_messages
214WHERE conversation_id = $1
215AND deleted_at IS NULL
216        "#,
217        conversation_id
218    )
219    .fetch_all(&mut *tx)
220    .await?;
221    // Should have the same order as in the conversation.
222    msgs.sort_by_key(|a| a.order_number);
223    let mut res = vec![];
224    for m in msgs {
225        let msg = message_row_to_message(&mut tx, m).await?;
226        res.push(msg);
227    }
228    tx.commit().await?;
229    Ok(res)
230}
231
232pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<ChatbotConversationMessage> {
233    let mut tx = conn.begin().await?;
234
235    let row = sqlx::query_as!(
236        ChatbotConversationMessageRow,
237        r#"
238UPDATE chatbot_conversation_messages
239SET deleted_at = NOW()
240WHERE id = $1
241RETURNING *
242        "#,
243        id
244    )
245    .fetch_one(&mut *tx)
246    .await?;
247
248    // delete the child
249    let child = delete_message_fields(&mut tx, row.id).await?;
250
251    let res = ChatbotConversationMessage::from_row(row, child);
252    tx.commit().await?;
253    Ok(res)
254}
255
256pub async fn update(
257    conn: &mut PgConnection,
258    id: Uuid,
259    text: &str,
260    message_is_complete: bool,
261    used_tokens: i32,
262) -> ModelResult<ChatbotConversationMessage> {
263    let mut tx = conn.begin().await?;
264
265    let row = sqlx::query_as!(
266        ChatbotConversationMessageRow,
267        r#"
268UPDATE chatbot_conversation_messages
269SET updated_at = NOW()
270WHERE id = $1
271RETURNING *
272        "#,
273        id
274    )
275    .fetch_one(&mut *tx)
276    .await?;
277
278    // update the parent
279    let child = chatbot_conversation_message_messages::update(
280        &mut tx,
281        row.id,
282        text,
283        message_is_complete,
284        used_tokens,
285    )
286    .await?;
287
288    let res = ChatbotConversationMessage::from_row(row, Message::Text(child));
289    tx.commit().await?;
290
291    Ok(res)
292}
293
294pub async fn message_row_to_message(
295    conn: &mut PgConnection,
296    row: ChatbotConversationMessageRow,
297) -> ModelResult<ChatbotConversationMessage> {
298    let inner_message = get_message_fields(conn, row.id).await?;
299    let res = ChatbotConversationMessage::from_row(row, inner_message);
300    Ok(res)
301}
302
303pub async fn get_message_fields(conn: &mut PgConnection, message_id: Uuid) -> ModelResult<Message> {
304    if let Some(message) =
305        chatbot_conversation_message_messages::get_by_message_id(conn, message_id).await?
306    {
307        Ok(Message::Text(message))
308    } else if let Some(tool_call) =
309        chatbot_conversation_message_tool_calls::get_by_message_id(conn, message_id).await?
310    {
311        Ok(Message::ToolCall(tool_call))
312    } else if let Some(tool_output) =
313        chatbot_conversation_message_tool_outputs::get_by_message_id(conn, message_id).await?
314    {
315        Ok(Message::ToolOutput(tool_output))
316    } else if let Some(reasoning) =
317        chatbot_conversation_message_reasoning::get_by_message_id(conn, message_id).await?
318    {
319        Ok(Message::Reasoning(reasoning))
320    } else {
321        Err(ModelError::new(
322            ModelErrorType::RecordNotFound,
323            "No inner message found for this ChatbotConversationMessage",
324            None,
325        ))
326    }
327}
328
329pub async fn delete_message_fields(
330    conn: &mut PgConnection,
331    message_id: Uuid,
332) -> ModelResult<Message> {
333    if let Some(message) =
334        chatbot_conversation_message_messages::get_by_message_id(conn, message_id).await?
335    {
336        let res = chatbot_conversation_message_messages::delete(conn, message.id).await?;
337        Ok(Message::Text(res))
338    } else if let Some(tool_call) =
339        chatbot_conversation_message_tool_calls::get_by_message_id(conn, message_id).await?
340    {
341        let res = chatbot_conversation_message_tool_calls::delete(conn, tool_call.id).await?;
342        Ok(Message::ToolCall(res))
343    } else if let Some(tool_output) =
344        chatbot_conversation_message_tool_outputs::get_by_message_id(conn, message_id).await?
345    {
346        let res = chatbot_conversation_message_tool_outputs::delete(conn, tool_output.id).await?;
347        Ok(Message::ToolOutput(res))
348    } else if let Some(reasoning) =
349        chatbot_conversation_message_reasoning::get_by_message_id(conn, message_id).await?
350    {
351        let res = chatbot_conversation_message_reasoning::delete(conn, reasoning.id).await?;
352        Ok(Message::Reasoning(res))
353    } else {
354        Err(ModelError::new(
355            ModelErrorType::RecordNotFound,
356            "No inner message found for this ChatbotConversationMessage",
357            None,
358        ))
359    }
360}