oxirs_chat/
chat_session.rs

1//! Chat session implementation with message history and statistics
2
3use crate::error::Result;
4use crate::messages::{Message, MessageRole};
5use crate::session_manager::{SessionData, SessionMetrics, SessionState};
6use chrono::{DateTime, Duration, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11/// Chat session managing a single conversation
12pub struct ChatSession {
13    /// Session identifier
14    pub id: String,
15    /// Messages in this session
16    pub messages: Vec<Message>,
17    /// Session creation time
18    pub created_at: DateTime<Utc>,
19    /// Last activity time
20    pub last_activity: DateTime<Utc>,
21    /// Session metadata
22    pub metadata: HashMap<String, serde_json::Value>,
23    /// Session state
24    pub state: SessionState,
25    /// Reference to the RDF store
26    pub store: Arc<dyn oxirs_core::Store>,
27    /// Session metrics
28    pub metrics: SessionMetrics,
29}
30
31impl ChatSession {
32    /// Create a new chat session
33    pub fn new(id: String, store: Arc<dyn oxirs_core::Store>) -> Self {
34        Self {
35            id,
36            messages: Vec::new(),
37            created_at: Utc::now(),
38            last_activity: Utc::now(),
39            metadata: HashMap::new(),
40            state: SessionState::Active,
41            store,
42            metrics: SessionMetrics::default(),
43        }
44    }
45
46    /// Add a message to the session
47    pub fn add_message(&mut self, message: Message) -> Result<()> {
48        // Update metrics
49        self.metrics.total_messages += 1;
50        match message.role {
51            MessageRole::User => self.metrics.user_messages += 1,
52            MessageRole::Assistant => self.metrics.assistant_messages += 1,
53            _ => {}
54        }
55
56        if let Some(tokens) = message.token_count {
57            self.metrics.total_tokens_used += tokens;
58        }
59
60        self.messages.push(message);
61        self.last_activity = Utc::now();
62        self.metrics.last_updated = Utc::now();
63        Ok(())
64    }
65
66    /// Get all messages in the session
67    pub fn get_messages(&self) -> &[Message] {
68        &self.messages
69    }
70
71    /// Get recent messages (last N)
72    pub fn get_recent_messages(&self, count: usize) -> &[Message] {
73        let start = self.messages.len().saturating_sub(count);
74        &self.messages[start..]
75    }
76
77    /// Get statistics for this session
78    pub fn get_statistics(&self) -> SessionStatistics {
79        let duration = (Utc::now() - self.created_at).num_seconds() as u64;
80
81        SessionStatistics {
82            session_id: self.id.clone(),
83            total_messages: self.messages.len(),
84            user_messages: self.metrics.user_messages,
85            assistant_messages: self.metrics.assistant_messages,
86            total_tokens: self.metrics.total_tokens_used,
87            avg_response_time_ms: self.metrics.average_response_time * 1000.0, // Convert to ms
88            session_duration_seconds: duration,
89            rag_retrievals: self.metrics.successful_queries, // Map to successful_queries
90            sparql_queries: self.metrics.successful_queries,
91            error_count: self.metrics.error_count,
92            created_at: self.created_at,
93            last_activity: self.last_activity,
94        }
95    }
96
97    /// Check if session should expire
98    pub fn should_expire(&self, timeout: Duration) -> bool {
99        Utc::now() - self.last_activity > timeout
100    }
101
102    /// Export session data for persistence
103    pub fn export_data(&self) -> SessionData {
104        use crate::session_manager::ChatConfig;
105        use std::collections::HashSet;
106
107        SessionData {
108            id: self.id.clone(),
109            config: ChatConfig::default(),
110            created_at: self.created_at,
111            last_activity: self.last_activity,
112            messages: self.messages.clone(),
113            user_preferences: self
114                .metadata
115                .iter()
116                .map(|(k, v)| (k.clone(), v.to_string()))
117                .collect(),
118            session_state: self.state.clone(),
119            context_summary: None,
120            pinned_messages: HashSet::new(),
121            current_topics: Vec::new(),
122            topic_history: Vec::new(),
123            performance_metrics: self.metrics.clone(),
124        }
125    }
126
127    /// Convert session to data format for persistence
128    pub fn to_data(&self) -> SessionData {
129        self.export_data()
130    }
131
132    /// Create session from data
133    pub fn from_data(data: SessionData, store: Arc<dyn oxirs_core::Store>) -> Self {
134        let metadata: HashMap<String, serde_json::Value> = data
135            .user_preferences
136            .iter()
137            .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
138            .collect();
139
140        Self {
141            id: data.id,
142            messages: data.messages,
143            created_at: data.created_at,
144            last_activity: data.last_activity,
145            metadata,
146            state: data.session_state,
147            store,
148            metrics: data.performance_metrics,
149        }
150    }
151
152    /// Update a metric value
153    pub fn record_rag_retrieval(&mut self) {
154        self.metrics.successful_queries += 1;
155        self.metrics.last_updated = Utc::now();
156    }
157
158    pub fn record_sparql_query(&mut self) {
159        self.metrics.successful_queries += 1;
160        self.metrics.last_updated = Utc::now();
161    }
162
163    pub fn record_error(&mut self) {
164        self.metrics.error_count += 1;
165        self.metrics.failed_queries += 1;
166        self.metrics.last_updated = Utc::now();
167    }
168
169    pub fn record_response_time(&mut self, response_time_ms: u64) {
170        self.metrics.update_response_time(response_time_ms);
171        self.metrics.last_updated = Utc::now();
172    }
173}
174
175/// Session statistics for analytics
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct SessionStatistics {
178    pub session_id: String,
179    pub total_messages: usize,
180    pub user_messages: usize,
181    pub assistant_messages: usize,
182    pub total_tokens: usize,
183    pub avg_response_time_ms: f64,
184    pub session_duration_seconds: u64,
185    pub rag_retrievals: usize,
186    pub sparql_queries: usize,
187    pub error_count: usize,
188    pub created_at: DateTime<Utc>,
189    pub last_activity: DateTime<Utc>,
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn test_session_creation() {
198        let store = Arc::new(oxirs_core::ConcreteStore::new().unwrap());
199        let session = ChatSession::new("test-session".to_string(), store);
200        assert_eq!(session.id, "test-session");
201        assert_eq!(session.messages.len(), 0);
202        assert_eq!(session.state, SessionState::Active);
203    }
204}