diff --git a/DynamicIsland/components/Notch/NotchNotesView.swift b/DynamicIsland/components/Notch/NotchNotesView.swift index 4fe1119..48a54af 100644 --- a/DynamicIsland/components/Notch/NotchNotesView.swift +++ b/DynamicIsland/components/Notch/NotchNotesView.swift @@ -158,9 +158,13 @@ struct NotchNotesView: View { return } - // Fallback to text if not editing and clipboard has text - if !isEditingNewNote && selectedNoteId == nil { - if let text = pasteboard.string(forType: .string) { + // Handle text paste + if let text = pasteboard.string(forType: .string) { + if isEditingNewNote || selectedNoteId != nil { + // In editor: insert text at the end of content since we intercepted the shortcut + editorContent.append(text) + } else { + // Not in editor: create a new note with the pasted text createNoteWithContent(text) } } diff --git a/DynamicIsland/components/ScreenAssistant/ChatPanels.swift b/DynamicIsland/components/ScreenAssistant/ChatPanels.swift index aa7549d..095d491 100644 --- a/DynamicIsland/components/ScreenAssistant/ChatPanels.swift +++ b/DynamicIsland/components/ScreenAssistant/ChatPanels.swift @@ -203,6 +203,25 @@ struct ChatMessagesView: View { .foregroundColor(.primary) Spacer() + + Button(action: { + screenAssistantManager.resetConversationContext() + }) { + HStack(spacing: 6) { + Image(systemName: "arrow.counterclockwise") + .font(.caption) + Text("Reset Context") + .font(.caption) + } + .foregroundColor(.secondary) + .padding(.horizontal, 8) + .padding(.vertical, 5) + .background(Color.gray.opacity(0.12)) + .cornerRadius(8) + } + .disabled(screenAssistantManager.isLoading) + .buttonStyle(PlainButtonStyle()) + .help("Clear conversation and attachments") Button(action: { screenAssistantManager.closePanels() diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index 736752a..86b462f 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -6558,27 +6558,27 @@ struct ClipboardSettings: View { HStack { Text("Display Mode") Spacer() - Picker("Display Mode", selection: $clipboardDisplayMode) { + Picker("", selection: $clipboardDisplayMode) { ForEach(ClipboardDisplayMode.allCases, id: \.self) { mode in Text(mode.displayName).tag(mode) } } .pickerStyle(.menu) - .frame(width: 100) + .frame(minWidth: 100) } .settingsHighlight(id: highlightID("Display Mode")) HStack { Text("History Size") Spacer() - Picker("History Size", selection: $clipboardHistorySize) { + Picker("", selection: $clipboardHistorySize) { Text("3 items").tag(3) Text("5 items").tag(5) Text("7 items").tag(7) Text("10 items").tag(10) } .pickerStyle(.menu) - .frame(width: 100) + .frame(minWidth: 100) } .settingsHighlight(id: highlightID("History Size")) @@ -6769,16 +6769,16 @@ struct ScreenAssistantSettings: View { HStack { Text("Display Mode") Spacer() - Picker("Display Mode", selection: $screenAssistantDisplayMode) { + Picker("", selection: $screenAssistantDisplayMode) { ForEach(ScreenAssistantDisplayMode.allCases, id: \.self) { mode in Text(mode.displayName).tag(mode) } } .pickerStyle(.menu) - .frame(width: 100) + .frame(minWidth: 100) } .settingsHighlight(id: highlightID("Display Mode")) - + HStack { Text("Attached Files") Spacer() @@ -6896,27 +6896,27 @@ struct ColorPickerSettings: View { HStack { Text("Display Mode") Spacer() - Picker("Display Mode", selection: $colorPickerDisplayMode) { + Picker("", selection: $colorPickerDisplayMode) { ForEach(ColorPickerDisplayMode.allCases, id: \.self) { mode in Text(mode.displayName).tag(mode) } } .pickerStyle(.menu) - .frame(width: 100) + .frame(minWidth: 100) } .settingsHighlight(id: highlightID("Display Mode")) - + HStack { Text("History Size") Spacer() - Picker("History Size", selection: $colorHistorySize) { + Picker("", selection: $colorHistorySize) { Text("5 colors").tag(5) Text("10 colors").tag(10) Text("15 colors").tag(15) Text("20 colors").tag(20) } .pickerStyle(.menu) - .frame(width: 100) + .frame(minWidth: 100) } .settingsHighlight(id: highlightID("History Size")) diff --git a/DynamicIsland/managers/ScreenAssistantManager.swift b/DynamicIsland/managers/ScreenAssistantManager.swift index 6577ba6..da0a0cd 100644 --- a/DynamicIsland/managers/ScreenAssistantManager.swift +++ b/DynamicIsland/managers/ScreenAssistantManager.swift @@ -120,6 +120,7 @@ class ScreenAssistantManager: NSObject, ObservableObject { private var audioRecorder: AVAudioRecorder? private var recordingTimer: Timer? + private var activeRequest: URLSessionTask? // Panel management private var chatMessagesPanel: ChatMessagesPanel? @@ -664,10 +665,18 @@ class ScreenAssistantManager: NSObject, ObservableObject { let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in DispatchQueue.main.async { guard let self = self else { return } + + // Ensure this callback belongs to the current in-flight request + guard self.activeRequest === task else { return } + + self.isLoading = false + self.activeRequest = nil + self.handleResponse(data: data, response: response, error: error, provider: provider) } } + activeRequest = task task.resume() } @@ -690,10 +699,18 @@ class ScreenAssistantManager: NSObject, ObservableObject { let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in DispatchQueue.main.async { guard let self = self else { return } + + // Ensure this callback belongs to the current in-flight request + guard self.activeRequest === task else { return } + + self.isLoading = false + self.activeRequest = nil + self.handleResponse(data: data, response: response, error: error, provider: .openai) } } + activeRequest = task task.resume() } @@ -717,17 +734,29 @@ class ScreenAssistantManager: NSObject, ObservableObject { let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in DispatchQueue.main.async { guard let self = self else { return } + + // Ensure this callback belongs to the current in-flight request + guard self.activeRequest === task else { return } + + self.isLoading = false + self.activeRequest = nil + self.handleResponse(data: data, response: response, error: error, provider: .claude) } } + activeRequest = task task.resume() } // MARK: - Response Handlers private func handleResponse(data: Data?, response: URLResponse?, error: Error?, provider: AIModelProvider) { - isLoading = false + // Check if the request was cancelled (e.g., by resetConversationContext) + if let error = error as? NSError, error.code == NSURLErrorCancelled { + print("ℹ️ ScreenAssistant: Request was cancelled") + return + } if let error = error { print("❌ ScreenAssistant: Network error - \(error)") @@ -1103,7 +1132,17 @@ class ScreenAssistantManager: NSObject, ObservableObject { } func clearChat() { + resetConversationContext() + } + + func resetConversationContext() { + // Cancel any in-flight request + activeRequest?.cancel() + activeRequest = nil + + isLoading = false chatMessages.removeAll() + clearAllFiles() } private func addAssistantMessage(_ content: String) {