Add explicit Suno handoff action
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user