butterfly-bot 0.4.0

Butterfly Bot is an opinionated personal-ops AI assistant built for people who want results, not setup overhead.
Documentation
use async_trait::async_trait;
use tokio::sync::Mutex;

use crate::error::Result;
use crate::interfaces::brain::{BrainContext, BrainEvent, BrainPlugin};

const DATA_COLLECTION: &str = "We keep conversation data locally or in the configured store so the assistant can remember context. We avoid collecting unnecessary personal data.";
const DATA_USAGE: &str =
    "Data is used to provide your session and memory. It is not sold or used for advertising.";
const DATA_DELETION: &str = "You can delete your conversation history at any time; local data is removed when you clear it.";
const SECURITY: &str =
    "We use local-first defaults and standard transport encryption for network calls.";

pub struct TrustTransparencyBrain {
    last_response: Mutex<Option<String>>,
}

impl TrustTransparencyBrain {
    pub fn new() -> Self {
        Self {
            last_response: Mutex::new(None),
        }
    }

    pub async fn last_response(&self) -> Option<String> {
        let guard = self.last_response.lock().await;
        guard.clone()
    }

    fn classify(message: &str) -> Option<&'static str> {
        let lower = message.to_lowercase();
        if lower.contains("data") && lower.contains("collect") {
            return Some(DATA_COLLECTION);
        }
        if lower.contains("data") && (lower.contains("use") || lower.contains("train")) {
            return Some(DATA_USAGE);
        }
        if lower.contains("delete") || lower.contains("remove my data") {
            return Some(DATA_DELETION);
        }
        if lower.contains("secure") || lower.contains("encryption") {
            return Some(SECURITY);
        }
        None
    }
}

#[async_trait]
impl BrainPlugin for TrustTransparencyBrain {
    fn name(&self) -> &str {
        "trust_transparency"
    }

    fn description(&self) -> &str {
        "Explains privacy and trust practices when asked"
    }

    async fn on_event(&self, event: BrainEvent, _ctx: &BrainContext) -> Result<()> {
        if let BrainEvent::UserMessage { text, .. } = event {
            if let Some(response) = Self::classify(&text) {
                let mut guard = self.last_response.lock().await;
                *guard = Some(response.to_string());
            }
        }
        Ok(())
    }
}