Simplified Design for Permission setup
This commit is contained in:
parent
b1524970c1
commit
4d1bb409ed
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user