use anyhow::Result;
use parking_lot::RwLock;
use super::persona::{default_personas, Persona};
use super::persona_store::PersonaStore;
#[derive(Debug)]
pub struct PersonaManager {
store: PersonaStore,
active_persona_id: RwLock<Option<String>>,
}
impl PersonaManager {
pub fn new() -> Self {
let store = PersonaStore::new();
let manager = Self {
store,
active_persona_id: RwLock::new(None),
};
manager.create_default_personas();
manager
}
pub fn with_defaults(personas: Vec<Persona>) -> Self {
let store = PersonaStore::new();
store.load_from_slice(&personas);
let this = Self {
store,
active_persona_id: RwLock::new(None),
};
if let Some(first) = this.store.list_enabled().into_iter().next() {
*this.active_persona_id.write() = Some(first.id);
}
this
}
pub fn get_active_persona(&self) -> Option<Persona> {
let active_id = self.active_persona_id.read().clone();
active_id.and_then(|id| self.store.get(&id))
}
pub fn set_active_persona(&self, id: &str) -> Result<()> {
let persona = self
.store
.get(id)
.ok_or_else(|| anyhow::anyhow!("Persona '{}' not found", id))?;
if !persona.enabled {
anyhow::bail!("Persona '{}' is disabled", id);
}
*self.active_persona_id.write() = Some(id.to_string());
tracing::info!(persona_id = %id, name = %persona.name, "Active persona set");
Ok(())
}
pub fn active_system_prompt(&self) -> String {
self.get_active_persona()
.map(|p| p.system_prompt.clone())
.unwrap_or_else(|| {
"You are a helpful AI assistant that follows the Ouroboros methodology: \
specify before you build, evaluate before you ship."
.to_string()
})
}
pub fn create_default_personas(&self) {
let defaults = default_personas();
for persona in defaults {
if self.store.get(&persona.id).is_none() {
self.store.register(persona);
}
}
{
let mut active = self.active_persona_id.write();
if active.is_none() {
*active = Some("dev".to_string());
}
}
tracing::info!("Default personas initialized");
}
pub fn first_enabled(&self) -> Option<Persona> {
self.store.list_enabled().into_iter().next()
}
pub fn store(&self) -> &PersonaStore {
&self.store
}
pub fn active_persona_id(&self) -> Option<String> {
self.active_persona_id.read().clone()
}
}
impl Default for PersonaManager {
fn default() -> Self {
Self::new()
}
}
impl Clone for PersonaManager {
fn clone(&self) -> Self {
let personas: Vec<Persona> = self.store.list_all();
let store = PersonaStore::new();
store.load_from_slice(&personas);
Self {
store,
active_persona_id: RwLock::new(self.active_persona_id.read().clone()),
}
}
}