Model Management

Download models on-demand, track progress, check availability, and manage storage with KuzcoModelManager.

Checking Model Availability

Check if a model is already downloaded before use:

import Kuzco
let manager = KuzcoModelManager.shared
// Check single model
let isAvailable = await manager.isModelAvailable(.qwen3_4b)
if isAvailable {
print("Model ready to use")
} else {
print("Model needs to be downloaded")
}
// Check multiple models
let models: [KuzcoModel] = [.qwen3_4b, .qwen3_8b, .stableDiffusion21]
for model in models {
let available = await manager.isModelAvailable(model)
print("\(model): \(available ? "Ready" : "Not downloaded")")
}

Downloading Models

Download models with progress tracking:

ModelDownloader.swift
import SwiftUI
import Kuzco
struct ModelDownloadView: View {
@State private var progress: Double = 0
@State private var isDownloading = false
@State private var downloadComplete = false
@State private var errorMessage: String?
var body: some View {
VStack(spacing: 20) {
Text("Qwen3 4B")
.font(.headline)
if isDownloading {
VStack {
ProgressView(value: progress)
.progressViewStyle(.linear)
Text("\(Int(progress * 100))% - Downloading...")
.font(.caption)
.foregroundColor(.secondary)
}
} else if downloadComplete {
Label("Download Complete", systemImage: "checkmark.circle.fill")
.foregroundColor(.green)
} else if let error = errorMessage {
Text(error)
.foregroundColor(.red)
}
Button(isDownloading ? "Downloading..." : "Download Model") {
downloadModel()
}
.disabled(isDownloading || downloadComplete)
}
.padding()
}
func downloadModel() {
isDownloading = true
errorMessage = nil
Task {
do {
for try await update in KuzcoModelManager.shared.downloadModel(.qwen3_4b) {
progress = update.progress
if update.isComplete {
downloadComplete = true
}
}
} catch {
errorMessage = error.localizedDescription
}
isDownloading = false
}
}
}

Download Progress Structure

The download stream provides DownloadProgress updates:

for try await progress in manager.downloadModel(.qwen3_4b) {
// Progress from 0.0 to 1.0
print("Progress: \(Int(progress.progress * 100))%")
// Bytes downloaded and total
print("Downloaded: \(progress.bytesDownloaded) / \(progress.totalBytes)")
// Download speed (bytes per second)
if let speed = progress.bytesPerSecond {
print("Speed: \(speed / 1024 / 1024) MB/s")
}
// Estimated time remaining
if let eta = progress.estimatedTimeRemaining {
print("ETA: \(Int(eta)) seconds")
}
// Check completion
if progress.isComplete {
print("Download finished!")
}
}

Download All Required Models

Download multiple models for your app's features:

func downloadRequiredModels() async throws {
let requiredModels: [KuzcoModel] = [
.qwen3_4b, // For chat
.qwen3VL, // For vision
.stableDiffusion21 // For image generation
]
for model in requiredModels {
let available = await KuzcoModelManager.shared.isModelAvailable(model)
if !available {
print("Downloading \(model)...")
for try await progress in KuzcoModelManager.shared.downloadModel(model) {
print("\(model): \(Int(progress.progress * 100))%")
}
print("\(model) downloaded!")
}
}
print("All models ready!")
}

Cancel Downloads

Cancel ongoing downloads using task cancellation:

class DownloadManager: ObservableObject {
@Published var progress: Double = 0
@Published var isDownloading = false
private var downloadTask: Task<Void, Never>?
func startDownload(model: KuzcoModel) {
isDownloading = true
downloadTask = Task {
do {
for try await update in KuzcoModelManager.shared.downloadModel(model) {
await MainActor.run {
progress = update.progress
}
}
} catch is CancellationError {
print("Download cancelled")
} catch {
print("Error: \(error)")
}
await MainActor.run {
isDownloading = false
}
}
}
func cancelDownload() {
downloadTask?.cancel()
}
}

Deleting Models

Remove downloaded models to free up storage:

