91 lines
4.3 KiB
Swift
91 lines
4.3 KiB
Swift
import AppKit
|
|
|
|
@MainActor
|
|
final class TranslationSettingsController: NSViewController {
|
|
private let keyField = NSSecureTextField()
|
|
private let status = NSTextField(wrappingLabelWithString: "")
|
|
private let credentials: TranslationController.Credentials
|
|
|
|
init(credentials: TranslationController.Credentials = .keychain) {
|
|
self.credentials = credentials
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError("Conjure creates Translate settings programmatically") }
|
|
|
|
override func loadView() {
|
|
view = NSView()
|
|
keyField.placeholderString = "OpenAI API key (saved in Keychain)"
|
|
keyField.setAccessibilityLabel("OpenAI API key")
|
|
let save = NSButton(title: "Save Key", target: self, action: #selector(saveKey))
|
|
let forget = NSButton(title: "Forget Key", target: self, action: #selector(forgetKey))
|
|
for button in [save, forget] {
|
|
button.bezelStyle = .rounded
|
|
button.contentTintColor = ThemePalette.accent
|
|
}
|
|
let controls = NSStackView(views: [keyField, save, forget])
|
|
controls.orientation = .horizontal
|
|
controls.alignment = .centerY
|
|
controls.spacing = 8
|
|
keyField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
keyField.widthAnchor.constraint(greaterThanOrEqualToConstant: 220).isActive = true
|
|
status.setAccessibilityLabel("Translation credential status and errors")
|
|
let myMemory = NSTextField(wrappingLabelWithString:
|
|
"MyMemory · No key · 500 bytes/request · 5,000 characters/day. Text is sent to MyMemory only when you translate; avoid sensitive content."
|
|
)
|
|
let openAI = NSTextField(wrappingLabelWithString:
|
|
"OpenAI · gpt-6-luna · Text and optional situation/context are sent to OpenAI only when you explicitly translate, not when advancing to context. Saving a key does not send your text. Your API key is stored in Keychain."
|
|
)
|
|
for label in [status, myMemory, openAI] {
|
|
label.textColor = ThemePalette.secondaryText
|
|
}
|
|
let stack = NSStackView(views: [controls, status, myMemory, openAI])
|
|
stack.orientation = .vertical
|
|
stack.alignment = .leading
|
|
stack.spacing = 12
|
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
view.addSubview(stack)
|
|
NSLayoutConstraint.activate([
|
|
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
stack.topAnchor.constraint(equalTo: view.topAnchor),
|
|
stack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
controls.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
|
])
|
|
for label in [status, myMemory, openAI] {
|
|
label.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
}
|
|
do {
|
|
setStatus(try credentials.load() == nil ? "No OpenAI API key saved." : "OpenAI API key saved in Keychain.")
|
|
} catch { setStatus(error.localizedDescription, isError: true) }
|
|
}
|
|
|
|
@objc private func saveKey() {
|
|
let key = keyField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !key.isEmpty, !key.contains(where: { $0.isNewline }) else {
|
|
setStatus("Enter a valid API key before saving.", isError: true)
|
|
return
|
|
}
|
|
do {
|
|
try credentials.save(key)
|
|
keyField.stringValue = ""
|
|
setStatus("API key saved in Keychain.")
|
|
NotificationCenter.default.post(name: Notification.Name("ConjureTranslationCredentialsChanged"), object: self)
|
|
} catch { setStatus(error.localizedDescription, isError: true) }
|
|
}
|
|
|
|
@objc private func forgetKey() {
|
|
do {
|
|
try credentials.forget()
|
|
keyField.stringValue = ""
|
|
setStatus("API key removed.")
|
|
NotificationCenter.default.post(name: Notification.Name("ConjureTranslationCredentialsChanged"), object: self)
|
|
} catch { setStatus(error.localizedDescription, isError: true) }
|
|
}
|
|
|
|
private func setStatus(_ message: String, isError: Bool = false) {
|
|
status.stringValue = message
|
|
status.textColor = isError ? ThemePalette.error : ThemePalette.secondaryText
|
|
}
|
|
}
|