use crate::domain::OutputFormat;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub vault: VaultConfig,
#[serde(default)]
pub output: OutputConfig,
#[serde(default)]
pub developer: DeveloperConfig,
#[serde(default)]
pub projects: Vec<ProjectConfig>,
#[serde(default)]
pub scenes: Vec<SceneConfig>,
#[serde(default)]
pub embedding: EmbeddingConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DeveloperConfig {
#[serde(default)]
pub note_roots: Vec<String>,
}
impl DeveloperConfig {
pub fn effective_note_roots(&self, project_note_roots: &[String]) -> Vec<String> {
let mut roots = self.note_roots.clone();
for root in project_note_roots {
if !roots.iter().any(|existing| existing == root) {
roots.push(root.clone());
}
}
roots
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WakeupProfileConfig {
Developer,
#[default]
Project,
}
impl From<WakeupProfileConfig> for crate::domain::WakeupProfile {
fn from(value: WakeupProfileConfig) -> Self {
match value {
WakeupProfileConfig::Developer => crate::domain::WakeupProfile::Developer,
WakeupProfileConfig::Project => crate::domain::WakeupProfile::Project,
}
}
}
impl From<crate::domain::WakeupProfile> for WakeupProfileConfig {
fn from(value: crate::domain::WakeupProfile) -> Self {
match value {
crate::domain::WakeupProfile::Developer => WakeupProfileConfig::Developer,
crate::domain::WakeupProfile::Project => WakeupProfileConfig::Project,
}
}
}
impl Default for AppConfig {
fn default() -> Self {
Self {
vault: VaultConfig {
root: PathBuf::new(),
limits: VaultLimits::default(),
},
output: OutputConfig::default(),
developer: DeveloperConfig::default(),
projects: Vec::new(),
scenes: Vec::new(),
embedding: EmbeddingConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VaultConfig {
pub root: PathBuf,
#[serde(default)]
pub limits: VaultLimits,
}
#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
pub struct VaultLimits {
#[serde(default = "default_max_files")]
pub max_files: usize,
#[serde(default = "default_max_file_bytes")]
pub max_file_bytes: u64,
#[serde(default = "default_max_total_bytes")]
pub max_total_bytes: u64,
#[serde(default = "default_max_depth")]
pub max_depth: usize,
}
impl Default for VaultLimits {
fn default() -> Self {
Self {
max_files: default_max_files(),
max_file_bytes: default_max_file_bytes(),
max_total_bytes: default_max_total_bytes(),
max_depth: default_max_depth(),
}
}
}
fn default_max_files() -> usize {
5_000
}
fn default_max_file_bytes() -> u64 {
512 * 1024
}
fn default_max_total_bytes() -> u64 {
16 * 1024 * 1024
}
fn default_max_depth() -> usize {
12
}
fn default_format() -> OutputFormat {
OutputFormat::Prompt
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
#[serde(default = "default_format")]
pub default_format: OutputFormat,
#[serde(default = "default_max_chars")]
pub max_chars: usize,
#[serde(default = "default_max_notes")]
pub max_notes: usize,
#[serde(default = "default_max_lifecycle")]
pub max_lifecycle: usize,
}
impl Default for OutputConfig {
fn default() -> Self {
Self {
default_format: default_format(),
max_chars: default_max_chars(),
max_notes: default_max_notes(),
max_lifecycle: default_max_lifecycle(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
pub id: String,
pub name: String,
#[serde(default)]
pub repo_paths: Vec<PathBuf>,
#[serde(default)]
pub note_roots: Vec<String>,
#[serde(default)]
pub default_tags: Vec<String>,
#[serde(default)]
pub modules: Vec<ModuleConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleConfig {
pub id: String,
#[serde(default)]
pub path_prefixes: Vec<String>,
#[serde(default)]
pub keywords: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneConfig {
pub id: String,
#[serde(default)]
pub keywords: Vec<String>,
#[serde(default)]
pub preferred_notes: Vec<String>,
}
fn default_max_chars() -> usize {
12_000
}
fn default_max_notes() -> usize {
8
}
fn default_max_lifecycle() -> usize {
5
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingConfig {
#[serde(default = "default_embedding_enabled")]
pub enabled: bool,
#[serde(default)]
pub model_id: Option<String>,
#[serde(default)]
pub model_path: Option<PathBuf>,
#[serde(default)]
pub index_path: Option<PathBuf>,
#[serde(default = "default_auto_index")]
pub auto_index: bool,
}
impl Default for EmbeddingConfig {
fn default() -> Self {
Self {
enabled: default_embedding_enabled(),
model_id: None,
model_path: None,
index_path: None,
auto_index: default_auto_index(),
}
}
}
impl EmbeddingConfig {
pub fn resolved_model_path(&self) -> Option<PathBuf> {
self.model_path.as_ref().map(|p| {
if p.starts_with("~") {
if let Some(home) = dirs_home() {
home.join(p.strip_prefix("~").unwrap_or(p))
} else {
p.clone()
}
} else {
p.clone()
}
})
}
pub fn resolved_index_path(&self) -> PathBuf {
self.index_path.clone().unwrap_or_else(|| {
dirs_home()
.unwrap_or_else(|| PathBuf::from("."))
.join(".spool")
.join("embedding-index.bin")
})
}
}
fn default_embedding_enabled() -> bool {
true
}
fn default_auto_index() -> bool {
true
}
fn dirs_home() -> Option<PathBuf> {
crate::support::home_dir()
}