697 lines
27 KiB
Swift
697 lines
27 KiB
Swift
import Cocoa
|
|
|
|
// MARK: - History Search Panel (Ctrl+R)
|
|
class HistorySearchPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {
|
|
private var tableView: NSTableView!
|
|
private var searchField: NSTextField!
|
|
private var filteredHistory: [String] = []
|
|
private var completion: ((String?) -> Void)?
|
|
|
|
init() {
|
|
super.init(
|
|
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
|
|
styleMask: [.titled, .closable, .resizable],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
|
|
title = "Command History (Ctrl+R)"
|
|
isReleasedWhenClosed = false
|
|
setupUI()
|
|
}
|
|
|
|
private func setupUI() {
|
|
let container = NSView(frame: contentView!.bounds)
|
|
container.autoresizingMask = [.width, .height]
|
|
|
|
// Search field
|
|
let searchLabel = NSTextField(labelWithString: "Search:")
|
|
searchLabel.frame = NSRect(x: 20, y: container.bounds.height - 40, width: 60, height: 20)
|
|
searchLabel.autoresizingMask = [.minYMargin]
|
|
|
|
searchField = NSTextField(frame: NSRect(x: 85, y: container.bounds.height - 42, width: container.bounds.width - 105, height: 24))
|
|
searchField.placeholderString = "Type to search history (fuzzy search)..."
|
|
searchField.autoresizingMask = [.width, .minYMargin]
|
|
searchField.delegate = self
|
|
|
|
// Table view
|
|
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 50, width: container.bounds.width - 40, height: container.bounds.height - 110))
|
|
scrollView.autoresizingMask = [.width, .height]
|
|
scrollView.hasVerticalScroller = true
|
|
scrollView.borderType = .bezelBorder
|
|
|
|
tableView = NSTableView(frame: scrollView.bounds)
|
|
tableView.headerView = nil
|
|
tableView.rowHeight = 24
|
|
|
|
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("command"))
|
|
column.width = scrollView.bounds.width - 20
|
|
tableView.addTableColumn(column)
|
|
|
|
tableView.dataSource = self
|
|
tableView.delegate = self
|
|
tableView.target = self
|
|
tableView.doubleAction = #selector(selectCommand)
|
|
|
|
scrollView.documentView = tableView
|
|
|
|
// Buttons
|
|
let selectButton = NSButton(frame: NSRect(x: container.bounds.width - 120, y: 10, width: 100, height: 30))
|
|
selectButton.title = "Use Command"
|
|
selectButton.bezelStyle = .rounded
|
|
selectButton.target = self
|
|
selectButton.action = #selector(selectCommand)
|
|
selectButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
|
cancelButton.title = "Cancel"
|
|
cancelButton.bezelStyle = .rounded
|
|
cancelButton.target = self
|
|
cancelButton.action = #selector(cancel)
|
|
cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
let infoLabel = NSTextField(labelWithString: "Tip: Double-click or press Enter to use a command")
|
|
infoLabel.frame = NSRect(x: 20, y: 15, width: 400, height: 20)
|
|
infoLabel.textColor = .secondaryLabelColor
|
|
infoLabel.font = NSFont.systemFont(ofSize: 11)
|
|
infoLabel.autoresizingMask = [.maxYMargin]
|
|
|
|
container.addSubview(searchLabel)
|
|
container.addSubview(searchField)
|
|
container.addSubview(scrollView)
|
|
container.addSubview(selectButton)
|
|
container.addSubview(cancelButton)
|
|
container.addSubview(infoLabel)
|
|
|
|
contentView = container
|
|
|
|
filteredHistory = HistoryManager.shared.getRecentCommands(50)
|
|
tableView.reloadData()
|
|
}
|
|
|
|
func controlTextDidChange(_ obj: Notification) {
|
|
let query = searchField.stringValue
|
|
filteredHistory = HistoryManager.shared.searchHistory(query)
|
|
tableView.reloadData()
|
|
}
|
|
|
|
@objc private func selectCommand() {
|
|
let row = tableView.selectedRow
|
|
guard row >= 0 && row < filteredHistory.count else { return }
|
|
completion?(filteredHistory[row])
|
|
close()
|
|
}
|
|
|
|
@objc private func cancel() {
|
|
completion?(nil)
|
|
close()
|
|
}
|
|
|
|
func show(relativeTo window: NSWindow, completion: @escaping (String?) -> Void) {
|
|
self.completion = completion
|
|
center()
|
|
searchField.stringValue = ""
|
|
filteredHistory = HistoryManager.shared.getRecentCommands(50)
|
|
tableView.reloadData()
|
|
makeKeyAndOrderFront(nil)
|
|
searchField.becomeFirstResponder()
|
|
}
|
|
|
|
// MARK: - NSTableViewDataSource
|
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
|
return filteredHistory.count
|
|
}
|
|
|
|
// MARK: - NSTableViewDelegate
|
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
let text = NSTextField(labelWithString: filteredHistory[row])
|
|
text.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
|
|
text.lineBreakMode = .byTruncatingTail
|
|
return text
|
|
}
|
|
}
|
|
|
|
// MARK: - Snippet Panel
|
|
class SnippetPanel: NSPanel, NSTableViewDataSource, NSTableViewDelegate, NSTextFieldDelegate {
|
|
private var tableView: NSTableView!
|
|
private var searchField: NSTextField!
|
|
private var filteredSnippets: [SnippetManager.Snippet] = []
|
|
private var completion: ((SnippetManager.Snippet?) -> Void)?
|
|
|
|
init() {
|
|
super.init(
|
|
contentRect: NSRect(x: 0, y: 0, width: 700, height: 500),
|
|
styleMask: [.titled, .closable, .resizable],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
|
|
title = "Command Snippets"
|
|
isReleasedWhenClosed = false
|
|
setupUI()
|
|
}
|
|
|
|
private func setupUI() {
|
|
let container = NSView(frame: contentView!.bounds)
|
|
container.autoresizingMask = [.width, .height]
|
|
|
|
// Search field
|
|
searchField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 42, width: container.bounds.width - 140, height: 24))
|
|
searchField.placeholderString = "Search snippets..."
|
|
searchField.autoresizingMask = [.width, .minYMargin]
|
|
searchField.delegate = self
|
|
|
|
// Add button
|
|
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 110, y: container.bounds.height - 45, width: 90, height: 30))
|
|
addButton.title = "Add New"
|
|
addButton.bezelStyle = .rounded
|
|
addButton.target = self
|
|
addButton.action = #selector(addSnippet)
|
|
addButton.autoresizingMask = [.minXMargin, .minYMargin]
|
|
|
|
// Table view
|
|
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 50, width: container.bounds.width - 40, height: container.bounds.height - 110))
|
|
scrollView.autoresizingMask = [.width, .height]
|
|
scrollView.hasVerticalScroller = true
|
|
scrollView.borderType = .bezelBorder
|
|
|
|
tableView = NSTableView(frame: scrollView.bounds)
|
|
tableView.rowHeight = 24
|
|
|
|
let nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("name"))
|
|
nameColumn.title = "Name"
|
|
nameColumn.width = 150
|
|
|
|
let commandColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("command"))
|
|
commandColumn.title = "Command"
|
|
commandColumn.width = 350
|
|
|
|
let categoryColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("category"))
|
|
categoryColumn.title = "Category"
|
|
categoryColumn.width = 100
|
|
|
|
tableView.addTableColumn(nameColumn)
|
|
tableView.addTableColumn(commandColumn)
|
|
tableView.addTableColumn(categoryColumn)
|
|
|
|
tableView.dataSource = self
|
|
tableView.delegate = self
|
|
tableView.target = self
|
|
tableView.doubleAction = #selector(selectSnippet)
|
|
|
|
scrollView.documentView = tableView
|
|
|
|
// Empty state label
|
|
let emptyLabel = NSTextField(wrappingLabelWithString: "No snippets yet.\n\nTo create a snippet:\n1. Select text in the terminal\n2. Right-click and choose 'Save as Snippet'\n\nOr click 'Add New' above.")
|
|
emptyLabel.frame = NSRect(x: 0, y: 0, width: scrollView.bounds.width - 40, height: 100)
|
|
emptyLabel.alignment = .center
|
|
emptyLabel.textColor = .secondaryLabelColor
|
|
emptyLabel.font = NSFont.systemFont(ofSize: 13)
|
|
emptyLabel.tag = 999 // Tag to identify empty state label
|
|
scrollView.addSubview(emptyLabel)
|
|
|
|
// Buttons
|
|
let useButton = NSButton(frame: NSRect(x: container.bounds.width - 120, y: 10, width: 100, height: 30))
|
|
useButton.title = "Use Snippet"
|
|
useButton.bezelStyle = .rounded
|
|
useButton.target = self
|
|
useButton.action = #selector(selectSnippet)
|
|
useButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 10, width: 100, height: 30))
|
|
cancelButton.title = "Cancel"
|
|
cancelButton.bezelStyle = .rounded
|
|
cancelButton.target = self
|
|
cancelButton.action = #selector(cancel)
|
|
cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
container.addSubview(searchField)
|
|
container.addSubview(addButton)
|
|
container.addSubview(scrollView)
|
|
container.addSubview(useButton)
|
|
container.addSubview(cancelButton)
|
|
|
|
contentView = container
|
|
|
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
|
tableView.reloadData()
|
|
updateEmptyState()
|
|
}
|
|
|
|
private func updateEmptyState() {
|
|
if let scrollView = tableView.enclosingScrollView,
|
|
let emptyLabel = scrollView.viewWithTag(999) {
|
|
emptyLabel.isHidden = !filteredSnippets.isEmpty
|
|
}
|
|
}
|
|
|
|
func controlTextDidChange(_ obj: Notification) {
|
|
let query = searchField.stringValue
|
|
filteredSnippets = SnippetManager.shared.searchSnippets(query)
|
|
tableView.reloadData()
|
|
updateEmptyState()
|
|
}
|
|
|
|
@objc private func addSnippet() {
|
|
let alert = NSAlert()
|
|
alert.messageText = "Add New Snippet"
|
|
alert.informativeText = "Enter snippet details:"
|
|
|
|
let nameField = NSTextField(frame: NSRect(x: 0, y: 90, width: 300, height: 24))
|
|
nameField.placeholderString = "Name"
|
|
|
|
let commandField = NSTextField(frame: NSRect(x: 0, y: 60, width: 300, height: 24))
|
|
commandField.placeholderString = "Command"
|
|
|
|
let categoryField = NSTextField(frame: NSRect(x: 0, y: 30, width: 300, height: 24))
|
|
categoryField.placeholderString = "Category (optional)"
|
|
|
|
let descField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
|
|
descField.placeholderString = "Description (optional)"
|
|
|
|
let view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 120))
|
|
view.addSubview(nameField)
|
|
view.addSubview(commandField)
|
|
view.addSubview(categoryField)
|
|
view.addSubview(descField)
|
|
|
|
alert.accessoryView = view
|
|
alert.addButton(withTitle: "Add")
|
|
alert.addButton(withTitle: "Cancel")
|
|
|
|
if alert.runModal() == .alertFirstButtonReturn {
|
|
let snippet = SnippetManager.Snippet(
|
|
name: nameField.stringValue,
|
|
command: commandField.stringValue,
|
|
description: descField.stringValue,
|
|
category: categoryField.stringValue.isEmpty ? "General" : categoryField.stringValue
|
|
)
|
|
SnippetManager.shared.addSnippet(snippet)
|
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
|
tableView.reloadData()
|
|
updateEmptyState()
|
|
}
|
|
}
|
|
|
|
@objc private func selectSnippet() {
|
|
let row = tableView.selectedRow
|
|
guard row >= 0 && row < filteredSnippets.count else { return }
|
|
completion?(filteredSnippets[row])
|
|
close()
|
|
}
|
|
|
|
@objc private func cancel() {
|
|
completion?(nil)
|
|
close()
|
|
}
|
|
|
|
func show(relativeTo window: NSWindow, completion: @escaping (SnippetManager.Snippet?) -> Void) {
|
|
self.completion = completion
|
|
center()
|
|
searchField.stringValue = ""
|
|
filteredSnippets = SnippetManager.shared.getAllSnippets()
|
|
tableView.reloadData()
|
|
updateEmptyState()
|
|
makeKeyAndOrderFront(nil)
|
|
}
|
|
|
|
// MARK: - NSTableViewDataSource
|
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
|
return filteredSnippets.count
|
|
}
|
|
|
|
// MARK: - NSTableViewDelegate
|
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
let snippet = filteredSnippets[row]
|
|
let identifier = tableColumn?.identifier.rawValue ?? ""
|
|
|
|
let text: String
|
|
switch identifier {
|
|
case "name": text = snippet.name
|
|
case "command": text = snippet.command
|
|
case "category": text = snippet.category
|
|
default: text = ""
|
|
}
|
|
|
|
let textField = NSTextField(labelWithString: text)
|
|
textField.font = NSFont.systemFont(ofSize: 12)
|
|
textField.lineBreakMode = .byTruncatingTail
|
|
return textField
|
|
}
|
|
|
|
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
|
|
return 24
|
|
}
|
|
|
|
func tableViewSelectionDidChange(_ notification: Notification) {
|
|
// Update UI based on selection if needed
|
|
}
|
|
}
|
|
|
|
// MARK: - AI Generator Panel
|
|
class AIGeneratorPanel: NSPanel, NSTextFieldDelegate {
|
|
private var promptField: NSTextField!
|
|
private var resultField: NSTextView!
|
|
private var generateButton: NSButton!
|
|
private var useButton: NSButton!
|
|
private var statusLabel: NSTextField!
|
|
private var completion: ((String?) -> Void)?
|
|
|
|
init() {
|
|
super.init(
|
|
contentRect: NSRect(x: 0, y: 0, width: 600, height: 350),
|
|
styleMask: [.titled, .closable, .resizable],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
|
|
title = "AI Command Generator"
|
|
isReleasedWhenClosed = false
|
|
setupUI()
|
|
}
|
|
|
|
private func setupUI() {
|
|
let container = NSView(frame: contentView!.bounds)
|
|
container.autoresizingMask = [.width, .height]
|
|
|
|
// Prompt field
|
|
let promptLabel = NSTextField(labelWithString: "Describe what you want to do:")
|
|
promptLabel.frame = NSRect(x: 20, y: container.bounds.height - 35, width: 250, height: 20)
|
|
promptLabel.autoresizingMask = [.minYMargin]
|
|
|
|
promptField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 65, width: container.bounds.width - 40, height: 24))
|
|
promptField.placeholderString = "e.g., find all PDF files larger than 10MB"
|
|
promptField.autoresizingMask = [.width, .minYMargin]
|
|
promptField.delegate = self
|
|
|
|
// Generate button
|
|
generateButton = NSButton(frame: NSRect(x: 20, y: container.bounds.height - 105, width: 120, height: 30))
|
|
generateButton.title = "Generate"
|
|
generateButton.bezelStyle = .rounded
|
|
generateButton.target = self
|
|
generateButton.action = #selector(generate)
|
|
generateButton.autoresizingMask = [.minYMargin]
|
|
|
|
// Status label
|
|
statusLabel = NSTextField(labelWithString: "")
|
|
statusLabel.frame = NSRect(x: 150, y: container.bounds.height - 100, width: 300, height: 20)
|
|
statusLabel.textColor = .secondaryLabelColor
|
|
statusLabel.font = NSFont.systemFont(ofSize: 11)
|
|
statusLabel.autoresizingMask = [.minYMargin]
|
|
|
|
// Result field
|
|
let resultLabel = NSTextField(labelWithString: "Generated Command:")
|
|
resultLabel.frame = NSRect(x: 20, y: container.bounds.height - 135, width: 200, height: 20)
|
|
resultLabel.autoresizingMask = [.minYMargin]
|
|
|
|
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 60, width: container.bounds.width - 40, height: container.bounds.height - 205))
|
|
scrollView.autoresizingMask = [.width, .height]
|
|
scrollView.hasVerticalScroller = true
|
|
scrollView.borderType = .bezelBorder
|
|
|
|
resultField = NSTextView(frame: scrollView.bounds)
|
|
resultField.isEditable = true
|
|
resultField.font = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
|
|
resultField.string = "Generated command will appear here..."
|
|
resultField.textColor = .secondaryLabelColor
|
|
|
|
scrollView.documentView = resultField
|
|
|
|
// Buttons
|
|
useButton = NSButton(frame: NSRect(x: container.bounds.width - 120, y: 15, width: 100, height: 30))
|
|
useButton.title = "Use Command"
|
|
useButton.bezelStyle = .rounded
|
|
useButton.target = self
|
|
useButton.action = #selector(useCommand)
|
|
useButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
let cancelButton = NSButton(frame: NSRect(x: container.bounds.width - 230, y: 15, width: 100, height: 30))
|
|
cancelButton.title = "Cancel"
|
|
cancelButton.bezelStyle = .rounded
|
|
cancelButton.target = self
|
|
cancelButton.action = #selector(cancel)
|
|
cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
|
|
|
|
let infoLabel = NSTextField(labelWithString: "Tip: You can edit the generated command before using it")
|
|
infoLabel.frame = NSRect(x: 20, y: 20, width: 400, height: 20)
|
|
infoLabel.textColor = .secondaryLabelColor
|
|
infoLabel.font = NSFont.systemFont(ofSize: 11)
|
|
infoLabel.autoresizingMask = [.maxYMargin]
|
|
|
|
container.addSubview(promptLabel)
|
|
container.addSubview(promptField)
|
|
container.addSubview(generateButton)
|
|
container.addSubview(statusLabel)
|
|
container.addSubview(resultLabel)
|
|
container.addSubview(scrollView)
|
|
container.addSubview(useButton)
|
|
container.addSubview(cancelButton)
|
|
container.addSubview(infoLabel)
|
|
|
|
contentView = container
|
|
}
|
|
|
|
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
|
|
if commandSelector == #selector(NSResponder.insertNewline(_:)) {
|
|
generate()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
@objc private func generate() {
|
|
let prompt = promptField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !prompt.isEmpty else {
|
|
statusLabel.stringValue = "Please enter a description"
|
|
return
|
|
}
|
|
|
|
generateButton.isEnabled = false
|
|
statusLabel.stringValue = "Generating..."
|
|
resultField.string = "Generating command..."
|
|
resultField.textColor = .secondaryLabelColor
|
|
|
|
AIManager.shared.generateCommand(from: prompt) { [weak self] result in
|
|
DispatchQueue.main.async {
|
|
self?.generateButton.isEnabled = true
|
|
switch result {
|
|
case .success(let command):
|
|
self?.resultField.string = command
|
|
self?.resultField.textColor = .labelColor
|
|
self?.statusLabel.stringValue = "✓ Generated successfully"
|
|
case .failure(let error):
|
|
self?.resultField.string = "Error: \(error.localizedDescription)"
|
|
self?.resultField.textColor = .systemRed
|
|
self?.statusLabel.stringValue = "✗ Generation failed"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func useCommand() {
|
|
let command = resultField.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !command.isEmpty && !command.hasPrefix("Error:") && !command.hasPrefix("Generating") && !command.hasPrefix("Generated command") else {
|
|
return
|
|
}
|
|
completion?(command)
|
|
close()
|
|
}
|
|
|
|
@objc private func cancel() {
|
|
completion?(nil)
|
|
close()
|
|
}
|
|
|
|
func show(relativeTo window: NSWindow, completion: @escaping (String?) -> Void) {
|
|
self.completion = completion
|
|
center()
|
|
promptField.stringValue = ""
|
|
resultField.string = "Generated command will appear here..."
|
|
resultField.textColor = .secondaryLabelColor
|
|
statusLabel.stringValue = ""
|
|
makeKeyAndOrderFront(nil)
|
|
promptField.becomeFirstResponder()
|
|
}
|
|
}
|
|
|
|
// MARK: - Settings Window
|
|
class SettingsWindowController: NSWindowController {
|
|
convenience init() {
|
|
let window = NSWindow(
|
|
contentRect: NSRect(x: 0, y: 0, width: 500, height: 450),
|
|
styleMask: [.titled, .closable],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
window.title = "Settings"
|
|
window.center()
|
|
window.isReleasedWhenClosed = false
|
|
|
|
self.init(window: window)
|
|
setupUI()
|
|
}
|
|
|
|
private func setupUI() {
|
|
guard let window = window else { return }
|
|
|
|
let container = NSView(frame: window.contentView!.bounds)
|
|
container.autoresizingMask = [.width, .height]
|
|
|
|
var yPos = container.bounds.height - 40
|
|
|
|
// Theme selection
|
|
let themeLabel = NSTextField(labelWithString: "Theme:")
|
|
themeLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let themePopup = NSPopUpButton(frame: NSRect(x: 150, y: yPos - 5, width: 200, height: 30))
|
|
themePopup.addItems(withTitles: TerminalSettings.Theme.allThemes.map { $0.name })
|
|
themePopup.selectItem(at: TerminalSettings.Theme.allThemes.firstIndex(where: { $0.name == TerminalSettings.shared.theme.name }) ?? 0)
|
|
themePopup.target = self
|
|
themePopup.action = #selector(themeChanged(_:))
|
|
|
|
container.addSubview(themeLabel)
|
|
container.addSubview(themePopup)
|
|
|
|
yPos -= 50
|
|
|
|
// Font size
|
|
let fontLabel = NSTextField(labelWithString: "Font Size:")
|
|
fontLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let fontField = NSTextField(frame: NSRect(x: 150, y: yPos, width: 60, height: 24))
|
|
fontField.stringValue = "\(Int(TerminalSettings.shared.fontSize))"
|
|
fontField.target = self
|
|
fontField.action = #selector(fontSizeChanged(_:))
|
|
|
|
container.addSubview(fontLabel)
|
|
container.addSubview(fontField)
|
|
|
|
yPos -= 50
|
|
|
|
// Auto-suggestions toggle
|
|
let suggestLabel = NSTextField(labelWithString: "Auto-suggestions:")
|
|
suggestLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let suggestCheckbox = NSButton(checkboxWithTitle: "Enable", target: self, action: #selector(suggestionsToggled(_:)))
|
|
suggestCheckbox.frame = NSRect(x: 150, y: yPos - 2, width: 100, height: 24)
|
|
suggestCheckbox.state = TerminalSettings.shared.enableAutoSuggestions ? .on : .off
|
|
|
|
container.addSubview(suggestLabel)
|
|
container.addSubview(suggestCheckbox)
|
|
|
|
yPos -= 50
|
|
|
|
// AI Provider
|
|
let aiLabel = NSTextField(labelWithString: "AI Provider:")
|
|
aiLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let aiPopup = NSPopUpButton(frame: NSRect(x: 150, y: yPos - 5, width: 200, height: 30))
|
|
aiPopup.addItems(withTitles: TerminalSettings.AIProvider.allCases.map { $0.rawValue })
|
|
aiPopup.selectItem(withTitle: TerminalSettings.shared.aiProvider.rawValue)
|
|
aiPopup.target = self
|
|
aiPopup.action = #selector(aiProviderChanged(_:))
|
|
|
|
container.addSubview(aiLabel)
|
|
container.addSubview(aiPopup)
|
|
|
|
yPos -= 50
|
|
|
|
// OpenAI Key
|
|
let openAILabel = NSTextField(labelWithString: "OpenAI Key:")
|
|
openAILabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let openAIField = NSSecureTextField(frame: NSRect(x: 150, y: yPos, width: 320, height: 24))
|
|
openAIField.placeholderString = "sk-..."
|
|
openAIField.stringValue = TerminalSettings.shared.openAIKey
|
|
openAIField.target = self
|
|
openAIField.action = #selector(openAIKeyChanged(_:))
|
|
|
|
container.addSubview(openAILabel)
|
|
container.addSubview(openAIField)
|
|
|
|
yPos -= 50
|
|
|
|
// Ollama URL
|
|
let ollamaLabel = NSTextField(labelWithString: "Ollama URL:")
|
|
ollamaLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let ollamaField = NSTextField(frame: NSRect(x: 150, y: yPos, width: 320, height: 24))
|
|
ollamaField.placeholderString = "http://localhost:11434"
|
|
ollamaField.stringValue = TerminalSettings.shared.ollamaURL
|
|
ollamaField.target = self
|
|
ollamaField.action = #selector(ollamaURLChanged(_:))
|
|
|
|
container.addSubview(ollamaLabel)
|
|
container.addSubview(ollamaField)
|
|
|
|
yPos -= 50
|
|
|
|
// Ollama Model
|
|
let modelLabel = NSTextField(labelWithString: "Ollama Model:")
|
|
modelLabel.frame = NSRect(x: 20, y: yPos, width: 120, height: 20)
|
|
|
|
let modelField = NSTextField(frame: NSRect(x: 150, y: yPos, width: 320, height: 24))
|
|
modelField.placeholderString = "llama3.2:latest"
|
|
modelField.stringValue = TerminalSettings.shared.ollamaModel
|
|
modelField.target = self
|
|
modelField.action = #selector(ollamaModelChanged(_:))
|
|
|
|
container.addSubview(modelLabel)
|
|
container.addSubview(modelField)
|
|
|
|
yPos -= 50
|
|
|
|
// Info label
|
|
let infoLabel = NSTextField(wrappingLabelWithString: "Note: For Ollama, make sure it's running locally. For OpenAI, you need an API key from platform.openai.com")
|
|
infoLabel.frame = NSRect(x: 20, y: 20, width: 460, height: 40)
|
|
infoLabel.textColor = .secondaryLabelColor
|
|
infoLabel.font = NSFont.systemFont(ofSize: 11)
|
|
|
|
container.addSubview(infoLabel)
|
|
|
|
window.contentView = container
|
|
}
|
|
|
|
@objc private func themeChanged(_ sender: NSPopUpButton) {
|
|
let themes = TerminalSettings.Theme.allThemes
|
|
if sender.indexOfSelectedItem < themes.count {
|
|
TerminalSettings.shared.theme = themes[sender.indexOfSelectedItem]
|
|
NotificationCenter.default.post(name: NSNotification.Name("ThemeChanged"), object: nil)
|
|
}
|
|
}
|
|
|
|
@objc private func fontSizeChanged(_ sender: NSTextField) {
|
|
if let size = Double(sender.stringValue), size >= 8 && size <= 72 {
|
|
TerminalSettings.shared.fontSize = CGFloat(size)
|
|
NotificationCenter.default.post(name: NSNotification.Name("ThemeChanged"), object: nil)
|
|
}
|
|
}
|
|
|
|
@objc private func suggestionsToggled(_ sender: NSButton) {
|
|
TerminalSettings.shared.enableAutoSuggestions = sender.state == .on
|
|
}
|
|
|
|
@objc private func aiProviderChanged(_ sender: NSPopUpButton) {
|
|
let providers = TerminalSettings.AIProvider.allCases
|
|
if sender.indexOfSelectedItem < providers.count {
|
|
TerminalSettings.shared.aiProvider = providers[sender.indexOfSelectedItem]
|
|
AIManager.shared.updateService()
|
|
}
|
|
}
|
|
|
|
@objc private func openAIKeyChanged(_ sender: NSTextField) {
|
|
TerminalSettings.shared.openAIKey = sender.stringValue
|
|
AIManager.shared.updateService()
|
|
}
|
|
|
|
@objc private func ollamaURLChanged(_ sender: NSTextField) {
|
|
TerminalSettings.shared.ollamaURL = sender.stringValue
|
|
AIManager.shared.updateService()
|
|
}
|
|
|
|
@objc private func ollamaModelChanged(_ sender: NSTextField) {
|
|
TerminalSettings.shared.ollamaModel = sender.stringValue
|
|
AIManager.shared.updateService()
|
|
}
|
|
}
|