i-self 0.4.3

Personal developer-companion CLI: scans your repos, indexes code semantically, watches your activity, and moves AI-agent sessions between tools (Claude Code, Aider, Goose, OpenAI Codex CLI, Continue.dev, OpenCode).
#![allow(dead_code)]

use crate::storage::{DeveloperProfile, Storage};
use crate::semantic::{SemanticSearch, SemanticConfig};
use crate::ai::{AIConfig, CodeAssistant};
use crate::team::TeamProfile;
use std::collections::HashMap;

/// Shared application state for the web dashboard
pub struct AppState {
    pub storage: Storage,
    pub profile: Option<DeveloperProfile>,
    pub semantic_search: Option<SemanticSearch>,
    pub ai_assistant: Option<CodeAssistant>,
    pub team_profiles: HashMap<String, TeamProfile>,
    pub config: DashboardConfig,
}

#[derive(Debug, Clone)]
pub struct DashboardConfig {
    pub title: String,
    pub enable_ai: bool,
    pub enable_semantic_search: bool,
    pub theme: String,
}

impl Default for DashboardConfig {
    fn default() -> Self {
        Self {
            title: "i-self Dashboard".to_string(),
            enable_ai: true,
            enable_semantic_search: true,
            theme: "dark".to_string(),
        }
    }
}

impl AppState {
    pub async fn new() -> anyhow::Result<Self> {
        let storage = Storage::new()?;
        let profile = storage.load_profile().await.ok();
        
        Ok(Self {
            storage,
            profile,
            semantic_search: None,
            ai_assistant: None,
            team_profiles: HashMap::new(),
            config: DashboardConfig::default(),
        })
    }

    pub async fn load_semantic_search(&mut self) -> anyhow::Result<()> {
        use crate::semantic::{EmbeddingGenerator, EmbeddingStorage, SemanticSearch};

        let storage = EmbeddingStorage::new(
            self.storage.base_dir(),
            SemanticConfig::default()
        )?;

        // Manifest-checked load: refuses to surface vectors produced by a
        // different embedder (e.g. dashboard launched without OPENAI_API_KEY
        // after the index was built with one). If the manifest doesn't match,
        // we end up with an empty index — preferable to silently mixing
        // OpenAI vectors with hash-bucket queries.
        let probe = EmbeddingGenerator::new(SemanticConfig::default())?;
        let embeddings = storage.load_embeddings_for(&probe.id()).await?;

        if !embeddings.is_empty() {
            self.semantic_search = Some(SemanticSearch::new(
                SemanticConfig::default(),
                embeddings
            )?);
        }

        Ok(())
    }

    pub fn init_ai(&mut self, config: AIConfig) -> anyhow::Result<()> {
        self.ai_assistant = Some(CodeAssistant::new(config)?);
        Ok(())
    }
}