import SwiftUI struct TaskPanelView: View { @ObservedObject var taskStore: TaskStore var body: some View { ScrollView { LazyVStack(alignment: .leading, spacing: 20) { if taskStore.sortedTasks.isEmpty { emptyState } else { Text(taskStore.activeCount > 0 ? "Hope is working on \(taskStore.activeCount) task\(taskStore.activeCount == 1 ? "" : "s"). You can keep talking." : "Results from the things you’ve asked Hope to work on.") .font(.subheadline).foregroundStyle(.secondary) ForEach(taskStore.sortedTasks) { task in TaskCardView(task: task) } } } .padding() } .navigationTitle("Activity") .background(Color(.systemGroupedBackground)) } private var emptyState: some View { ContentUnavailableView { Label("A little help, in the background", systemImage: "checklist") } description: { Text("Ask Hope to research a topic or compare your options. Follow the progress and read the results here.") } .padding(.top, 48) } } struct TaskCardView: View { let task: TaskItem @State private var showSteps = false var body: some View { VStack(alignment: .leading, spacing: 14) { HStack { Label(statusTitle, systemImage: statusSymbol) .font(.subheadline.weight(.medium)) .foregroundStyle(dotColor) Spacer() Text(task.createdAt, style: .time).font(.caption).foregroundStyle(.secondary) } Text(task.description).font(.headline).fixedSize(horizontal: false, vertical: true) if !task.steps.isEmpty { DisclosureGroup("\(task.steps.count) progress updates", isExpanded: $showSteps) { VStack(alignment: .leading, spacing: 12) { ForEach(task.steps) { step in VStack(alignment: .leading, spacing: 4) { Text(stepLabel(step.role)) .font(.caption.weight(.semibold)).foregroundStyle(.secondary) Text(step.content ?? "") .font(.subheadline).textSelection(.enabled) } } }.padding(.top, 8) } .font(.subheadline) } if let result = task.result { block(result, color: .green, background: Color.green.opacity(0.12)) } if let error = task.error { block(error, color: .red, background: Color.red.opacity(0.12)) } } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) .background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 16)) } private var statusTitle: String { switch task.status { case .pending: "Queued" case .running: "In progress" case .completed: "Complete" case .failed: "Needs attention" } } private var statusSymbol: String { switch task.status { case .pending: "clock" case .running: "arrow.triangle.2.circlepath" case .completed: "checkmark.circle.fill" case .failed: "exclamationmark.circle" } } private var dotColor: Color { switch task.status { case .pending: return .gray case .running: return HopeStyle.tint case .completed: return .green case .failed: return .red } } private func block(_ text: String, color: Color, background: Color) -> some View { Text(text) .font(.body) .foregroundStyle(.primary) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) } private func stepLabel(_ role: String?) -> String { switch role { case "thinking": return "Thinking" case "tool_call": return "Tool" case "tool_result": return "Result" case "text": return "Note" default: return role ?? "Step" } } private func stepColor(_ role: String?) -> Color { switch role { case "thinking": return .blue case "tool_call": return .indigo case "tool_result": return .secondary default: return .primary } } } #Preview { NavigationStack { TaskPanelView(taskStore: { let store = TaskStore() #if DEBUG store.injectSampleEvents() #endif return store }()) } }