diff --git a/VoiceInk/Models/PredefinedModels.swift b/VoiceInk/Models/PredefinedModels.swift index 0a76c97..2681aca 100644 --- a/VoiceInk/Models/PredefinedModels.swift +++ b/VoiceInk/Models/PredefinedModels.swift @@ -49,7 +49,7 @@ import Foundation displayName: "Tiny", size: "75 MiB", supportedLanguages: getLanguageDictionary(isMultilingual: true), - description: "Tiny model, fastest, least accurate, supports multiple languages", + description: "Tiny model, fastest, least accurate", speed: 0.95, accuracy: 0.6, ramUsage: 0.3, @@ -72,7 +72,7 @@ import Foundation size: "142 MiB", supportedLanguages: getLanguageDictionary(isMultilingual: false), description: "Base model optimized for English, good balance between speed and accuracy", - speed: 0.8, + speed: 0.85, accuracy: 0.75, ramUsage: 0.5, hash: "137c40403d78fd54d454da0f9bd998f78703390c" @@ -82,8 +82,8 @@ import Foundation displayName: "Large v2", size: "2.9 GiB", supportedLanguages: getLanguageDictionary(isMultilingual: true), - description: "Large model v2, slower than Medium but more accurate, supports multiple languages", - speed: 0.5, + description: "Large model v2, slower than Medium but more accurate", + speed: 0.3, accuracy: 0.96, ramUsage: 3.8, hash: "0f4c8e30f21cf1769f637135f521436792c48186" @@ -93,8 +93,8 @@ import Foundation displayName: "Large v3", size: "2.9 GiB", supportedLanguages: getLanguageDictionary(isMultilingual: true, isLargeV3: true), - description: "Large model v3, very slow but most accurate, supports multiple languages", - speed: 0.5, + description: "Large model v3, very slow but most accurate", + speed: 0.3, accuracy: 0.98, ramUsage: 3.9, hash: "ad82bf6a9043ceed055076d0fd39f5f186ff8062" @@ -105,8 +105,8 @@ import Foundation size: "1.5 GiB", supportedLanguages: getLanguageDictionary(isMultilingual: true, isLargeV3: true), description: - "Large model v3 Turbo, faster than v3 with similar accuracy, supports multiple languages", - speed: 0.7, + "Large model v3 Turbo, faster than v3 with similar accuracy", + speed: 0.75, accuracy: 0.97, ramUsage: 1.8, hash: "4af2b29d7ec73d781377bfd1758ca957a807e941" @@ -117,8 +117,8 @@ import Foundation size: "547 MiB", supportedLanguages: getLanguageDictionary(isMultilingual: true, isLargeV3: true), description: "Quantized version of Large v3 Turbo, faster with slightly lower accuracy", - speed: 0.7, - accuracy: 0.96, + speed: 0.75, + accuracy: 0.95, ramUsage: 1.0, hash: "e050f7970618a659205450ad97eb95a18d69c9ee" ), @@ -127,7 +127,7 @@ import Foundation CloudModel( name: "whisper-large-v3-turbo", displayName: "Whisper Large v3 Turbo (Groq)", - description: "Groq's ultra-fast Whisper Large v3 Turbo model with lightning-speed inference", + description: "Whisper Large v3 Turbo model with Groq'slightning-speed inference", provider: .groq, speed: 0.65, accuracy: 0.96, @@ -139,7 +139,7 @@ import Foundation displayName: "Scribe v1 (ElevenLabs)", description: "ElevenLabs' Scribe model for fast and accurate transcription.", provider: .elevenLabs, - speed: 0.75, + speed: 0.7, accuracy: 0.98, isMultilingual: true, supportedLanguages: getLanguageDictionary(isMultilingual: true, isLargeV3: true) diff --git a/VoiceInk/Views/Metrics/MetricsSetupView.swift b/VoiceInk/Views/Metrics/MetricsSetupView.swift index f167f24..7bbd381 100644 --- a/VoiceInk/Views/Metrics/MetricsSetupView.swift +++ b/VoiceInk/Views/Metrics/MetricsSetupView.swift @@ -7,183 +7,162 @@ struct MetricsSetupView: View { @State private var isScreenRecordingEnabled = CGPreflightScreenCaptureAccess() var body: some View { - GeometryReader { geometry in - ScrollView { - VStack(spacing: geometry.size.height * 0.05) { - // Header - VStack(spacing: geometry.size.height * 0.02) { - AppIconView() + ScrollView { + VStack(spacing: 24) { + // Header + VStack(spacing: 12) { + AppIconView() + .frame(width: 80, height: 80) + .padding(.bottom, 20) + + VStack(spacing: 4) { + Text("Welcome to VoiceInk") + .font(.system(size: 28, weight: .bold, design: .rounded)) + .multilineTextAlignment(.center) - VStack(spacing: geometry.size.height * 0.01) { - Text("Welcome to VoiceInk") - .font(.system(size: min(32, geometry.size.width * 0.05), weight: .bold, design: .rounded)) - .multilineTextAlignment(.center) - - Text("Complete the setup to get started") - .font(.system(size: min(16, geometry.size.width * 0.025))) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) + Text("Complete the setup to get started") + .font(.system(size: 16)) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + } + .padding(.top, 20) + .padding(.bottom, 20) + + // Setup Steps + VStack(alignment: .leading, spacing: 0) { + ForEach(0..<4) { index in + setupStep(for: index) + if index < 3 { + Divider().padding(.leading, 70) } } - .padding(.top, geometry.size.height * 0.03) - - // Setup Steps - VStack(spacing: geometry.size.height * 0.02) { - ForEach(0..<4) { index in - setupStep(for: index, geometry: geometry) - } - } - .padding(.horizontal, geometry.size.width * 0.03) - - Spacer(minLength: geometry.size.height * 0.02) - - // Action Button - actionButton - .frame(maxWidth: min(600, geometry.size.width * 0.8)) - - // Help Text - helpText - .padding(.bottom, geometry.size.height * 0.03) - } - .padding(.horizontal, geometry.size.width * 0.05) - .frame(minHeight: geometry.size.height) - .background { - Color(.controlBackgroundColor) } + .background(Color(NSColor.textBackgroundColor)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.gray.opacity(0.2), lineWidth: 1) + ) + .padding(.horizontal) + + Spacer(minLength: 20) + + // Action Button + actionButton + .frame(maxWidth: 400) + + // Help Text + helpText } + .padding() } - .frame(minWidth: 500, minHeight: 400) + .frame(minWidth: 500, minHeight: 600) + .background(Color(NSColor.windowBackgroundColor)) } - private func setupStep(for index: Int, geometry: GeometryProxy) -> some View { - let isCompleted: Bool - let icon: String - let title: String - let description: String + private func setupStep(for index: Int) -> some View { + let stepInfo: (isCompleted: Bool, icon: String, title: String, description: String) switch index { case 0: - isCompleted = KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) != nil - icon = "command" - title = "Set Keyboard Shortcut" - description = "Set up a keyboard shortcut to use VoiceInk anywhere" + stepInfo = ( + isCompleted: KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) != nil, + icon: "command", + title: "Set Keyboard Shortcut", + description: "Use VoiceInk anywhere with a shortcut." + ) case 1: - isCompleted = isAccessibilityEnabled - icon = "hand.raised" - title = "Enable Accessibility" - description = "Allow VoiceInk to paste transcribed text directly at your cursor position" + stepInfo = ( + isCompleted: isAccessibilityEnabled, + icon: "hand.raised.fill", + title: "Enable Accessibility", + description: "Paste transcribed text at your cursor." + ) case 2: - isCompleted = isScreenRecordingEnabled - icon = "video" - title = "Enable Screen Recording" - description = "Allow VoiceInk to understand context from your screen for transcript Enhancement" + stepInfo = ( + isCompleted: isScreenRecordingEnabled, + icon: "video.fill", + title: "Enable Screen Recording", + description: "Get better transcriptions with screen context." + ) default: - isCompleted = whisperState.currentTranscriptionModel != nil - icon = "arrow.down" - title = "Download Model" - description = "Choose and download an AI model" + stepInfo = ( + isCompleted: whisperState.currentTranscriptionModel != nil, + icon: "arrow.down.to.line", + title: "Download Model", + description: "Choose an AI model to start transcribing." + ) } - return HStack(spacing: geometry.size.width * 0.03) { - // Status Icon - ZStack { - Circle() - .fill(isCompleted ? - Color(nsColor: .controlAccentColor).opacity(0.15) : - Color(nsColor: .systemRed).opacity(0.15)) - .frame(width: min(44, geometry.size.width * 0.08), height: min(44, geometry.size.width * 0.08)) - - Image(systemName: "\(icon).circle") - .font(.system(size: min(24, geometry.size.width * 0.04), weight: .medium)) - .foregroundColor(isCompleted ? Color(nsColor: .controlAccentColor) : Color(nsColor: .systemRed)) - .symbolRenderingMode(.hierarchical) - } + return HStack(spacing: 16) { + Image(systemName: stepInfo.icon) + .font(.system(size: 18)) + .frame(width: 40, height: 40) + .background((stepInfo.isCompleted ? Color.green : Color.accentColor).opacity(0.1)) + .foregroundColor(stepInfo.isCompleted ? .green : Color.accentColor) + .clipShape(Circle()) - // Text - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.system(size: min(16, geometry.size.width * 0.025), weight: .semibold)) - Text(description) - .font(.system(size: min(14, geometry.size.width * 0.022))) + VStack(alignment: .leading, spacing: 3) { + Text(stepInfo.title) + .font(.headline) + .fontWeight(.semibold) + Text(stepInfo.description) + .font(.subheadline) .foregroundColor(.secondary) } Spacer() - // Status indicator - if isCompleted { - Image(systemName: "checkmark.seal.fill") - .font(.system(size: min(26, geometry.size.width * 0.045), weight: .semibold)) - .foregroundColor(Color.green.opacity(0.95)) - .symbolRenderingMode(.hierarchical) + if stepInfo.isCompleted { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 24)) + .foregroundColor(.green) } else { - Circle() - .stroke(Color(nsColor: .systemRed), lineWidth: 2) - .frame(width: min(24, geometry.size.width * 0.04), height: min(24, geometry.size.width * 0.04)) + Image(systemName: "chevron.right") + .font(.system(size: 14, weight: .bold)) + .foregroundColor(Color(NSColor.separatorColor)) } } - .padding(.horizontal, geometry.size.width * 0.03) - .padding(.vertical, geometry.size.height * 0.02) - .background( - RoundedRectangle(cornerRadius: 16) - .fill(Color(.windowBackgroundColor)) - .shadow( - color: Color.black.opacity(0.05), - radius: 8, - x: 0, - y: 4 - ) - ) + .padding() } private var actionButton: some View { - Button(action: { - if isShortcutAndAccessibilityGranted { - openModelManagement() - } else { - // Handle different permission requests based on which one is missing - if KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) == nil { - openSettings() - } else if !AXIsProcessTrusted() { - if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { - NSWorkspace.shared.open(url) - } - } else if !CGPreflightScreenCaptureAccess() { - CGRequestScreenCaptureAccess() - // After requesting, open system preferences as fallback - if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") { - NSWorkspace.shared.open(url) - } - } - } - }) { - HStack(spacing: 8) { - Text(isShortcutAndAccessibilityGranted ? "Download Model" : getActionButtonTitle()) + Button(action: handleActionButton) { + HStack { + Text(getActionButtonTitle()) + .fontWeight(.semibold) Image(systemName: "arrow.right") - .font(.system(size: 14, weight: .semibold)) } - .font(.headline) .frame(maxWidth: .infinity) - .frame(height: 50) - .background( - LinearGradient( - colors: [ - Color(nsColor: .controlAccentColor), - Color(nsColor: .controlAccentColor).opacity(0.8) - ], - startPoint: .leading, - endPoint: .trailing - ) - ) + .padding(.vertical, 12) + .background(Color.accentColor) .foregroundColor(.white) - .clipShape(RoundedRectangle(cornerRadius: 14)) + .cornerRadius(12) } .buttonStyle(.plain) - .shadow( - color: Color(nsColor: .controlAccentColor).opacity(0.3), - radius: 10, - y: 5 - ) + .shadow(color: Color.accentColor.opacity(0.3), radius: 8, y: 4) + } + + private func handleActionButton() { + if isShortcutAndAccessibilityGranted { + openModelManagement() + } else { + // Handle different permission requests based on which one is missing + if KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder) == nil { + openSettings() + } else if !AXIsProcessTrusted() { + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { + NSWorkspace.shared.open(url) + } + } else if !CGPreflightScreenCaptureAccess() { + CGRequestScreenCaptureAccess() + // After requesting, open system preferences as fallback + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") { + NSWorkspace.shared.open(url) + } + } + } } private func getActionButtonTitle() -> String { @@ -193,13 +172,15 @@ struct MetricsSetupView: View { return "Enable Accessibility" } else if !CGPreflightScreenCaptureAccess() { return "Enable Screen Recording" + } else if whisperState.currentTranscriptionModel == nil { + return "Download Model" } - return "Open Settings" + return "Get Started" } private var helpText: some View { Text("Need help? Check the Help menu for support options") - .font(.system(size: 13)) + .font(.caption) .foregroundColor(.secondary) }