Initial Push

This commit is contained in:
2026-01-31 23:01:54 +00:00
parent 44d036c8d6
commit 5874b31464
32 changed files with 7171 additions and 0 deletions

479
UIPanels.swift Normal file
View File

@@ -0,0 +1,479 @@
import Cocoa
// MARK: - History Search Panel
class HistorySearchPanel: NSPanel {
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: 500, height: 400),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
title = "Command History"
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 - 50, width: container.bounds.width - 40, height: 30))
searchField.placeholderString = "Search history..."
searchField.target = self
searchField.action = #selector(searchChanged)
searchField.autoresizingMask = [.width, .minYMargin]
// Table view
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 20, width: container.bounds.width - 40, height: container.bounds.height - 90))
scrollView.autoresizingMask = [.width, .height]
scrollView.hasVerticalScroller = true
tableView = NSTableView(frame: scrollView.bounds)
tableView.headerView = nil
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("command"))
column.width = scrollView.bounds.width
tableView.addTableColumn(column)
tableView.dataSource = self
tableView.delegate = self
tableView.target = self
tableView.doubleAction = #selector(selectCommand)
scrollView.documentView = tableView
container.addSubview(searchField)
container.addSubview(scrollView)
contentView = container
filteredHistory = HistoryManager.shared.getRecentCommands(50)
tableView.reloadData()
}
@objc private func searchChanged() {
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()
}
func show(relativeTo window: NSWindow, completion: @escaping (String?) -> Void) {
self.completion = completion
center()
makeKeyAndOrderFront(nil)
}
}
extension HistorySearchPanel: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return filteredHistory.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let text = NSTextField(labelWithString: filteredHistory[row])
text.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
return text
}
}
// MARK: - Snippet Panel
class SnippetPanel: NSPanel {
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: 600, height: 500),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
title = "Command Snippets"
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 - 50, width: container.bounds.width - 140, height: 30))
searchField.placeholderString = "Search snippets..."
searchField.target = self
searchField.action = #selector(searchChanged)
searchField.autoresizingMask = [.width, .minYMargin]
// Add button
let addButton = NSButton(frame: NSRect(x: container.bounds.width - 110, y: container.bounds.height - 50, 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: 20, width: container.bounds.width - 40, height: container.bounds.height - 90))
scrollView.autoresizingMask = [.width, .height]
scrollView.hasVerticalScroller = true
tableView = NSTableView(frame: scrollView.bounds)
let nameColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("name"))
nameColumn.title = "Name"
nameColumn.width = 150
let commandColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("command"))
commandColumn.title = "Command"
commandColumn.width = 300
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
container.addSubview(searchField)
container.addSubview(addButton)
container.addSubview(scrollView)
contentView = container
filteredSnippets = SnippetManager.shared.getAllSnippets()
tableView.reloadData()
}
@objc private func searchChanged() {
let query = searchField.stringValue
filteredSnippets = SnippetManager.shared.searchSnippets(query)
tableView.reloadData()
}
@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: 60, width: 300, height: 24))
nameField.placeholderString = "Name"
let commandField = NSTextField(frame: NSRect(x: 0, y: 30, width: 300, height: 24))
commandField.placeholderString = "Command"
let categoryField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
categoryField.placeholderString = "Category"
let view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 90))
view.addSubview(nameField)
view.addSubview(commandField)
view.addSubview(categoryField)
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,
category: categoryField.stringValue.isEmpty ? "General" : categoryField.stringValue
)
SnippetManager.shared.addSnippet(snippet)
filteredSnippets = SnippetManager.shared.getAllSnippets()
tableView.reloadData()
}
}
@objc private func selectSnippet() {
let row = tableView.selectedRow
guard row >= 0 && row < filteredSnippets.count else { return }
completion?(filteredSnippets[row])
close()
}
func show(relativeTo window: NSWindow, completion: @escaping (SnippetManager.Snippet?) -> Void) {
self.completion = completion
center()
makeKeyAndOrderFront(nil)
}
}
extension SnippetPanel: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return filteredSnippets.count
}
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)
return textField
}
}
// MARK: - AI Generator Panel
class AIGeneratorPanel: NSPanel {
private var promptField: NSTextField!
private var resultField: NSTextView!
private var generateButton: NSButton!
private var completion: ((String?) -> Void)?
init() {
super.init(
contentRect: NSRect(x: 0, y: 0, width: 500, height: 300),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
title = "AI Command Generator"
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 - 40, width: 200, height: 20)
promptLabel.autoresizingMask = [.minYMargin]
promptField = NSTextField(frame: NSRect(x: 20, y: container.bounds.height - 70, width: container.bounds.width - 40, height: 24))
promptField.placeholderString = "e.g., find all PDF files larger than 10MB"
promptField.autoresizingMask = [.width, .minYMargin]
// Generate button
generateButton = NSButton(frame: NSRect(x: 20, y: container.bounds.height - 110, width: 100, height: 30))
generateButton.title = "Generate"
generateButton.bezelStyle = .rounded
generateButton.target = self
generateButton.action = #selector(generate)
generateButton.autoresizingMask = [.minYMargin]
// Result field
let scrollView = NSScrollView(frame: NSRect(x: 20, y: 20, width: container.bounds.width - 40, height: container.bounds.height - 160))
scrollView.autoresizingMask = [.width, .height]
scrollView.hasVerticalScroller = true
resultField = NSTextView(frame: scrollView.bounds)
resultField.isEditable = false
resultField.font = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
resultField.string = "Generated command will appear here..."
scrollView.documentView = resultField
// Use button
let useButton = NSButton(frame: NSRect(x: container.bounds.width - 120, y: container.bounds.height - 110, width: 100, height: 30))
useButton.title = "Use Command"
useButton.bezelStyle = .rounded
useButton.target = self
useButton.action = #selector(useCommand)
useButton.autoresizingMask = [.minXMargin, .minYMargin]
container.addSubview(promptLabel)
container.addSubview(promptField)
container.addSubview(generateButton)
container.addSubview(scrollView)
container.addSubview(useButton)
contentView = container
}
@objc private func generate() {
let prompt = promptField.stringValue
guard !prompt.isEmpty else { return }
generateButton.isEnabled = false
resultField.string = "Generating..."
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
case .failure(let error):
self?.resultField.string = "Error: \(error.localizedDescription)"
}
}
}
}
@objc private func useCommand() {
let command = resultField.string
guard !command.isEmpty && !command.hasPrefix("Error:") && !command.hasPrefix("Generating") else { return }
completion?(command)
close()
}
func show(relativeTo window: NSWindow, completion: @escaping (String?) -> Void) {
self.completion = completion
center()
makeKeyAndOrderFront(nil)
}
}
// MARK: - Settings Window
class SettingsWindowController: NSWindowController {
convenience init() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 500, height: 400),
styleMask: [.titled, .closable],
backing: .buffered,
defer: false
)
window.title = "Settings"
window.center()
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: 100, height: 20)
let themePopup = NSPopUpButton(frame: NSRect(x: 130, y: yPos - 5, width: 200, height: 30))
themePopup.addItems(withTitles: TerminalSettings.Theme.allThemes.map { $0.name })
themePopup.target = self
themePopup.action = #selector(themeChanged(_:))
container.addSubview(themeLabel)
container.addSubview(themePopup)
yPos -= 50
// AI Provider
let aiLabel = NSTextField(labelWithString: "AI Provider:")
aiLabel.frame = NSRect(x: 20, y: yPos, width: 100, height: 20)
let aiPopup = NSPopUpButton(frame: NSRect(x: 130, y: yPos - 5, width: 200, height: 30))
aiPopup.addItems(withTitles: ["None", "OpenAI", "Ollama"])
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: 100, height: 20)
let openAIField = NSSecureTextField(frame: NSRect(x: 130, y: yPos, width: 300, 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: 100, height: 20)
let ollamaField = NSTextField(frame: NSRect(x: 130, y: yPos, width: 300, 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: 100, height: 20)
let modelField = NSTextField(frame: NSRect(x: 130, y: yPos, width: 300, height: 24))
modelField.placeholderString = "llama2"
modelField.stringValue = TerminalSettings.shared.ollamaModel
modelField.target = self
modelField.action = #selector(ollamaModelChanged(_:))
container.addSubview(modelLabel)
container.addSubview(modelField)
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]
}
}
@objc private func aiProviderChanged(_ sender: NSPopUpButton) {
let providers: [TerminalSettings.AIProvider] = [.none, .openai, .ollama]
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()
}
}