// // AskView.swift // AT-IRC // // Owner questions: availability toggle, pending/all list, and answering with // an option button or free text. // import SwiftUI struct AskView: View { @Bindable var store: ChatStore @Environment(\.dismiss) private var dismiss @State private var showAll = false private var visibleAsks: [AskQuestion] { showAll ? store.asks : store.pendingAsks } var body: some View { NavigationStack { List { availabilitySection filterSection questionsSection } .navigationTitle("Questions") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Done") { dismiss() } } } .refreshable { await store.refreshAsks(force: true) } .task { await store.refreshAsks(force: true) } } } private var availabilitySection: some View { Section { Toggle(isOn: Binding( get: { store.ownerMode != "autonomous" }, set: { available in Task { await store.setOwnerMode(available ? "available" : "autonomous") } } )) { VStack(alignment: .leading, spacing: 2) { Text(store.ownerMode == "autonomous" ? "Away" : "Available") Text(store.ownerMode == "autonomous" ? "Agents research and decide on their own; decisions and ADRs show up here." : "New questions notify you and wait for your answer.") .font(.caption) .foregroundStyle(.secondary) } } if let error = store.asksError { Text(error) .font(.caption) .foregroundStyle(.red) } } } private var filterSection: some View { Section { Picker("Show", selection: $showAll) { Text("Pending").tag(false) Text("All").tag(true) } .pickerStyle(.segmented) } } private var questionsSection: some View { Section("Questions") { if visibleAsks.isEmpty { Text(showAll ? "No questions yet." : "No pending questions.") .foregroundStyle(.secondary) } else { ForEach(visibleAsks) { ask in AskRow(ask: ask, store: store) } } } } } private struct AskRow: View { let ask: AskQuestion let store: ChatStore @State private var reply = "" @State private var sending = false var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Text(ask.asker) .font(.caption.weight(.semibold)) Spacer() Text(ask.status.capitalized) .font(.caption2) .foregroundStyle(statusColor) } Text(ask.question) .font(.body) if let context = ask.context, !context.isEmpty { Text(context) .font(.caption) .foregroundStyle(.secondary) } HStack(spacing: 8) { if let channel = ask.channel, !channel.isEmpty { Text(channel) } if let date = ask.date { Text(date, format: .relative(presentation: .named)) } } .font(.caption2) .foregroundStyle(.tertiary) switch ask.status { case "pending": pendingControls case "answered": Text("Answered by \(ask.answeredBy ?? "owner"): \(ask.answer ?? "")") .font(.callout) case "decided": Text("Decided by \(ask.decidedBy ?? "agent"): \(ask.decision ?? ask.answer ?? "")") .font(.callout) if let adr = ask.adr, !adr.isEmpty { if let url = URL(string: adr), url.scheme != nil { Link("ADR: \(adr)", destination: url) .font(.caption) } else { Text("ADR: \(adr)") .font(.caption2) .foregroundStyle(.secondary) } } default: EmptyView() } } .padding(.vertical, 4) } @ViewBuilder private var pendingControls: some View { if let options = ask.options, !options.isEmpty { ForEach(Array(options.enumerated()), id: \.offset) { index, option in Button(option) { submit(option, choice: index) } .buttonStyle(.bordered) .disabled(sending) } } HStack { TextField("Answer", text: $reply) .textFieldStyle(.roundedBorder) Button { submit(reply, choice: nil) } label: { Image(systemName: "arrow.up.circle.fill") .font(.title2) } .buttonStyle(.plain) .disabled(sending || reply.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } } private var statusColor: Color { switch ask.status { case "pending": return .orange case "answered": return .green case "decided": return .blue default: return .gray } } private func submit(_ answer: String, choice: Int?) { let text = answer.trimmingCharacters(in: .whitespacesAndNewlines) guard !text.isEmpty || choice != nil else { return } sending = true Task { await store.answer(ask, answer: text, choice: choice) sending = false reply = "" } } }