Merge master into main

This commit is contained in:
diyaa
2026-09-13 20:23:35 +02:00
7 changed files with 112 additions and 0 deletions
@@ -1,8 +1,50 @@
<<<<<<< HEAD
import SwiftUI import SwiftUI
struct RootView: View { struct RootView: View {
var body: some View { var body: some View {
ProjectBrowserView() 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
} }
} }
@@ -2,6 +2,7 @@ import Foundation
public struct AppConfiguration: Equatable, Sendable { public struct AppConfiguration: Equatable, Sendable {
public let openAIAPIKey: String? public let openAIAPIKey: String?
<<<<<<< HEAD
public let privateApplicationRules: PrivateApplicationRules? public let privateApplicationRules: PrivateApplicationRules?
public init( public init(
@@ -10,12 +11,21 @@ public struct AppConfiguration: Equatable, Sendable {
) { ) {
self.openAIAPIKey = openAIAPIKey self.openAIAPIKey = openAIAPIKey
self.privateApplicationRules = privateApplicationRules self.privateApplicationRules = privateApplicationRules
=======
public init(openAIAPIKey: String?) {
self.openAIAPIKey = openAIAPIKey
>>>>>>> origin/master
} }
public static func development(environment: [String: String] = ProcessInfo.processInfo.environment) -> AppConfiguration { public static func development(environment: [String: String] = ProcessInfo.processInfo.environment) -> AppConfiguration {
AppConfiguration( AppConfiguration(
<<<<<<< HEAD
openAIAPIKey: environment["MUSIC_ASSISTANT_OPENAI_API_KEY"], openAIAPIKey: environment["MUSIC_ASSISTANT_OPENAI_API_KEY"],
privateApplicationRules: PrivateApplicationRules(content: environment["MUSIC_ASSISTANT_PRIVATE_AI_RULES"]) privateApplicationRules: PrivateApplicationRules(content: environment["MUSIC_ASSISTANT_PRIVATE_AI_RULES"])
=======
openAIAPIKey: environment["MUSIC_ASSISTANT_OPENAI_API_KEY"]
>>>>>>> origin/master
) )
} }
} }
@@ -343,6 +343,7 @@ public extension SongProject {
versions.first { $0.id == versionID } versions.first { $0.id == versionID }
} }
<<<<<<< HEAD
mutating func restoreVersion(id versionID: String, restoredAt: Date = Date()) -> Bool { mutating func restoreVersion(id versionID: String, restoredAt: Date = Date()) -> Bool {
guard let version = version(id: versionID) else { return false } guard let version = version(id: versionID) else { return false }
restoreSnapshot(version.snapshot, updatedAt: restoredAt) restoreSnapshot(version.snapshot, updatedAt: restoredAt)
@@ -373,6 +374,8 @@ public extension SongProject {
self.updatedAt = updatedAt self.updatedAt = updatedAt
} }
=======
>>>>>>> origin/master
mutating func appendSection(_ section: SongSection) { mutating func appendSection(_ section: SongSection) {
var orderedSection = section var orderedSection = section
orderedSection.order = nextSectionOrder orderedSection.order = nextSectionOrder
@@ -1,3 +1,4 @@
<<<<<<< HEAD
import Foundation import Foundation
public protocol AIService: Sendable { public protocol AIService: Sendable {
@@ -166,5 +167,24 @@ public struct SongProjectUpdateResult: Equatable, Sendable {
self.changedScopes = changedScopes self.changedScopes = changedScopes
self.notes = notes self.notes = notes
self.followUpQuestions = followUpQuestions 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
} }
} }
@@ -4,18 +4,28 @@ import XCTest
final class AppConfigurationTests: XCTestCase { final class AppConfigurationTests: XCTestCase {
func testDevelopmentConfigurationReadsKnownEnvironmentKeys() { func testDevelopmentConfigurationReadsKnownEnvironmentKeys() {
let configuration = AppConfiguration.development(environment: [ let configuration = AppConfiguration.development(environment: [
<<<<<<< HEAD
"MUSIC_ASSISTANT_OPENAI_API_KEY": "openai-test-key", "MUSIC_ASSISTANT_OPENAI_API_KEY": "openai-test-key",
"MUSIC_ASSISTANT_PRIVATE_AI_RULES": " private product rules " "MUSIC_ASSISTANT_PRIVATE_AI_RULES": " private product rules "
]) ])
XCTAssertEqual(configuration.openAIAPIKey, "openai-test-key") XCTAssertEqual(configuration.openAIAPIKey, "openai-test-key")
XCTAssertEqual(configuration.privateApplicationRules?.content, "private product rules") 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() { func testDevelopmentConfigurationAllowsMissingSecrets() {
let configuration = AppConfiguration.development(environment: [:]) let configuration = AppConfiguration.development(environment: [:])
XCTAssertNil(configuration.openAIAPIKey) XCTAssertNil(configuration.openAIAPIKey)
<<<<<<< HEAD
XCTAssertNil(configuration.privateApplicationRules) XCTAssertNil(configuration.privateApplicationRules)
=======
>>>>>>> origin/master
} }
} }
@@ -119,6 +119,7 @@ final class SongProjectModelTests: XCTestCase {
XCTAssertEqual(project.genres.map(\.id), ["folk", "electronic"]) XCTAssertEqual(project.genres.map(\.id), ["folk", "electronic"])
} }
<<<<<<< HEAD
func testRestoresPreviousVersionWithoutDestroyingLaterVersions() { func testRestoresPreviousVersionWithoutDestroyingLaterVersions() {
let createdAt = Date(timeIntervalSince1970: 100) let createdAt = Date(timeIntervalSince1970: 100)
let restoredAt = Date(timeIntervalSince1970: 900) let restoredAt = Date(timeIntervalSince1970: 900)
@@ -177,6 +178,8 @@ final class SongProjectModelTests: XCTestCase {
XCTAssertEqual(project, restoredProject) XCTAssertEqual(project, restoredProject)
} }
=======
>>>>>>> origin/master
func testGenreStylesAreModeledAsMultiSelect() { func testGenreStylesAreModeledAsMultiSelect() {
let arabicPop = GenreStyle(id: "arabic-pop", name: "Arabic Pop") let arabicPop = GenreStyle(id: "arabic-pop", name: "Arabic Pop")
let synthwave = GenreStyle(id: "synthwave", name: "Synthwave") let synthwave = GenreStyle(id: "synthwave", name: "Synthwave")
+24
View File
@@ -35,6 +35,7 @@ requirement is missing and blocks implementation, record it in
- [x] Create new/open/save song projects locally. - [x] Create new/open/save song projects locally.
- [x] Implement project list. - [x] Implement project list.
- [x] Implement immutable or snapshot-based version history. - [x] Implement immutable or snapshot-based version history.
<<<<<<< HEAD
- [x] Restore a previous version without destroying later versions. - [x] Restore a previous version without destroying later versions.
- [x] Add autosave strategy that does not create excessive 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. timestamp-based placement.
- [x] Add Manual/Auto toggles for supported controls. - [x] Add Manual/Auto toggles for supported controls.
- [x] Build final review screen where every generated field can be - [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. edited.
## Phase 4 --- OpenAI Integration ## Phase 4 --- OpenAI Integration
<<<<<<< HEAD
- [x] Create provider-independent `AIService` interface. - [x] Create provider-independent `AIService` interface.
- [x] Implement OpenAI client. - [x] Implement OpenAI client.
- [x] Define private application-rule injection mechanism. - [x] Define private application-rule injection mechanism.
- [x] Define structured AI response schema for SongProject updates. - [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 idea → complete project generation.
- [ ] Implement existing lyrics → correction/improvement flow. - [ ] Implement existing lyrics → correction/improvement flow.
- [ ] Implement Auto mode for structure, arrangement, BPM/key/maqam - [ ] Implement Auto mode for structure, arrangement, BPM/key/maqam