1#![allow(dead_code)]
8use crate::{
9 analytics::{AnalyticsManager, ExportFormat},
10 fine_tuning::{FineTuner, FineTuningConfig},
11 model_info::{ModelInfo, Version},
12 registry::{ModelRegistry, RegistryEntry, SearchQuery},
13};
14use clap::{Args, Parser, Subcommand, ValueEnum};
15use serde::{Deserialize, Serialize};
16use std::path::PathBuf;
17use std::time::Duration;
18use torsh_core::error::{Result, TorshError};
19
20#[derive(Parser)]
22#[command(name = "torsh-hub")]
23#[command(about = "A CLI tool for managing ToRSh machine learning models")]
24#[command(version = "0.1.0-alpha.2")]
25#[command(long_about = None)]
26pub struct Cli {
27 #[command(subcommand)]
28 pub command: Commands,
29
30 #[arg(short, long, global = true)]
32 pub config: Option<PathBuf>,
33
34 #[arg(short, long, global = true)]
36 pub verbose: bool,
37
38 #[arg(short, long, global = true, value_enum, default_value_t = OutputFormat::Table)]
40 pub output: OutputFormat,
41
42 #[arg(long, global = true)]
44 pub hub_url: Option<String>,
45
46 #[arg(long, global = true)]
48 pub token: Option<String>,
49}
50
51#[derive(Subcommand)]
52pub enum Commands {
53 #[command(subcommand)]
55 Model(ModelCommands),
56
57 Search(SearchArgs),
59
60 Download(DownloadArgs),
62
63 Upload(UploadArgs),
65
66 #[command(subcommand)]
68 FineTune(FineTuneCommands),
69
70 #[command(subcommand)]
72 Analytics(AnalyticsCommands),
73
74 #[command(subcommand)]
76 Config(ConfigCommands),
77
78 #[command(subcommand)]
80 Cache(CacheCommands),
81
82 #[command(subcommand)]
84 Auth(AuthCommands),
85
86 #[command(subcommand)]
88 Registry(RegistryCommands),
89}
90
91#[derive(Subcommand)]
92pub enum ModelCommands {
93 List(ListArgs),
95 Info(InfoArgs),
97 Load(LoadArgs),
99 Validate(ValidateArgs),
101 Convert(ConvertArgs),
103 Compare(CompareArgs),
105}
106
107#[derive(Subcommand)]
108pub enum FineTuneCommands {
109 Start(FineTuneStartArgs),
111 Resume(FineTuneResumeArgs),
113 Stop(FineTuneStopArgs),
115 List(FineTuneListArgs),
117 Status(FineTuneStatusArgs),
119}
120
121#[derive(Subcommand)]
122pub enum AnalyticsCommands {
123 Usage(UsageArgs),
125 Performance(PerformanceArgs),
127 Export(ExportArgs),
129 Dashboard,
131 Report(ReportArgs),
133}
134
135#[derive(Subcommand)]
136pub enum ConfigCommands {
137 Show,
139 Set(ConfigSetArgs),
141 Reset,
143 Validate,
145}
146
147#[derive(Subcommand)]
148pub enum CacheCommands {
149 List,
151 Clear(CacheClearArgs),
153 Stats,
155 Optimize,
157}
158
159#[derive(Subcommand)]
160pub enum AuthCommands {
161 Login(LoginArgs),
163 Logout,
165 Whoami,
167 Refresh,
169}
170
171#[derive(Subcommand)]
172pub enum RegistryCommands {
173 Add(RegistryAddArgs),
175 Remove(RegistryRemoveArgs),
177 Update(RegistryUpdateArgs),
179 Sync,
181}
182
183#[derive(Args)]
184pub struct SearchArgs {
185 pub query: String,
187
188 #[arg(short, long)]
190 pub category: Option<String>,
191
192 #[arg(short = 't', long)]
194 pub model_type: Option<String>,
195
196 #[arg(long)]
198 pub min_accuracy: Option<f32>,
199
200 #[arg(long)]
202 pub max_size: Option<u64>,
203
204 #[arg(short, long, value_enum, default_value_t = SortBy::Relevance)]
206 pub sort: SortBy,
207
208 #[arg(short, long, default_value_t = 10)]
210 pub limit: usize,
211}
212
213#[derive(Args)]
214pub struct DownloadArgs {
215 pub model: String,
217
218 #[arg(short, long)]
220 pub output: Option<PathBuf>,
221
222 #[arg(short, long)]
224 pub force: bool,
225
226 #[arg(short, long)]
228 pub revision: Option<String>,
229
230 #[arg(long)]
232 pub resume: bool,
233
234 #[arg(long, default_value_t = true)]
236 pub verify: bool,
237}
238
239#[derive(Args)]
240pub struct UploadArgs {
241 pub path: PathBuf,
243
244 #[arg(short, long)]
246 pub name: String,
247
248 #[arg(short, long)]
250 pub description: Option<String>,
251
252 #[arg(short, long)]
254 pub tags: Vec<String>,
255
256 #[arg(short, long)]
258 pub category: Option<String>,
259
260 #[arg(short, long)]
262 pub license: Option<String>,
263
264 #[arg(long)]
266 pub private: bool,
267
268 #[arg(long)]
270 pub skip_validation: bool,
271}
272
273#[derive(Args)]
274pub struct ListArgs {
275 #[arg(short, long)]
277 pub category: Option<String>,
278
279 #[arg(long)]
281 pub owned: bool,
282
283 #[arg(long)]
285 pub public: bool,
286
287 #[arg(short, long)]
289 pub detailed: bool,
290}
291
292#[derive(Args)]
293pub struct InfoArgs {
294 pub model: String,
296
297 #[arg(long)]
299 pub technical: bool,
300
301 #[arg(long)]
303 pub stats: bool,
304
305 #[arg(long)]
307 pub downloads: bool,
308}
309
310#[derive(Args)]
311pub struct LoadArgs {
312 pub model: String,
314
315 #[arg(short, long, value_enum, default_value_t = Device::Auto)]
317 pub device: Device,
318
319 #[arg(short, long, value_enum)]
321 pub precision: Option<Precision>,
322
323 #[arg(short, long, value_enum, default_value_t = OptimizationLevel::Standard)]
325 pub optimization: OptimizationLevel,
326
327 #[arg(long)]
329 pub profile: bool,
330}
331
332#[derive(Args)]
333pub struct ValidateArgs {
334 pub model: String,
336
337 #[arg(short, long, value_enum, default_value_t = ValidationLevel::Standard)]
339 pub level: ValidationLevel,
340
341 #[arg(long)]
343 pub fix: bool,
344}
345
346#[derive(Args)]
347pub struct ConvertArgs {
348 pub input: PathBuf,
350
351 pub output: PathBuf,
353
354 #[arg(short, long, value_enum)]
356 pub format: ModelFormat,
357
358 #[arg(short, long)]
360 pub optimize: Vec<String>,
361}
362
363#[derive(Args)]
364pub struct CompareArgs {
365 pub model1: String,
367
368 pub model2: String,
370
371 #[arg(short, long)]
373 pub metrics: Vec<String>,
374
375 #[arg(short, long)]
377 pub dataset: Option<PathBuf>,
378}
379
380#[derive(Args)]
381pub struct FineTuneStartArgs {
382 pub model: String,
384
385 #[arg(short, long)]
387 pub dataset: PathBuf,
388
389 #[arg(short, long)]
391 pub validation: Option<PathBuf>,
392
393 #[arg(short, long, default_value_t = 1e-4)]
395 pub learning_rate: f32,
396
397 #[arg(short, long, default_value_t = 10)]
399 pub epochs: usize,
400
401 #[arg(short, long, default_value_t = 32)]
403 pub batch_size: usize,
404
405 #[arg(short, long, value_enum, default_value_t = Strategy::Full)]
407 pub strategy: Strategy,
408
409 #[arg(short, long)]
411 pub output: PathBuf,
412
413 #[arg(short, long)]
415 pub config_file: Option<PathBuf>,
416}
417
418#[derive(Args)]
419pub struct FineTuneResumeArgs {
420 pub job_or_checkpoint: String,
422}
423
424#[derive(Args)]
425pub struct FineTuneStopArgs {
426 pub job_id: String,
428
429 #[arg(long, default_value_t = true)]
431 pub save_checkpoint: bool,
432}
433
434#[derive(Args)]
435pub struct FineTuneListArgs {
436 #[arg(short, long)]
438 pub active: bool,
439
440 #[arg(short, long)]
442 pub detailed: bool,
443}
444
445#[derive(Args)]
446pub struct FineTuneStatusArgs {
447 pub job_id: String,
449
450 #[arg(short, long)]
452 pub follow: bool,
453}
454
455#[derive(Args)]
456pub struct UsageArgs {
457 pub model: Option<String>,
459
460 #[arg(short, long, value_enum, default_value_t = TimePeriod::LastWeek)]
462 pub period: TimePeriod,
463
464 #[arg(long)]
466 pub by_category: bool,
467}
468
469#[derive(Args)]
470pub struct PerformanceArgs {
471 pub model: Option<String>,
473
474 #[arg(short, long)]
476 pub detailed: bool,
477
478 #[arg(long)]
480 pub baseline: Option<String>,
481}
482
483#[derive(Args)]
484pub struct ExportArgs {
485 pub output: PathBuf,
487
488 #[arg(short, long, value_enum, default_value_t = ExportFormat::JSON)]
490 pub format: ExportFormat,
491
492 #[arg(long)]
494 pub start_date: Option<String>,
495
496 #[arg(long)]
498 pub end_date: Option<String>,
499
500 #[arg(long)]
502 pub include_users: bool,
503}
504
505#[derive(Args)]
506pub struct ReportArgs {
507 pub model: Option<String>,
509
510 #[arg(short, long, value_enum, default_value_t = ReportType::Summary)]
512 pub report_type: ReportType,
513
514 #[arg(short, long)]
516 pub output: Option<PathBuf>,
517}
518
519#[derive(Args)]
520pub struct ConfigSetArgs {
521 pub key: String,
523
524 pub value: String,
526}
527
528#[derive(Args)]
529pub struct CacheClearArgs {
530 pub model: Option<String>,
532
533 #[arg(long)]
535 pub older_than: Option<String>,
536
537 #[arg(long)]
539 pub all: bool,
540}
541
542#[derive(Args)]
543pub struct LoginArgs {
544 pub username: Option<String>,
546
547 #[arg(long)]
549 pub token: Option<String>,
550
551 #[arg(long)]
553 pub hub_url: Option<String>,
554}
555
556#[derive(Args)]
557pub struct RegistryAddArgs {
558 pub path: PathBuf,
560
561 #[arg(short, long)]
563 pub name: String,
564
565 #[arg(short, long)]
567 pub version: Option<String>,
568}
569
570#[derive(Args)]
571pub struct RegistryRemoveArgs {
572 pub name: String,
574
575 #[arg(short, long)]
577 pub version: Option<String>,
578}
579
580#[derive(Args)]
581pub struct RegistryUpdateArgs {
582 pub name: String,
584
585 #[arg(long)]
587 pub description: Option<String>,
588
589 #[arg(long)]
591 pub tags: Option<Vec<String>>,
592}
593
594#[derive(ValueEnum, Clone, Debug)]
595pub enum OutputFormat {
596 Table,
597 JSON,
598 YAML,
599 CSV,
600}
601
602#[derive(ValueEnum, Clone, Debug)]
603pub enum SortBy {
604 Relevance,
605 Name,
606 Date,
607 Downloads,
608 Size,
609 Accuracy,
610}
611
612#[derive(ValueEnum, Clone, Debug)]
613pub enum Device {
614 Auto,
615 CPU,
616 CUDA,
617 Metal,
618 WebGPU,
619}
620
621#[derive(ValueEnum, Clone, Debug)]
622pub enum Precision {
623 FP32,
624 FP16,
625 BF16,
626 INT8,
627 INT4,
628}
629
630#[derive(ValueEnum, Clone, Debug)]
631pub enum OptimizationLevel {
632 None,
633 Basic,
634 Standard,
635 Aggressive,
636}
637
638#[derive(ValueEnum, Clone, Debug)]
639pub enum ValidationLevel {
640 Basic,
641 Standard,
642 Strict,
643}
644
645#[derive(ValueEnum, Clone, Debug)]
646pub enum ModelFormat {
647 Torsh,
648 ONNX,
649 PyTorch,
650 TensorFlow,
651 TensorRT,
652}
653
654#[derive(ValueEnum, Clone, Debug)]
655pub enum Strategy {
656 Full,
657 Partial,
658 HeadOnly,
659 LoRA,
660 Adapter,
661}
662
663#[derive(ValueEnum, Clone, Debug)]
664pub enum TimePeriod {
665 LastHour,
666 LastDay,
667 LastWeek,
668 LastMonth,
669 LastYear,
670 All,
671}
672
673#[derive(ValueEnum, Clone, Debug)]
674pub enum ReportType {
675 Summary,
676 Detailed,
677 Performance,
678 Usage,
679 Trends,
680}
681
682pub struct CliApp {
684 pub config: CliConfig,
685 pub registry: ModelRegistry,
686 pub analytics: Option<AnalyticsManager>,
687}
688
689#[derive(Debug, Clone, Serialize, Deserialize)]
691pub struct CliConfig {
692 pub hub_url: String,
693 pub cache_dir: PathBuf,
694 pub download_timeout: Duration,
695 pub max_concurrent_downloads: usize,
696 pub default_device: String,
697 pub auth_token: Option<String>,
698 pub analytics_enabled: bool,
699}
700
701impl Default for CliConfig {
702 fn default() -> Self {
703 Self {
704 hub_url: "https://hub.torsh.dev".to_string(),
705 cache_dir: dirs::cache_dir().unwrap_or_default().join("torsh-hub"),
706 download_timeout: Duration::from_secs(300),
707 max_concurrent_downloads: 4,
708 default_device: "auto".to_string(),
709 auth_token: None,
710 analytics_enabled: true,
711 }
712 }
713}
714
715impl CliApp {
716 pub fn new(config_path: Option<PathBuf>) -> Result<Self> {
718 let config = if let Some(path) = config_path {
719 Self::load_config(&path)?
720 } else {
721 CliConfig::default()
722 };
723
724 let registry = ModelRegistry::new(config.cache_dir.clone())?;
725
726 let analytics = if config.analytics_enabled {
727 Some(AnalyticsManager::new(config.cache_dir.join("analytics"))?)
728 } else {
729 None
730 };
731
732 Ok(Self {
733 config,
734 registry,
735 analytics,
736 })
737 }
738
739 fn load_config(path: &PathBuf) -> Result<CliConfig> {
741 let content = std::fs::read_to_string(path)
742 .map_err(|e| torsh_core::error::TorshError::IoError(e.to_string()))?;
743
744 let config: CliConfig = toml::from_str(&content)
745 .map_err(|e| torsh_core::error::TorshError::SerializationError(e.to_string()))?;
746
747 Ok(config)
748 }
749
750 pub fn save_config(&self, path: &PathBuf) -> Result<()> {
752 let content = toml::to_string_pretty(&self.config)
753 .map_err(|e| torsh_core::error::TorshError::SerializationError(e.to_string()))?;
754
755 std::fs::write(path, content)
756 .map_err(|e| torsh_core::error::TorshError::IoError(e.to_string()))?;
757
758 Ok(())
759 }
760
761 pub async fn execute_search(&mut self, args: &SearchArgs) -> Result<()> {
763 let category = if let Some(cat) = &args.category {
764 Some(cat.parse::<crate::registry::ModelCategory>()?)
765 } else {
766 None
767 };
768
769 let query = SearchQuery {
770 text: Some(args.query.clone()),
771 category,
772 architecture: args.model_type.clone(),
773 min_accuracy: args.min_accuracy,
774 max_model_size_mb: args.max_size.map(|size| size as f32),
775 limit: args.limit,
776 ..Default::default()
777 };
778
779 let results = self.registry.search(&query);
780
781 self.display_search_results(&results, &args.sort)?;
782 Ok(())
783 }
784
785 pub async fn execute_download(&mut self, args: &DownloadArgs) -> Result<()> {
787 println!("Downloading model: {}", args.model);
788
789 if let Some(analytics) = &mut self.analytics {
790 let action = crate::analytics::UserAction {
791 action_type: crate::analytics::ActionType::ModelDownload,
792 timestamp: std::time::SystemTime::now(),
793 model_id: Some(args.model.clone()),
794 parameters: std::collections::HashMap::new(),
795 success: true,
796 error_message: None,
797 };
798 analytics.record_user_action("cli_user", action)?;
799 }
800
801 println!("Download completed successfully");
803 Ok(())
804 }
805
806 pub async fn execute_fine_tune_start(&mut self, args: &FineTuneStartArgs) -> Result<()> {
808 let config = if let Some(config_path) = &args.config_file {
809 self.load_fine_tune_config(config_path)?
810 } else {
811 match args.strategy {
812 Strategy::Full => {
813 let num_classes = self
814 .detect_num_classes(
815 &args.model,
816 &Some(args.dataset.to_string_lossy().to_string()),
817 )
818 .await
819 .unwrap_or(1000);
820 crate::fine_tuning::utils::image_classification_config(num_classes)
821 }
822 Strategy::HeadOnly => {
823 let num_classes = self
824 .detect_num_classes(
825 &args.model,
826 &Some(args.dataset.to_string_lossy().to_string()),
827 )
828 .await
829 .unwrap_or(1000);
830 crate::fine_tuning::utils::image_classification_config(num_classes)
831 }
832 Strategy::LoRA => {
833 let rank = self
834 .determine_lora_rank(&args.model, args.batch_size)
835 .await
836 .unwrap_or(64);
837 crate::fine_tuning::utils::lora_config(rank)
838 }
839 Strategy::Adapter => {
840 let num_classes = self
841 .detect_num_classes(
842 &args.model,
843 &Some(args.dataset.to_string_lossy().to_string()),
844 )
845 .await
846 .unwrap_or(1000);
847 let mut config =
848 crate::fine_tuning::utils::image_classification_config(num_classes);
849 self.configure_adapter_strategy(&mut config, &args.model)
850 .await
851 .unwrap_or(());
852 config
853 }
854 _ => FineTuningConfig::default(),
855 }
856 };
857
858 let model_info = ModelInfo::new(
860 args.model.clone(),
861 "user".to_string(),
862 Version::new(1, 0, 0),
863 );
864 let _trainer = FineTuner::new(config, model_info)?;
865
866 println!("Starting fine-tuning for model: {}", args.model);
867 println!("Fine-tuning configuration:");
868 println!(" Learning rate: {}", args.learning_rate);
869 println!(" Epochs: {}", args.epochs);
870 println!(" Batch size: {}", args.batch_size);
871 println!(" Strategy: {:?}", args.strategy);
872
873 Ok(())
875 }
876
877 pub async fn execute_analytics_usage(&self, args: &UsageArgs) -> Result<()> {
879 if let Some(analytics) = &self.analytics {
880 let report = analytics.generate_report(args.model.as_deref())?;
881 self.display_analytics_report(&report)?;
882 } else {
883 println!("Analytics not enabled");
884 }
885 Ok(())
886 }
887
888 fn display_search_results(&self, results: &[&RegistryEntry], _sort_by: &SortBy) -> Result<()> {
890 if results.is_empty() {
891 println!("No models found");
892 return Ok(());
893 }
894
895 println!("Found {} models:", results.len());
896 println!(
897 "{:<30} {:<15} {:<20} {:<10}",
898 "Name", "Category", "Author", "Downloads"
899 );
900 println!("{}", "-".repeat(80));
901
902 for model in results {
903 println!(
904 "{:<30} {:<15} {:<20} {:<10}",
905 model.name,
906 format!("{:?}", model.category),
907 model.author,
908 model.downloads
909 );
910 }
911
912 Ok(())
913 }
914
915 fn display_analytics_report(&self, report: &crate::analytics::AnalyticsReport) -> Result<()> {
917 println!(
918 "Analytics Report - {}",
919 chrono::DateTime::<chrono::Utc>::from(report.timestamp)
920 );
921
922 if let Some(model_id) = &report.model_id {
923 println!("Model: {}", model_id);
924 }
925
926 if let Some(usage_stats) = &report.usage_stats {
927 println!("\nUsage Statistics:");
928 println!(" Total loads: {}", usage_stats.total_loads);
929 println!(" Total inferences: {}", usage_stats.total_inferences);
930 println!(
931 " Average inference time: {:?}",
932 usage_stats.average_inference_time
933 );
934 println!(" Error rate: {:.2}%", usage_stats.error_rate * 100.0);
935 }
936
937 println!("\nReal-time Metrics:");
938 println!(
939 " Active models: {}",
940 report.real_time_metrics.active_models
941 );
942 println!(
943 " Active sessions: {}",
944 report.real_time_metrics.total_active_sessions
945 );
946 println!(
947 " Current memory usage: {} MB",
948 report.real_time_metrics.current_memory_usage / 1024 / 1024
949 );
950 println!(
951 " Requests per second: {:.2}",
952 report.real_time_metrics.requests_per_second
953 );
954
955 if !report.trending_models.is_empty() {
956 println!("\nTrending Models:");
957 for model in &report.trending_models {
958 println!(
959 " {}: {:.2} (growth: {:.1}%)",
960 model.model_id,
961 model.trend_score,
962 model.growth_rate * 100.0
963 );
964 }
965 }
966
967 Ok(())
968 }
969
970 fn load_fine_tune_config(&self, path: &PathBuf) -> Result<FineTuningConfig> {
972 let content = std::fs::read_to_string(path)
973 .map_err(|e| torsh_core::error::TorshError::IoError(e.to_string()))?;
974
975 let config: FineTuningConfig = toml::from_str(&content)
976 .map_err(|e| torsh_core::error::TorshError::SerializationError(e.to_string()))?;
977
978 Ok(config)
979 }
980
981 async fn detect_num_classes(&self, model: &str, data_path: &Option<String>) -> Result<usize> {
983 if let Some(ref path) = data_path {
985 if let Ok(count) = self.count_class_directories(path) {
986 return Ok(count);
987 }
988 }
989
990 if let Ok(metadata) = self.get_model_metadata(model).await {
992 if let Some(classes) = metadata.get("num_classes") {
993 if let Some(count) = classes.as_u64() {
994 return Ok(count as usize);
995 }
996 }
997 }
998
999 Ok(match model {
1001 m if m.contains("imagenet") => 1000,
1002 m if m.contains("cifar10") => 10,
1003 m if m.contains("cifar100") => 100,
1004 m if m.contains("mnist") => 10,
1005 _ => 1000, })
1007 }
1008
1009 async fn determine_lora_rank(&self, model: &str, batch_size: usize) -> Result<usize> {
1011 let param_count = self
1013 .estimate_model_parameters(model)
1014 .await
1015 .unwrap_or(25_000_000); let base_rank = match param_count {
1019 n if n < 10_000_000 => 16, n if n < 100_000_000 => 64, n if n < 1_000_000_000 => 128, _ => 256, };
1024
1025 let adjusted_rank = match batch_size {
1027 n if n <= 8 => (base_rank as f32 * 1.5) as usize,
1028 n if n <= 16 => base_rank,
1029 n if n <= 32 => (base_rank as f32 * 0.8) as usize,
1030 _ => (base_rank as f32 * 0.6) as usize,
1031 };
1032
1033 let final_rank = adjusted_rank.next_power_of_two().min(512).max(8);
1035
1036 Ok(final_rank)
1037 }
1038
1039 async fn configure_adapter_strategy(
1041 &self,
1042 _config: &mut FineTuningConfig,
1043 model: &str,
1044 ) -> Result<()> {
1045 let adapter_config = if model.contains("bert") || model.contains("transformer") {
1047 serde_json::json!({
1049 "adapter_layers": ["attention", "ffn"],
1050 "adapter_dim": 64,
1051 "adapter_alpha": 16,
1052 "adapter_dropout": 0.1
1053 })
1054 } else if model.contains("resnet") || model.contains("efficientnet") {
1055 serde_json::json!({
1057 "adapter_layers": ["conv_blocks"],
1058 "adapter_dim": 32,
1059 "adapter_alpha": 8,
1060 "adapter_dropout": 0.05
1061 })
1062 } else {
1063 serde_json::json!({
1065 "adapter_layers": ["all"],
1066 "adapter_dim": 64,
1067 "adapter_alpha": 16,
1068 "adapter_dropout": 0.1
1069 })
1070 };
1071
1072 println!("Adapter configuration: {}", adapter_config);
1075
1076 Ok(())
1077 }
1078
1079 fn count_class_directories(&self, path: &str) -> Result<usize> {
1081 use std::fs;
1082
1083 let path = std::path::Path::new(path);
1084 if !path.exists() {
1085 return Err(TorshError::IoError(format!(
1086 "Data path does not exist: {}",
1087 path.display()
1088 )));
1089 }
1090
1091 let train_path = path.join("train");
1093 let classes_path = if train_path.exists() {
1094 train_path
1095 } else {
1096 path.to_path_buf()
1097 };
1098
1099 let mut class_count = 0;
1100 if let Ok(entries) = fs::read_dir(classes_path) {
1101 for entry in entries.flatten() {
1102 if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
1103 class_count += 1;
1104 }
1105 }
1106 }
1107
1108 if class_count == 0 {
1109 return Err(TorshError::Other(
1110 "No class directories found in dataset path".to_string(),
1111 ));
1112 }
1113
1114 Ok(class_count)
1115 }
1116
1117 async fn get_model_metadata(&self, model: &str) -> Result<serde_json::Value> {
1119 let _registry = crate::registry::ModelRegistry::new("./torsh_registry");
1121
1122 let metadata = serde_json::json!({
1124 "name": model,
1125 "architecture": self.infer_architecture(model),
1126 "num_classes": self.infer_default_classes(model)
1127 });
1128
1129 Ok(metadata)
1130 }
1131
1132 async fn estimate_model_parameters(&self, model: &str) -> Result<usize> {
1134 let param_count = match model {
1136 m if m.contains("resnet18") => 11_689_512,
1137 m if m.contains("resnet50") => 25_557_032,
1138 m if m.contains("resnet101") => 44_549_160,
1139 m if m.contains("efficientnet-b0") => 5_288_548,
1140 m if m.contains("efficientnet-b1") => 7_794_184,
1141 m if m.contains("efficientnet-b2") => 9_109_994,
1142 m if m.contains("bert-base") => 110_000_000,
1143 m if m.contains("bert-large") => 340_000_000,
1144 m if m.contains("gpt2") => 117_000_000,
1145 m if m.contains("t5-small") => 60_000_000,
1146 m if m.contains("t5-base") => 220_000_000,
1147 _ => 25_000_000, };
1149
1150 Ok(param_count)
1151 }
1152
1153 fn infer_architecture(&self, model: &str) -> &str {
1155 if model.contains("resnet") {
1156 "resnet"
1157 } else if model.contains("efficientnet") {
1158 "efficientnet"
1159 } else if model.contains("bert") {
1160 "bert"
1161 } else if model.contains("gpt") {
1162 "gpt"
1163 } else if model.contains("t5") {
1164 "t5"
1165 } else {
1166 "unknown"
1167 }
1168 }
1169
1170 fn infer_default_classes(&self, model: &str) -> usize {
1172 if model.contains("imagenet") {
1173 1000
1174 } else if model.contains("cifar10") {
1175 10
1176 } else if model.contains("cifar100") {
1177 100
1178 } else if model.contains("mnist") {
1179 10
1180 } else {
1181 1000 }
1183 }
1184}
1185
1186pub async fn run_cli(cli: Cli) -> Result<()> {
1188 let mut app = CliApp::new(cli.config)?;
1189
1190 match cli.command {
1191 Commands::Search(args) => {
1192 app.execute_search(&args).await?;
1193 }
1194 Commands::Download(args) => {
1195 app.execute_download(&args).await?;
1196 }
1197 Commands::FineTune(FineTuneCommands::Start(args)) => {
1198 app.execute_fine_tune_start(&args).await?;
1199 }
1200 Commands::Analytics(AnalyticsCommands::Usage(args)) => {
1201 app.execute_analytics_usage(&args).await?;
1202 }
1203 Commands::Model(ModelCommands::List(_args)) => {
1204 println!("Listing models...");
1205 }
1207 Commands::Model(ModelCommands::Info(args)) => {
1208 println!("Model info for: {}", args.model);
1209 }
1211 Commands::Config(ConfigCommands::Show) => {
1212 println!("Current configuration:");
1213 println!("Hub URL: {}", app.config.hub_url);
1214 println!("Cache directory: {}", app.config.cache_dir.display());
1215 println!("Default device: {}", app.config.default_device);
1216 println!("Analytics enabled: {}", app.config.analytics_enabled);
1217 }
1218 Commands::Auth(AuthCommands::Login(args)) => {
1219 println!("Logging in...");
1220 if let Some(token) = args.token {
1221 app.config.auth_token = Some(token);
1222 println!("Token authentication successful");
1223 } else {
1224 println!("Interactive login not implemented yet");
1225 }
1226 }
1227 _ => {
1228 println!("Command not implemented yet");
1229 }
1230 }
1231
1232 Ok(())
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238
1239 #[test]
1240 fn test_cli_config_default() {
1241 let config = CliConfig::default();
1242 assert_eq!(config.hub_url, "https://hub.torsh.dev");
1243 assert_eq!(config.max_concurrent_downloads, 4);
1244 assert_eq!(config.default_device, "auto");
1245 }
1246
1247 #[test]
1248 fn test_cli_app_creation() {
1249 let result = CliApp::new(None);
1250 assert!(result.is_ok());
1251 }
1252}