diff --git a/Sources/MusicAssistantApp/Presentation/RootView.swift b/Sources/MusicAssistantApp/Presentation/RootView.swift index 3f1ddf5..0aa6e56 100644 --- a/Sources/MusicAssistantApp/Presentation/RootView.swift +++ b/Sources/MusicAssistantApp/Presentation/RootView.swift @@ -1,8 +1,50 @@ +<<<<<<< HEAD import SwiftUI struct RootView: View { var body: some View { ProjectBrowserView() +======= +import MusicAssistantCore +import SwiftUI + +struct RootView: View { + private let appName = "AI Music Studio" + private let boundarySummary = ProductBoundarySummary.initial + + var body: some View { + NavigationSplitView { + List { + Label("Projects", systemImage: "music.note.list") + Label("Settings", systemImage: "gearshape") + } + .navigationTitle(appName) + } detail: { + VStack(alignment: .leading, spacing: 16) { + Text("New Song Project") + .font(.largeTitle.weight(.semibold)) + + Text(boundarySummary.description) + .font(.body) + .foregroundStyle(.secondary) + .frame(maxWidth: 560, alignment: .leading) + + Divider() + + VStack(alignment: .leading, spacing: 8) { + Text("Foundation status") + .font(.headline) + + Label("Presentation, Domain, Services, Integrations, and Persistence boundaries are separated.", systemImage: "checkmark.circle") + Label("External providers are represented by protocols only.", systemImage: "lock.shield") + Label("Secrets are expected from local environment configuration.", systemImage: "key") + } + .labelStyle(.titleAndIcon) + } + .padding(32) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + } +>>>>>>> origin/master } } diff --git a/Sources/MusicAssistantCore/Configuration/AppConfiguration.swift b/Sources/MusicAssistantCore/Configuration/AppConfiguration.swift index 7389899..382af01 100644 --- a/Sources/MusicAssistantCore/Configuration/AppConfiguration.swift +++ b/Sources/MusicAssistantCore/Configuration/AppConfiguration.swift @@ -2,6 +2,7 @@ import Foundation public struct AppConfiguration: Equatable, Sendable { public let openAIAPIKey: String? +<<<<<<< HEAD public let privateApplicationRules: PrivateApplicationRules? public init( @@ -10,12 +11,21 @@ public struct AppConfiguration: Equatable, Sendable { ) { self.openAIAPIKey = openAIAPIKey self.privateApplicationRules = privateApplicationRules +======= + + public init(openAIAPIKey: String?) { + self.openAIAPIKey = openAIAPIKey +>>>>>>> origin/master } public static func development(environment: [String: String] = ProcessInfo.processInfo.environment) -> AppConfiguration { AppConfiguration( +<<<<<<< HEAD openAIAPIKey: environment["MUSIC_ASSISTANT_OPENAI_API_KEY"], privateApplicationRules: PrivateApplicationRules(content: environment["MUSIC_ASSISTANT_PRIVATE_AI_RULES"]) +======= + openAIAPIKey: environment["MUSIC_ASSISTANT_OPENAI_API_KEY"] +>>>>>>> origin/master ) } } diff --git a/Sources/MusicAssistantCore/Domain/SongProject.swift b/Sources/MusicAssistantCore/Domain/SongProject.swift index 169ed81..316766a 100644 --- a/Sources/MusicAssistantCore/Domain/SongProject.swift +++ b/Sources/MusicAssistantCore/Domain/SongProject.swift @@ -343,6 +343,7 @@ public extension SongProject { versions.first { $0.id == versionID } } +<<<<<<< HEAD mutating func restoreVersion(id versionID: String, restoredAt: Date = Date()) -> Bool { guard let version = version(id: versionID) else { return false } restoreSnapshot(version.snapshot, updatedAt: restoredAt) @@ -373,6 +374,8 @@ public extension SongProject { self.updatedAt = updatedAt } +======= +>>>>>>> origin/master mutating func appendSection(_ section: SongSection) { var orderedSection = section orderedSection.order = nextSectionOrder diff --git a/Sources/MusicAssistantCore/Services/AI/AIService.swift b/Sources/MusicAssistantCore/Services/AI/AIService.swift index c57af24..564d58c 100644 --- a/Sources/MusicAssistantCore/Services/AI/AIService.swift +++ b/Sources/MusicAssistantCore/Services/AI/AIService.swift @@ -1,3 +1,4 @@ +<<<<<<< HEAD import Foundation public protocol AIService: Sendable { @@ -166,5 +167,24 @@ public struct SongProjectUpdateResult: Equatable, Sendable { self.changedScopes = changedScopes self.notes = notes self.followUpQuestions = followUpQuestions +======= +public protocol AIService: Sendable { + func planSongProject(from request: SongPlanningRequest) async throws -> SongPlanningDraft +} + +public struct SongPlanningRequest: Equatable, Sendable { + public let userIntent: String + + public init(userIntent: String) { + self.userIntent = userIntent + } +} + +public struct SongPlanningDraft: Equatable, Sendable { + public let summary: String + + public init(summary: String) { + self.summary = summary +>>>>>>> origin/master } } diff --git a/Tests/MusicAssistantCoreTests/AppConfigurationTests.swift b/Tests/MusicAssistantCoreTests/AppConfigurationTests.swift index 77b8025..480316c 100644 --- a/Tests/MusicAssistantCoreTests/AppConfigurationTests.swift +++ b/Tests/MusicAssistantCoreTests/AppConfigurationTests.swift @@ -4,18 +4,28 @@ import XCTest final class AppConfigurationTests: XCTestCase { func testDevelopmentConfigurationReadsKnownEnvironmentKeys() { let configuration = AppConfiguration.development(environment: [ +<<<<<<< HEAD "MUSIC_ASSISTANT_OPENAI_API_KEY": "openai-test-key", "MUSIC_ASSISTANT_PRIVATE_AI_RULES": " private product rules " ]) XCTAssertEqual(configuration.openAIAPIKey, "openai-test-key") XCTAssertEqual(configuration.privateApplicationRules?.content, "private product rules") +======= + "MUSIC_ASSISTANT_OPENAI_API_KEY": "openai-test-key" + ]) + + XCTAssertEqual(configuration.openAIAPIKey, "openai-test-key") +>>>>>>> origin/master } func testDevelopmentConfigurationAllowsMissingSecrets() { let configuration = AppConfiguration.development(environment: [:]) XCTAssertNil(configuration.openAIAPIKey) +<<<<<<< HEAD XCTAssertNil(configuration.privateApplicationRules) +======= +>>>>>>> origin/master } } diff --git a/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift b/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift index fe9d478..993daa5 100644 --- a/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift +++ b/Tests/MusicAssistantCoreTests/SongProjectModelTests.swift @@ -119,6 +119,7 @@ final class SongProjectModelTests: XCTestCase { XCTAssertEqual(project.genres.map(\.id), ["folk", "electronic"]) } +<<<<<<< HEAD func testRestoresPreviousVersionWithoutDestroyingLaterVersions() { let createdAt = Date(timeIntervalSince1970: 100) let restoredAt = Date(timeIntervalSince1970: 900) @@ -177,6 +178,8 @@ final class SongProjectModelTests: XCTestCase { XCTAssertEqual(project, restoredProject) } +======= +>>>>>>> origin/master func testGenreStylesAreModeledAsMultiSelect() { let arabicPop = GenreStyle(id: "arabic-pop", name: "Arabic Pop") let synthwave = GenreStyle(id: "synthwave", name: "Synthwave") diff --git a/docs/TASKS.md b/docs/TASKS.md index 16481a9..e641061 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -35,6 +35,7 @@ requirement is missing and blocks implementation, record it in - [x] Create new/open/save song projects locally. - [x] Implement project list. - [x] Implement immutable or snapshot-based version history. +<<<<<<< HEAD - [x] Restore a previous version without destroying later versions. - [x] Add autosave strategy that does not create excessive versions. @@ -49,14 +50,37 @@ requirement is missing and blocks implementation, record it in timestamp-based placement. - [x] Add Manual/Auto toggles for supported controls. - [x] Build final review screen where every generated field can be +======= +- [ ] Restore a previous version without destroying later versions. +- [ ] Add autosave strategy that does not create excessive versions. + +## Phase 3 --- Core macOS UI + +- [ ] Build project browser. +- [ ] Build natural chat workspace as the primary entry point. +- [ ] Build editable project inspector for duration, genres, + instruments, vocals and language. +- [ ] Build song structure editor. +- [ ] Build arrangement editor supporting section-based and + timestamp-based placement. +- [ ] Add Manual/Auto toggles for supported controls. +- [ ] Build final review screen where every generated field can be +>>>>>>> origin/master edited. ## Phase 4 --- OpenAI Integration +<<<<<<< HEAD - [x] Create provider-independent `AIService` interface. - [x] Implement OpenAI client. - [x] Define private application-rule injection mechanism. - [x] Define structured AI response schema for SongProject updates. +======= +- [ ] Create provider-independent `AIService` interface. +- [ ] Implement OpenAI client. +- [ ] Define private application-rule injection mechanism. +- [ ] Define structured AI response schema for SongProject updates. +>>>>>>> origin/master - [ ] Implement idea → complete project generation. - [ ] Implement existing lyrics → correction/improvement flow. - [ ] Implement Auto mode for structure, arrangement, BPM/key/maqam