Skip to main content

spool/config/
model.rs

1use crate::domain::OutputFormat;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct AppConfig {
7    pub vault: VaultConfig,
8    #[serde(default)]
9    pub output: OutputConfig,
10    #[serde(default)]
11    pub developer: DeveloperConfig,
12    #[serde(default)]
13    pub projects: Vec<ProjectConfig>,
14    #[serde(default)]
15    pub scenes: Vec<SceneConfig>,
16    #[serde(default)]
17    pub embedding: EmbeddingConfig,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, Default)]
21pub struct DeveloperConfig {
22    #[serde(default)]
23    pub note_roots: Vec<String>,
24}
25
26impl DeveloperConfig {
27    pub fn effective_note_roots(&self, project_note_roots: &[String]) -> Vec<String> {
28        let mut roots = self.note_roots.clone();
29        for root in project_note_roots {
30            if !roots.iter().any(|existing| existing == root) {
31                roots.push(root.clone());
32            }
33        }
34        roots
35    }
36}
37
38#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
39#[serde(rename_all = "snake_case")]
40pub enum WakeupProfileConfig {
41    Developer,
42    #[default]
43    Project,
44}
45
46impl From<WakeupProfileConfig> for crate::domain::WakeupProfile {
47    fn from(value: WakeupProfileConfig) -> Self {
48        match value {
49            WakeupProfileConfig::Developer => crate::domain::WakeupProfile::Developer,
50            WakeupProfileConfig::Project => crate::domain::WakeupProfile::Project,
51        }
52    }
53}
54
55impl From<crate::domain::WakeupProfile> for WakeupProfileConfig {
56    fn from(value: crate::domain::WakeupProfile) -> Self {
57        match value {
58            crate::domain::WakeupProfile::Developer => WakeupProfileConfig::Developer,
59            crate::domain::WakeupProfile::Project => WakeupProfileConfig::Project,
60        }
61    }
62}
63
64impl Default for AppConfig {
65    fn default() -> Self {
66        Self {
67            vault: VaultConfig {
68                root: PathBuf::new(),
69                limits: VaultLimits::default(),
70            },
71            output: OutputConfig::default(),
72            developer: DeveloperConfig::default(),
73            projects: Vec::new(),
74            scenes: Vec::new(),
75            embedding: EmbeddingConfig::default(),
76        }
77    }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct VaultConfig {
82    pub root: PathBuf,
83    #[serde(default)]
84    pub limits: VaultLimits,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
88#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
89pub struct VaultLimits {
90    #[serde(default = "default_max_files")]
91    pub max_files: usize,
92    #[serde(default = "default_max_file_bytes")]
93    pub max_file_bytes: u64,
94    #[serde(default = "default_max_total_bytes")]
95    pub max_total_bytes: u64,
96    #[serde(default = "default_max_depth")]
97    pub max_depth: usize,
98}
99
100impl Default for VaultLimits {
101    fn default() -> Self {
102        Self {
103            max_files: default_max_files(),
104            max_file_bytes: default_max_file_bytes(),
105            max_total_bytes: default_max_total_bytes(),
106            max_depth: default_max_depth(),
107        }
108    }
109}
110
111fn default_max_files() -> usize {
112    5_000
113}
114
115fn default_max_file_bytes() -> u64 {
116    512 * 1024
117}
118
119fn default_max_total_bytes() -> u64 {
120    16 * 1024 * 1024
121}
122
123fn default_max_depth() -> usize {
124    12
125}
126
127fn default_format() -> OutputFormat {
128    OutputFormat::Prompt
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct OutputConfig {
133    #[serde(default = "default_format")]
134    pub default_format: OutputFormat,
135    #[serde(default = "default_max_chars")]
136    pub max_chars: usize,
137    #[serde(default = "default_max_notes")]
138    pub max_notes: usize,
139    #[serde(default = "default_max_lifecycle")]
140    pub max_lifecycle: usize,
141}
142
143impl Default for OutputConfig {
144    fn default() -> Self {
145        Self {
146            default_format: default_format(),
147            max_chars: default_max_chars(),
148            max_notes: default_max_notes(),
149            max_lifecycle: default_max_lifecycle(),
150        }
151    }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct ProjectConfig {
156    pub id: String,
157    pub name: String,
158    #[serde(default)]
159    pub repo_paths: Vec<PathBuf>,
160    #[serde(default)]
161    pub note_roots: Vec<String>,
162    #[serde(default)]
163    pub default_tags: Vec<String>,
164    #[serde(default)]
165    pub modules: Vec<ModuleConfig>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct ModuleConfig {
170    pub id: String,
171    #[serde(default)]
172    pub path_prefixes: Vec<String>,
173    #[serde(default)]
174    pub keywords: Vec<String>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct SceneConfig {
179    pub id: String,
180    #[serde(default)]
181    pub keywords: Vec<String>,
182    #[serde(default)]
183    pub preferred_notes: Vec<String>,
184}
185
186fn default_max_chars() -> usize {
187    12_000
188}
189
190fn default_max_notes() -> usize {
191    8
192}
193
194fn default_max_lifecycle() -> usize {
195    5
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct EmbeddingConfig {
200    #[serde(default = "default_embedding_enabled")]
201    pub enabled: bool,
202    #[serde(default)]
203    pub model_id: Option<String>,
204    #[serde(default)]
205    pub model_path: Option<PathBuf>,
206    #[serde(default)]
207    pub index_path: Option<PathBuf>,
208    #[serde(default = "default_auto_index")]
209    pub auto_index: bool,
210}
211
212impl Default for EmbeddingConfig {
213    fn default() -> Self {
214        Self {
215            enabled: default_embedding_enabled(),
216            model_id: None,
217            model_path: None,
218            index_path: None,
219            auto_index: default_auto_index(),
220        }
221    }
222}
223
224impl EmbeddingConfig {
225    pub fn resolved_model_path(&self) -> Option<PathBuf> {
226        self.model_path.as_ref().map(|p| {
227            if p.starts_with("~") {
228                if let Some(home) = dirs_home() {
229                    home.join(p.strip_prefix("~").unwrap_or(p))
230                } else {
231                    p.clone()
232                }
233            } else {
234                p.clone()
235            }
236        })
237    }
238
239    pub fn resolved_index_path(&self) -> PathBuf {
240        self.index_path.clone().unwrap_or_else(|| {
241            dirs_home()
242                .unwrap_or_else(|| PathBuf::from("."))
243                .join(".spool")
244                .join("embedding-index.bin")
245        })
246    }
247}
248
249fn default_embedding_enabled() -> bool {
250    true
251}
252
253fn default_auto_index() -> bool {
254    true
255}
256
257fn dirs_home() -> Option<PathBuf> {
258    crate::support::home_dir()
259}