Files
wisper-key/Sources/WhisperKey/SettingsWindow.swift
T

94 lines
4.0 KiB
Swift

import AppKit
@MainActor
final class SettingsWindowController: NSWindowController {
static let shared = SettingsWindowController()
private let keyField = NSSecureTextField()
private let glossaryField = NSTextField()
private let modelPopup = NSPopUpButton()
private let fillerCheckbox = NSButton(checkboxWithTitle: "Удалять «э», «эм», «как бы», «короче»", target: nil, action: nil)
private init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 560, height: 300),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
window.title = "Настройки WhisperKey"
window.center()
super.init(window: window)
buildUI()
}
required init?(coder: NSCoder) { nil }
func show() {
loadValues()
showWindow(nil)
NSApp.activate(ignoringOtherApps: true)
}
private func buildUI() {
guard let content = window?.contentView else { return }
let grid = NSGridView(views: [
[label("OpenAI API key"), keyField],
[label("Облачная модель"), modelPopup],
[label("Словарь терминов"), glossaryField],
[NSView(), fillerCheckbox],
])
grid.translatesAutoresizingMaskIntoConstraints = false
grid.rowSpacing = 14
grid.columnSpacing = 16
grid.column(at: 0).xPlacement = .trailing
modelPopup.addItems(withTitles: ["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"])
let note = NSTextField(wrappingLabelWithString: "F5 — локально, подтверждённый текст появляется по мере речи. Option+F5 — OpenAI с обычными крупными фрагментами. После 5 секунд тишины запись завершается автоматически.")
note.textColor = .secondaryLabelColor
note.translatesAutoresizingMaskIntoConstraints = false
let save = NSButton(title: "Сохранить", target: self, action: #selector(save))
save.keyEquivalent = "\r"
save.translatesAutoresizingMaskIntoConstraints = false
content.addSubview(grid)
content.addSubview(note)
content.addSubview(save)
NSLayoutConstraint.activate([
grid.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 24),
grid.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -24),
grid.topAnchor.constraint(equalTo: content.topAnchor, constant: 28),
note.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 24),
note.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -24),
note.topAnchor.constraint(equalTo: grid.bottomAnchor, constant: 24),
save.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -24),
save.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -20),
])
}
private func label(_ value: String) -> NSTextField {
NSTextField(labelWithString: value)
}
private func loadValues() {
let settings = AppSettings.shared
keyField.stringValue = settings.apiKey() ?? ""
glossaryField.stringValue = settings.glossary
modelPopup.selectItem(withTitle: settings.cloudModel)
fillerCheckbox.state = settings.removeFillers ? .on : .off
}
@objc private func save() {
do {
let settings = AppSettings.shared
if !keyField.stringValue.isEmpty { try settings.saveAPIKey(keyField.stringValue) }
settings.glossary = glossaryField.stringValue
settings.cloudModel = modelPopup.titleOfSelectedItem ?? "gpt-4o-transcribe"
settings.removeFillers = fillerCheckbox.state == .on
window?.close()
} catch {
let alert = NSAlert(error: error)
alert.runModal()
}
}
}