Files
projectbutler/ProjectButler/Sources/AppState 2.swift

158 lines
4.2 KiB
Swift

import SwiftUI
@MainActor
class AppState: ObservableObject {
// API endpoints
let memoryAPI = "http://localhost:3202/api/memories"
let portAPI = "http://localhost:3201/api/ports"
let portStatsAPI = "http://localhost:3201/api/ports/stats"
let asrStatusAPI = "http://localhost:4125/api/asr-status"
// Data
@Published var projects: [Project] = []
@Published var portStats: PortStats = PortStats()
@Published var asrStatus: ASRStatus = ASRStatus()
@Published var isLoading = false
@Published var lastRefresh: Date?
@Published var searchText = ""
// Services status
@Published var memoryOnline = false
@Published var portsOnline = false
@Published var asrOnline = false
private var refreshTask: Task<Void, Never>?
init() {
startAutoRefresh()
}
deinit {
refreshTask?.cancel()
}
private func startAutoRefresh() {
refreshTask = Task { @MainActor [weak self] in
guard let self = self else { return }
// Initial refresh
await self.refresh()
// Auto-refresh every 30 seconds
while !Task.isCancelled {
try? await Task.sleep(nanoseconds: 30_000_000_000)
if !Task.isCancelled {
await self.refresh()
}
}
}
}
func refresh() async {
isLoading = true
async let p: Void = fetchProjects()
async let s: Void = fetchPortStats()
async let a: Void = fetchASRStatus()
_ = await (p, s, a)
isLoading = false
lastRefresh = Date()
}
var filteredProjects: [Project] {
if searchText.isEmpty { return projects }
let q = searchText.lowercased()
return projects.filter {
$0.name.lowercased().contains(q) ||
$0.slug.lowercased().contains(q) ||
($0.what ?? "").lowercased().contains(q)
}
}
var activeProjects: [Project] {
projects.filter { $0.status == "active" || $0.status == "进行中" }
}
// MARK: - Fetch
private func fetchProjects() async {
guard let url = URL(string: memoryAPI) else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoded = try JSONDecoder().decode([Project].self, from: data)
projects = decoded
memoryOnline = true
} catch {
memoryOnline = false
}
}
private func fetchPortStats() async {
guard let url = URL(string: portStatsAPI) else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoded = try JSONDecoder().decode(PortStats.self, from: data)
portStats = decoded
portsOnline = true
} catch {
portsOnline = false
}
}
private func fetchASRStatus() async {
guard let url = URL(string: asrStatusAPI) else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoded = try JSONDecoder().decode(ASRStatus.self, from: data)
asrStatus = decoded
asrOnline = true
} catch {
asrOnline = false
}
}
}
// MARK: - Models
struct Project: Codable, Identifiable {
var id: String { slug }
let slug: String
let name: String
let path: String?
let status: String?
let category: String?
let what: String?
let updatedAt: String?
let eventCount: Int?
enum CodingKeys: String, CodingKey {
case slug, name, path, status, category, what
case updatedAt = "updated_at"
case eventCount = "event_count"
}
}
struct PortStats: Codable {
var total: Int = 0
var project: Int = 0
var adhoc: Int = 0
var legacy: Int = 0
var running: Int = 0
var nextBlock: Int?
enum CodingKeys: String, CodingKey {
case total, project, adhoc, legacy, running
case nextBlock = "next_block"
}
}
struct ASRStatus: Codable {
var total: Int = 0
var done: Int = 0
var failed: Int = 0
var chars: Int = 0
var running: Bool = false
}