1use serde::{Deserialize, Serialize};
8use std::fmt;
9use uuid::Uuid;
10
11use super::types::DownloadId;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(tag = "kind", rename_all = "snake_case")]
28pub enum CompletionKey {
29 HfFile {
31 repo_id: String,
33 revision: String,
37 filename_canon: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
43 quantization: Option<String>,
44 },
45
46 UrlFile {
48 url: String,
50 filename: String,
52 },
53
54 LocalFile {
56 path: String,
58 },
59}
60
61impl fmt::Display for CompletionKey {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 Self::HfFile {
65 repo_id,
66 quantization,
67 ..
68 } => {
69 if let Some(quant) = quantization {
70 write!(f, "{repo_id} ({quant})")
71 } else {
72 write!(f, "{repo_id}")
73 }
74 }
75 Self::UrlFile { filename, .. } => write!(f, "{filename}"),
76 Self::LocalFile { path } => {
77 if let Some(name) = path.rsplit('/').next() {
79 write!(f, "{name}")
80 } else {
81 write!(f, "{path}")
82 }
83 }
84 }
85 }
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum CompletionKind {
92 Downloaded,
94 Failed,
96 Cancelled,
98 AlreadyPresent,
100}
101
102#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
104pub struct AttemptCounts {
105 pub downloaded: u32,
107 pub failed: u32,
109 pub cancelled: u32,
111}
112
113impl AttemptCounts {
114 #[must_use]
116 pub const fn from_kind(kind: CompletionKind) -> Self {
117 match kind {
118 CompletionKind::Downloaded => Self {
119 downloaded: 1,
120 failed: 0,
121 cancelled: 0,
122 },
123 CompletionKind::Failed => Self {
124 downloaded: 0,
125 failed: 1,
126 cancelled: 0,
127 },
128 CompletionKind::Cancelled => Self {
129 downloaded: 0,
130 failed: 0,
131 cancelled: 1,
132 },
133 CompletionKind::AlreadyPresent => Self {
134 downloaded: 0,
135 failed: 0,
136 cancelled: 0,
137 },
138 }
139 }
140
141 pub const fn increment(&mut self, kind: CompletionKind) {
143 match kind {
144 CompletionKind::Downloaded => self.downloaded += 1,
145 CompletionKind::Failed => self.failed += 1,
146 CompletionKind::Cancelled => self.cancelled += 1,
147 CompletionKind::AlreadyPresent => {
148 }
151 }
152 }
153
154 #[must_use]
156 pub const fn total(&self) -> u32 {
157 self.downloaded + self.failed + self.cancelled
158 }
159
160 #[must_use]
162 pub const fn has_retries(&self) -> bool {
163 self.total() > 1
164 }
165}
166
167#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
169pub struct CompletionDetail {
170 pub key: CompletionKey,
172 pub display_name: String,
174 pub last_result: CompletionKind,
176 pub last_completed_at_ms: u64,
178 pub download_ids: Vec<DownloadId>,
181 pub attempt_counts: AttemptCounts,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
190pub struct QueueRunSummary {
191 pub run_id: Uuid,
193 pub started_at_ms: u64,
195 pub completed_at_ms: u64,
197
198 pub total_attempts_downloaded: u32,
201 pub total_attempts_failed: u32,
203 pub total_attempts_cancelled: u32,
205
206 pub unique_models_downloaded: u32,
209 pub unique_models_failed: u32,
211 pub unique_models_cancelled: u32,
213
214 pub truncated: bool,
216
217 pub items: Vec<CompletionDetail>,
220}
221
222impl QueueRunSummary {
223 #[must_use]
225 pub const fn total_unique_models(&self) -> u32 {
226 self.unique_models_downloaded + self.unique_models_failed + self.unique_models_cancelled
227 }
228
229 #[must_use]
231 pub const fn total_attempts(&self) -> u32 {
232 self.total_attempts_downloaded + self.total_attempts_failed + self.total_attempts_cancelled
233 }
234
235 #[must_use]
237 pub fn has_retries(&self) -> bool {
238 self.items
239 .iter()
240 .any(|item| item.attempt_counts.has_retries())
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_completion_key_display() {
250 let key = CompletionKey::HfFile {
251 repo_id: "unsloth/Llama-3-GGUF".to_string(),
252 revision: "main".to_string(),
253 filename_canon: "model.gguf".to_string(),
254 quantization: Some("Q4_K_M".to_string()),
255 };
256 assert_eq!(key.to_string(), "unsloth/Llama-3-GGUF (Q4_K_M)");
257
258 let key_no_quant = CompletionKey::HfFile {
259 repo_id: "unsloth/Llama-3-GGUF".to_string(),
260 revision: "main".to_string(),
261 filename_canon: "model.gguf".to_string(),
262 quantization: None,
263 };
264 assert_eq!(key_no_quant.to_string(), "unsloth/Llama-3-GGUF");
265 }
266
267 #[test]
268 fn test_attempt_counts() {
269 let mut counts = AttemptCounts::from_kind(CompletionKind::Downloaded);
270 assert_eq!(counts.downloaded, 1);
271 assert_eq!(counts.total(), 1);
272 assert!(!counts.has_retries());
273
274 counts.increment(CompletionKind::Failed);
275 assert_eq!(counts.failed, 1);
276 assert_eq!(counts.total(), 2);
277 assert!(counts.has_retries());
278
279 counts.increment(CompletionKind::Downloaded);
280 assert_eq!(counts.downloaded, 2);
281 assert_eq!(counts.total(), 3);
282 }
283
284 #[test]
285 fn test_queue_run_summary_totals() {
286 let summary = QueueRunSummary {
287 run_id: Uuid::nil(),
288 started_at_ms: 0,
289 completed_at_ms: 100_000,
290 total_attempts_downloaded: 5,
291 total_attempts_failed: 1,
292 total_attempts_cancelled: 0,
293 unique_models_downloaded: 3,
294 unique_models_failed: 1,
295 unique_models_cancelled: 0,
296 items: vec![],
297 truncated: false,
298 };
299
300 assert_eq!(summary.total_attempts(), 6);
301 assert_eq!(summary.total_unique_models(), 4);
302 }
303}