Files
music-assistant/Sources/MusicAssistantApp/Presentation/FinalReviewView.swift
T

198 lines
6.7 KiB
Swift

import MusicAssistantCore
import SwiftUI
struct FinalReviewView: View {
@Binding var project: SongProject
@State private var newFieldKey = ""
@State private var newFieldValue = ""
var body: some View {
VStack(alignment: .leading, spacing: 12) {
ArabicLyricsReviewView(project: $project)
Divider()
TextField("Approved lyrics", text: $project.lyrics.text, axis: .vertical)
.lineLimit(4...10)
if !project.orderedSections.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Text("Section lyrics")
.font(.headline)
ForEach(project.orderedSections) { section in
TextField(section.title, text: sectionLyricsBinding(for: section.id), axis: .vertical)
.lineLimit(2...5)
}
}
}
Divider()
HStack {
Text("Suno fields")
.font(.headline)
Spacer()
Button {
ensureSunoOutput()
} label: {
Label("Prepare fields", systemImage: "wand.and.stars")
}
.disabled(project.sunoOutput != nil)
}
TextField("Suno lyrics", text: sunoLyricsBinding, axis: .vertical)
.lineLimit(4...10)
TextField("Suno style prompt", text: sunoStylePromptBinding, axis: .vertical)
.lineLimit(3...8)
if !additionalFieldKeys.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Text("Additional fields")
.font(.headline)
ForEach(additionalFieldKeys, id: \.self) { key in
HStack(alignment: .top, spacing: 8) {
TextField("Field", text: additionalFieldKeyBinding(for: key))
TextField("Value", text: additionalFieldValueBinding(for: key), axis: .vertical)
.lineLimit(1...4)
Button {
project.sunoOutput?.additionalFields.removeValue(forKey: key)
} label: {
Label("Remove field", systemImage: "minus.circle")
.labelStyle(.iconOnly)
}
.buttonStyle(.borderless)
.help("Remove field")
}
}
}
}
HStack {
TextField("Field name", text: $newFieldKey)
TextField("Value", text: $newFieldValue)
Button(action: addAdditionalField) {
Label("Add field", systemImage: "plus.circle.fill")
.labelStyle(.iconOnly)
}
.buttonStyle(.borderless)
.disabled(newFieldKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
.help("Add field")
}
}
}
private var additionalFieldKeys: [String] {
guard let additionalFields = project.sunoOutput?.additionalFields else { return [] }
return additionalFields.keys.sorted { $0.localizedStandardCompare($1) == .orderedAscending }
}
private var sunoLyricsBinding: Binding<String> {
Binding {
project.sunoOutput?.lyricsText ?? ""
} set: { newValue in
ensureSunoOutput()
project.sunoOutput?.lyricsText = newValue
}
}
private var sunoStylePromptBinding: Binding<String> {
Binding {
project.sunoOutput?.stylePrompt ?? ""
} set: { newValue in
ensureSunoOutput()
project.sunoOutput?.stylePrompt = newValue
}
}
private func sectionLyricsBinding(for sectionID: String) -> Binding<String> {
Binding {
project.lyrics.sectionTexts[sectionID] ?? ""
} set: { newValue in
if newValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
project.lyrics.sectionTexts.removeValue(forKey: sectionID)
} else {
project.lyrics.sectionTexts[sectionID] = newValue
}
}
}
private func additionalFieldKeyBinding(for key: String) -> Binding<String> {
Binding {
key
} set: { newKey in
let trimmedKey = newKey.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedKey.isEmpty, trimmedKey != key else { return }
ensureSunoOutput()
let value = project.sunoOutput?.additionalFields.removeValue(forKey: key) ?? ""
project.sunoOutput?.additionalFields[trimmedKey] = value
}
}
private func additionalFieldValueBinding(for key: String) -> Binding<String> {
Binding {
project.sunoOutput?.additionalFields[key] ?? ""
} set: { newValue in
ensureSunoOutput()
project.sunoOutput?.additionalFields[key] = newValue
}
}
private func addAdditionalField() {
let key = newFieldKey.trimmingCharacters(in: .whitespacesAndNewlines)
guard !key.isEmpty else { return }
ensureSunoOutput()
project.sunoOutput?.additionalFields[key] = newFieldValue
newFieldKey = ""
newFieldValue = ""
}
private func ensureSunoOutput() {
guard project.sunoOutput == nil else { return }
let compiledOutput = (try? SongProjectPromptCompiler().compile(project: project))
?? CompiledSunoOutput(lyricsText: project.lyrics.text, stylePrompt: "")
project.sunoOutput = SunoOutput(
lyricsText: compiledOutput.lyricsText,
stylePrompt: compiledOutput.stylePrompt,
generatedAt: Date()
)
}
}
#Preview {
FinalReviewPreview()
}
private struct FinalReviewPreview: View {
@State private var project = SongProject(
title: "Review",
idea: "Review generated fields",
sections: [
SongSection(id: "verse", type: .verse, title: "Verse"),
SongSection(id: "chorus", type: .chorus, title: "Chorus", order: 1)
],
lyrics: Lyrics(text: "Draft lyrics", sectionTexts: ["verse": "Verse text"]),
sunoOutput: SunoOutput(
lyricsText: "Draft lyrics",
stylePrompt: "Cinematic pop with oud",
additionalFields: ["mood": "Hopeful"]
)
)
var body: some View {
Form {
FinalReviewView(project: $project)
}
.formStyle(.grouped)
.frame(width: 420)
}
}