use crate::models::Message; use crate::state::{AppState, BASE_URL}; #[derive(serde::Deserialize)] struct History { cursor: i64, messages: Vec, } #[tauri::command] pub async fn list_messages( state: tauri::State<'_, AppState>, conversation_id: String, ) -> Result, String> { let history: History = state .authed_get(&format!( "{}/conversations/{}/messages", BASE_URL, conversation_id )) .send() .await .map_err(|e| e.to_string())? .json() .await .map_err(|e| e.to_string())?; // Loading the history *is* a resync: adopt the cursor it came with, so the // next realtime signal asks for a delta from the right place. state.cursors.set(&conversation_id, history.cursor); Ok(history.messages) } #[tauri::command] pub async fn send_message( state: tauri::State<'_, AppState>, conversation_id: String, content: String, ) -> Result { let user_id = state .user_id .lock() .unwrap() .clone() .ok_or_else(|| "Not logged in".to_string())?; // The id comes from here, not from the server. Without it the optimistically // rendered message cannot be matched against the copy that arrives over the // realtime channel, and the same message shows up twice. It also makes a // retry after an unclear network error a no-op instead of a duplicate. let body = serde_json::json!({ "id": uuid::Uuid::new_v4().to_string(), "conversation_id": conversation_id, "user_id": user_id, "content": content, }); state .authed_post(&format!("{}/messages", BASE_URL)) .json(&body) .send() .await .map_err(|e| e.to_string())? .json() .await .map_err(|e| e.to_string()) }