From 01c42842707891fdbafdd5e89398fd65be5ec363 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:14:11 +0000 Subject: [PATCH 1/6] Add Groq provider to screen assistant model selection and API routing Agent-Logs-Url: https://github.com/AkhilKonduru1/Atoll/sessions/d40c666d-f589-48ca-9539-1fd33c799962 Co-authored-by: AkhilKonduru1 <210101196+AkhilKonduru1@users.noreply.github.com> --- .../ScreenAssistant/ChatPanels.swift | 5 +- .../ScreenAssistant/ModelSelectionPanel.swift | 49 ++++++++++++++++--- .../managers/ScreenAssistantManager.swift | 38 +++++++++++++- DynamicIsland/models/Constants.swift | 10 ++++ 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/DynamicIsland/components/ScreenAssistant/ChatPanels.swift b/DynamicIsland/components/ScreenAssistant/ChatPanels.swift index 095d491..2f79dee 100644 --- a/DynamicIsland/components/ScreenAssistant/ChatPanels.swift +++ b/DynamicIsland/components/ScreenAssistant/ChatPanels.swift @@ -450,6 +450,7 @@ struct ChatInputView: View { case .openai: return "brain.head.profile" case .claude: return "doc.text" case .local: return "server.rack" + case .groq: return "bolt.fill" } } @@ -472,6 +473,8 @@ struct ChatInputView: View { case .local: // Local models don't need API keys apiKey = "local" + case .groq: + apiKey = Defaults[.groqApiKey] } if apiKey.isEmpty { @@ -806,4 +809,4 @@ struct ScreenshotPopoverBackground: NSViewRepresentable { ScreenCaptureVisibilityManager.shared.unregister(window) } } -} \ No newline at end of file +} diff --git a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift index e8f819c..f369a38 100644 --- a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift +++ b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift @@ -126,6 +126,7 @@ struct ModelSelectionView: View { @State private var openaiApiKey: String = Defaults[.openaiApiKey] @State private var claudeApiKey: String = Defaults[.claudeApiKey] @State private var localEndpoint: String = Defaults[.localModelEndpoint] + @State private var groqApiKey: String = Defaults[.groqApiKey] @State private var showingApiKeyAlert = false @@ -170,14 +171,21 @@ struct ModelSelectionView: View { .foregroundColor(.primary) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { - ForEach(AIModelProvider.allCases) { provider in + ForEach(AIModelProvider.allCases.filter { $0 != .groq }) { provider in ProviderCard( provider: provider, isSelected: selectedProvider == provider, - onSelect: { selectedProvider = provider } + onSelect: { selectProvider(provider) } ) } } + + ProviderCard( + provider: .groq, + isSelected: selectedProvider == .groq, + onSelect: { selectProvider(.groq) }, + isWide: true + ) } Divider() @@ -245,7 +253,8 @@ struct ModelSelectionView: View { geminiApiKey: $geminiApiKey, openaiApiKey: $openaiApiKey, claudeApiKey: $claudeApiKey, - localEndpoint: $localEndpoint + localEndpoint: $localEndpoint, + groqApiKey: $groqApiKey ) } } @@ -291,22 +300,31 @@ struct ModelSelectionView: View { return !claudeApiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty case .local: return !localEndpoint.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + case .groq: + return !groqApiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } private func loadCurrentConfiguration() { selectedProvider = Defaults[.selectedAIProvider] - // Default to the latest Gemini 2.5 Flash if no model is selected - selectedModel = Defaults[.selectedAIModel] ?? AIModel(id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", supportsThinking: true) + selectedModel = Defaults[.selectedAIModel] + if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { + selectedModel = selectedProvider.supportedModels.first + } enableThinking = Defaults[.enableThinkingMode] geminiApiKey = Defaults[.geminiApiKey] openaiApiKey = Defaults[.openaiApiKey] claudeApiKey = Defaults[.claudeApiKey] localEndpoint = Defaults[.localModelEndpoint] + groqApiKey = Defaults[.groqApiKey] } private func saveConfiguration() { + if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { + selectedModel = selectedProvider.supportedModels.first + } + Defaults[.selectedAIProvider] = selectedProvider Defaults[.selectedAIModel] = selectedModel Defaults[.enableThinkingMode] = enableThinking @@ -315,12 +333,20 @@ struct ModelSelectionView: View { Defaults[.openaiApiKey] = openaiApiKey Defaults[.claudeApiKey] = claudeApiKey Defaults[.localModelEndpoint] = localEndpoint + Defaults[.groqApiKey] = groqApiKey closePanel() // Notify that configuration changed NotificationCenter.default.post(name: .aiModelConfigurationChanged, object: nil) } + + private func selectProvider(_ provider: AIModelProvider) { + selectedProvider = provider + if !provider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { + selectedModel = provider.supportedModels.first + } + } private func closePanel() { if let window = NSApp.windows.first(where: { $0 is ModelSelectionPanel }) { @@ -334,6 +360,7 @@ struct ProviderCard: View { let provider: AIModelProvider let isSelected: Bool let onSelect: () -> Void + var isWide: Bool = false var body: some View { VStack(spacing: 12) { @@ -374,6 +401,7 @@ struct ProviderCard: View { onSelect() } .animation(.easeInOut(duration: 0.2), value: isSelected) + .frame(maxWidth: .infinity, minHeight: isWide ? 110 : nil) } private func iconForProvider(_ provider: AIModelProvider) -> String { @@ -382,6 +410,7 @@ struct ProviderCard: View { case .openai: return "brain.head.profile" case .claude: return "doc.text" case .local: return "server.rack" + case .groq: return "bolt.fill" } } } @@ -441,6 +470,7 @@ struct ApiConfigurationSection: View { @Binding var openaiApiKey: String @Binding var claudeApiKey: String @Binding var localEndpoint: String + @Binding var groqApiKey: String var body: some View { VStack(spacing: 12) { @@ -477,6 +507,13 @@ struct ApiConfigurationSection: View { helpText: "Ollama or compatible API endpoint", isSecure: false ) + case .groq: + ApiKeyField( + title: "Groq API Key", + placeholder: "Enter your Groq API key", + value: $groqApiKey, + helpText: "Get your API key from Groq Console" + ) } } .padding(16) @@ -534,4 +571,4 @@ struct ModelSelectionVisualEffectView: NSViewRepresentable { // MARK: - Notification Extension extension Notification.Name { static let aiModelConfigurationChanged = Notification.Name("aiModelConfigurationChanged") -} \ No newline at end of file +} diff --git a/DynamicIsland/managers/ScreenAssistantManager.swift b/DynamicIsland/managers/ScreenAssistantManager.swift index 9bf0c2c..d6fe4e0 100644 --- a/DynamicIsland/managers/ScreenAssistantManager.swift +++ b/DynamicIsland/managers/ScreenAssistantManager.swift @@ -397,6 +397,8 @@ class ScreenAssistantManager: NSObject, ObservableObject { sendToClaudeAPI(message: message, files: files) case .local: sendToLocalAPI(message: message, files: files) + case .groq: + sendToGroqAPI(message: message, files: files) } } @@ -445,6 +447,36 @@ class ScreenAssistantManager: NSObject, ObservableObject { performOpenAIRequest(url: url, requestBody: buildOpenAIRequestBody(message: message, files: files, model: modelId), apiKey: apiKey) } + + private func sendToGroqAPI(message: String, files: [ScreenAssistantFile]) { + let apiKey = Defaults[.groqApiKey] + guard !apiKey.isEmpty else { + print("❌ ScreenAssistant: No Groq API key configured") + addAssistantMessage("Error: No Groq API key configured. Please set your API key in model settings.") + isLoading = false + return + } + + // Get selected model or default to llama-3.3-70b-versatile + let selectedModel = Defaults[.selectedAIModel] + let modelId = AIModelProvider.groq.supportedModels.contains(where: { $0.id == selectedModel?.id }) + ? selectedModel!.id + : "llama-3.3-70b-versatile" + + guard let url = URL(string: "https://api.groq.com/openai/v1/chat/completions") else { + print("❌ ScreenAssistant: Invalid Groq API URL") + addAssistantMessage("Error: Invalid API URL") + isLoading = false + return + } + + performOpenAIRequest( + url: url, + requestBody: buildOpenAIRequestBody(message: message, files: files, model: modelId), + apiKey: apiKey, + provider: .groq + ) + } private func sendToClaudeAPI(message: String, files: [ScreenAssistantFile]) { let apiKey = Defaults[.claudeApiKey] @@ -682,7 +714,7 @@ class ScreenAssistantManager: NSObject, ObservableObject { task?.resume() } - private func performOpenAIRequest(url: URL, requestBody: [String: Any], apiKey: String) { + private func performOpenAIRequest(url: URL, requestBody: [String: Any], apiKey: String, provider: AIModelProvider = .openai) { var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") @@ -710,7 +742,7 @@ class ScreenAssistantManager: NSObject, ObservableObject { self.isLoading = false self.activeRequest = nil - self.handleResponse(data: data, response: response, error: error, provider: .openai) + self.handleResponse(data: data, response: response, error: error, provider: provider) } } @@ -792,6 +824,8 @@ class ScreenAssistantManager: NSObject, ObservableObject { parseGeminiResponse(data: data) case .openai: parseOpenAIResponse(data: data) + case .groq: + parseOpenAIResponse(data: data) case .claude: parseClaudeResponse(data: data) case .local: diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index c15f9ea..e45029b 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -611,6 +611,7 @@ enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable case openai = "OpenAI GPT" case claude = "Claude" case local = "Local Model" + case groq = "Groq" var id: String { self.rawValue } @@ -624,6 +625,7 @@ enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable case .openai: return "OpenAI's GPT models with advanced reasoning" case .claude: return "Anthropic's Claude with strong analytical skills" case .local: return "Local AI model (Ollama or similar)" + case .groq: return "Groq's fast inference for OpenAI-compatible models" } } @@ -664,6 +666,13 @@ enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable AIModel(id: "llama3.2", name: "Llama 3.2", supportsThinking: false), AIModel(id: "qwen2.5", name: "Qwen 2.5", supportsThinking: false) ] + case .groq: + return [ + AIModel(id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B Versatile", supportsThinking: false), + AIModel(id: "llama-3.1-8b-instant", name: "Llama 3.1 8B Instant", supportsThinking: false), + AIModel(id: "qwen-qwq-32b", name: "Qwen QWQ 32B", supportsThinking: true), + AIModel(id: "mixtral-8x7b-32768", name: "Mixtral 8x7B", supportsThinking: false) + ] } } } @@ -1052,6 +1061,7 @@ extension Defaults.Keys { static let geminiApiKey = Key("geminiApiKey", default: "") static let openaiApiKey = Key("openaiApiKey", default: "") static let claudeApiKey = Key("claudeApiKey", default: "") + static let groqApiKey = Key("groqApiKey", default: "") static let selectedAIProvider = Key("selectedAIProvider", default: .gemini) static let selectedAIModel = Key("selectedAIModel", default: nil) static let enableThinkingMode = Key("enableThinkingMode", default: false) From e8b8ea560247518ff932a79ba7159da2de9684b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:14:55 +0000 Subject: [PATCH 2/6] Address review feedback for Groq provider changes Agent-Logs-Url: https://github.com/AkhilKonduru1/Atoll/sessions/d40c666d-f589-48ca-9539-1fd33c799962 Co-authored-by: AkhilKonduru1 <210101196+AkhilKonduru1@users.noreply.github.com> --- .../components/ScreenAssistant/ModelSelectionPanel.swift | 3 ++- DynamicIsland/managers/ScreenAssistantManager.swift | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift index f369a38..a9f7758 100644 --- a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift +++ b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift @@ -117,6 +117,7 @@ class ModelSelectionPanel: NSPanel { // MARK: - Model Selection View struct ModelSelectionView: View { + private let primaryProviders: [AIModelProvider] = [.gemini, .openai, .claude, .local] @State private var selectedProvider: AIModelProvider = Defaults[.selectedAIProvider] @State private var selectedModel: AIModel? = Defaults[.selectedAIModel] @State private var enableThinking: Bool = Defaults[.enableThinkingMode] @@ -171,7 +172,7 @@ struct ModelSelectionView: View { .foregroundColor(.primary) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { - ForEach(AIModelProvider.allCases.filter { $0 != .groq }) { provider in + ForEach(primaryProviders) { provider in ProviderCard( provider: provider, isSelected: selectedProvider == provider, diff --git a/DynamicIsland/managers/ScreenAssistantManager.swift b/DynamicIsland/managers/ScreenAssistantManager.swift index d6fe4e0..f7f9b06 100644 --- a/DynamicIsland/managers/ScreenAssistantManager.swift +++ b/DynamicIsland/managers/ScreenAssistantManager.swift @@ -459,9 +459,9 @@ class ScreenAssistantManager: NSObject, ObservableObject { // Get selected model or default to llama-3.3-70b-versatile let selectedModel = Defaults[.selectedAIModel] - let modelId = AIModelProvider.groq.supportedModels.contains(where: { $0.id == selectedModel?.id }) - ? selectedModel!.id - : "llama-3.3-70b-versatile" + let modelId = selectedModel?.id.flatMap { selectedId in + AIModelProvider.groq.supportedModels.contains(where: { $0.id == selectedId }) ? selectedId : nil + } ?? "llama-3.3-70b-versatile" guard let url = URL(string: "https://api.groq.com/openai/v1/chat/completions") else { print("❌ ScreenAssistant: Invalid Groq API URL") From 5c2ab196143a3e98a36bd9b96fb4f99a686a3466 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:15:36 +0000 Subject: [PATCH 3/6] Refine Groq model selection fallback and provider card layout constants Agent-Logs-Url: https://github.com/AkhilKonduru1/Atoll/sessions/d40c666d-f589-48ca-9539-1fd33c799962 Co-authored-by: AkhilKonduru1 <210101196+AkhilKonduru1@users.noreply.github.com> --- .../ScreenAssistant/ModelSelectionPanel.swift | 3 ++- DynamicIsland/managers/ScreenAssistantManager.swift | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift index a9f7758..8550708 100644 --- a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift +++ b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift @@ -358,6 +358,7 @@ struct ModelSelectionView: View { // MARK: - Provider Card struct ProviderCard: View { + private let wideCardMinHeight: CGFloat = 110 let provider: AIModelProvider let isSelected: Bool let onSelect: () -> Void @@ -402,7 +403,7 @@ struct ProviderCard: View { onSelect() } .animation(.easeInOut(duration: 0.2), value: isSelected) - .frame(maxWidth: .infinity, minHeight: isWide ? 110 : nil) + .frame(maxWidth: .infinity, minHeight: isWide ? wideCardMinHeight : nil) } private func iconForProvider(_ provider: AIModelProvider) -> String { diff --git a/DynamicIsland/managers/ScreenAssistantManager.swift b/DynamicIsland/managers/ScreenAssistantManager.swift index f7f9b06..2f10cba 100644 --- a/DynamicIsland/managers/ScreenAssistantManager.swift +++ b/DynamicIsland/managers/ScreenAssistantManager.swift @@ -459,9 +459,13 @@ class ScreenAssistantManager: NSObject, ObservableObject { // Get selected model or default to llama-3.3-70b-versatile let selectedModel = Defaults[.selectedAIModel] - let modelId = selectedModel?.id.flatMap { selectedId in - AIModelProvider.groq.supportedModels.contains(where: { $0.id == selectedId }) ? selectedId : nil - } ?? "llama-3.3-70b-versatile" + let modelId: String + if let selectedId = selectedModel?.id, + AIModelProvider.groq.supportedModels.contains(where: { $0.id == selectedId }) { + modelId = selectedId + } else { + modelId = "llama-3.3-70b-versatile" + } guard let url = URL(string: "https://api.groq.com/openai/v1/chat/completions") else { print("❌ ScreenAssistant: Invalid Groq API URL") From bb8f157d476945b9f4a39a40c9690a0b49e325ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:16:05 +0000 Subject: [PATCH 4/6] Deduplicate OpenAI and Groq response parsing switch cases Agent-Logs-Url: https://github.com/AkhilKonduru1/Atoll/sessions/d40c666d-f589-48ca-9539-1fd33c799962 Co-authored-by: AkhilKonduru1 <210101196+AkhilKonduru1@users.noreply.github.com> --- DynamicIsland/managers/ScreenAssistantManager.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DynamicIsland/managers/ScreenAssistantManager.swift b/DynamicIsland/managers/ScreenAssistantManager.swift index 2f10cba..17b28ed 100644 --- a/DynamicIsland/managers/ScreenAssistantManager.swift +++ b/DynamicIsland/managers/ScreenAssistantManager.swift @@ -826,9 +826,7 @@ class ScreenAssistantManager: NSObject, ObservableObject { switch provider { case .gemini: parseGeminiResponse(data: data) - case .openai: - parseOpenAIResponse(data: data) - case .groq: + case .openai, .groq: parseOpenAIResponse(data: data) case .claude: parseClaudeResponse(data: data) From c640921fa52d49a1a369a8126241a0acb274320c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:16:34 +0000 Subject: [PATCH 5/6] Extract shared model validation helper in model selection panel Agent-Logs-Url: https://github.com/AkhilKonduru1/Atoll/sessions/d40c666d-f589-48ca-9539-1fd33c799962 Co-authored-by: AkhilKonduru1 <210101196+AkhilKonduru1@users.noreply.github.com> --- .../ScreenAssistant/ModelSelectionPanel.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift index 8550708..4786607 100644 --- a/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift +++ b/DynamicIsland/components/ScreenAssistant/ModelSelectionPanel.swift @@ -309,9 +309,7 @@ struct ModelSelectionView: View { private func loadCurrentConfiguration() { selectedProvider = Defaults[.selectedAIProvider] selectedModel = Defaults[.selectedAIModel] - if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { - selectedModel = selectedProvider.supportedModels.first - } + ensureValidModelSelection() enableThinking = Defaults[.enableThinkingMode] geminiApiKey = Defaults[.geminiApiKey] @@ -322,9 +320,7 @@ struct ModelSelectionView: View { } private func saveConfiguration() { - if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { - selectedModel = selectedProvider.supportedModels.first - } + ensureValidModelSelection() Defaults[.selectedAIProvider] = selectedProvider Defaults[.selectedAIModel] = selectedModel @@ -344,8 +340,12 @@ struct ModelSelectionView: View { private func selectProvider(_ provider: AIModelProvider) { selectedProvider = provider - if !provider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { - selectedModel = provider.supportedModels.first + ensureValidModelSelection() + } + + private func ensureValidModelSelection() { + if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) { + selectedModel = selectedProvider.supportedModels.first } } From 659a90ad9f5db22bba791df540681d5751bd9665 Mon Sep 17 00:00:00 2001 From: AkhilKonduru1 Date: Wed, 15 Apr 2026 19:54:52 -0500 Subject: [PATCH 6/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- DynamicIsland/models/Constants.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index e45029b..743c49e 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -670,7 +670,7 @@ enum AIModelProvider: String, CaseIterable, Identifiable, Defaults.Serializable return [ AIModel(id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B Versatile", supportsThinking: false), AIModel(id: "llama-3.1-8b-instant", name: "Llama 3.1 8B Instant", supportsThinking: false), - AIModel(id: "qwen-qwq-32b", name: "Qwen QWQ 32B", supportsThinking: true), + AIModel(id: "qwen-qwq-32b", name: "Qwen QWQ 32B", supportsThinking: false), AIModel(id: "mixtral-8x7b-32768", name: "Mixtral 8x7B", supportsThinking: false) ] }