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}