use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};
use uuid::Uuid;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConversationMessage {
pub role: String,
pub content: String,
pub timestamp: SystemTime,
}
#[derive(Clone, Debug)]
pub struct Conversation {
pub id: String,
pub messages: Vec<ConversationMessage>,
pub metadata: HashMap<String, String>,
pub created_at: SystemTime,
pub updated_at: SystemTime,
}
impl Conversation {
pub fn new() -> Self {
let now = SystemTime::now();
Self {
id: Uuid::new_v4().to_string(),
messages: Vec::new(),
metadata: HashMap::new(),
created_at: now,
updated_at: now,
}
}
pub fn add_message(&mut self, role: &str, content: &str) {
self.messages.push(ConversationMessage {
role: role.to_string(),
content: content.to_string(),
timestamp: SystemTime::now(),
});
self.updated_at = SystemTime::now();
}
pub fn get_messages(&self) -> &[ConversationMessage] {
&self.messages
}
pub fn set_metadata(&mut self, key: &str, value: &str) {
self.metadata.insert(key.to_string(), value.to_string());
}
}
impl Default for Conversation {
fn default() -> Self {
Self::new()
}
}
pub struct ConversationManager {
conversations: Arc<RwLock<HashMap<String, Conversation>>>,
max_age: Duration,
}
impl ConversationManager {
pub fn new(max_age_hours: u64) -> Self {
let max_age = Duration::from_secs(max_age_hours * 3600);
Self {
conversations: Arc::new(RwLock::new(HashMap::new())),
max_age,
}
}
pub fn create_conversation(&self) -> Result<Conversation, String> {
let conversation = Conversation::new();
let id = conversation.id.clone();
if let Ok(mut conversations) = self.conversations.write() {
conversations.insert(id, conversation.clone());
Ok(conversation)
} else {
Err("Falha ao adquirir lock".to_string())
}
}
pub fn get_conversation(&self, id: &str) -> Option<Conversation> {
if let Ok(conversations) = self.conversations.read() {
conversations.get(id).cloned()
} else {
None
}
}
pub fn update_conversation(&self, conversation: Conversation) -> Result<(), String> {
if let Ok(mut conversations) = self.conversations.write() {
conversations.insert(conversation.id.clone(), conversation);
Ok(())
} else {
Err("Falha ao adquirir lock".to_string())
}
}
pub fn add_message_to_conversation(
&self,
conversation_id: &str,
role: &str,
content: &str,
) -> Result<(), String> {
if let Ok(mut conversations) = self.conversations.write() {
if let Some(conversation) = conversations.get_mut(conversation_id) {
conversation.add_message(role, content);
conversation.updated_at = SystemTime::now();
Ok(())
} else {
Err(format!("Conversa {} não encontrada", conversation_id))
}
} else {
Err("Falha ao adquirir lock".to_string())
}
}
pub fn cleanup_old_conversations(&self) -> usize {
let now = SystemTime::now();
let mut count = 0;
if let Ok(mut conversations) = self.conversations.write() {
let ids_to_remove: Vec<String> = conversations
.iter()
.filter(|(_, conv)| {
now.duration_since(conv.updated_at)
.map(|duration| duration > self.max_age)
.unwrap_or(false)
})
.map(|(id, _)| id.clone())
.collect();
for id in ids_to_remove {
conversations.remove(&id);
count += 1;
}
}
count
}
pub fn get_arc_clone(&self) -> Arc<RwLock<HashMap<String, Conversation>>> {
Arc::clone(&self.conversations)
}
}
impl Clone for ConversationManager {
fn clone(&self) -> Self {
Self {
conversations: Arc::clone(&self.conversations),
max_age: self.max_age,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_create_and_get_conversation() {
let manager = ConversationManager::new(24);
let conversation = manager.create_conversation().unwrap();
let id = conversation.id.clone();
let retrieved = manager.get_conversation(&id).unwrap();
assert_eq!(retrieved.id, id);
assert!(retrieved.messages.is_empty());
}
#[test]
fn test_add_message_to_conversation() {
let manager = ConversationManager::new(24);
let conversation = manager.create_conversation().unwrap();
let id = conversation.id.clone();
manager
.add_message_to_conversation(&id, "user", "Pergunta 1")
.unwrap();
manager
.add_message_to_conversation(&id, "assistant", "Resposta 1")
.unwrap();
let retrieved = manager.get_conversation(&id).unwrap();
assert_eq!(retrieved.messages.len(), 2);
assert_eq!(retrieved.messages[0].role, "user");
assert_eq!(retrieved.messages[0].content, "Pergunta 1");
assert_eq!(retrieved.messages[1].role, "assistant");
assert_eq!(retrieved.messages[1].content, "Resposta 1");
}
#[test]
fn test_conversation_metadata() {
let manager = ConversationManager::new(24);
let mut conversation = manager.create_conversation().unwrap();
conversation.set_metadata("language", "pt-br");
conversation.set_metadata("model", "gpt-4");
manager.update_conversation(conversation.clone()).unwrap();
let retrieved = manager.get_conversation(&conversation.id).unwrap();
assert_eq!(retrieved.metadata.get("language").unwrap(), "pt-br");
assert_eq!(retrieved.metadata.get("model").unwrap(), "gpt-4");
}
#[test]
fn test_add_message_nonexistent_conversation() {
let manager = ConversationManager::new(24);
let result = manager.add_message_to_conversation("id-inexistente", "user", "Olá");
assert!(result.is_err());
}
#[test]
fn test_cleanup_old_conversations() {
let manager = ConversationManager::new(0);
let conv1 = manager.create_conversation().unwrap();
let _conv2 = manager.create_conversation().unwrap();
let _conv3 = manager.create_conversation().unwrap();
thread::sleep(Duration::from_millis(10));
let removed = manager.cleanup_old_conversations();
assert_eq!(removed, 3);
assert!(manager.get_conversation(&conv1.id).is_none());
}
}