mockforge_intelligence/voice/
conversation.rs1use mockforge_foundation::Result;
7use mockforge_openapi::OpenApiSpec;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use uuid::Uuid;
11
12pub struct ConversationManager {
14 conversations: HashMap<String, ConversationState>,
16}
17
18impl ConversationManager {
19 pub fn new() -> Self {
21 Self {
22 conversations: HashMap::new(),
23 }
24 }
25
26 pub fn start_conversation(&mut self) -> String {
28 let id = Uuid::new_v4().to_string();
29 let state = ConversationState {
30 id: id.clone(),
31 context: ConversationContext {
32 conversation_id: id.clone(),
33 current_spec: None,
34 history: Vec::new(),
35 metadata: HashMap::new(),
36 },
37 created_at: chrono::Utc::now(),
38 updated_at: chrono::Utc::now(),
39 };
40 self.conversations.insert(id.clone(), state);
41 id
42 }
43
44 pub fn get_conversation(&self, id: &str) -> Option<&ConversationState> {
46 self.conversations.get(id)
47 }
48
49 pub fn get_conversation_mut(&mut self, id: &str) -> Option<&mut ConversationState> {
51 self.conversations.get_mut(id)
52 }
53
54 pub fn update_conversation(
56 &mut self,
57 id: &str,
58 command: &str,
59 spec: Option<OpenApiSpec>,
60 ) -> Result<()> {
61 let state = self.conversations.get_mut(id).ok_or_else(|| {
62 mockforge_foundation::Error::internal(format!("Conversation {} not found", id))
63 })?;
64
65 state.context.history.push(ConversationEntry {
67 timestamp: chrono::Utc::now(),
68 command: command.to_string(),
69 spec_snapshot: spec
70 .as_ref()
71 .map(|s| serde_json::to_value(s.spec.clone()).unwrap_or(serde_json::Value::Null)),
72 });
73
74 state.context.current_spec = spec;
76
77 state.updated_at = chrono::Utc::now();
79
80 Ok(())
81 }
82
83 pub fn remove_conversation(&mut self, id: &str) -> bool {
85 self.conversations.remove(id).is_some()
86 }
87
88 pub fn list_conversations(&self) -> Vec<&ConversationState> {
90 self.conversations.values().collect()
91 }
92}
93
94impl Default for ConversationManager {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ConversationState {
103 pub id: String,
105 pub context: ConversationContext,
107 pub created_at: chrono::DateTime<chrono::Utc>,
109 pub updated_at: chrono::DateTime<chrono::Utc>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ConversationContext {
116 pub conversation_id: String,
118 #[serde(skip)]
120 pub current_spec: Option<OpenApiSpec>,
121 pub history: Vec<ConversationEntry>,
123 #[serde(default)]
125 pub metadata: HashMap<String, serde_json::Value>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ConversationEntry {
131 pub timestamp: chrono::DateTime<chrono::Utc>,
133 pub command: String,
135 #[serde(default)]
137 pub spec_snapshot: Option<serde_json::Value>,
138}
139
140impl ConversationContext {
141 pub fn new(conversation_id: String) -> Self {
143 Self {
144 conversation_id,
145 current_spec: None,
146 history: Vec::new(),
147 metadata: HashMap::new(),
148 }
149 }
150
151 pub fn update_spec(&mut self, spec: OpenApiSpec) {
153 self.current_spec = Some(spec);
154 }
155
156 pub fn get_spec(&self) -> Option<&OpenApiSpec> {
158 self.current_spec.as_ref()
159 }
160
161 pub fn add_command(&mut self, command: String, spec_snapshot: Option<serde_json::Value>) {
163 self.history.push(ConversationEntry {
164 timestamp: chrono::Utc::now(),
165 command,
166 spec_snapshot,
167 });
168 }
169
170 pub fn get_summary(&self) -> String {
172 let mut summary = format!(
173 "Conversation ID: {}\nHistory: {} commands\n",
174 self.conversation_id,
175 self.history.len()
176 );
177
178 if let Some(ref spec) = self.current_spec {
179 summary.push_str(&format!(
180 "Current API: {}\nVersion: {}\n",
181 spec.title(),
182 spec.version()
183 ));
184 } else {
185 summary.push_str("Current API: None (new conversation)\n");
186 }
187
188 if !self.history.is_empty() {
189 summary.push_str("\nRecent commands:\n");
190 for entry in self.history.iter().rev().take(5) {
191 summary.push_str(&format!("- {}\n", entry.command));
192 }
193 }
194
195 summary
196 }
197}