import SwiftUI struct TaskPanelView: View { @ObservedObject var taskStore: TaskStore var body: some View { ScrollView { VStack(spacing: 12) { if taskStore.sortedTasks.isEmpty { emptyState } else { ForEach(taskStore.sortedTasks) { task in TaskCardView(task: task) } } } .padding() } .navigationTitle("Background tasks") #if DEBUG .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Sample", action: { taskStore.injectSampleEvents() }) .font(.caption) } } #endif } private var emptyState: some View { VStack(spacing: 8) { Text("Nothing running") .font(.headline) Text("Ask Hope to research or compare something.") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 48) } } struct TaskCardView: View { let task: TaskItem var body: some View { VStack(alignment: .leading, spacing: 8) { HStack(spacing: 8) { statusDot Text(task.description) .font(.subheadline.weight(.semibold)) .lineLimit(2) Spacer() } if !task.steps.isEmpty { VStack(alignment: .leading, spacing: 4) { ForEach(task.steps) { step in HStack(alignment: .top, spacing: 6) { Text(stepLabel(step.role)) .font(.caption2.weight(.semibold)) .foregroundStyle(stepColor(step.role)) Text(step.content ?? "") .font(.caption) .foregroundStyle(stepColor(step.role)) .lineLimit(3) } } } } 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(12) .frame(maxWidth: .infinity, alignment: .leading) .background(Color(.secondarySystemBackground), in: RoundedRectangle(cornerRadius: 14)) } private var statusDot: some View { Circle() .fill(dotColor) .frame(width: 9, height: 9) } private var dotColor: Color { switch task.status { case .pending: return .gray case .running: return .blue case .completed: return .green case .failed: return .red } } private func block(_ text: String, color: Color, background: Color) -> some View { Text(text) .font(.subheadline) .foregroundStyle(color) .frame(maxWidth: .infinity, alignment: .leading) .padding(10) .background(background, in: RoundedRectangle(cornerRadius: 10)) } 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 }()) } }