updated Powermode with session manager
This commit is contained in:
parent
5b43f3a2b3
commit
a26ec91e1b
@ -4,6 +4,7 @@ import SwiftUI
|
|||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
updateActivationPolicy()
|
updateActivationPolicy()
|
||||||
|
cleanupLegacyUserDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||||
@ -42,4 +43,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
NSApp.windows.first?.makeKeyAndOrderFront(nil)
|
NSApp.windows.first?.makeKeyAndOrderFront(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func cleanupLegacyUserDefaults() {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
defaults.removeObject(forKey: "defaultPowerModeConfigV2")
|
||||||
|
defaults.removeObject(forKey: "isPowerModeEnabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,7 +180,7 @@ class MiniRecorderShortcutManager: ObservableObject {
|
|||||||
if index < availableConfigurations.count {
|
if index < availableConfigurations.count {
|
||||||
let selectedConfig = availableConfigurations[index]
|
let selectedConfig = availableConfigurations[index]
|
||||||
powerModeManager.setActiveConfiguration(selectedConfig)
|
powerModeManager.setActiveConfiguration(selectedConfig)
|
||||||
await ActiveWindowService.shared.applyConfiguration(selectedConfig)
|
await PowerModeSessionManager.shared.beginSession(with: selectedConfig)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
guard let enhancementService = await self.whisperState.getEnhancementService() else { return }
|
guard let enhancementService = await self.whisperState.getEnhancementService() else { return }
|
||||||
|
|||||||
@ -26,132 +26,42 @@ class ActiveWindowService: ObservableObject {
|
|||||||
|
|
||||||
func applyConfigurationForCurrentApp() async {
|
func applyConfigurationForCurrentApp() async {
|
||||||
guard let frontmostApp = NSWorkspace.shared.frontmostApplication,
|
guard let frontmostApp = NSWorkspace.shared.frontmostApplication,
|
||||||
let bundleIdentifier = frontmostApp.bundleIdentifier else { return }
|
let bundleIdentifier = frontmostApp.bundleIdentifier else {
|
||||||
|
await PowerModeSessionManager.shared.endSession()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
currentApplication = frontmostApp
|
currentApplication = frontmostApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var configToApply: PowerModeConfig?
|
||||||
|
|
||||||
if let browserType = BrowserType.allCases.first(where: { $0.bundleIdentifier == bundleIdentifier }) {
|
if let browserType = BrowserType.allCases.first(where: { $0.bundleIdentifier == bundleIdentifier }) {
|
||||||
logger.debug("🌐 Detected Browser: \(browserType.displayName)")
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
logger.debug("📝 Attempting to get URL from \(browserType.displayName)")
|
|
||||||
let currentURL = try await browserURLService.getCurrentURL(from: browserType)
|
let currentURL = try await browserURLService.getCurrentURL(from: browserType)
|
||||||
logger.debug("📍 Successfully got URL: \(currentURL)")
|
|
||||||
|
|
||||||
if let config = PowerModeManager.shared.getConfigurationForURL(currentURL) {
|
if let config = PowerModeManager.shared.getConfigurationForURL(currentURL) {
|
||||||
logger.debug("⚙️ Found URL Configuration: \(config.name) for URL: \(currentURL)")
|
configToApply = config
|
||||||
await MainActor.run {
|
|
||||||
PowerModeManager.shared.setActiveConfiguration(config)
|
|
||||||
}
|
|
||||||
await applyConfiguration(config)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
logger.debug("📝 No URL configuration found for: \(currentURL)")
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("❌ Failed to get URL from \(browserType.displayName): \(error.localizedDescription)")
|
logger.error("❌ Failed to get URL from \(browserType.displayName): \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let config = PowerModeManager.shared.getConfigurationForApp(bundleIdentifier) {
|
if configToApply == nil {
|
||||||
|
configToApply = PowerModeManager.shared.getConfigurationForApp(bundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let config = configToApply {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
PowerModeManager.shared.setActiveConfiguration(config)
|
PowerModeManager.shared.setActiveConfiguration(config)
|
||||||
}
|
}
|
||||||
await applyConfiguration(config)
|
await PowerModeSessionManager.shared.beginSession(with: config)
|
||||||
} else {
|
} else {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
PowerModeManager.shared.setActiveConfiguration(nil)
|
PowerModeManager.shared.setActiveConfiguration(nil)
|
||||||
}
|
}
|
||||||
}
|
await PowerModeSessionManager.shared.endSession()
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies a specific configuration
|
|
||||||
func applyConfiguration(_ config: PowerModeConfig) async {
|
|
||||||
guard let enhancementService = enhancementService else { return }
|
|
||||||
|
|
||||||
// Capture current state before making changes
|
|
||||||
let wasScreenCaptureEnabled = await MainActor.run {
|
|
||||||
enhancementService.useScreenCaptureContext
|
|
||||||
}
|
|
||||||
let wasEnhancementEnabled = await MainActor.run {
|
|
||||||
enhancementService.isEnhancementEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
await MainActor.run {
|
|
||||||
enhancementService.isEnhancementEnabled = config.isAIEnhancementEnabled
|
|
||||||
enhancementService.useScreenCaptureContext = config.useScreenCapture
|
|
||||||
|
|
||||||
if config.isAIEnhancementEnabled {
|
|
||||||
if let promptId = config.selectedPrompt,
|
|
||||||
let uuid = UUID(uuidString: promptId) {
|
|
||||||
enhancementService.selectedPromptId = uuid
|
|
||||||
} else {
|
|
||||||
if let firstPrompt = enhancementService.allPrompts.first {
|
|
||||||
enhancementService.selectedPromptId = firstPrompt.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.isAIEnhancementEnabled,
|
|
||||||
let aiService = enhancementService.getAIService() {
|
|
||||||
|
|
||||||
if let providerName = config.selectedAIProvider,
|
|
||||||
let provider = AIProvider(rawValue: providerName) {
|
|
||||||
aiService.selectedProvider = provider
|
|
||||||
|
|
||||||
if let model = config.selectedAIModel,
|
|
||||||
!model.isEmpty {
|
|
||||||
aiService.selectModel(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let language = config.selectedLanguage {
|
|
||||||
UserDefaults.standard.set(language, forKey: "SelectedLanguage")
|
|
||||||
NotificationCenter.default.post(name: .languageDidChange, object: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let whisperState = self.whisperState,
|
|
||||||
let modelName = config.selectedTranscriptionModelName,
|
|
||||||
let selectedModel = await whisperState.allAvailableModels.first(where: { $0.name == modelName }) {
|
|
||||||
|
|
||||||
let currentModelName = await MainActor.run { whisperState.currentTranscriptionModel?.name }
|
|
||||||
|
|
||||||
// Only change the model if it's different from the current one.
|
|
||||||
if currentModelName != modelName {
|
|
||||||
// Set the new model as default. This works for both local and cloud models.
|
|
||||||
await whisperState.setDefaultTranscriptionModel(selectedModel)
|
|
||||||
|
|
||||||
switch selectedModel.provider {
|
|
||||||
case .local:
|
|
||||||
await whisperState.cleanupModelResources()
|
|
||||||
|
|
||||||
if let localModel = await whisperState.availableModels.first(where: { $0.name == selectedModel.name }) {
|
|
||||||
do {
|
|
||||||
try await whisperState.loadModel(localModel)
|
|
||||||
} catch {
|
|
||||||
logger.error("❌ Power Mode: Failed to load local model '\(localModel.name)': \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .parakeet:
|
|
||||||
await whisperState.cleanupModelResources()
|
|
||||||
|
|
||||||
default:
|
|
||||||
await whisperState.cleanupModelResources()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for UI changes and model loading to complete first
|
|
||||||
try? await Task.sleep(nanoseconds: 1_500_000_000) // 1.5 seconds
|
|
||||||
|
|
||||||
// Then check if we should capture
|
|
||||||
if config.isAIEnhancementEnabled && config.useScreenCapture {
|
|
||||||
await enhancementService.captureScreenContext()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,7 +72,7 @@ struct PowerModePopover: View {
|
|||||||
private func applySelectedConfiguration() {
|
private func applySelectedConfiguration() {
|
||||||
Task {
|
Task {
|
||||||
if let config = selectedConfig {
|
if let config = selectedConfig {
|
||||||
await ActiveWindowService.shared.applyConfiguration(config)
|
await PowerModeSessionManager.shared.beginSession(with: config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
210
VoiceInk/PowerMode/PowerModeSessionManager.swift
Normal file
210
VoiceInk/PowerMode/PowerModeSessionManager.swift
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
// Represents the state of the application that can be modified by a Power Mode.
|
||||||
|
// This struct captures the settings that will be temporarily overridden.
|
||||||
|
struct ApplicationState: Codable {
|
||||||
|
var isEnhancementEnabled: Bool
|
||||||
|
var useScreenCaptureContext: Bool
|
||||||
|
var selectedPromptId: String? // Storing as String for Codable simplicity
|
||||||
|
var selectedAIProvider: String?
|
||||||
|
var selectedAIModel: String?
|
||||||
|
var selectedLanguage: String?
|
||||||
|
var transcriptionModelName: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents an active Power Mode session.
|
||||||
|
struct PowerModeSession: Codable {
|
||||||
|
let id: UUID
|
||||||
|
let startTime: Date
|
||||||
|
var originalState: ApplicationState
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class PowerModeSessionManager {
|
||||||
|
static let shared = PowerModeSessionManager()
|
||||||
|
private let sessionKey = "powerModeActiveSession.v1"
|
||||||
|
|
||||||
|
private var whisperState: WhisperState?
|
||||||
|
private var enhancementService: AIEnhancementService?
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
// Attempt to recover a session on startup in case of a crash.
|
||||||
|
recoverSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(whisperState: WhisperState, enhancementService: AIEnhancementService) {
|
||||||
|
self.whisperState = whisperState
|
||||||
|
self.enhancementService = enhancementService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begins a new Power Mode session. It captures the current state,
|
||||||
|
// applies the new configuration, and saves the session.
|
||||||
|
func beginSession(with config: PowerModeConfig) async {
|
||||||
|
guard let whisperState = whisperState, let enhancementService = enhancementService else {
|
||||||
|
print("SessionManager not configured.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Capture the current application state.
|
||||||
|
let originalState = ApplicationState(
|
||||||
|
isEnhancementEnabled: enhancementService.isEnhancementEnabled,
|
||||||
|
useScreenCaptureContext: enhancementService.useScreenCaptureContext,
|
||||||
|
selectedPromptId: enhancementService.selectedPromptId?.uuidString,
|
||||||
|
selectedAIProvider: enhancementService.getAIService()?.selectedProvider.rawValue,
|
||||||
|
selectedAIModel: enhancementService.getAIService()?.currentModel,
|
||||||
|
selectedLanguage: UserDefaults.standard.string(forKey: "SelectedLanguage"),
|
||||||
|
transcriptionModelName: whisperState.currentTranscriptionModel?.name
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. Create and save the session.
|
||||||
|
let newSession = PowerModeSession(
|
||||||
|
id: UUID(),
|
||||||
|
startTime: Date(),
|
||||||
|
originalState: originalState
|
||||||
|
)
|
||||||
|
saveSession(newSession)
|
||||||
|
|
||||||
|
// 3. Apply the new configuration's settings.
|
||||||
|
await applyConfiguration(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ends the current Power Mode session and restores the original state.
|
||||||
|
func endSession() async {
|
||||||
|
guard let session = loadSession() else { return }
|
||||||
|
|
||||||
|
// Restore the original state from the session.
|
||||||
|
await restoreState(session.originalState)
|
||||||
|
|
||||||
|
// Clear the session from UserDefaults.
|
||||||
|
clearSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies the settings from a PowerModeConfig.
|
||||||
|
private func applyConfiguration(_ config: PowerModeConfig) async {
|
||||||
|
guard let enhancementService = enhancementService else { return }
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
enhancementService.isEnhancementEnabled = config.isAIEnhancementEnabled
|
||||||
|
enhancementService.useScreenCaptureContext = config.useScreenCapture
|
||||||
|
|
||||||
|
if config.isAIEnhancementEnabled {
|
||||||
|
if let promptId = config.selectedPrompt, let uuid = UUID(uuidString: promptId) {
|
||||||
|
enhancementService.selectedPromptId = uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
if let aiService = enhancementService.getAIService() {
|
||||||
|
if let providerName = config.selectedAIProvider, let provider = AIProvider(rawValue: providerName) {
|
||||||
|
aiService.selectedProvider = provider
|
||||||
|
}
|
||||||
|
if let model = config.selectedAIModel {
|
||||||
|
aiService.selectModel(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let language = config.selectedLanguage {
|
||||||
|
UserDefaults.standard.set(language, forKey: "SelectedLanguage")
|
||||||
|
NotificationCenter.default.post(name: .languageDidChange, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let whisperState = whisperState,
|
||||||
|
let modelName = config.selectedTranscriptionModelName,
|
||||||
|
let selectedModel = await whisperState.allAvailableModels.first(where: { $0.name == modelName }),
|
||||||
|
whisperState.currentTranscriptionModel?.name != modelName {
|
||||||
|
await handleModelChange(to: selectedModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restores the application state from a saved state object.
|
||||||
|
private func restoreState(_ state: ApplicationState) async {
|
||||||
|
guard let enhancementService = enhancementService else { return }
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
enhancementService.isEnhancementEnabled = state.isEnhancementEnabled
|
||||||
|
enhancementService.useScreenCaptureContext = state.useScreenCaptureContext
|
||||||
|
enhancementService.selectedPromptId = state.selectedPromptId.flatMap(UUID.init)
|
||||||
|
|
||||||
|
if let aiService = enhancementService.getAIService() {
|
||||||
|
if let providerName = state.selectedAIProvider, let provider = AIProvider(rawValue: providerName) {
|
||||||
|
aiService.selectedProvider = provider
|
||||||
|
}
|
||||||
|
if let model = state.selectedAIModel {
|
||||||
|
aiService.selectModel(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let language = state.selectedLanguage {
|
||||||
|
UserDefaults.standard.set(language, forKey: "SelectedLanguage")
|
||||||
|
NotificationCenter.default.post(name: .languageDidChange, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let whisperState = whisperState,
|
||||||
|
let modelName = state.transcriptionModelName,
|
||||||
|
let selectedModel = await whisperState.allAvailableModels.first(where: { $0.name == modelName }),
|
||||||
|
whisperState.currentTranscriptionModel?.name != modelName {
|
||||||
|
await handleModelChange(to: selectedModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the logic for switching transcription models.
|
||||||
|
private func handleModelChange(to newModel: any TranscriptionModel) async {
|
||||||
|
guard let whisperState = whisperState else { return }
|
||||||
|
|
||||||
|
await whisperState.setDefaultTranscriptionModel(newModel)
|
||||||
|
|
||||||
|
switch newModel.provider {
|
||||||
|
case .local:
|
||||||
|
await whisperState.cleanupModelResources()
|
||||||
|
if let localModel = await whisperState.availableModels.first(where: { $0.name == newModel.name }) {
|
||||||
|
do {
|
||||||
|
try await whisperState.loadModel(localModel)
|
||||||
|
} catch {
|
||||||
|
// Log error appropriately
|
||||||
|
print("Power Mode: Failed to load local model '\(localModel.name)': \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .parakeet:
|
||||||
|
await whisperState.cleanupModelResources()
|
||||||
|
// Parakeet models are loaded on demand, so we only need to clean up.
|
||||||
|
|
||||||
|
default:
|
||||||
|
await whisperState.cleanupModelResources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func recoverSession() {
|
||||||
|
guard let session = loadSession() else { return }
|
||||||
|
print("Recovering abandoned Power Mode session.")
|
||||||
|
Task {
|
||||||
|
await endSession()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UserDefaults Persistence
|
||||||
|
|
||||||
|
private func saveSession(_ session: PowerModeSession) {
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(session)
|
||||||
|
UserDefaults.standard.set(data, forKey: sessionKey)
|
||||||
|
} catch {
|
||||||
|
print("Error saving Power Mode session: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadSession() -> PowerModeSession? {
|
||||||
|
guard let data = UserDefaults.standard.data(forKey: sessionKey) else { return nil }
|
||||||
|
do {
|
||||||
|
return try JSONDecoder().decode(PowerModeSession.self, from: data)
|
||||||
|
} catch {
|
||||||
|
print("Error loading Power Mode session: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func clearSession() {
|
||||||
|
UserDefaults.standard.removeObject(forKey: sessionKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -106,6 +106,11 @@ class WhisperState: NSObject, ObservableObject {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
// Configure the session manager
|
||||||
|
if let enhancementService = enhancementService {
|
||||||
|
PowerModeSessionManager.shared.configure(whisperState: self, enhancementService: enhancementService)
|
||||||
|
}
|
||||||
|
|
||||||
// Set the whisperState reference after super.init()
|
// Set the whisperState reference after super.init()
|
||||||
self.localTranscriptionService = LocalTranscriptionService(modelsDirectory: self.modelsDirectory, whisperState: self)
|
self.localTranscriptionService = LocalTranscriptionService(modelsDirectory: self.modelsDirectory, whisperState: self)
|
||||||
|
|
||||||
@ -214,6 +219,7 @@ class WhisperState: NSObject, ObservableObject {
|
|||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
recordingState = .idle
|
recordingState = .idle
|
||||||
}
|
}
|
||||||
|
await PowerModeSessionManager.shared.endSession()
|
||||||
await cleanupModelResources()
|
await cleanupModelResources()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -367,6 +373,7 @@ class WhisperState: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await self.dismissMiniRecorder()
|
await self.dismissMiniRecorder()
|
||||||
|
await PowerModeSessionManager.shared.endSession()
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
do {
|
do {
|
||||||
@ -400,6 +407,7 @@ class WhisperState: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await self.dismissMiniRecorder()
|
await self.dismissMiniRecorder()
|
||||||
|
await PowerModeSessionManager.shared.endSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user