#[derive(Debug, Clone, Default)]
pub struct ModelZooIndex {
pub version: String,
pub updated_at: String,
pub models: Vec<ModelZooEntry>,
pub featured: Vec<String>,
pub categories: HashMap<ModelCategory, usize>,
pub tags: HashMap<String, usize>,
}
impl ModelZooIndex {
#[must_use]
pub fn new(version: impl Into<String>) -> Self {
Self {
version: version.into(),
updated_at: String::new(),
models: Vec::new(),
featured: Vec::new(),
categories: HashMap::new(),
tags: HashMap::new(),
}
}
pub fn add_model(&mut self, entry: ModelZooEntry) {
let category = entry.model_type.category();
*self.categories.entry(category).or_insert(0) += 1;
for tag in &entry.tags {
*self.tags.entry(tag.clone()).or_insert(0) += 1;
}
self.models.push(entry);
}
pub fn feature_model(&mut self, id: impl Into<String>) {
let id = id.into();
if self.models.iter().any(|m| m.id == id) && !self.featured.contains(&id) {
self.featured.push(id);
}
}
#[must_use]
pub fn get_model(&self, id: &str) -> Option<&ModelZooEntry> {
self.models.iter().find(|m| m.id == id)
}
#[must_use]
pub fn get_featured(&self) -> Vec<&ModelZooEntry> {
self.featured
.iter()
.filter_map(|id| self.get_model(id))
.collect()
}
#[must_use]
pub fn search(&self, query: &str) -> Vec<&ModelZooEntry> {
self.models
.iter()
.filter(|m| m.matches_query(query))
.collect()
}
#[must_use]
pub fn filter_by_tag(&self, tag: &str) -> Vec<&ModelZooEntry> {
self.models.iter().filter(|m| m.has_tag(tag)).collect()
}
#[must_use]
pub fn filter_by_category(&self, category: ModelCategory) -> Vec<&ModelZooEntry> {
self.models
.iter()
.filter(|m| m.model_type.category() == category)
.collect()
}
#[must_use]
pub fn filter_by_quality(&self, min_score: f32) -> Vec<&ModelZooEntry> {
self.models
.iter()
.filter(|m| m.quality_score >= min_score)
.collect()
}
#[must_use]
pub fn most_popular(&self, limit: usize) -> Vec<&ModelZooEntry> {
let mut models: Vec<_> = self.models.iter().collect();
models.sort_by(|a, b| b.downloads.cmp(&a.downloads));
models.into_iter().take(limit).collect()
}
#[must_use]
pub fn highest_quality(&self, limit: usize) -> Vec<&ModelZooEntry> {
let mut models: Vec<_> = self.models.iter().collect();
models.sort_by(|a, b| {
b.quality_score
.partial_cmp(&a.quality_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
models.into_iter().take(limit).collect()
}
#[must_use]
pub fn most_recent(&self, limit: usize) -> Vec<&ModelZooEntry> {
let mut models: Vec<_> = self.models.iter().collect();
models.sort_by(|a, b| b.created_at.cmp(&a.created_at));
models.into_iter().take(limit).collect()
}
#[must_use]
pub fn total_models(&self) -> usize {
self.models.len()
}
#[must_use]
pub fn all_tags(&self) -> Vec<&str> {
let mut tags: Vec<_> = self.tags.keys().map(String::as_str).collect();
tags.sort_unstable();
tags
}
#[must_use]
pub fn stats(&self) -> ZooStats {
let total_downloads: u64 = self.models.iter().map(|m| m.downloads).sum();
let total_size: u64 = self.models.iter().map(|m| m.size_bytes).sum();
let avg_quality = if self.models.is_empty() {
0.0
} else {
self.models.iter().map(|m| m.quality_score).sum::<f32>() / self.models.len() as f32
};
ZooStats {
total_models: self.models.len(),
total_downloads,
total_size_bytes: total_size,
avg_quality_score: avg_quality,
category_counts: self.categories.clone(),
tag_counts: self.tags.clone(),
}
}
}
impl fmt::Display for ModelZooIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Model Zoo Index v{}", self.version)?;
writeln!(f, "Total Models: {}", self.models.len())?;
writeln!(f, "Featured: {}", self.featured.len())?;
writeln!(
f,
"Categories: {:?}",
self.categories.keys().collect::<Vec<_>>()
)?;
writeln!(f, "Tags: {}", self.tags.len())?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ZooStats {
pub total_models: usize,
pub total_downloads: u64,
pub total_size_bytes: u64,
pub avg_quality_score: f32,
pub category_counts: HashMap<ModelCategory, usize>,
pub tag_counts: HashMap<String, usize>,
}
impl ZooStats {
#[must_use]
pub fn human_total_size(&self) -> String {
human_bytes(self.total_size_bytes)
}
}
fn human_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{bytes} B")
}
}
#[cfg(test)]
mod tests;