Initial commit
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import Foundation
|
||||
|
||||
public protocol AIService: Sendable {
|
||||
func generateSongProject(from request: SongProjectGenerationRequest) async throws -> SongProjectGenerationResult
|
||||
func reviseLyrics(from request: LyricsRevisionRequest) async throws -> LyricsRevisionResult
|
||||
func proposeProjectUpdate(from request: SongProjectUpdateRequest) async throws -> SongProjectUpdateResult
|
||||
}
|
||||
|
||||
public struct AIConversationMessage: Equatable, Identifiable, Sendable {
|
||||
public let id: String
|
||||
public var role: AIConversationRole
|
||||
public var content: String
|
||||
|
||||
public init(
|
||||
id: String = UUID().uuidString,
|
||||
role: AIConversationRole,
|
||||
content: String
|
||||
) {
|
||||
self.id = id
|
||||
self.role = role
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
public enum AIConversationRole: String, Equatable, Sendable {
|
||||
case user
|
||||
case assistant
|
||||
case system
|
||||
}
|
||||
|
||||
public struct AIRequestContext: Equatable, Sendable {
|
||||
public var userInstruction: String
|
||||
public var conversation: [AIConversationMessage]
|
||||
public var localeIdentifier: String?
|
||||
internal var privateApplicationRules: PrivateApplicationRules?
|
||||
|
||||
public init(
|
||||
userInstruction: String,
|
||||
conversation: [AIConversationMessage] = [],
|
||||
localeIdentifier: String? = nil
|
||||
) {
|
||||
self.userInstruction = userInstruction
|
||||
self.conversation = conversation
|
||||
self.localeIdentifier = localeIdentifier
|
||||
self.privateApplicationRules = nil
|
||||
}
|
||||
|
||||
internal mutating func injectPrivateApplicationRules(_ rules: PrivateApplicationRules?) {
|
||||
privateApplicationRules = rules
|
||||
}
|
||||
}
|
||||
|
||||
public struct SongProjectGenerationRequest: Equatable, Sendable {
|
||||
public var context: AIRequestContext
|
||||
public var seedProject: SongProject?
|
||||
public var discussionMode: ConversationMode
|
||||
|
||||
public init(
|
||||
context: AIRequestContext,
|
||||
seedProject: SongProject? = nil,
|
||||
discussionMode: ConversationMode = .auto
|
||||
) {
|
||||
self.context = context
|
||||
self.seedProject = seedProject
|
||||
self.discussionMode = discussionMode
|
||||
}
|
||||
}
|
||||
|
||||
public struct SongProjectGenerationResult: Equatable, Sendable {
|
||||
public var project: SongProject
|
||||
public var notes: [String]
|
||||
public var followUpQuestions: [String]
|
||||
|
||||
public init(
|
||||
project: SongProject,
|
||||
notes: [String] = [],
|
||||
followUpQuestions: [String] = []
|
||||
) {
|
||||
self.project = project
|
||||
self.notes = notes
|
||||
self.followUpQuestions = followUpQuestions
|
||||
}
|
||||
}
|
||||
|
||||
public struct LyricsRevisionRequest: Equatable, Sendable {
|
||||
public var context: AIRequestContext
|
||||
public var project: SongProject
|
||||
public var sourceLyrics: Lyrics
|
||||
public var mode: LyricsRevisionMode
|
||||
|
||||
public init(
|
||||
context: AIRequestContext,
|
||||
project: SongProject,
|
||||
sourceLyrics: Lyrics,
|
||||
mode: LyricsRevisionMode
|
||||
) {
|
||||
self.context = context
|
||||
self.project = project
|
||||
self.sourceLyrics = sourceLyrics
|
||||
self.mode = mode
|
||||
}
|
||||
}
|
||||
|
||||
public enum LyricsRevisionMode: String, Equatable, Sendable {
|
||||
case writeFromIdea
|
||||
case correct
|
||||
case improve
|
||||
case restructure
|
||||
case complete
|
||||
}
|
||||
|
||||
public struct LyricsRevisionResult: Equatable, Sendable {
|
||||
public var lyrics: Lyrics
|
||||
public var notes: [String]
|
||||
|
||||
public init(lyrics: Lyrics, notes: [String] = []) {
|
||||
self.lyrics = lyrics
|
||||
self.notes = notes
|
||||
}
|
||||
}
|
||||
|
||||
public struct SongProjectUpdateRequest: Equatable, Sendable {
|
||||
public var context: AIRequestContext
|
||||
public var project: SongProject
|
||||
public var allowedScopes: [SongProjectUpdateScope]
|
||||
|
||||
public init(
|
||||
context: AIRequestContext,
|
||||
project: SongProject,
|
||||
allowedScopes: [SongProjectUpdateScope]
|
||||
) {
|
||||
self.context = context
|
||||
self.project = project
|
||||
self.allowedScopes = allowedScopes
|
||||
}
|
||||
}
|
||||
|
||||
public enum SongProjectUpdateScope: String, Codable, Equatable, Sendable {
|
||||
case duration
|
||||
case genres
|
||||
case instruments
|
||||
case vocals
|
||||
case languages
|
||||
case lyrics
|
||||
case structure
|
||||
case arrangement
|
||||
case musicalParameters
|
||||
case emotionalArc
|
||||
case productionDirections
|
||||
case sunoOutput
|
||||
}
|
||||
|
||||
public struct SongProjectUpdateResult: Equatable, Sendable {
|
||||
public var project: SongProject
|
||||
public var changedScopes: [SongProjectUpdateScope]
|
||||
public var notes: [String]
|
||||
public var followUpQuestions: [String]
|
||||
|
||||
public init(
|
||||
project: SongProject,
|
||||
changedScopes: [SongProjectUpdateScope] = [],
|
||||
notes: [String] = [],
|
||||
followUpQuestions: [String] = []
|
||||
) {
|
||||
self.project = project
|
||||
self.changedScopes = changedScopes
|
||||
self.notes = notes
|
||||
self.followUpQuestions = followUpQuestions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import Foundation
|
||||
|
||||
public struct PrivateApplicationRules: Equatable, Sendable {
|
||||
public let content: String
|
||||
|
||||
public init?(content: String?) {
|
||||
guard let content else { return nil }
|
||||
|
||||
let trimmedContent = content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedContent.isEmpty else { return nil }
|
||||
|
||||
self.content = trimmedContent
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ApplicationRuleProviding: Sendable {
|
||||
func privateApplicationRules() throws -> PrivateApplicationRules?
|
||||
}
|
||||
|
||||
public struct StaticApplicationRuleProvider: ApplicationRuleProviding {
|
||||
private let rules: PrivateApplicationRules?
|
||||
|
||||
public init(rules: PrivateApplicationRules?) {
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
public func privateApplicationRules() throws -> PrivateApplicationRules? {
|
||||
rules
|
||||
}
|
||||
}
|
||||
|
||||
public final class ApplicationRuleInjectingAIService: AIService, Sendable {
|
||||
private let baseService: any AIService
|
||||
private let ruleProvider: any ApplicationRuleProviding
|
||||
|
||||
public init(
|
||||
baseService: any AIService,
|
||||
ruleProvider: any ApplicationRuleProviding
|
||||
) {
|
||||
self.baseService = baseService
|
||||
self.ruleProvider = ruleProvider
|
||||
}
|
||||
|
||||
public func generateSongProject(from request: SongProjectGenerationRequest) async throws -> SongProjectGenerationResult {
|
||||
try await baseService.generateSongProject(from: requestWithInjectedRules(request))
|
||||
}
|
||||
|
||||
public func reviseLyrics(from request: LyricsRevisionRequest) async throws -> LyricsRevisionResult {
|
||||
try await baseService.reviseLyrics(from: requestWithInjectedRules(request))
|
||||
}
|
||||
|
||||
public func proposeProjectUpdate(from request: SongProjectUpdateRequest) async throws -> SongProjectUpdateResult {
|
||||
try await baseService.proposeProjectUpdate(from: requestWithInjectedRules(request))
|
||||
}
|
||||
|
||||
private func requestWithInjectedRules(_ request: SongProjectGenerationRequest) throws -> SongProjectGenerationRequest {
|
||||
var request = request
|
||||
request.context.injectPrivateApplicationRules(try ruleProvider.privateApplicationRules())
|
||||
return request
|
||||
}
|
||||
|
||||
private func requestWithInjectedRules(_ request: LyricsRevisionRequest) throws -> LyricsRevisionRequest {
|
||||
var request = request
|
||||
request.context.injectPrivateApplicationRules(try ruleProvider.privateApplicationRules())
|
||||
return request
|
||||
}
|
||||
|
||||
private func requestWithInjectedRules(_ request: SongProjectUpdateRequest) throws -> SongProjectUpdateRequest {
|
||||
var request = request
|
||||
request.context.injectPrivateApplicationRules(try ruleProvider.privateApplicationRules())
|
||||
return request
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
|
||||
public struct StructuredSongProjectUpdateResponse: Codable, Equatable, Sendable {
|
||||
public static let schemaName = "song_project_update_response"
|
||||
|
||||
public var project: SongProjectSnapshot
|
||||
public var changedScopes: [SongProjectUpdateScope]
|
||||
public var notes: [String]
|
||||
public var followUpQuestions: [String]
|
||||
|
||||
public init(
|
||||
project: SongProjectSnapshot,
|
||||
changedScopes: [SongProjectUpdateScope] = [],
|
||||
notes: [String] = [],
|
||||
followUpQuestions: [String] = []
|
||||
) {
|
||||
self.project = project
|
||||
self.changedScopes = changedScopes
|
||||
self.notes = notes
|
||||
self.followUpQuestions = followUpQuestions
|
||||
}
|
||||
|
||||
public func makeUpdateResult(
|
||||
updating existingProject: SongProject,
|
||||
updatedAt: Date = Date()
|
||||
) -> SongProjectUpdateResult {
|
||||
var updatedProject = existingProject
|
||||
updatedProject.restoreSnapshot(project, updatedAt: updatedAt)
|
||||
|
||||
return SongProjectUpdateResult(
|
||||
project: updatedProject,
|
||||
changedScopes: changedScopes,
|
||||
notes: notes,
|
||||
followUpQuestions: followUpQuestions
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user