Add enhancement prompt support when power mode disabled
This commit is contained in:
parent
8537a59b2a
commit
a0a1257e3b
@ -448,7 +448,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 134;
|
||||
CURRENT_PROJECT_VERSION = 136;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = V6J6A3VWY2;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -463,7 +463,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.34;
|
||||
MARKETING_VERSION = 1.36;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@ -481,7 +481,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 134;
|
||||
CURRENT_PROJECT_VERSION = 136;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = V6J6A3VWY2;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -496,7 +496,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 1.34;
|
||||
MARKETING_VERSION = 1.36;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@ -308,21 +308,21 @@ class HotkeyManager: ObservableObject {
|
||||
|
||||
let powerModeManager = PowerModeManager.shared
|
||||
|
||||
// Only proceed if Power Mode is enabled
|
||||
guard powerModeManager.isPowerModeEnabled else { return }
|
||||
|
||||
let availableConfigurations = powerModeManager.getAllAvailableConfigurations()
|
||||
|
||||
if index < availableConfigurations.count {
|
||||
let selectedConfig = availableConfigurations[index]
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
let availableConfigurations = powerModeManager.getAllAvailableConfigurations()
|
||||
if index < availableConfigurations.count {
|
||||
let selectedConfig = availableConfigurations[index]
|
||||
powerModeManager.setActiveConfiguration(selectedConfig)
|
||||
await ActiveWindowService.shared.applyConfiguration(selectedConfig)
|
||||
}
|
||||
} else {
|
||||
guard let enhancementService = await self.whisperState.getEnhancementService(),
|
||||
enhancementService.isEnhancementEnabled else { return }
|
||||
|
||||
// Set as active configuration
|
||||
powerModeManager.setActiveConfiguration(selectedConfig)
|
||||
|
||||
// Apply the configuration
|
||||
await ActiveWindowService.shared.applyConfiguration(selectedConfig)
|
||||
|
||||
print("🎯 Switched to Power Mode: \(selectedConfig.name) via Command+\(index + 1)")
|
||||
let availablePrompts = enhancementService.allPrompts
|
||||
if index < availablePrompts.count {
|
||||
enhancementService.setActivePrompt(availablePrompts[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
VoiceInk/Views/Recorder/EnhancementPromptPopover.swift
Normal file
91
VoiceInk/Views/Recorder/EnhancementPromptPopover.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import SwiftUI
|
||||
|
||||
// Enhancement Prompt Popover for recorder views
|
||||
struct EnhancementPromptPopover: View {
|
||||
@EnvironmentObject var enhancementService: AIEnhancementService
|
||||
@State private var selectedPrompt: CustomPrompt?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
// Enhancement Toggle at the top
|
||||
HStack(spacing: 8) {
|
||||
Toggle("Enhancement Prompt", isOn: $enhancementService.isEnhancementEnabled)
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
Divider()
|
||||
.background(Color.white.opacity(0.1))
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
// Available Enhancement Prompts
|
||||
ForEach(enhancementService.allPrompts) { prompt in
|
||||
EnhancementPromptRow(
|
||||
prompt: prompt,
|
||||
isSelected: selectedPrompt?.id == prompt.id,
|
||||
isDisabled: !enhancementService.isEnhancementEnabled,
|
||||
action: {
|
||||
enhancementService.setActivePrompt(prompt)
|
||||
selectedPrompt = prompt
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
.frame(width: 200)
|
||||
.frame(maxHeight: 300)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color.black)
|
||||
.environment(\.colorScheme, .dark)
|
||||
.onAppear {
|
||||
// Set the initially selected prompt
|
||||
selectedPrompt = enhancementService.activePrompt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Row view for each enhancement prompt in the popover
|
||||
struct EnhancementPromptRow: View {
|
||||
let prompt: CustomPrompt
|
||||
let isSelected: Bool
|
||||
let isDisabled: Bool
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 8) {
|
||||
// Use the icon from the prompt
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(isDisabled ? .white.opacity(0.2) : .white.opacity(0.7))
|
||||
|
||||
Text(prompt.title)
|
||||
.foregroundColor(isDisabled ? .white.opacity(0.3) : .white.opacity(0.9))
|
||||
.font(.system(size: 13))
|
||||
.lineLimit(1)
|
||||
|
||||
if isSelected {
|
||||
Spacer()
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundColor(isDisabled ? .green.opacity(0.3) : .green)
|
||||
.font(.system(size: 10))
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(.vertical, 4)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.background(isSelected ? Color.white.opacity(0.1) : Color.clear)
|
||||
.cornerRadius(4)
|
||||
.disabled(isDisabled)
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,11 @@ struct MiniRecorderView: View {
|
||||
@ObservedObject var recorder: Recorder
|
||||
@EnvironmentObject var windowManager: MiniWindowManager
|
||||
@State private var showPowerModePopover = false
|
||||
@State private var showEnhancementPromptPopover = false
|
||||
@ObservedObject private var powerModeManager = PowerModeManager.shared
|
||||
|
||||
@EnvironmentObject private var enhancementService: AIEnhancementService
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if windowManager.isVisible {
|
||||
@ -14,10 +17,7 @@ struct MiniRecorderView: View {
|
||||
.fill(.clear)
|
||||
.background(
|
||||
ZStack {
|
||||
// Base dark background
|
||||
Color.black.opacity(0.9)
|
||||
|
||||
// Subtle gradient overlay
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color.black.opacity(0.95),
|
||||
@ -26,21 +26,17 @@ struct MiniRecorderView: View {
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
|
||||
// Very subtle visual effect for depth
|
||||
VisualEffectView(material: .hudWindow, blendingMode: .withinWindow)
|
||||
.opacity(0.05)
|
||||
}
|
||||
.clipShape(Capsule())
|
||||
)
|
||||
.overlay {
|
||||
// Subtle inner border
|
||||
Capsule()
|
||||
.strokeBorder(Color.white.opacity(0.1), lineWidth: 0.5)
|
||||
}
|
||||
.overlay {
|
||||
HStack(spacing: 0) {
|
||||
// Record Button - on the left
|
||||
NotchRecordButton(
|
||||
isRecording: whisperState.isRecording,
|
||||
isProcessing: whisperState.isProcessing
|
||||
@ -50,7 +46,6 @@ struct MiniRecorderView: View {
|
||||
.frame(width: 24)
|
||||
.padding(.leading, 8)
|
||||
|
||||
// Visualizer - centered and expanded
|
||||
Group {
|
||||
if whisperState.isProcessing {
|
||||
StaticVisualizer(color: .white)
|
||||
@ -65,21 +60,39 @@ struct MiniRecorderView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
// Power Mode Button - on the right
|
||||
NotchToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
disabled: !powerModeManager.isPowerModeEnabled
|
||||
) {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
NotchToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
disabled: false
|
||||
) {
|
||||
showPowerModePopover.toggle()
|
||||
}
|
||||
}
|
||||
.frame(width: 24)
|
||||
.padding(.trailing, 8)
|
||||
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
|
||||
PowerModePopover()
|
||||
.frame(width: 24)
|
||||
.padding(.trailing, 8)
|
||||
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
|
||||
PowerModePopover()
|
||||
}
|
||||
} else {
|
||||
NotchToggleButton(
|
||||
isEnabled: enhancementService.isEnhancementEnabled,
|
||||
icon: enhancementService.activePrompt?.icon.rawValue ?? "brain",
|
||||
color: .blue,
|
||||
disabled: false
|
||||
) {
|
||||
if enhancementService.isEnhancementEnabled {
|
||||
showEnhancementPromptPopover.toggle()
|
||||
} else {
|
||||
enhancementService.isEnhancementEnabled = true
|
||||
}
|
||||
}
|
||||
.frame(width: 24)
|
||||
.padding(.trailing, 8)
|
||||
.popover(isPresented: $showEnhancementPromptPopover, arrowEdge: .bottom) {
|
||||
EnhancementPromptPopover()
|
||||
.environmentObject(enhancementService)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
|
||||
@ -57,6 +57,7 @@ class MiniWindowManager: ObservableObject {
|
||||
|
||||
let miniRecorderView = MiniRecorderView(whisperState: whisperState, recorder: recorder)
|
||||
.environmentObject(self)
|
||||
.environmentObject(whisperState.enhancementService!)
|
||||
|
||||
let hostingController = NSHostingController(rootView: miniRecorderView)
|
||||
panel.contentView = hostingController.view
|
||||
|
||||
@ -6,8 +6,11 @@ struct NotchRecorderView: View {
|
||||
@EnvironmentObject var windowManager: NotchWindowManager
|
||||
@State private var isHovering = false
|
||||
@State private var showPowerModePopover = false
|
||||
@State private var showEnhancementPromptPopover = false
|
||||
@ObservedObject private var powerModeManager = PowerModeManager.shared
|
||||
|
||||
@EnvironmentObject private var enhancementService: AIEnhancementService
|
||||
|
||||
private var menuBarHeight: CGFloat {
|
||||
if let screen = NSScreen.main {
|
||||
if screen.safeAreaInsets.top > 0 {
|
||||
@ -18,27 +21,21 @@ struct NotchRecorderView: View {
|
||||
return NSStatusBar.system.thickness
|
||||
}
|
||||
|
||||
// Calculate exact notch width
|
||||
private var exactNotchWidth: CGFloat {
|
||||
if let screen = NSScreen.main {
|
||||
// On MacBooks with notch, safeAreaInsets.left represents half the notch width
|
||||
if screen.safeAreaInsets.left > 0 {
|
||||
// Multiply by 2 because safeAreaInsets.left is half the notch width
|
||||
return screen.safeAreaInsets.left * 2
|
||||
}
|
||||
// Fallback for non-notched Macs - use a standard width
|
||||
return 200
|
||||
}
|
||||
return 200 // Default fallback
|
||||
return 200
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if windowManager.isVisible {
|
||||
HStack(spacing: 0) {
|
||||
// Left side group with fixed width
|
||||
HStack(spacing: 8) {
|
||||
// Record Button
|
||||
NotchRecordButton(
|
||||
isRecording: whisperState.isRecording,
|
||||
isProcessing: whisperState.isProcessing
|
||||
@ -47,36 +44,51 @@ struct NotchRecorderView: View {
|
||||
}
|
||||
.frame(width: 22)
|
||||
|
||||
// Power Mode Button - moved from right side
|
||||
NotchToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
disabled: !powerModeManager.isPowerModeEnabled
|
||||
) {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
NotchToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
disabled: false
|
||||
) {
|
||||
showPowerModePopover.toggle()
|
||||
}
|
||||
}
|
||||
.frame(width: 22)
|
||||
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
|
||||
PowerModePopover()
|
||||
.frame(width: 22)
|
||||
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
|
||||
PowerModePopover()
|
||||
}
|
||||
} else {
|
||||
NotchToggleButton(
|
||||
isEnabled: enhancementService.isEnhancementEnabled,
|
||||
icon: enhancementService.activePrompt?.icon.rawValue ?? "brain",
|
||||
color: .blue,
|
||||
disabled: false
|
||||
) {
|
||||
if enhancementService.isEnhancementEnabled {
|
||||
showEnhancementPromptPopover.toggle()
|
||||
} else {
|
||||
enhancementService.isEnhancementEnabled = true
|
||||
}
|
||||
}
|
||||
.frame(width: 22)
|
||||
.popover(isPresented: $showEnhancementPromptPopover, arrowEdge: .bottom) {
|
||||
EnhancementPromptPopover()
|
||||
.environmentObject(enhancementService)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(width: 64) // Increased width for both controls
|
||||
.frame(width: 64)
|
||||
.padding(.leading, 16)
|
||||
|
||||
// Center section with exact notch width
|
||||
Rectangle()
|
||||
.fill(Color.clear)
|
||||
.frame(width: exactNotchWidth)
|
||||
.contentShape(Rectangle()) // Make the entire area tappable
|
||||
.contentShape(Rectangle())
|
||||
|
||||
// Right side group with visualizer only
|
||||
HStack(spacing: 0) {
|
||||
Spacer() // Push visualizer to the right
|
||||
Spacer()
|
||||
|
||||
Group {
|
||||
if whisperState.isProcessing {
|
||||
@ -89,12 +101,11 @@ struct NotchRecorderView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
.scaleEffect(y: min(1.0, (menuBarHeight - 8) / 25), anchor: .center)
|
||||
.frame(width: 30)
|
||||
.padding(.trailing, 8) // Add padding to keep it away from the edge
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
.frame(width: 64) // Increased width to match left side
|
||||
.frame(width: 64)
|
||||
.padding(.trailing, 16)
|
||||
}
|
||||
.frame(height: menuBarHeight)
|
||||
@ -115,7 +126,6 @@ struct NotchRecorderView: View {
|
||||
|
||||
|
||||
|
||||
// New toggle button component matching the notch aesthetic
|
||||
struct NotchToggleButton: View {
|
||||
let isEnabled: Bool
|
||||
let icon: String
|
||||
@ -131,11 +141,22 @@ struct NotchToggleButton: View {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
private var isEmoji: Bool {
|
||||
return !icon.contains(".") && !icon.contains("-") && icon.unicodeScalars.contains { !$0.isASCII }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
Text(icon)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(disabled ? .white.opacity(0.3) : (isEnabled ? .white : .white.opacity(0.6)))
|
||||
Group {
|
||||
if isEmoji {
|
||||
Text(icon)
|
||||
.font(.system(size: 12))
|
||||
} else {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
}
|
||||
.foregroundColor(disabled ? .white.opacity(0.3) : (isEnabled ? .white : .white.opacity(0.6)))
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(disabled)
|
||||
@ -161,12 +182,10 @@ struct NotchRecordButton: View {
|
||||
ProcessingIndicator(color: .white)
|
||||
.frame(width: 14, height: 14)
|
||||
} else if isRecording {
|
||||
// Show white square for recording state
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
} else {
|
||||
// Show white circle for idle state
|
||||
Circle()
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
@ -183,7 +202,6 @@ struct NotchRecordButton: View {
|
||||
} else if isRecording {
|
||||
return .red
|
||||
} else {
|
||||
// Neutral gray for idle state
|
||||
return Color(red: 0.3, green: 0.3, blue: 0.35)
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ class NotchWindowManager: ObservableObject {
|
||||
|
||||
let notchRecorderView = NotchRecorderView(whisperState: whisperState, recorder: recorder)
|
||||
.environmentObject(self)
|
||||
.environmentObject(whisperState.enhancementService!)
|
||||
|
||||
let hostingController = NotchRecorderHostingController(rootView: notchRecorderView)
|
||||
panel.contentView = hostingController.view
|
||||
|
||||
3
powerModeConfigurationsV2
Normal file
3
powerModeConfigurationsV2
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user