use crate::model_package::{ModelPackage, ModelTask};
use latexsnipper_foundation::{Result, SnipperError};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelManifest {
pub id: String,
pub task: ModelTask,
pub version: String,
pub adapter: String,
pub input: ManifestTensorSpec,
pub output: Vec<ManifestTensorSpec>,
pub files: ModelFiles,
#[serde(default)]
pub preprocessing: Option<ManifestPreprocessing>,
#[serde(default)]
pub decoding: Option<ManifestDecoding>,
#[serde(default)]
pub checksums: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestTensorSpec {
pub name: String,
pub shape: Vec<i64>,
pub dtype: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ModelFiles {
#[serde(default)]
pub primary: Option<String>,
#[serde(default)]
pub encoder: Option<String>,
#[serde(default)]
pub decoder: Option<String>,
#[serde(default)]
pub tokenizer: Option<String>,
#[serde(default)]
pub config: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestPreprocessing {
#[serde(default)]
pub resize: Option<ManifestResize>,
#[serde(default)]
pub mean: Option<Vec<f32>>,
#[serde(default)]
pub std: Option<Vec<f32>>,
#[serde(default)]
pub color_format: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestResize {
#[serde(default)]
pub width: Option<u32>,
#[serde(default)]
pub height: Option<u32>,
#[serde(default)]
pub keep_ratio: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManifestDecoding {
#[serde(rename = "type")]
pub decoding_type: String,
#[serde(default)]
pub beam_width: Option<usize>,
#[serde(default)]
pub blank_id: Option<usize>,
#[serde(default)]
pub output_layout: Option<String>,
#[serde(default)]
pub logits_kind: Option<String>,
#[serde(default)]
pub temperature: Option<f32>,
}
use serde::{Deserialize, Serialize};
pub type AdapterFactory =
Box<dyn Fn(&ModelManifest, &Path) -> Result<Box<dyn ModelPackage>> + Send + Sync>;
pub struct ModelRegistry {
models: HashMap<String, ModelEntry>,
dirs: Vec<PathBuf>,
adapter_factories: HashMap<String, AdapterFactory>,
}
struct ModelEntry {
manifest: ModelManifest,
dir: PathBuf,
}
impl ModelRegistry {
pub fn new() -> Self {
Self {
models: HashMap::new(),
dirs: Vec::new(),
adapter_factories: HashMap::new(),
}
}
pub fn register_adapter(
&mut self,
adapter_name: impl Into<String>,
factory: impl Fn(&ModelManifest, &Path) -> Result<Box<dyn ModelPackage>> + Send + Sync + 'static,
) {
self.adapter_factories
.insert(adapter_name.into(), Box::new(factory));
}
pub fn create_package(
&self,
manifest: &ModelManifest,
model_dir: &Path,
) -> Result<Option<Box<dyn ModelPackage>>> {
let factory = match self.adapter_factories.get(&manifest.adapter) {
Some(f) => f,
None => return Ok(None),
};
let package = factory(manifest, model_dir)?;
Ok(Some(package))
}
pub fn registered_adapters(&self) -> Vec<&str> {
self.adapter_factories.keys().map(|s| s.as_str()).collect()
}
pub fn from_dir(path: impl AsRef<Path>) -> Result<Self> {
let mut registry = Self::new();
registry.register_dir(path)?;
Ok(registry)
}
pub fn register_dir(&mut self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref().to_path_buf();
if !path.is_dir() {
return Err(SnipperError::Model(format!(
"Not a directory: {}",
path.display()
)));
}
self.dirs.push(path.clone());
for entry in std::fs::read_dir(&path)
.map_err(|e| SnipperError::Model(format!("Failed to read {}: {}", path.display(), e)))?
{
let entry =
entry.map_err(|e| SnipperError::Model(format!("Failed to read entry: {}", e)))?;
if !entry.path().is_dir() {
continue;
}
let model_dir = entry.path();
let manifest_path = model_dir.join("manifest.toml");
if manifest_path.exists() {
if let Ok(manifest) = Self::load_manifest(&manifest_path) {
let id = manifest.id.clone();
self.models.insert(
id,
ModelEntry {
manifest,
dir: model_dir,
},
);
}
}
}
Ok(())
}
fn load_manifest(path: &Path) -> Result<ModelManifest> {
let content = std::fs::read_to_string(path).map_err(|e| {
SnipperError::Model(format!("Failed to read {}: {}", path.display(), e))
})?;
toml::from_str(&content)
.map_err(|e| SnipperError::Model(format!("Failed to parse {}: {}", path.display(), e)))
}
pub fn get(&self, id: &str) -> Option<&ModelManifest> {
self.models.get(id).map(|e| &e.manifest)
}
pub fn get_dir(&self, id: &str) -> Option<&Path> {
self.models.get(id).map(|e| e.dir.as_path())
}
pub fn find_by_task(&self, task: ModelTask) -> Vec<(&ModelManifest, &Path)> {
self.models
.iter()
.filter(|(_, e)| e.manifest.task == task)
.map(|(_, e)| (&e.manifest, e.dir.as_path()))
.collect()
}
pub fn list_ids(&self) -> Vec<&str> {
self.models.keys().map(|s| s.as_str()).collect()
}
pub fn has(&self, id: &str) -> bool {
self.models.contains_key(id)
}
}
impl Default for ModelRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn register_builtin_adapters(_registry: &mut ModelRegistry) {
}