updated Powermode with session manager
This commit is contained in:
parent
5b43f3a2b3
commit
a26ec91e1b
@ -4,6 +4,7 @@ import SwiftUI
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
updateActivationPolicy()
|
||||
cleanupLegacyUserDefaults()
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
@ -42,4 +43,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
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 {
|
||||
let selectedConfig = availableConfigurations[index]
|
||||
powerModeManager.setActiveConfiguration(selectedConfig)
|
||||
await ActiveWindowService.shared.applyConfiguration(selectedConfig)
|
||||
await PowerModeSessionManager.shared.beginSession(with: selectedConfig)
|
||||
}
|
||||
} else {
|
||||
guard let enhancementService = await self.whisperState.getEnhancementService() else { return }
|
||||
|
||||
@ -26,132 +26,42 @@ class ActiveWindowService: ObservableObject {
|
||||
|
||||
func applyConfigurationForCurrentApp() async {
|
||||
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 {
|
||||
currentApplication = frontmostApp
|
||||
}
|
||||
|
||||
|
||||
var configToApply: PowerModeConfig?
|
||||
|
||||
if let browserType = BrowserType.allCases.first(where: { $0.bundleIdentifier == bundleIdentifier }) {
|
||||
logger.debug("🌐 Detected Browser: \(browserType.displayName)")
|
||||
|
||||
do {
|
||||
logger.debug("📝 Attempting to get URL from \(browserType.displayName)")
|
||||
let currentURL = try await browserURLService.getCurrentURL(from: browserType)
|
||||
logger.debug("📍 Successfully got URL: \(currentURL)")
|
||||
|
||||
if let config = PowerModeManager.shared.getConfigurationForURL(currentURL) {
|
||||
logger.debug("⚙️ Found URL Configuration: \(config.name) for URL: \(currentURL)")
|
||||
await MainActor.run {
|
||||
PowerModeManager.shared.setActiveConfiguration(config)
|
||||
}
|
||||
await applyConfiguration(config)
|
||||
return
|
||||
} else {
|
||||
logger.debug("📝 No URL configuration found for: \(currentURL)")
|
||||
configToApply = config
|
||||
}
|
||||
} catch {
|
||||
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 {
|
||||
PowerModeManager.shared.setActiveConfiguration(config)
|
||||
}
|
||||
await applyConfiguration(config)
|
||||
await PowerModeSessionManager.shared.beginSession(with: config)
|
||||
} else {
|
||||
await MainActor.run {
|
||||
PowerModeManager.shared.setActiveConfiguration(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
await PowerModeSessionManager.shared.endSession()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ struct PowerModePopover: View {
|
||||
private func applySelectedConfiguration() {
|
||||
Task {
|
||||
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()
|
||||
|
||||
// Configure the session manager
|
||||
if let enhancementService = enhancementService {
|
||||
PowerModeSessionManager.shared.configure(whisperState: self, enhancementService: enhancementService)
|
||||
}
|
||||
|
||||
// Set the whisperState reference after super.init()
|
||||
self.localTranscriptionService = LocalTranscriptionService(modelsDirectory: self.modelsDirectory, whisperState: self)
|
||||
|
||||
@ -214,6 +219,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
await MainActor.run {
|
||||
recordingState = .idle
|
||||
}
|
||||
await PowerModeSessionManager.shared.endSession()
|
||||
await cleanupModelResources()
|
||||
return
|
||||
}
|
||||
@ -367,6 +373,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
await self.dismissMiniRecorder()
|
||||
await PowerModeSessionManager.shared.endSession()
|
||||
|
||||
} catch {
|
||||
do {
|
||||
@ -400,6 +407,7 @@ class WhisperState: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
await self.dismissMiniRecorder()
|
||||
await PowerModeSessionManager.shared.endSession()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user