import ByreAPI import SwiftUI struct WorkflowsView: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { Text("Workflows run a brief through stages, each its own task. Run asks your lead to start one, so you can add the project and the brief first.") .foregroundStyle(Palette.muted) .fixedSize(horizontal: false, vertical: true) .padding(.top, 16) WorkflowList() } .padding(.horizontal, 16) .padding(.top, 8) .padding(.bottom, 16) .frame(maxWidth: .infinity, alignment: .leading) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Palette.bg) } } private struct WorkflowList: View { @Environment(Store.self) private var store @ViewState private var list: [WorkflowSummary]? @ViewState private var failure: String? @ViewState private var loads = 0 @ViewState private var off: (() -> Void)? var body: some View { VStack(alignment: .leading, spacing: 0) { if let list { if list.isEmpty { hint("No saved workflows. Ask your lead to write one.") } else { ForEach(list.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }, id: \.name) { w in WorkflowRow(workflow: w) } } } else if let failure { hint(failure) } } .padding(.top, 8) .onAppear { off = store.events.add { ev in if ev.kind == "workflow.updated" || ev.kind == "workflow.deleted" { load() } } load() } .onDisappear { off?() off = nil loads += 1 } } private func hint(_ text: String) -> some View { Text(text).foregroundStyle(Palette.muted) } private func load() { loads += 1 let n = loads Task { do { let loaded = try await store.api.client.listWorkflows().ok.body.json if n == loads { list = loaded } } catch { failure = errorText(error) list = nil } } } } private struct WorkflowRow: View { let workflow: WorkflowSummary @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays var body: some View { HStack(alignment: .firstTextBaseline, spacing: 12) { VStack(alignment: .leading, spacing: 2) { Text(workflow.name) .font(Fonts.font(13, weight: 700)) if !workflow.description.isEmpty { Text(workflow.description) .foregroundStyle(Palette.muted) } } .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) .fixedSize(horizontal: false, vertical: true) Button("run") { run() } .buttonStyle(SmallLinkButtonStyle()) .help("Open the Lead tab with a request to run this workflow in its composer") } .padding(.vertical, 6) .overlay(alignment: .top) { Rectangle().fill(Palette.border).frame(height: 1) } } private func run() { let key = "message:lead" let ask = "Run the \"\(workflow.name)\" workflow: " let draft = store.drafts?.composerText(key) ?? "" store.drafts?.setComposerText(key, draft.isEmpty ? ask : "\(draft)\n\n\(ask)") overlays.leftTab = .lead overlays.leadFocus += 1 } }