94 lines
3.9 KiB
Swift
94 lines
3.9 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()
|
|
}
|
|
}
|
|
}
|