mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
commit
d4cb3ef2cf
4 changed files with 98 additions and 10 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -126,6 +127,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 +172,21 @@ struct ModelSelectionView: View {
|
|||
.foregroundColor(.primary)
|
||||
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) {
|
||||
ForEach(AIModelProvider.allCases) { provider in
|
||||
ForEach(primaryProviders) { 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 +254,8 @@ struct ModelSelectionView: View {
|
|||
geminiApiKey: $geminiApiKey,
|
||||
openaiApiKey: $openaiApiKey,
|
||||
claudeApiKey: $claudeApiKey,
|
||||
localEndpoint: $localEndpoint
|
||||
localEndpoint: $localEndpoint,
|
||||
groqApiKey: $groqApiKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -291,22 +301,27 @@ 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]
|
||||
ensureValidModelSelection()
|
||||
enableThinking = Defaults[.enableThinkingMode]
|
||||
|
||||
geminiApiKey = Defaults[.geminiApiKey]
|
||||
openaiApiKey = Defaults[.openaiApiKey]
|
||||
claudeApiKey = Defaults[.claudeApiKey]
|
||||
localEndpoint = Defaults[.localModelEndpoint]
|
||||
groqApiKey = Defaults[.groqApiKey]
|
||||
}
|
||||
|
||||
private func saveConfiguration() {
|
||||
ensureValidModelSelection()
|
||||
|
||||
Defaults[.selectedAIProvider] = selectedProvider
|
||||
Defaults[.selectedAIModel] = selectedModel
|
||||
Defaults[.enableThinkingMode] = enableThinking
|
||||
|
|
@ -315,12 +330,24 @@ 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
|
||||
ensureValidModelSelection()
|
||||
}
|
||||
|
||||
private func ensureValidModelSelection() {
|
||||
if selectedModel == nil || !selectedProvider.supportedModels.contains(where: { $0.id == selectedModel?.id }) {
|
||||
selectedModel = selectedProvider.supportedModels.first
|
||||
}
|
||||
}
|
||||
|
||||
private func closePanel() {
|
||||
if let window = NSApp.windows.first(where: { $0 is ModelSelectionPanel }) {
|
||||
|
|
@ -331,9 +358,11 @@ struct ModelSelectionView: View {
|
|||
|
||||
// MARK: - Provider Card
|
||||
struct ProviderCard: View {
|
||||
private let wideCardMinHeight: CGFloat = 110
|
||||
let provider: AIModelProvider
|
||||
let isSelected: Bool
|
||||
let onSelect: () -> Void
|
||||
var isWide: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
|
|
@ -374,6 +403,7 @@ struct ProviderCard: View {
|
|||
onSelect()
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.2), value: isSelected)
|
||||
.frame(maxWidth: .infinity, minHeight: isWide ? wideCardMinHeight : nil)
|
||||
}
|
||||
|
||||
private func iconForProvider(_ provider: AIModelProvider) -> String {
|
||||
|
|
@ -382,6 +412,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 +472,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 +509,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 +573,4 @@ struct ModelSelectionVisualEffectView: NSViewRepresentable {
|
|||
// MARK: - Notification Extension
|
||||
extension Notification.Name {
|
||||
static let aiModelConfigurationChanged = Notification.Name("aiModelConfigurationChanged")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue