Skip to content
19 changes: 19 additions & 0 deletions Packages/OsaurusCore/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
// Configure local notifications
NotificationService.shared.configureOnLaunch()

// If PocketTTS models are already on disk, preload them so the first
// speaker tap plays immediately without routing to settings.
TTSService.shared.refreshModelState()

// Set up observers for server state changes
setupObservers()

Expand Down Expand Up @@ -165,7 +169,7 @@
#endif

// Initialize directory access early so security-scoped bookmark is active
let _ = DirectoryPickerService.shared

Check warning on line 172 in Packages/OsaurusCore/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Prefer `_ = foo()` over `let _ = foo()` when discarding a result from a function (redundant_discardable_let)

if LaunchGuard.isSafeMode {
NotificationService.shared.postSafeModeActive()
Expand Down Expand Up @@ -394,6 +398,14 @@
object: nil
)

// Route "user tapped speaker but model isn't ready" to the TTS settings tab.
NotificationCenter.default.addObserver(
self,
selector: #selector(handleOpenTTSSettings(_:)),
name: .openTTSSettingsRequested,
object: nil
)

// Listen for chat view closed to resume VAD
NotificationCenter.default.addObserver(
self,
Expand Down Expand Up @@ -487,6 +499,13 @@
}
}

@objc private func handleOpenTTSSettings(_ notification: Notification) {
Task { @MainActor in
ManagementStateManager.shared.voiceSubTabRequest = "TTS"
showManagementWindow(initialTab: .voice)
}
}

public func application(_ application: NSApplication, open urls: [URL]) {
for url in urls {
handleDeepLink(url)
Expand Down Expand Up @@ -851,7 +870,7 @@
}

@objc private func handleServeCommand(_ note: Notification) {
var desiredPort: Int? = nil

Check warning on line 873 in Packages/OsaurusCore/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Optional should be implicitly initialized without nil (implicit_optional_initialization)
var exposeFlag: Bool = false
if let ui = note.userInfo {
if let p = ui["port"] as? Int {
Expand Down
8 changes: 8 additions & 0 deletions Packages/OsaurusCore/Folder/ChatExecutionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ public enum ChatExecutionContext {

/// The agent ID whose context is active for the current execution.
@TaskLocal public static var currentAgentId: UUID?

/// Assistant turn dispatching the current tool call. Used by `speak`
/// to bind TTS playback to the right message bubble
@TaskLocal public static var currentAssistantTurnId: UUID?

/// Specific tool invocation id. Used by `speak` so the inline card
/// can swap its check for a spinner while its audio plays
@TaskLocal public static var currentToolCallId: String?
}
3 changes: 3 additions & 0 deletions Packages/OsaurusCore/Managers/Chat/ChatWindowState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ final class ChatWindowState: ObservableObject {
}

func switchAgent(to newAgentId: UUID) {
TTSService.shared.stop()
if !session.turns.isEmpty { session.save() }
agentId = newAgentId
removeEphemeralProviderIfNeeded()
Expand All @@ -150,6 +151,7 @@ final class ChatWindowState: ObservableObject {
}

func startNewChat() {
TTSService.shared.stop()
if !session.turns.isEmpty { session.save() }
flushCurrentSession()
session.reset(for: agentId)
Expand All @@ -158,6 +160,7 @@ final class ChatWindowState: ObservableObject {

func loadSession(_ sessionData: ChatSessionData) {
guard sessionData.id != session.sessionId else { return }
TTSService.shared.stop()
if !session.turns.isEmpty { session.save() }
flushCurrentSession()

Expand Down
4 changes: 4 additions & 0 deletions Packages/OsaurusCore/Managers/ManagementStateManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ public final class ManagementStateManager: ObservableObject {
/// Persists the last selected tab within the current app session.
@Published public var selectedTab: ManagementTab = .settings

/// One-shot request to focus a specific sub-tab inside `VoiceView`.
/// VoiceView observes this and resets it to nil after applying.
@Published public var voiceSubTabRequest: String?

private init() {}
}
Loading
Loading