use crate::openapi::OpenApiSpec;
use crate::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
pub struct ConversationManager {
conversations: HashMap<String, ConversationState>,
}
impl ConversationManager {
pub fn new() -> Self {
Self {
conversations: HashMap::new(),
}
}
pub fn start_conversation(&mut self) -> String {
let id = Uuid::new_v4().to_string();
let state = ConversationState {
id: id.clone(),
context: ConversationContext {
conversation_id: id.clone(),
current_spec: None,
history: Vec::new(),
metadata: HashMap::new(),
},
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
self.conversations.insert(id.clone(), state);
id
}
pub fn get_conversation(&self, id: &str) -> Option<&ConversationState> {
self.conversations.get(id)
}
pub fn get_conversation_mut(&mut self, id: &str) -> Option<&mut ConversationState> {
self.conversations.get_mut(id)
}
pub fn update_conversation(
&mut self,
id: &str,
command: &str,
spec: Option<OpenApiSpec>,
) -> Result<()> {
let state = self
.conversations
.get_mut(id)
.ok_or_else(|| crate::Error::internal(format!("Conversation {} not found", id)))?;
state.context.history.push(ConversationEntry {
timestamp: chrono::Utc::now(),
command: command.to_string(),
spec_snapshot: spec
.as_ref()
.map(|s| serde_json::to_value(s.spec.clone()).unwrap_or(serde_json::Value::Null)),
});
state.context.current_spec = spec;
state.updated_at = chrono::Utc::now();
Ok(())
}
pub fn remove_conversation(&mut self, id: &str) -> bool {
self.conversations.remove(id).is_some()
}
pub fn list_conversations(&self) -> Vec<&ConversationState> {
self.conversations.values().collect()
}
}
impl Default for ConversationManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationState {
pub id: String,
pub context: ConversationContext,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationContext {
pub conversation_id: String,
#[serde(skip)]
pub current_spec: Option<OpenApiSpec>,
pub history: Vec<ConversationEntry>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConversationEntry {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub command: String,
#[serde(default)]
pub spec_snapshot: Option<serde_json::Value>,
}
impl ConversationContext {
pub fn new(conversation_id: String) -> Self {
Self {
conversation_id,
current_spec: None,
history: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn update_spec(&mut self, spec: OpenApiSpec) {
self.current_spec = Some(spec);
}
pub fn get_spec(&self) -> Option<&OpenApiSpec> {
self.current_spec.as_ref()
}
pub fn add_command(&mut self, command: String, spec_snapshot: Option<serde_json::Value>) {
self.history.push(ConversationEntry {
timestamp: chrono::Utc::now(),
command,
spec_snapshot,
});
}
pub fn get_summary(&self) -> String {
let mut summary = format!(
"Conversation ID: {}\nHistory: {} commands\n",
self.conversation_id,
self.history.len()
);
if let Some(ref spec) = self.current_spec {
summary.push_str(&format!(
"Current API: {}\nVersion: {}\n",
spec.title(),
spec.version()
));
} else {
summary.push_str("Current API: None (new conversation)\n");
}
if !self.history.is_empty() {
summary.push_str("\nRecent commands:\n");
for entry in self.history.iter().rev().take(5) {
summary.push_str(&format!("- {}\n", entry.command));
}
}
summary
}
}