diff --git a/mac/Sources/Byre/Phone.swift b/mac/Sources/Byre/Phone.swift index eac1c5d..0e74d86 100644 --- a/mac/Sources/Byre/Phone.swift +++ b/mac/Sources/Byre/Phone.swift @@ -140,10 +140,6 @@ struct PhoneView: View { .environment(overlays) } } - .fullScreenCover(isPresented: Binding(get: { QuickRunRequest.shared.shown }, set: { QuickRunRequest.shared.shown = $0 })) { - QuickRunPage() - .environment(store) - } .onAppear { store.paneFocus.attach() } diff --git a/mac/Sources/Byre/QuickRun.swift b/mac/Sources/Byre/QuickRun.swift index ccd698b..adb8788 100644 --- a/mac/Sources/Byre/QuickRun.swift +++ b/mac/Sources/Byre/QuickRun.swift @@ -116,16 +116,46 @@ struct ByreShortcuts: AppShortcutsProvider { } } +@MainActor +private struct QuickRunChoice { + let origin: String + let store: Store + let overlays: Overlays + let run: Components.Schemas.QuickRun + + var project: String { + store.projects.first { $0.id == run.projectId }?.name ?? run.projectId + } +} + struct QuickRunPage: View { - @Environment(Store.self) private var store + @ViewState private var picked: String? @ViewState private var text = "" @ViewState private var starting = false @ViewState private var failure: String? @FocusState private var focused: Bool + private var choices: [QuickRunChoice] { + Servers.shared.origins.compactMap { origin in + guard let connection = Servers.shared.connections[origin], let store = connection.store, let run = store.me?.quickRun, !run.workflow.isEmpty else { + return nil + } + return QuickRunChoice(origin: origin, store: store, overlays: connection.overlays, run: run) + } + } + + private var connecting: Bool { + Servers.shared.connections.values.contains { + if case .starting = $0.phase { + return true + } + return false + } + } + var body: some View { - let q = store.me?.quickRun ?? .init(projectId: "", workflow: "") - let project = store.projects.first { $0.id == q.projectId } + let choices = choices + let choice = choices.first { $0.origin == picked } ?? (choices.count == 1 ? choices.first : nil) let prompt = text.trimmingCharacters(in: .whitespacesAndNewlines) VStack(alignment: .leading, spacing: 12) { HStack { @@ -134,29 +164,55 @@ struct QuickRunPage: View { } .disabled(starting) Spacer() - if !q.workflow.isEmpty { + if let choice { Button("Start") { - start(q, prompt) + start(choice, 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 { + if let choice { VStack(alignment: .leading, spacing: 2) { - Text(q.workflow) + Text(choice.run.workflow) .font(Fonts.font(17, weight: 700)) - Text(project?.name ?? q.projectId) + Text("\(choice.project) · \(serverName(choice.origin))") .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) + start(choice, prompt) } + .environment(choice.store) + .environment(choice.overlays) + .onAppear { + focused = true + } + } else if !choices.isEmpty { + ForEach(choices, id: \.origin) { c in + Button { + picked = c.origin + } label: { + VStack(alignment: .leading, spacing: 2) { + Text(c.run.workflow) + .font(Fonts.font(17, weight: 700)) + Text("\(c.project) · \(serverName(c.origin))") + .foregroundStyle(Palette.muted) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .overlay(RoundedRectangle(cornerRadius: 6).stroke(Servers.shared.color(c.origin))) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } + } else if connecting { + Text("Connecting to your servers…") + .foregroundStyle(Palette.muted) + } else { + Text("Choose the workflow the Action button starts in More → Settings.") + .foregroundStyle(Palette.muted) + .fixedSize(horizontal: false, vertical: true) } if let failure { Text(failure) @@ -172,12 +228,9 @@ struct QuickRunPage: View { .tint(Palette.accent) .background(Palette.bg) .font(Fonts.font(13)) - .onAppear { - focused = true - } } - private func start(_ q: Components.Schemas.QuickRun, _ prompt: String) { + private func start(_ choice: QuickRunChoice, _ prompt: String) { if prompt.isEmpty || starting { return } @@ -185,9 +238,10 @@ struct QuickRunPage: View { 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 + let task = try await choice.store.api.client.createTask(path: .init(projectId: choice.run.projectId), body: .json(.init(prompt: prompt, workflow: choice.run.workflow))).created.body.json close() - store.focusTask(task.id) + Servers.shared.current = choice.origin + choice.store.focusTask(task.id) } catch { starting = false failure = errorText(error) @@ -196,6 +250,7 @@ struct QuickRunPage: View { } private func close() { + picked = nil text = "" starting = false failure = nil diff --git a/mac/Sources/Byre/Settings.swift b/mac/Sources/Byre/Settings.swift index 4587378..4f8f0fe 100644 --- a/mac/Sources/Byre/Settings.swift +++ b/mac/Sources/Byre/Settings.swift @@ -55,7 +55,7 @@ struct SettingsView: View { try await store.api.client.updateDefaultModel(body: .json(.init(model: m))).ok.body.json } heading("Action button workflow") - Text("The iPhone's Action button, set to byre's Run workflow shortcut, opens a page to type a prompt and start this workflow with it.") + Text("The iPhone's Action button, set to byre's Run workflow shortcut, opens a page to type a prompt and start this workflow with it. When more than one server has one set, the page asks which to start.") .foregroundStyle(Palette.muted) .fixedSize(horizontal: false, vertical: true) QuickRunSetting() diff --git a/mac/Sources/Byre/Startup.swift b/mac/Sources/Byre/Startup.swift index f3d4582..abbbd48 100644 --- a/mac/Sources/Byre/Startup.swift +++ b/mac/Sources/Byre/Startup.swift @@ -584,6 +584,9 @@ struct ServerWindow: View { .tint(Palette.accent) .background(Palette.bg) .font(Fonts.font(13)) + .fullScreenCover(isPresented: Binding(get: { QuickRunRequest.shared.shown }, set: { QuickRunRequest.shared.shown = $0 })) { + QuickRunPage() + } } #endif