Add explicit Suno handoff action

This commit is contained in:
diyaa
2026-09-14 22:22:38 +02:00
parent 61267e201e
commit 448d390671
4 changed files with 88 additions and 2 deletions
@@ -3,9 +3,11 @@ import SwiftUI
struct FinalReviewView: View { struct FinalReviewView: View {
@Binding var project: SongProject @Binding var project: SongProject
@Environment(\.openURL) private var openURL
@State private var newFieldKey = "" @State private var newFieldKey = ""
@State private var newFieldValue = "" @State private var newFieldValue = ""
@State private var handoffStatus: HandoffStatus?
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
@@ -42,6 +44,16 @@ struct FinalReviewView: View {
Label("Prepare fields", systemImage: "wand.and.stars") Label("Prepare fields", systemImage: "wand.and.stars")
} }
.disabled(project.sunoOutput != nil) .disabled(project.sunoOutput != nil)
Button(action: sendToSuno) {
Label("Send to Suno", systemImage: "arrow.up.forward.app")
}
}
if let handoffStatus {
Label(handoffStatus.title, systemImage: handoffStatus.systemImage)
.font(.caption)
.foregroundStyle(handoffStatus.foregroundStyle)
} }
TextField("Suno lyrics", text: sunoLyricsBinding, axis: .vertical) TextField("Suno lyrics", text: sunoLyricsBinding, axis: .vertical)
@@ -165,6 +177,62 @@ struct FinalReviewView: View {
generatedAt: Date() generatedAt: Date()
) )
} }
private func sendToSuno() {
ensureSunoOutput()
guard let output = project.sunoOutput else { return }
do {
try CompiledSunoOutputValidator().validate(
CompiledSunoOutput(lyricsText: output.lyricsText, stylePrompt: output.stylePrompt)
)
} catch let validationError as LocalizedError {
handoffStatus = .failed(validationError.errorDescription ?? "Unable to prepare the Suno handoff.")
return
} catch {
handoffStatus = .failed("Unable to prepare the Suno handoff.")
return
}
openURL(SunoHandoffDestination.createPageURL) { accepted in
handoffStatus = accepted
? .opened
: .failed("Unable to open the Suno Create page.")
}
}
}
private enum HandoffStatus: Equatable {
case opened
case failed(String)
var title: String {
switch self {
case .opened:
return "Suno Create opened in your browser."
case let .failed(message):
return message
}
}
var systemImage: String {
switch self {
case .opened:
return "checkmark.circle.fill"
case .failed:
return "exclamationmark.triangle.fill"
}
}
var foregroundStyle: Color {
switch self {
case .opened:
return .green
case .failed:
return .red
}
}
} }
#Preview { #Preview {
@@ -1,3 +1,9 @@
import Foundation
public protocol SunoHandoffService: Sendable { public protocol SunoHandoffService: Sendable {
func handOff(_ output: CompiledSunoOutput) async throws func handOff(_ output: CompiledSunoOutput) async throws
} }
public enum SunoHandoffDestination {
public static let createPageURL = URL(string: "https://suno.com/create")!
}
@@ -0,0 +1,12 @@
import MusicAssistantCore
import XCTest
final class SunoHandoffDestinationTests: XCTestCase {
func testCreatePageUsesTheOfficialSunoCreateURL() {
let url = SunoHandoffDestination.createPageURL
XCTAssertEqual(url.scheme, "https")
XCTAssertEqual(url.host, "suno.com")
XCTAssertEqual(url.path, "/create")
}
}
+2 -2
View File
@@ -107,8 +107,8 @@ requirement is missing and blocks implementation, record it in
## Phase 8 --- Suno Handoff ## Phase 8 --- Suno Handoff
- [ ] Implement explicit `Send to Suno` action. - [x] Implement explicit `Send to Suno` action.
- [ ] Open the appropriate Suno creation surface. - [x] Open the appropriate Suno creation surface.
- [ ] Fill supported fields with approved project output. - [ ] Fill supported fields with approved project output.
- [ ] Never trigger Generate automatically. - [ ] Never trigger Generate automatically.
- [ ] Detect/report when fields cannot be filled rather than silently - [ ] Detect/report when fields cannot be filled rather than silently