vibetunnel/VibeTunnel/Presentation/Views/Settings/AdvancedSettingsView.swift
2025-06-17 22:09:02 +02:00

226 lines
11 KiB
Swift

import SwiftUI
/// Advanced settings tab for power user options
struct AdvancedSettingsView: View {
@AppStorage("debugMode")
private var debugMode = false
@AppStorage("cleanupOnStartup")
private var cleanupOnStartup = true
@State private var cliInstaller = CLIInstaller()
var body: some View {
NavigationStack {
Form {
// Integration section
Section {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Install CLI Tool")
Spacer()
if cliInstaller.isInstalled {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("CLI tool is installed")
.foregroundColor(.secondary)
}
} else {
Button("Install 'vt' Command") {
Task {
await cliInstaller.install()
}
}
.buttonStyle(.bordered)
.disabled(cliInstaller.isInstalling)
}
}
if cliInstaller.isInstalling {
ProgressView()
.scaleEffect(0.8)
}
if let error = cliInstaller.lastError {
Text(error)
.font(.caption)
.foregroundColor(.red)
} else if cliInstaller.isInstalled {
Text("The 'vt' command line tool is installed at /usr/local/bin/vt")
.font(.caption)
.foregroundStyle(.secondary)
} else {
Text("Install the 'vt' command line tool to /usr/local/bin for terminal access.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
} header: {
Text("Integration")
.font(.headline)
}
// Terminal preference section
TerminalPreferenceSection()
// Advanced section
Section {
VStack(alignment: .leading, spacing: 4) {
Toggle("Clean up old sessions on startup", isOn: $cleanupOnStartup)
Text("Automatically remove terminated sessions when the app starts.")
.font(.caption)
.foregroundStyle(.secondary)
}
} header: {
Text("Advanced")
.font(.headline)
}
// Debug section
Section {
VStack(alignment: .leading, spacing: 4) {
Toggle("Debug mode", isOn: $debugMode)
Text("Enable additional logging and debugging features.")
.font(.caption)
.foregroundStyle(.secondary)
}
} header: {
Text("Debug")
.font(.headline)
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
.navigationTitle("Advanced Settings")
}
.onAppear {
cliInstaller.checkInstallationStatus()
}
}
}
// MARK: - Terminal Preference Section
private struct TerminalPreferenceSection: View {
@AppStorage("preferredTerminal") private var preferredTerminal = Terminal.terminal.rawValue
@State private var terminalLauncher = TerminalLauncher.shared
@State private var showingError = false
@State private var errorMessage = ""
@State private var errorTitle = "Terminal Launch Failed"
var body: some View {
Section {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Preferred Terminal")
Spacer()
Picker("", selection: $preferredTerminal) {
ForEach(Terminal.installed, id: \.rawValue) { terminal in
HStack {
if let icon = terminal.appIcon {
Image(nsImage: icon)
.resizable()
.frame(width: 16, height: 16)
}
Text(terminal.displayName)
}
.tag(terminal.rawValue)
}
}
.pickerStyle(.menu)
.labelsHidden()
}
Text("Select which terminal application to use when creating new sessions")
.font(.caption)
.foregroundStyle(.secondary)
// Test button
HStack {
Text("Test Terminal")
Spacer()
Button("Test with 'echo'") {
Task {
do {
try terminalLauncher.launchCommand("echo 'VibeTunnel Terminal Test: Success!'")
} catch {
// Log the error
print("Failed to launch terminal test: \(error)")
// Set up alert content based on error type
if let terminalError = error as? TerminalLauncherError {
switch terminalError {
case .appleScriptPermissionDenied:
errorTitle = "Permission Denied"
errorMessage = "VibeTunnel needs permission to control terminal applications.\n\nPlease grant Automation permission in System Settings > Privacy & Security > Automation."
case .accessibilityPermissionDenied:
errorTitle = "Accessibility Permission Required"
errorMessage = "VibeTunnel needs Accessibility permission to send keystrokes to \(Terminal(rawValue: preferredTerminal)?.displayName ?? "terminal").\n\nPlease grant permission in System Settings > Privacy & Security > Accessibility."
case .terminalNotFound:
errorTitle = "Terminal Not Found"
errorMessage = "The selected terminal application could not be found. Please select a different terminal."
case .appleScriptExecutionFailed(let details, let errorCode):
if let code = errorCode {
switch code {
case -1_743:
errorTitle = "Permission Denied"
errorMessage = "VibeTunnel needs permission to control terminal applications.\n\nPlease grant Automation permission in System Settings > Privacy & Security > Automation."
case -1_728:
errorTitle = "Terminal Not Available"
errorMessage = "The terminal application is not running or cannot be controlled.\n\nDetails: \(details)"
case -1_708:
errorTitle = "Terminal Communication Error"
errorMessage = "The terminal did not respond to the command.\n\nDetails: \(details)"
case -25211:
errorTitle = "Accessibility Permission Required"
errorMessage = "System Events requires Accessibility permission to send keystrokes.\n\nPlease grant permission in System Settings > Privacy & Security > Accessibility."
default:
errorTitle = "Terminal Launch Failed"
errorMessage = "AppleScript error \(code): \(details)"
}
} else {
errorTitle = "Terminal Launch Failed"
errorMessage = "Failed to launch terminal: \(details)"
}
case .processLaunchFailed(let details):
errorTitle = "Process Launch Failed"
errorMessage = "Failed to start terminal process: \(details)"
}
} else {
errorTitle = "Terminal Launch Failed"
errorMessage = error.localizedDescription
}
showingError = true
}
}
}
.buttonStyle(.bordered)
}
Text("Opens a new terminal window with a test command")
.font(.caption)
.foregroundStyle(.secondary)
}
} header: {
Text("Terminal")
.font(.headline)
} footer: {
Text(
"VibeTunnel will use this terminal when launching new terminal sessions."
)
.font(.caption)
.frame(maxWidth: .infinity)
.multilineTextAlignment(.center)
}
.alert(errorTitle, isPresented: $showingError) {
Button("OK") { }
if errorTitle == "Permission Denied" {
Button("Open System Settings") {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") {
NSWorkspace.shared.open(url)
}
}
}
} message: {
Text(errorMessage)
}
}
}