initial commit

This commit is contained in:
2026-01-08 19:16:32 +00:00
commit d89aedd5af
121 changed files with 8509 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
//
// CandidatesView.swift
// iris
//
// Created by Codex.
//
import SwiftUI
struct CandidatesView: View {
@StateObject private var model = CandidatesViewModel()
var body: some View {
NavigationStack {
List {
Section("Source") {
LabeledContent("Location") {
Text("London (demo)")
}
if let updated = model.lastUpdatedAt {
LabeledContent("Last update") { Text(timeOnly(from: updated)) }
} else {
LabeledContent("Last update") { Text("Never") }
}
if let error = model.lastError {
Text(error)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
if !model.diagnostics.isEmpty {
Section("Diagnostics") {
ForEach(model.diagnostics.keys.sorted(), id: \.self) { key in
LabeledContent(key) {
Text(model.diagnostics[key] ?? "")
.font(.caption)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
}
}
}
Section("Candidates (\(model.candidates.count))") {
if model.candidates.isEmpty {
Text(model.isLoading ? "Loading…" : "No candidates")
.foregroundStyle(.secondary)
} else {
ForEach(model.candidates, id: \.id) { candidate in
CandidateRow(candidate: candidate)
}
}
}
}
.navigationTitle("Candidates")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(model.isLoading ? "Refreshing…" : "Refresh") {
model.refresh()
}
.disabled(model.isLoading)
}
}
.onAppear { model.refresh() }
}
}
private func timeOnly(from date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .medium
return formatter.string(from: date)
}
}
private struct CandidateRow: View {
let candidate: Candidate
var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline) {
Text(candidate.title)
.font(.headline)
.lineLimit(1)
Spacer()
Text(candidate.type.rawValue)
.font(.caption)
.foregroundStyle(.secondary)
}
Text(candidate.subtitle)
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(1)
HStack(spacing: 12) {
Text(String(format: "conf %.2f", candidate.confidence))
Text("ttl \(candidate.ttlSec)s")
Text(expiresText(now: Int(Date().timeIntervalSince1970)))
}
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
}
private func expiresText(now: Int) -> String {
let expiresAt = candidate.createdAt + candidate.ttlSec
let remaining = expiresAt - now
if remaining <= 0 { return "expired" }
if remaining < 60 { return "in \(remaining)s" }
return "in \(remaining / 60)m"
}
}
struct CandidatesView_Previews: PreviewProvider {
static var previews: some View {
CandidatesView()
}
}