import ByreAPI import SwiftUI struct QuickRunSetting: View { @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays @ViewState private var workflows: [WorkflowSummary] = [] @ViewState private var off: (() -> Void)? var body: some View { let current = store.me?.quickRun ?? .init(projectId: "", workflow: "") HStack(spacing: 6) { menu(current.workflow.isEmpty ? "None" : current.workflow, help: "Workflow the Action button starts") { Button("None") { save(.init(projectId: "", workflow: "")) } ForEach(workflows, id: \.name) { w in Button(w.name) { save(.init(projectId: current.projectId.isEmpty ? store.projects.first?.id ?? "" : current.projectId, workflow: w.name)) } } } if !current.workflow.isEmpty { Text("in") .foregroundStyle(Palette.muted) menu(store.projects.first { $0.id == current.projectId }?.name ?? current.projectId, help: "Project the workflow runs in") { ForEach(store.projects, id: \.id) { p in Button(p.name) { save(.init(projectId: p.id, workflow: current.workflow)) } } } } } .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 } } private func menu(_ label: String, help: String, @ViewBuilder _ items: () -> some View) -> some View { Menu { items() } label: { HStack(spacing: 8) { Text(label) Image(systemName: "chevron.up.chevron.down") .font(Fonts.font(9, weight: 600)) } .foregroundStyle(Palette.text) .modifier(FieldStyle()) .contentShape(Rectangle()) } .menuStyle(.button) .buttonStyle(.plain) .menuIndicator(.hidden) .fixedSize() .help(help) } private func load() { Task { do { workflows = try await store.api.client.listWorkflows().ok.body.json } catch { overlays.showAlert(errorText(error)) } } } private func save(_ q: Components.Schemas.QuickRun) { Task { do { store.me = try await store.api.client.updateQuickRun(body: .json(q)).ok.body.json } catch { overlays.showAlert(errorText(error)) } } } } #if os(iOS) import AppIntents @MainActor @Observable final class QuickRunRequest { static let shared = QuickRunRequest() var shown = false } struct QuickRunIntent: AppIntent { static let title: LocalizedStringResource = "Run byre workflow" static let description = IntentDescription("Opens byre to type a prompt and start your Action button workflow with it.") static let supportedModes: IntentModes = .foreground @MainActor func perform() async throws -> some IntentResult { QuickRunRequest.shared.shown = true return .result() } } struct ByreShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut(intent: QuickRunIntent(), phrases: ["Run a workflow in \(.applicationName)"], shortTitle: "Run workflow", systemImageName: "bolt") } } struct QuickRunPage: View { @Environment(Store.self) private var store @ViewState private var text = "" @ViewState private var starting = false @ViewState private var failure: String? @FocusState private var focused: Bool var body: some View { let q = store.me?.quickRun ?? .init(projectId: "", workflow: "") let project = store.projects.first { $0.id == q.projectId } let prompt = text.trimmingCharacters(in: .whitespacesAndNewlines) VStack(alignment: .leading, spacing: 12) { HStack { Button("Cancel") { close() } .disabled(starting) Spacer() if !q.workflow.isEmpty { Button("Start") { start(q, prompt) } .buttonStyle(WebButtonStyle(primary: true)) .disabled(prompt.isEmpty || starting) } } .font(Fonts.font(15, weight: 700)) if q.workflow.isEmpty { Text("Choose the workflow the Action button starts in More → Settings.") .foregroundStyle(Palette.muted) .fixedSize(horizontal: false, vertical: true) } else { VStack(alignment: .leading, spacing: 2) { Text(q.workflow) .font(Fonts.font(17, weight: 700)) Text(project?.name ?? q.projectId) .foregroundStyle(Palette.muted) } AgentEditor(text: $text, placeholder: "What should it do?", key: "quick-run", height: 220, focus: $focused, border: focused ? Palette.accent : Palette.border) { start(q, prompt) } } if let failure { Text(failure) .foregroundStyle(Palette.danger) .textSelection(.enabled) .fixedSize(horizontal: false, vertical: true) } Spacer(minLength: 0) } .padding(16) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .foregroundStyle(Palette.text) .tint(Palette.accent) .background(Palette.bg) .font(Fonts.font(13)) .onAppear { focused = true } } private func start(_ q: Components.Schemas.QuickRun, _ prompt: String) { if prompt.isEmpty || starting { return } starting = true failure = nil Task { do { let task = try await store.api.client.createTask(path: .init(projectId: q.projectId), body: .json(.init(prompt: prompt, workflow: q.workflow))).created.body.json close() store.focusTask(task.id) } catch { starting = false failure = errorText(error) } } } private func close() { text = "" starting = false failure = nil QuickRunRequest.shared.shown = false } } #endif