-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathModelPicker.swift
More file actions
122 lines (108 loc) · 3.53 KB
/
ModelPicker.swift
File metadata and controls
122 lines (108 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import SwiftUI
import ChatService
import Persist
import ComposableArchitecture
import GitHubCopilotService
public let SELECTED_LLM_KEY = "selectedLLM"
extension AppState {
func getSelectedModelFamily() -> String? {
if let savedModel = get(key: SELECTED_LLM_KEY),
let modelFamily = savedModel["modelFamily"]?.stringValue {
return modelFamily
}
return nil
}
func getSelectedModelName() -> String? {
if let savedModel = get(key: SELECTED_LLM_KEY),
let modelName = savedModel["modelName"]?.stringValue {
return modelName
}
return nil
}
func setSelectedModel(_ model: LLMModel) {
update(key: SELECTED_LLM_KEY, value: model)
}
}
extension CopilotModelManager {
static func getAvailableChatLLMs() -> [LLMModel] {
let LLMs = CopilotModelManager.getAvailableLLMs()
let availableModels = LLMs.filter(
{ $0.scopes.contains(.chatPanel) }
).map {
LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily)
}
return availableModels.isEmpty ? [defaultModel] : availableModels
}
}
struct LLMModel: Codable, Hashable {
let modelName: String
let modelFamily: String
}
let defaultModel = LLMModel(modelName: "GPT-4o", modelFamily: "gpt-4o")
struct ModelPicker: View {
@State private var selectedModel = defaultModel.modelName
@State private var isHovered = false
@State private var isPressed = false
init() {
self.updateCurrentModel()
}
var models: [LLMModel] {
CopilotModelManager.getAvailableChatLLMs()
}
func updateCurrentModel() {
selectedModel = AppState.shared.getSelectedModelName() ?? defaultModel.modelName
}
var body: some View {
WithPerceptionTracking {
Menu(selectedModel) {
ForEach(models, id: \.self) { option in
Button {
selectedModel = option.modelName
AppState.shared.setSelectedModel(option)
} label: {
if selectedModel == option.modelName {
Text("✓ \(option.modelName)")
} else {
Text(" \(option.modelName)")
}
}
}
}
.menuStyle(BorderlessButtonMenuStyle())
.frame(maxWidth: labelWidth())
.padding(4)
.background(
RoundedRectangle(cornerRadius: 5)
.fill(isHovered ? Color.gray.opacity(0.1) : Color.clear)
)
.onHover { hovering in
isHovered = hovering
}
.onAppear() {
updateCurrentModel()
Task {
await refreshModels()
}
}
.help("Pick Model")
}
}
func labelWidth() -> CGFloat {
let font = NSFont.systemFont(ofSize: NSFont.systemFontSize)
let attributes = [NSAttributedString.Key.font: font]
let width = selectedModel.size(withAttributes: attributes).width
return CGFloat(width + 20)
}
@MainActor
func refreshModels() async {
let copilotModels = await ChatService.shared.copilotModels()
if !copilotModels.isEmpty {
CopilotModelManager.updateLLMs(copilotModels)
}
}
}
struct ModelPicker_Previews: PreviewProvider {
static var previews: some View {
ModelPicker()
}
}