Files
suixinkan_uikit/suixinkan/Features/TravelAlbum/ViewModels/TravelAlbumAIJobViewModels.swift

145 lines
5.1 KiB
Swift

import Foundation
/// AI 修图任务列表状态,负责筛选、游标分页、去重和静默刷新。
final class TravelAlbumAIJobListViewModel {
private(set) var items: [TravelAlbumAIJobSummary] = []
private(set) var selectedFilter: TravelAlbumAIJobFilter = .all
private(set) var isLoading = false
private(set) var isRefreshing = false
private(set) var isLoadingMore = false
private(set) var errorMessage: String?
private var nextCursor: String?
private var hasMore = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var containsInProgressJobs: Bool { items.contains { $0.status.isInProgress } }
var canLoadMore: Bool { hasMore && !isLoadingMore }
/// 选择筛选项并重新加载第一页。
func selectFilter(_ filter: TravelAlbumAIJobFilter, api: any TravelAlbumServing) async {
guard selectedFilter != filter else { return }
selectedFilter = filter
items = []
notify()
await loadFirstPage(api: api)
}
/// 首次加载或下拉刷新第一页。
func loadFirstPage(api: any TravelAlbumServing, refreshing: Bool = false, silent: Bool = false) async {
guard !isLoading && !isRefreshing else { return }
if refreshing { isRefreshing = true } else if !silent { isLoading = true }
errorMessage = nil
notify()
defer {
isLoading = false
isRefreshing = false
notify()
}
await request(cursor: nil, reset: true, api: api, allowCursorRecovery: false, silent: silent)
}
/// 加载下一页。
func loadMore(api: any TravelAlbumServing) async {
guard canLoadMore, let cursor = nextCursor else { return }
isLoadingMore = true
notify()
defer { isLoadingMore = false; notify() }
await request(cursor: cursor, reset: false, api: api, allowCursorRecovery: true, silent: false)
}
private func request(
cursor: String?,
reset: Bool,
api: any TravelAlbumServing,
allowCursorRecovery: Bool,
silent: Bool
) async {
do {
let response = try await api.aiRetouchJobList(
statusGroup: selectedFilter,
limit: 20,
cursor: cursor
)
if reset {
items = response.items
} else {
var known = Set(items.map(\.id))
items += response.items.filter { known.insert($0.id).inserted }
}
nextCursor = response.nextCursor
hasMore = response.hasMore && response.nextCursor?.isEmpty == false
errorMessage = nil
} catch is CancellationError {
return
} catch {
let message = error.localizedDescription
if allowCursorRecovery && message.localizedCaseInsensitiveContains("cursor") {
nextCursor = nil
hasMore = false
await request(cursor: nil, reset: true, api: api, allowCursorRecovery: false, silent: silent)
return
}
if items.isEmpty { errorMessage = message.isEmpty ? "任务加载失败" : message }
if !silent { onShowMessage?(message.isEmpty ? "任务加载失败" : message) }
}
}
private func notify() { onStateChange?() }
}
/// AI 修图任务详情状态,负责刷新、404 识别和终态判断。
final class TravelAlbumAIJobDetailViewModel {
private(set) var detail: TravelAlbumAIJobDetail?
private(set) var isLoading = false
private(set) var isRefreshing = false
private(set) var errorMessage: String?
private(set) var isNotFound = false
let batchId: Int
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onNotFound: (() -> Void)?
init(batchId: Int) { self.batchId = batchId }
var shouldPoll: Bool { detail?.status.isInProgress == true }
/// 拉取任务详情;静默刷新失败时保留现有内容。
func load(api: any TravelAlbumServing, refreshing: Bool = false, silent: Bool = false) async {
guard batchId > 0, !isLoading && !isRefreshing else {
if batchId <= 0 { markNotFound() }
return
}
if refreshing { isRefreshing = true } else if detail == nil && !silent { isLoading = true }
errorMessage = nil
notify()
defer {
isLoading = false
isRefreshing = false
notify()
}
do {
detail = try await api.aiRetouchJobInfo(batchId: batchId)
isNotFound = false
} catch is CancellationError {
return
} catch APIError.httpStatus(let status, _) where status == 404 {
markNotFound()
} catch {
let message = error.localizedDescription.isEmpty ? "任务详情加载失败" : error.localizedDescription
if detail == nil { errorMessage = message }
if !silent { onShowMessage?(message) }
}
}
private func markNotFound() {
guard !isNotFound else { return }
isNotFound = true
onNotFound?()
}
private func notify() { onStateChange?() }
}