let manager = KuzcoModelManager.shared
// Delete a specific model
try await manager.deleteModel(.qwen3_8b)
// Delete multiple models
let modelsToDelete: [KuzcoModel] = [.qwen3_8b, .stableDiffusion21]
for model in modelsToDelete {
try await manager.deleteModel(model)
print("Deleted \(model)")
}
// Delete all downloaded models
try await manager.deleteAllModels()

Storage Information

Check storage usage for downloaded models:

let manager = KuzcoModelManager.shared
// Get total storage used by all models
let totalBytes = await manager.totalStorageUsed()
let totalGB = Double(totalBytes) / 1024 / 1024 / 1024
print("Total storage: \(String(format: "%.2f", totalGB)) GB")
// Get storage for a specific model
if let modelSize = await manager.storageUsed(for: .qwen3_4b) {
let sizeGB = Double(modelSize) / 1024 / 1024 / 1024
print("Qwen3 4B: \(String(format: "%.2f", sizeGB)) GB")
}
// List all downloaded models with sizes
let downloadedModels = await manager.downloadedModels()
for model in downloadedModels {
if let size = await manager.storageUsed(for: model) {
print("\(model): \(size / 1024 / 1024) MB")
}
}

Storage Locations

Models are stored in the app's documents directory:

// Default storage location
let modelsDirectory = KuzcoModelManager.shared.modelsDirectory
print("Models stored at: \(modelsDirectory.path)")
// Get path for a specific model
let modelPath = await KuzcoModelManager.shared.modelPath(for: .qwen3_4b)
print("Qwen3 4B path: \(modelPath?.path ?? "Not downloaded")")

Storage Considerations

Models can be large (1-5 GB each). Consider implementing storage management UI so users can delete unused models. Models persist across app updates.

Background Downloads

Enable background downloads for large models:

// Enable background downloads (app continues downloading when backgrounded)
KuzcoModelManager.shared.enableBackgroundDownloads = true
// Handle background completion in your AppDelegate or SceneDelegate
func handleBackgroundDownloadCompletion(identifier: String) {
KuzcoModelManager.shared.handleBackgroundCompletion(identifier: identifier)
}

Model Download UI Pattern

A complete model management UI pattern:

ModelManager.swift
import SwiftUI
import Kuzco
struct ModelManagerView: View {
@StateObject private var viewModel = ModelManagerViewModel()
var body: some View {
List {
ForEach(KuzcoModel.allCases, id: \.self) { model in
ModelRow(
model: model,
status: viewModel.status(for: model),
progress: viewModel.progress(for: model),
onDownload: { viewModel.download(model) },
onDelete: { viewModel.delete(model) },
onCancel: { viewModel.cancel(model) }
)
}
}
.navigationTitle("Models")
.task {
await viewModel.checkAvailability()
}
}
}
@MainActor
class ModelManagerViewModel: ObservableObject {
@Published private var statuses: [KuzcoModel: ModelStatus] = [:]
@Published private var progresses: [KuzcoModel: Double] = [:]
private var downloadTasks: [KuzcoModel: Task<Void, Never>] = [:]
enum ModelStatus {
case notDownloaded
case downloading
case downloaded
}
func checkAvailability() async {
for model in KuzcoModel.allCases {
let available = await KuzcoModelManager.shared.isModelAvailable(model)
statuses[model] = available ? .downloaded : .notDownloaded
}
}
func download(_ model: KuzcoModel) {
statuses[model] = .downloading
progresses[model] = 0
downloadTasks[model] = Task {
do {
for try await progress in KuzcoModelManager.shared.downloadModel(model) {
progresses[model] = progress.progress
if progress.isComplete {
statuses[model] = .downloaded
}
}
} catch {
statuses[model] = .notDownloaded
}
}
}
func cancel(_ model: KuzcoModel) {
downloadTasks[model]?.cancel()
statuses[model] = .notDownloaded
}
func delete(_ model: KuzcoModel) {
Task {
try? await KuzcoModelManager.shared.deleteModel(model)
statuses[model] = .notDownloaded
}
}
func status(for model: KuzcoModel) -> ModelStatus {
statuses[model] ?? .notDownloaded
}
func progress(for model: KuzcoModel) -> Double {
progresses[model] ?? 0
}
}