86 lines
3.3 KiB
Swift
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()
|
|
}
|
|
}
|