diff --git a/TypeaheadAI.xcodeproj/project.pbxproj b/TypeaheadAI.xcodeproj/project.pbxproj index fe97620..2a9f159 100644 --- a/TypeaheadAI.xcodeproj/project.pbxproj +++ b/TypeaheadAI.xcodeproj/project.pbxproj @@ -102,6 +102,7 @@ 2BCB017D2A9EF9A6009F9FAC /* RequestStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BCB017C2A9EF9A6009F9FAC /* RequestStatus.swift */; }; 2BCF84352A9DD90F00359841 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BCF84342A9DD90F00359841 /* HistoryManager.swift */; }; 2BCF843A2A9DE6DA00359841 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BCF84392A9DE6DA00359841 /* GeneralSettingsView.swift */; }; + 2BD3821C2B2B7A0100F96C19 /* LayoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BD3821B2B2B7A0100F96C19 /* LayoutManager.swift */; }; 2BD3821E2B2C4B2E00F96C19 /* CanSimulateEnter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BD3821D2B2C4B2E00F96C19 /* CanSimulateEnter.swift */; }; 2BDA45C32ABEE840006128BC /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDA45C22ABEE840006128BC /* MessageView.swift */; }; 2BDDB9892B27DDE100D52BF0 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 2BDDB9882B27DDE100D52BF0 /* SwiftSoup */; }; @@ -111,6 +112,7 @@ 2BDDB9932B28341C00D52BF0 /* FunctionManager+OpenApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDDB9922B28341C00D52BF0 /* FunctionManager+OpenApplication.swift */; }; 2BDDB9952B2834B100D52BF0 /* FunctionManager+OpenURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDDB9942B2834B100D52BF0 /* FunctionManager+OpenURL.swift */; }; 2BDDB9972B28352800D52BF0 /* FunctionManager+PerformUIAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDDB9962B28352800D52BF0 /* FunctionManager+PerformUIAction.swift */; }; + 2BDDB9992B2AAE8000D52BF0 /* CanSimulateEnter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDDB9982B2AAE8000D52BF0 /* CanSimulateEnter.swift */; }; 2BE0EC222AA0956C00E47C52 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE0EC212AA0956C00E47C52 /* ModalView.swift */; }; 2BE0EC272AA17F9100E47C52 /* MouseClickMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE0EC262AA17F9100E47C52 /* MouseClickMonitor.swift */; }; 2BE7BB882B258B4C00164F88 /* CanGetUIElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE7BB872B258B4C00164F88 /* CanGetUIElements.swift */; }; @@ -234,6 +236,7 @@ 2BCB017C2A9EF9A6009F9FAC /* RequestStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestStatus.swift; sourceTree = ""; }; 2BCF84342A9DD90F00359841 /* HistoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; 2BCF84392A9DE6DA00359841 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = ""; }; + 2BD3821B2B2B7A0100F96C19 /* LayoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutManager.swift; sourceTree = ""; }; 2BD3821D2B2C4B2E00F96C19 /* CanSimulateEnter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanSimulateEnter.swift; sourceTree = ""; }; 2BDA45C22ABEE840006128BC /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; 2BDDB98A2B27DDFF00D52BF0 /* String+XMLMarkdown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+XMLMarkdown.swift"; sourceTree = ""; }; @@ -242,6 +245,7 @@ 2BDDB9922B28341C00D52BF0 /* FunctionManager+OpenApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FunctionManager+OpenApplication.swift"; sourceTree = ""; }; 2BDDB9942B2834B100D52BF0 /* FunctionManager+OpenURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FunctionManager+OpenURL.swift"; sourceTree = ""; }; 2BDDB9962B28352800D52BF0 /* FunctionManager+PerformUIAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FunctionManager+PerformUIAction.swift"; sourceTree = ""; }; + 2BDDB9982B2AAE8000D52BF0 /* CanSimulateEnter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanSimulateEnter.swift; sourceTree = ""; }; 2BE0EC212AA0956C00E47C52 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; 2BE0EC242AA17DB600E47C52 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2BE0EC262AA17F9100E47C52 /* MouseClickMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseClickMonitor.swift; sourceTree = ""; }; @@ -385,6 +389,7 @@ 2B11FCE02B0C468100325F38 /* CanSimulateClose.swift */, 2BD3821D2B2C4B2E00F96C19 /* CanSimulateEnter.swift */, 2B27450F2AB03A3D00F37D3E /* CanSimulateCopy.swift */, + 2BDDB9982B2AAE8000D52BF0 /* CanSimulateEnter.swift */, 2B3792302AB83739008D812F /* CanSimulatePaste.swift */, 2B11FCE22B0C47FA00325F38 /* CanSimulateSelectAll.swift */, 2BE7BB872B258B4C00164F88 /* CanGetUIElements.swift */, @@ -454,6 +459,7 @@ 2BE0EC242AA17DB600E47C52 /* Info.plist */, 2BA7F0862A9ABBA8003D38BA /* TypeaheadAI.entitlements */, 2B3435022B1EA33500423EE8 /* String+HTMLParser.swift */, + 2BD3821B2B2B7A0100F96C19 /* LayoutManager.swift */, ); path = TypeaheadAI; sourceTree = ""; @@ -773,6 +779,7 @@ 2BA7F0B32A9ABCBF003D38BA /* MenuView.swift in Sources */, 2B92BDBB2AA3D10800E65CFA /* ModalManager.swift in Sources */, 2BCF843A2A9DE6DA00359841 /* GeneralSettingsView.swift in Sources */, + 2BD3821C2B2B7A0100F96C19 /* LayoutManager.swift in Sources */, 2B0116F32AF98D68000C78E1 /* LoggedOutAccountView.swift in Sources */, 2B33D87D2AAC3330001193A2 /* ProfileView.swift in Sources */, 2BA7F0B52A9ABCD7003D38BA /* QuickActionManager.swift in Sources */, @@ -794,7 +801,7 @@ 2B3792312AB83739008D812F /* CanSimulatePaste.swift in Sources */, 2B8CD4942B05D278003E0589 /* CanPerformOCR.swift in Sources */, 2B11FCE32B0C47FA00325F38 /* CanSimulateSelectAll.swift in Sources */, - 2BD3821E2B2C4B2E00F96C19 /* CanSimulateEnter.swift in Sources */, + 2BDDB9992B2AAE8000D52BF0 /* CanSimulateEnter.swift in Sources */, 2B1F20462B1987E400152F13 /* ConversationManager.swift in Sources */, 2B3FAC232AAAF44D00B2D405 /* LlamaWrapper.swift in Sources */, 2BDA45C32ABEE840006128BC /* MessageView.swift in Sources */, diff --git a/TypeaheadAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TypeaheadAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 98f3813..0000000 --- a/TypeaheadAI.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,212 +0,0 @@ -{ - "pins" : [ - { - "identity" : "appauth-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openid/AppAuth-iOS.git", - "state" : { - "revision" : "71cde449f13d453227e687458144bde372d30fc7", - "version" : "1.6.2" - } - }, - { - "identity" : "functions-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/functions-swift", - "state" : { - "revision" : "98878a6e3aebfeb6aaeda234db66d64196e4cf31", - "version" : "1.1.0" - } - }, - { - "identity" : "get", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Get", - "state" : { - "revision" : "12830cc64f31789ae6f4352d2d51d03a25fc3741", - "version" : "2.1.6" - } - }, - { - "identity" : "getextensions", - "kind" : "remoteSourceControl", - "location" : "https://github.com/binaryscraping/GetExtensions", - "state" : { - "revision" : "aa20f38721142eb6592b2c8f11179d32d7d70ae3", - "version" : "1.0.0" - } - }, - { - "identity" : "googlesignin-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleSignIn-iOS", - "state" : { - "revision" : "7932d33686c1dc4d7df7a919aae47361d1cdfda4", - "version" : "7.0.0" - } - }, - { - "identity" : "gotrue-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/gotrue-swift", - "state" : { - "revision" : "e53731e21569b2e9ce6f58763ad0fbce8e2e7603", - "version" : "1.3.0" - } - }, - { - "identity" : "gtm-session-fetcher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/gtm-session-fetcher.git", - "state" : { - "revision" : "d415594121c9e8a4f9d79cecee0965cf35e74dbd", - "version" : "3.1.1" - } - }, - { - "identity" : "gtmappauth", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GTMAppAuth.git", - "state" : { - "revision" : "cee3c709307912d040bd1e06ca919875a92339c6", - "version" : "2.0.0" - } - }, - { - "identity" : "highlighterswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/smittytone/HighlighterSwift", - "state" : { - "revision" : "c0d8c22d9548c5cf9e5f15b8e015cf788553adcc", - "version" : "1.1.3" - } - }, - { - "identity" : "keyboardshortcuts", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sindresorhus/KeyboardShortcuts", - "state" : { - "revision" : "b878f8132be59576fc87e39405b1914eff9f55d3", - "version" : "1.14.1" - } - }, - { - "identity" : "keychainaccess", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kishikawakatsumi/KeychainAccess", - "state" : { - "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", - "version" : "4.2.2" - } - }, - { - "identity" : "launchatlogin-modern", - "kind" : "remoteSourceControl", - "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern", - "state" : { - "branch" : "main", - "revision" : "9c41991631605c8ccfe0347bbcb5c659169f2ec5" - } - }, - { - "identity" : "llama.cpp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ggerganov/llama.cpp", - "state" : { - "branch" : "master", - "revision" : "e937066420b79a757bf80e9836eb12b88420a218" - } - }, - { - "identity" : "menubarextraaccess", - "kind" : "remoteSourceControl", - "location" : "https://github.com/orchetect/MenuBarExtraAccess", - "state" : { - "revision" : "f5896b47e15e114975897354c7e1082c51a2bffd", - "version" : "1.0.5" - } - }, - { - "identity" : "networkimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gonzalezreal/NetworkImage", - "state" : { - "revision" : "7aff8d1b31148d32c5933d75557d42f6323ee3d1", - "version" : "6.0.0" - } - }, - { - "identity" : "postgrest-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/postgrest-swift", - "state" : { - "revision" : "e9dae74d410b13383bf804f84efc16700c5a61c1", - "version" : "1.0.2" - } - }, - { - "identity" : "realtime-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/realtime-swift.git", - "state" : { - "revision" : "0b985c687fe963f6bd818ff77a35c27247b98bb4", - "version" : "0.0.2" - } - }, - { - "identity" : "settingsaccess", - "kind" : "remoteSourceControl", - "location" : "https://github.com/orchetect/SettingsAccess", - "state" : { - "revision" : "dbd2726bda227ff1b8eac32c043668c3b390d6b5", - "version" : "1.2.0" - } - }, - { - "identity" : "storage-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/storage-swift.git", - "state" : { - "revision" : "65cae9e1156711043cbdebb27b4df93cbef46a1b", - "version" : "0.1.4" - } - }, - { - "identity" : "supabase-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/supabase-community/supabase-swift", - "state" : { - "revision" : "7088b85247c8e56317f0b22b9d60655e8f762fec", - "version" : "0.3.0" - } - }, - { - "identity" : "swift-markdown-ui", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kchro3/swift-markdown-ui", - "state" : { - "branch" : "kchro3/make-blocknodes-public", - "revision" : "4fd836761e7178150adc370d216f748a21914684" - } - }, - { - "identity" : "swiftsoup", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scinfu/SwiftSoup", - "state" : { - "revision" : "8b6cf29eead8841a1fa7822481cb3af4ddaadba6", - "version" : "2.6.1" - } - }, - { - "identity" : "urlqueryencoder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/URLQueryEncoder", - "state" : { - "revision" : "4ce950479707ea109f229d7230ec074a133b15d7", - "version" : "0.2.1" - } - } - ], - "version" : 2 -} diff --git a/TypeaheadAI/Actors/SpecialOpenActor.swift b/TypeaheadAI/Actors/SpecialOpenActor.swift index 348d9ac..3c2cdd4 100644 --- a/TypeaheadAI/Actors/SpecialOpenActor.swift +++ b/TypeaheadAI/Actors/SpecialOpenActor.swift @@ -8,6 +8,7 @@ import AppKit import CoreServices import Foundation +import SwiftUI import os.log actor SpecialOpenActor: CanGetUIElements { diff --git a/TypeaheadAI/AppContextManager.swift b/TypeaheadAI/AppContextManager.swift index c47237c..a7b2e13 100644 --- a/TypeaheadAI/AppContextManager.swift +++ b/TypeaheadAI/AppContextManager.swift @@ -8,6 +8,7 @@ import AppKit import Foundation import SwiftUI +import UserNotifications import Vision import os.log diff --git a/TypeaheadAI/Extensions/AXUIElement+Extension.swift b/TypeaheadAI/Extensions/AXUIElement+Extension.swift index acd6ecf..3e63f6e 100644 --- a/TypeaheadAI/Extensions/AXUIElement+Extension.swift +++ b/TypeaheadAI/Extensions/AXUIElement+Extension.swift @@ -97,4 +97,11 @@ extension AXUIElement { return actions } + + func isFocused() -> Bool { + guard let value = self.value(forAttribute: kAXFocusedAttribute) as? Bool else { + return false + } + return value + } } diff --git a/TypeaheadAI/Functions/FunctionManager+PerformUIAction.swift b/TypeaheadAI/Functions/FunctionManager+PerformUIAction.swift index df05034..3cc3707 100644 --- a/TypeaheadAI/Functions/FunctionManager+PerformUIAction.swift +++ b/TypeaheadAI/Functions/FunctionManager+PerformUIAction.swift @@ -148,6 +148,8 @@ extension FunctionManager: CanSimulateEnter { try await simulateEnter() } } + } else { + try await Task.sleep(for: .milliseconds(100)) } } diff --git a/TypeaheadAI/LayoutManager.swift b/TypeaheadAI/LayoutManager.swift new file mode 100644 index 0000000..c83aaf5 --- /dev/null +++ b/TypeaheadAI/LayoutManager.swift @@ -0,0 +1,14 @@ +// +// LayoutManager.swift +// TypeaheadAI +// +// Created by Jeff Hara on 12/14/23. +// + +import Foundation + +class LayoutManager { + init() { + + } +} diff --git a/TypeaheadAI/Models/AppContext.swift b/TypeaheadAI/Models/AppContext.swift index 6f532f4..58c8e53 100644 --- a/TypeaheadAI/Models/AppContext.swift +++ b/TypeaheadAI/Models/AppContext.swift @@ -10,9 +10,9 @@ import Foundation struct AppContext: Codable, Equatable { let appName: String? let bundleIdentifier: String? + let pid: pid_t? var url: URL? = nil var screenshotPath: String? = nil var ocrText: String? = nil - var pid: pid_t? = nil var serializedUIElement: String? = nil } diff --git a/TypeaheadAI/Models/UIElement.swift b/TypeaheadAI/Models/UIElement.swift index 4b0dd53..e5eb686 100644 --- a/TypeaheadAI/Models/UIElement.swift +++ b/TypeaheadAI/Models/UIElement.swift @@ -18,6 +18,7 @@ struct UIElement: Identifiable, Codable, Equatable { let link: URL? let point: CGPoint? let size: CGSize? + let isFocused: Bool let domId: String? let domClasses: [String]? @@ -42,6 +43,7 @@ extension UIElement { self.role = role self.point = element.pointValue(forAttribute: kAXPositionAttribute) self.size = element.sizeValue(forAttribute: kAXSizeAttribute) + self.isFocused = element.isFocused() if let titleAttr = element.stringValue(forAttribute: kAXTitleAttribute), !titleAttr.isEmpty { self.title = titleAttr @@ -177,6 +179,10 @@ extension UIElement { } } + if self.isFocused { + text += ", (in focus)" + } + var line = "" if isIndexed { line += "\(indentation)\(self.shortId): \(text)" diff --git a/TypeaheadAI/Traits/CanGetUIElements.swift b/TypeaheadAI/Traits/CanGetUIElements.swift index e2487c7..0ec8662 100644 --- a/TypeaheadAI/Traits/CanGetUIElements.swift +++ b/TypeaheadAI/Traits/CanGetUIElements.swift @@ -9,11 +9,15 @@ import AppKit import Foundation protocol CanGetUIElements { + func getRootElement(appContext: AppContext?) -> AXUIElement? + func getUIElements(appContext: AppContext?) -> (UIElement?, ElementMap) + + func getUIElements(element: AXUIElement?) -> (UIElement?, ElementMap) } extension CanGetUIElements { - func getUIElements(appContext: AppContext?) -> (UIElement?, ElementMap) { + func getRootElement(appContext: AppContext?) -> AXUIElement? { var element: AXUIElement? = nil if let appContext = appContext, let pid = appContext.pid { element = AXUIElementCreateApplication(pid) @@ -21,6 +25,10 @@ extension CanGetUIElements { element = AXUIElementCreateSystemWide() } + return element + } + + func getUIElements(element: AXUIElement?) -> (UIElement?, ElementMap) { var elementMap = ElementMap() if let element = element, let uiElement = UIElement(from: element, callback: { uuid, element in elementMap[uuid] = element @@ -30,4 +38,9 @@ extension CanGetUIElements { return (nil, ElementMap()) } } + + func getUIElements(appContext: AppContext?) -> (UIElement?, ElementMap) { + let element = getRootElement(appContext: appContext) + return getUIElements(element: element) + } } diff --git a/TypeaheadAI/Views/Modal/Message/ChatBubble.swift b/TypeaheadAI/Views/Modal/Message/ChatBubble.swift index fb9cefa..e6decc6 100644 --- a/TypeaheadAI/Views/Modal/Message/ChatBubble.swift +++ b/TypeaheadAI/Views/Modal/Message/ChatBubble.swift @@ -41,10 +41,7 @@ struct ChatBubble: View where Content: View { @ViewBuilder var userMessage: some View { HStack(alignment: .bottom) { - Spacer() - userButtons - .padding(.leading, 10) content() .clipShape(ChatBubbleShape(direction: direction)) diff --git a/TypeaheadAI/Views/Settings/QuickActions/QuickActionDetails.swift b/TypeaheadAI/Views/Settings/QuickActions/QuickActionDetails.swift index dfc8861..b7f6d3f 100644 --- a/TypeaheadAI/Views/Settings/QuickActions/QuickActionDetails.swift +++ b/TypeaheadAI/Views/Settings/QuickActions/QuickActionDetails.swift @@ -56,181 +56,154 @@ struct QuickActionDetails: View { @ViewBuilder var readWriteView: some View { - ScrollView { - VStack { - // Read-Write Header - HStack { - Button(action: { - isEditing = false - onDelete?() - }, label: { - HStack { - Image(systemName: "trash.fill") - .foregroundStyle(.white) - Text("Delete") - .foregroundStyle(.white) - } - .padding(.vertical, 5) - .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 15) - .fill(.red)) - }) - .buttonStyle(.plain) - - Spacer() - - Button(action: { - isEditing = false - }, label: { - Text("Cancel") - .padding(.vertical, 5) - .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 15) - .fill(colorScheme == .dark ? .black.opacity(0.2) : .secondary.opacity(0.15)) - ) - }) - .buttonStyle(.plain) - - Button(action: { - isEditing = false - onSubmit?( - mutableLabel, - mutableDetails - ) - }, label: { - Text("Save") - .foregroundStyle(.white) - .padding(.vertical, 5) - .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 15) - .fill(Color.accentColor)) - }) - .buttonStyle(.plain) - } - .frame(maxWidth: .infinity) - + VStack { + ScrollView { // Body VStack(alignment: .leading) { + // Details + Text("Plan") + .font(.title3) + .foregroundStyle(Color.accentColor) - // Title - HStack { - Text("Name") - .frame(width: descWidth, alignment: .trailing) - - CustomTextField( - text: $mutableLabel, - placeholderText: quickAction.prompt ?? "Name of command", - autoCompleteSuggestions: [], - onEnter: { _ in }, - flushOnEnter: false - ) - .lineLimit(1) + TextEditor(text: $mutableDetails) + .font(.system(.body)) + .scrollContentBackground(.hidden) + .lineLimit(10) .padding(.vertical, 5) .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 15) + .background(RoundedRectangle(cornerRadius: 10) .fill(colorScheme == .dark ? .black.opacity(0.2) : .secondary.opacity(0.15)) ) - } - - // Details - HStack { - Text("Prompt") - .frame(width: descWidth, alignment: .trailing) - - TextEditor(text: $mutableDetails) - .font(.system(.body)) - .scrollContentBackground(.hidden) - .lineLimit(nil) - .padding(.vertical, 5) - .padding(.horizontal, 10) - .background(RoundedRectangle(cornerRadius: 10) - .fill(colorScheme == .dark ? .black.opacity(0.2) : .secondary.opacity(0.15)) - ) - .frame(minHeight: 50) - } - - Divider() + .frame(minHeight: 60) Text("Examples") .font(.title3) .foregroundStyle(Color.accentColor) // Examples - VStack { - Table(history, selection: $selectedRow) { - TableColumn("Copied Text") { entry in - Text(entry.copiedText ?? "none") - } - TableColumn("Pasted Text") { entry in - Text(entry.pastedResponse ?? "none") - } + Table(history, selection: $selectedRow) { + TableColumn("Copied Text") { entry in + Text(entry.copiedText ?? "none") + } + TableColumn("Pasted Text") { entry in + Text(entry.pastedResponse ?? "none") + } + } + .contextMenu(menuItems: { + Button { + selectedRow = nil + isSheetPresented = true + } label: { + Text("Add New Example") } - .contextMenu(menuItems: { + + if let selectedRow = selectedRow, let _ = selectedRow { Button { - selectedRow = nil isSheetPresented = true } label: { - Text("Add New Example") + Text("Edit") } - if let selectedRow = selectedRow, let _ = selectedRow { - Button { - isSheetPresented = true - } label: { - Text("Edit") - } - - Button { - confirmDelete = true - } label: { - Text("Delete") - } + Button { + confirmDelete = true + } label: { + Text("Delete") } - }) - .sheet(isPresented: $isSheetPresented, onDismiss: { - isSheetPresented = false - }) { - QuickActionExampleForm( - selectedRow: selectedRow, - onFetch: self.fetchHistoryEntry, - onSubmit: { (copiedText, pastedText) in - self.upsertExample( - copiedText: copiedText, - pastedText: pastedText - ) - isSheetPresented = false - }, - onCancel: { - isSheetPresented = false - } - ) } - .onDeleteCommand(perform: { - confirmDelete = true - }) - .alert(isPresented: $confirmDelete) { - Alert( - title: Text("Are you sure you want to delete this example?"), - message: Text("If you delete this, TypeaheadAI will forget about this example, and this action cannot be undone."), - primaryButton: .destructive(Text("Delete")) { - deleteSelectedRow() - }, - secondaryButton: .cancel() - ) - } - .cornerRadius(10) + }) + .sheet(isPresented: $isSheetPresented, onDismiss: { + isSheetPresented = false + }) { + QuickActionExampleForm( + selectedRow: selectedRow, + onFetch: self.fetchHistoryEntry, + onSubmit: { (copiedText, pastedText) in + self.upsertExample( + copiedText: copiedText, + pastedText: pastedText + ) + isSheetPresented = false + }, + onCancel: { + isSheetPresented = false + } + ) } + .onDeleteCommand(perform: { + confirmDelete = true + }) + .alert(isPresented: $confirmDelete) { + Alert( + title: Text("Are you sure you want to delete this example?"), + message: Text("If you delete this, TypeaheadAI will forget about this example, and this action cannot be undone."), + primaryButton: .destructive(Text("Delete")) { + deleteSelectedRow() + }, + secondaryButton: .cancel() + ) + } + .cornerRadius(10) .frame(maxWidth: .infinity, minHeight: 150, maxHeight: .infinity, alignment: .leading) } - .padding(10) .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .leading ) } - .padding(15) + + // Read-Write Footer + HStack { + Button(action: { + isEditing = false + onDelete?() + }, label: { + HStack { + Image(systemName: "trash.fill") + .foregroundStyle(.white) + Text("Delete") + .foregroundStyle(.white) + } + .padding(.vertical, 5) + .padding(.horizontal, 10) + .background(RoundedRectangle(cornerRadius: 15) + .fill(.red)) + }) + .buttonStyle(.plain) + + Spacer() + + Button(action: { + isEditing = false + }, label: { + Text("Cancel") + .padding(.vertical, 5) + .padding(.horizontal, 10) + .background(RoundedRectangle(cornerRadius: 15) + .fill(colorScheme == .dark ? .black.opacity(0.2) : .secondary.opacity(0.15)) + ) + }) + .buttonStyle(.plain) + + Button(action: { + isEditing = false + onSubmit?( + mutableLabel, + mutableDetails + ) + }, label: { + Text("Save") + .foregroundStyle(.white) + .padding(.vertical, 5) + .padding(.horizontal, 10) + .background(RoundedRectangle(cornerRadius: 15) + .fill(Color.accentColor)) + }) + .buttonStyle(.plain) + } + .frame(maxWidth: .infinity) } + .padding(15) } @ViewBuilder @@ -268,7 +241,7 @@ struct QuickActionDetails: View { VStack(alignment: .leading) { // Details VStack(alignment: .leading, spacing: 5) { - Text("Prompt") + Text("Plan") .foregroundStyle(Color.accentColor) Text(quickAction.details ?? "") } @@ -298,15 +271,14 @@ struct QuickActionDetails: View { .fill(colorScheme == .dark ? .black.opacity(0.2) : .secondary.opacity(0.15)) ) } - .padding(10) .frame( maxWidth: .infinity, maxHeight: .infinity, alignment: .leading ) } - .padding(15) - } + } + .padding(15) } private func fetchHistoryEntry(uuid: UUID) -> HistoryEntry? { diff --git a/TypeaheadAI/WindowManagers/ModalManager.swift b/TypeaheadAI/WindowManagers/ModalManager.swift index 93c733b..53381ba 100644 --- a/TypeaheadAI/WindowManagers/ModalManager.swift +++ b/TypeaheadAI/WindowManagers/ModalManager.swift @@ -75,7 +75,7 @@ class ModalManager: ObservableObject { @MainActor func cancelTasks() { - isPending = false + self.isPending = false self.clientManager?.cancelStreamingTask() NotificationCenter.default.post(