Add explicit Suno handoff action
This commit is contained in:
@@ -3,9 +3,11 @@ import SwiftUI
|
||||
|
||||
struct FinalReviewView: View {
|
||||
@Binding var project: SongProject
|
||||
@Environment(\.openURL) private var openURL
|
||||
|
||||
@State private var newFieldKey = ""
|
||||
@State private var newFieldValue = ""
|
||||
@State private var handoffStatus: HandoffStatus?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -42,6 +44,16 @@ struct FinalReviewView: View {
|
||||
Label("Prepare fields", systemImage: "wand.and.stars")
|
||||
}
|
||||
.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)
|
||||
@@ -165,6 +177,62 @@ struct FinalReviewView: View {
|
||||
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 {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
public protocol SunoHandoffService: Sendable {
|
||||
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
@@ -107,8 +107,8 @@ requirement is missing and blocks implementation, record it in
|
||||
|
||||
## Phase 8 --- Suno Handoff
|
||||
|
||||
- [ ] Implement explicit `Send to Suno` action.
|
||||
- [ ] Open the appropriate Suno creation surface.
|
||||
- [x] Implement explicit `Send to Suno` action.
|
||||
- [x] Open the appropriate Suno creation surface.
|
||||
- [ ] Fill supported fields with approved project output.
|
||||
- [ ] Never trigger Generate automatically.
|
||||
- [ ] Detect/report when fields cannot be filled rather than silently
|
||||
|
||||
Reference in New Issue
Block a user