Add enhancement prompt support when power mode disabled

This commit is contained in:
Beingpax 2025-06-22 23:37:03 +05:45
parent 8537a59b2a
commit a0a1257e3b
8 changed files with 200 additions and 73 deletions

View File

@ -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;

View File

@ -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])
}
}
}
}

View 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)
}
}

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

File diff suppressed because one or more lines are too long