Files
nucleic/Sources/NucleicApp/CovalenceProvisioningView.swift
T

86 lines
3.3 KiB
Swift

import SwiftUI
import Foundation
import NucleicCore
/// The detail pane for a Covalence chat that's been dispatched but has no runner yet (item 5). It
/// opens the instant the user hits send — replacing the old toast-then-wait — and shows live
/// provisioning progress ("provisioning", "waiting for a runner", …) until the chat lands on a
/// runner, at which point the store swaps in the real session view.
struct CovalenceProvisioningView: View {
let pending: PendingCovalenceDispatch
@Environment(AppStore.self) private var store
@Environment(\.appPalette) private var palette
private var projectName: String {
store.projectSummary(pending.projectID)?.name ?? "Covalence"
}
private var failed: Bool { pending.failure != nil }
var body: some View {
VStack(spacing: 0) {
header
Divider()
Spacer()
statusCard
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .center, spacing: 8) {
Image(systemName: "globe")
.font(.title3).foregroundStyle(.secondary)
Text(pending.titleLine)
.font(.system(size: 22, weight: .semibold))
.lineLimit(1)
Spacer()
}
Text("Covalence · \(projectName)")
.font(.caption).foregroundStyle(.secondary)
}
.padding(.horizontal, 20).padding(.vertical, 14)
}
private var statusCard: some View {
VStack(spacing: 16) {
if failed {
Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 30)).foregroundStyle(palette.attention)
} else {
ProgressView().controlSize(.large)
}
Text(store.pendingCovalenceStatus(pending))
.font(.headline)
.foregroundStyle(failed ? palette.attention : .primary)
.multilineTextAlignment(.center)
if !failed {
Text("This can take a few minutes on a cold start — your chat begins automatically "
+ "the moment a runner is ready.")
.font(.caption).foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.frame(maxWidth: 420)
}
// Echo the queued message so the user sees exactly what's about to run.
if !pending.message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(pending.message)
.font(.callout).foregroundStyle(.secondary)
.lineLimit(4).multilineTextAlignment(.leading)
.padding(12)
.frame(maxWidth: 460, alignment: .leading)
.background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 10))
}
if failed {
Button("Dismiss") { store.removePendingCovalence(recordID: pending.id) }
.buttonStyle(.bordered)
}
}
.padding(28)
.frame(maxWidth: 520)
.background(.regularMaterial, in: .rect(cornerRadius: 16))
.padding()
}
}