mockforge_core/voice/
conversation.rs1use crate::openapi::OpenApiSpec;
7use crate::Result;
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
62 .conversations
63 .get_mut(id)
64 .ok_or_else(|| crate::Error::generic(format!("Conversation {} not found", id)))?;
65
66 state.context.history.push(ConversationEntry {
68 timestamp: chrono::Utc::now(),
69 command: command.to_string(),
70 spec_snapshot: spec
71 .as_ref()
72 .map(|s| serde_json::to_value(s.spec.clone()).unwrap_or(serde_json::Value::Null)),
73 });
74
75 state.context.current_spec = spec;
77
78 state.updated_at = chrono::Utc::now();
80
81 Ok(())
82 }
83
84 pub fn remove_conversation(&mut self, id: &str) -> bool {
86 self.conversations.remove(id).is_some()
87 }
88
89 pub fn list_conversations(&self) -> Vec<&ConversationState> {
91 self.conversations.values().collect()
92 }
93}
94
95impl Default for ConversationManager {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ConversationState {
104 pub id: String,
106 pub context: ConversationContext,
108 pub created_at: chrono::DateTime<chrono::Utc>,
110 pub updated_at: chrono::DateTime<chrono::Utc>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct ConversationContext {
117 pub conversation_id: String,
119 #[serde(skip)]
121 pub current_spec: Option<OpenApiSpec>,
122 pub history: Vec<ConversationEntry>,
124 #[serde(default)]
126 pub metadata: HashMap<String, serde_json::Value>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ConversationEntry {
132 pub timestamp: chrono::DateTime<chrono::Utc>,
134 pub command: String,
136 #[serde(default)]
138 pub spec_snapshot: Option<serde_json::Value>,
139}
140
141impl ConversationContext {
142 pub fn new(conversation_id: String) -> Self {
144 Self {
145 conversation_id,
146 current_spec: None,
147 history: Vec::new(),
148 metadata: HashMap::new(),
149 }
150 }
151
152 pub fn update_spec(&mut self, spec: OpenApiSpec) {
154 self.current_spec = Some(spec);
155 }
156
157 pub fn get_spec(&self) -> Option<&OpenApiSpec> {
159 self.current_spec.as_ref()
160 }
161
162 pub fn add_command(&mut self, command: String, spec_snapshot: Option<serde_json::Value>) {
164 self.history.push(ConversationEntry {
165 timestamp: chrono::Utc::now(),
166 command,
167 spec_snapshot,
168 });
169 }
170
171 pub fn get_summary(&self) -> String {
173 let mut summary = format!(
174 "Conversation ID: {}\nHistory: {} commands\n",
175 self.conversation_id,
176 self.history.len()
177 );
178
179 if let Some(ref spec) = self.current_spec {
180 summary.push_str(&format!(
181 "Current API: {}\nVersion: {}\n",
182 spec.title(),
183 spec.version()
184 ));
185 } else {
186 summary.push_str("Current API: None (new conversation)\n");
187 }
188
189 if !self.history.is_empty() {
190 summary.push_str("\nRecent commands:\n");
191 for entry in self.history.iter().rev().take(5) {
192 summary.push_str(&format!("- {}\n", entry.command));
193 }
194 }
195
196 summary
197 }
198}