Skip to main content

llm_manager/config/
model_config.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::config::ModelOverride;
7use crate::config::config_base_dir;
8use crate::config::store::{load_all_from_dir, move_to_unused, save_yaml};
9
10/// Directory for per-model YAML configs.
11pub fn models_config_dir() -> PathBuf {
12    config_base_dir().join("llm-manager").join("models")
13}
14
15/// Directory for unused (deleted) model configs.
16pub fn unused_config_dir() -> PathBuf {
17    config_base_dir().join("llm-manager").join("unused")
18}
19
20/// Convert a display_name (relative path) to a filesystem-safe key.
21/// Replaces path separators with "__" to create unique keys.
22/// E.g. "qwen/model-v2" → "qwen__model-v2"
23pub fn key_from_display(display_name: &str) -> String {
24    display_name.replace(std::path::MAIN_SEPARATOR, "__")
25}
26
27/// Convert a filesystem-safe key back to a display_name.
28/// Reverses the key_from_display transformation.
29/// E.g. "qwen__model-v2" → "qwen/model-v2"
30pub fn display_from_key(key: &str) -> String {
31    key.replace("__", std::path::MAIN_SEPARATOR_STR)
32}
33
34/// Per-model configuration store.
35///
36/// Each model config is a YAML file stored in `~/.config/llm-manager/models/`.
37/// Files are named `<key>.yaml` where `key` is derived from the model's
38/// display_name (path relative to its model directory) with path separators
39/// replaced by "__".
40///
41/// Example: model at "models/qwen/model-v2.gguf" has display_name "qwen/model-v2"
42/// and config file "qwen__model-v2.yaml".
43///
44/// Deleting a model moves its config file to `~/.config/llm-manager/unused/`
45/// instead of removing it, allowing recovery.
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ModelConfigStore {
48    models_dir: PathBuf,
49    unused_dir: PathBuf,
50    model_dirs: Vec<PathBuf>,
51    cache: HashMap<String, ModelOverride>,
52}
53
54impl ModelConfigStore {
55    pub fn new(model_dirs: Vec<PathBuf>) -> Self {
56        let models_dir = models_config_dir();
57        let unused_dir = unused_config_dir();
58        let cache = load_all_from_dir(&models_dir);
59
60        Self {
61            models_dir,
62            unused_dir,
63            model_dirs,
64            cache,
65        }
66    }
67
68    /// Get the config for a model by its display_name.
69    pub fn get(&self, display_name: &str) -> Option<&ModelOverride> {
70        let key = key_from_display(display_name);
71        self.cache.get(&key)
72    }
73
74    /// Save (or update) a model config by its display_name.
75    pub fn save(&mut self, display_name: &str, config: &ModelOverride) {
76        let key = key_from_display(display_name);
77        save_yaml(&key, config, &self.models_dir, &self.unused_dir);
78        self.cache.insert(key, config.clone());
79    }
80
81    /// Delete a model config by its display_name.
82    pub fn delete(&mut self, display_name: &str) {
83        let key = key_from_display(display_name);
84        move_to_unused(&key, &self.models_dir, &self.unused_dir);
85        self.cache.remove(&key);
86    }
87
88    /// Get all model config display names (keys transformed back to display form).
89    pub fn keys(&self) -> Vec<String> {
90        let mut keys: Vec<String> = self
91            .cache
92            .keys()
93            .map(|k| display_from_key(k))
94            .collect();
95        keys.sort();
96        keys
97    }
98}
99
100impl Default for ModelConfigStore {
101    fn default() -> Self {
102        Self::new(vec![])
103    }
104}