feat: AI 模型支持 On-Demand Resources 按需下载

- 新增 ODRManager 管理模型资源下载
- EditorView 添加下载进度 UI
- Package.swift 移除内嵌模型资源
- 减小应用包体积约 64MB

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-03 22:23:59 +08:00
parent 6e60bea509
commit 64e962b6a4
3 changed files with 287 additions and 5 deletions

View File

@@ -37,6 +37,9 @@ struct EditorView: View {
// AI
@State private var aiEnhanceEnabled: Bool = false
@State private var aiModelNeedsDownload: Bool = false
@State private var aiModelDownloading: Bool = false
@State private var aiModelDownloadProgress: Double = 0
//
@State private var videoDiagnosis: VideoDiagnosis?
@@ -370,10 +373,45 @@ struct EditorView: View {
}
}
.tint(.purple)
.disabled(!AIEnhancer.isAvailable())
.disabled(!AIEnhancer.isAvailable() || aiModelDownloading)
.onChange(of: aiEnhanceEnabled) { _, newValue in
if newValue {
checkAndDownloadModel()
}
}
if aiEnhanceEnabled {
//
if aiModelDownloading {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 8) {
ProgressView()
.scaleEffect(0.8)
Text("正在下载 AI 模型...")
.font(.caption)
.foregroundStyle(.secondary)
}
ProgressView(value: aiModelDownloadProgress)
.tint(.purple)
Text(String(format: "%.0f%%", aiModelDownloadProgress * 100))
.font(.caption2)
.foregroundStyle(.secondary)
}
.padding(.leading, 4)
}
if aiEnhanceEnabled && !aiModelDownloading {
VStack(alignment: .leading, spacing: 6) {
if aiModelNeedsDownload {
HStack(spacing: 4) {
Image(systemName: "arrow.down.circle")
.foregroundStyle(.orange)
.font(.caption)
Text("首次使用需下载 AI 模型(约 64MB")
.font(.caption)
}
}
HStack(spacing: 4) {
Image(systemName: "sparkles")
.foregroundStyle(.purple)
@@ -415,6 +453,10 @@ struct EditorView: View {
.padding(16)
.background(Color.purple.opacity(0.1))
.clipShape(RoundedRectangle(cornerRadius: 12))
.task {
//
aiModelNeedsDownload = await AIEnhancer.needsDownload()
}
}
// MARK: -
@@ -681,6 +723,46 @@ struct EditorView: View {
return CropRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight)
}
private func checkAndDownloadModel() {
guard aiEnhanceEnabled else { return }
Task {
//
let needsDownload = await AIEnhancer.needsDownload()
await MainActor.run {
aiModelNeedsDownload = needsDownload
}
if needsDownload {
await MainActor.run {
aiModelDownloading = true
aiModelDownloadProgress = 0
}
do {
try await AIEnhancer.downloadModel { progress in
Task { @MainActor in
aiModelDownloadProgress = progress
}
}
await MainActor.run {
aiModelDownloading = false
aiModelNeedsDownload = false
}
} catch {
await MainActor.run {
aiModelDownloading = false
// AI
aiEnhanceEnabled = false
}
print("Failed to download AI model: \(error)")
}
}
}
}
private func startProcessing() {
Analytics.shared.log(.editorGenerateClick, parameters: [
"trimStart": trimStart,