Skip to content

Commit e7324cc

Browse files
committed
Merge branch 'release/0.30.3'
2 parents f8b34af + 973f3b3 commit e7324cc

31 files changed

Lines changed: 490 additions & 193 deletions

Core/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ let package = Package(
289289
name: "UpdateChecker",
290290
dependencies: [
291291
"Sparkle",
292+
.product(name: "Preferences", package: "Tool"),
292293
.product(name: "Logger", package: "Tool"),
293294
]
294295
),

Core/Sources/HostApp/AccountSettings/ChatModelManagement/ChatModelEdit.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct ChatModelEdit: ReducerProtocol {
1616
@BindingState var modelName: String = ""
1717
var apiKeyName: String { apiKeySelection.apiKeyName }
1818
var baseURL: String { baseURLSelection.baseURL }
19+
var isFullURL: Bool { baseURLSelection.isFullURL }
1920
var availableModelNames: [String] = []
2021
var availableAPIKeys: [String] = []
2122
var isTesting = false
@@ -76,6 +77,7 @@ struct ChatModelEdit: ReducerProtocol {
7677
info: .init(
7778
apiKeyName: state.apiKeyName,
7879
baseURL: state.baseURL,
80+
isFullURL: state.isFullURL,
7981
maxTokens: state.maxTokens,
8082
supportsFunctionCalling: state.supportsFunctionCalling,
8183
modelName: state.modelName
@@ -171,7 +173,7 @@ extension ChatModelEdit.State {
171173
apiKeyName: model.info.apiKeyName,
172174
apiKeyManagement: .init(availableAPIKeyNames: [model.info.apiKeyName])
173175
),
174-
baseURLSelection: .init(baseURL: model.info.baseURL)
176+
baseURLSelection: .init(baseURL: model.info.baseURL, isFullURL: model.info.isFullURL)
175177
)
176178
}
177179
}
@@ -185,6 +187,7 @@ extension ChatModel {
185187
info: .init(
186188
apiKeyName: state.apiKeyName,
187189
baseURL: state.baseURL.trimmingCharacters(in: .whitespacesAndNewlines),
190+
isFullURL: state.isFullURL,
188191
maxTokens: state.maxTokens,
189192
supportsFunctionCalling: {
190193
if case .googleAI = state.format {

Core/Sources/HostApp/AccountSettings/ChatModelManagement/ChatModelEditView.swift

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,29 @@ struct ChatModelEditView: View {
101101
}
102102
}
103103

104-
func baseURLTextField(prompt: Text?) -> some View {
104+
func baseURLTextField<V: View>(
105+
title: String = "Base URL",
106+
prompt: Text?,
107+
@ViewBuilder trailingContent: @escaping () -> V
108+
) -> some View {
105109
BaseURLPicker(
110+
title: title,
106111
prompt: prompt,
107112
store: store.scope(
108113
state: \.baseURLSelection,
109114
action: ChatModelEdit.Action.baseURLSelection
110-
)
115+
),
116+
trailingContent: trailingContent
111117
)
112118
}
113119

120+
func baseURLTextField(
121+
title: String = "Base URL",
122+
prompt: Text?
123+
) -> some View {
124+
baseURLTextField(title: title, prompt: prompt, trailingContent: { EmptyView() })
125+
}
126+
114127
var supportsFunctionCallingToggle: some View {
115128
WithViewStore(
116129
store,
@@ -202,7 +215,9 @@ struct ChatModelEditView: View {
202215

203216
@ViewBuilder
204217
var openAI: some View {
205-
baseURLTextField(prompt: Text("https://api.openai.com"))
218+
baseURLTextField(prompt: Text("https://api.openai.com")) {
219+
Text("/v1/chat/completions")
220+
}
206221
apiKeyNamePicker
207222

208223
WithViewStore(
@@ -260,7 +275,34 @@ struct ChatModelEditView: View {
260275

261276
@ViewBuilder
262277
var openAICompatible: some View {
263-
baseURLTextField(prompt: Text("https://"))
278+
WithViewStore(store.scope(
279+
state: \.baseURLSelection,
280+
action: ChatModelEdit.Action.baseURLSelection
281+
), removeDuplicates: { $0.isFullURL != $1.isFullURL }) { viewStore in
282+
Picker(
283+
selection: viewStore.$isFullURL,
284+
content: {
285+
Text("Base URL").tag(false)
286+
Text("Full URL").tag(true)
287+
},
288+
label: { Text("URL") }
289+
)
290+
.pickerStyle(.segmented)
291+
}
292+
293+
WithViewStore(store, observe: \.isFullURL) { viewStore in
294+
baseURLTextField(
295+
title: "",
296+
prompt: viewStore.state
297+
? Text("https://api.openai.com/v1/chat/completions")
298+
: Text("https://api.openai.com")
299+
) {
300+
if !viewStore.state {
301+
Text("/v1/chat/completions")
302+
}
303+
}
304+
}
305+
264306
apiKeyNamePicker
265307

266308
WithViewStore(
@@ -273,11 +315,11 @@ struct ChatModelEditView: View {
273315
maxTokensTextField
274316
supportsFunctionCallingToggle
275317
}
276-
318+
277319
@ViewBuilder
278320
var googleAI: some View {
279321
apiKeyNamePicker
280-
322+
281323
WithViewStore(
282324
store,
283325
removeDuplicates: { $0.modelName == $1.modelName }
@@ -334,6 +376,7 @@ struct ChatModelEditView: View {
334376
info: .init(
335377
apiKeyName: "key",
336378
baseURL: "apple.com",
379+
isFullURL: false,
337380
maxTokens: 3000,
338381
supportsFunctionCalling: false,
339382
modelName: "gpt-3.5-turbo"

Core/Sources/HostApp/AccountSettings/CodeiumView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,9 @@ struct CodeiumSignInView: View {
267267
}
268268
}) {
269269
Text(isGeneratingKey ? "Signing In.." : "Sign In")
270-
}.disabled(isGeneratingKey)
270+
}
271+
.disabled(isGeneratingKey)
272+
.keyboardShortcut(.defaultAction)
271273
}
272274
}
273275
.padding()

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelEdit.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct EmbeddingModelEdit: ReducerProtocol {
1515
@BindingState var modelName: String = ""
1616
var apiKeyName: String { apiKeySelection.apiKeyName }
1717
var baseURL: String { baseURLSelection.baseURL }
18+
var isFullURL: Bool { baseURLSelection.isFullURL }
1819
var availableModelNames: [String] = []
1920
var availableAPIKeys: [String] = []
2021
var isTesting = false
@@ -75,6 +76,7 @@ struct EmbeddingModelEdit: ReducerProtocol {
7576
info: .init(
7677
apiKeyName: state.apiKeyName,
7778
baseURL: state.baseURL,
79+
isFullURL: state.isFullURL,
7880
maxTokens: state.maxTokens,
7981
modelName: state.modelName
8082
)
@@ -157,7 +159,7 @@ extension EmbeddingModelEdit.State {
157159
apiKeyName: model.info.apiKeyName,
158160
apiKeyManagement: .init(availableAPIKeyNames: [model.info.apiKeyName])
159161
),
160-
baseURLSelection: .init(baseURL: model.info.baseURL)
162+
baseURLSelection: .init(baseURL: model.info.baseURL, isFullURL: model.info.isFullURL)
161163
)
162164
}
163165
}
@@ -171,6 +173,7 @@ extension EmbeddingModel {
171173
info: .init(
172174
apiKeyName: state.apiKeyName,
173175
baseURL: state.baseURL.trimmingCharacters(in: .whitespacesAndNewlines),
176+
isFullURL: state.isFullURL,
174177
maxTokens: state.maxTokens,
175178
modelName: state.modelName.trimmingCharacters(in: .whitespacesAndNewlines)
176179
)

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelEditView.swift

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct EmbeddingModelEditView: View {
6363
.onAppear {
6464
store.send(.appear)
6565
}
66+
.fixedSize(horizontal: false, vertical: true)
6667
}
6768

6869
var nameTextField: some View {
@@ -96,16 +97,29 @@ struct EmbeddingModelEditView: View {
9697
}
9798
}
9899

99-
func baseURLTextField(prompt: Text?) -> some View {
100+
func baseURLTextField<V: View>(
101+
title: String = "Base URL",
102+
prompt: Text?,
103+
@ViewBuilder trailingContent: @escaping () -> V
104+
) -> some View {
100105
BaseURLPicker(
106+
title: title,
101107
prompt: prompt,
102108
store: store.scope(
103109
state: \.baseURLSelection,
104110
action: EmbeddingModelEdit.Action.baseURLSelection
105-
)
111+
),
112+
trailingContent: trailingContent
106113
)
107114
}
108115

116+
func baseURLTextField(
117+
title: String = "Base URL",
118+
prompt: Text?
119+
) -> some View {
120+
baseURLTextField(title: title, prompt: prompt, trailingContent: { EmptyView() })
121+
}
122+
109123
struct MaxTokensTextField: Equatable {
110124
@BindingViewState var maxTokens: Int
111125
var suggestedMaxTokens: Int?
@@ -178,7 +192,9 @@ struct EmbeddingModelEditView: View {
178192

179193
@ViewBuilder
180194
var openAI: some View {
181-
baseURLTextField(prompt: Text("https://api.openai.com"))
195+
baseURLTextField(prompt: Text("https://api.openai.com")) {
196+
Text("/v1/embeddings")
197+
}
182198
apiKeyNamePicker
183199

184200
WithViewStore(
@@ -204,6 +220,17 @@ struct EmbeddingModelEditView: View {
204220
}
205221

206222
maxTokensTextField
223+
224+
VStack(alignment: .leading, spacing: 8) {
225+
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
226+
" To get an API key, please visit [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys)"
227+
)
228+
229+
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
230+
" If you don't have access to GPT-4, you may need to visit [https://platform.openai.com/account/billing/overview](https://platform.openai.com/account/billing/overview) to buy some credits. A ChatGPT Plus subscription is not enough to access GPT-4 through API."
231+
)
232+
}
233+
.padding(.vertical)
207234
}
208235

209236
@ViewBuilder
@@ -223,7 +250,34 @@ struct EmbeddingModelEditView: View {
223250

224251
@ViewBuilder
225252
var openAICompatible: some View {
226-
baseURLTextField(prompt: Text("https://"))
253+
WithViewStore(store.scope(
254+
state: \.baseURLSelection,
255+
action: EmbeddingModelEdit.Action.baseURLSelection
256+
), removeDuplicates: { $0.isFullURL != $1.isFullURL }) { viewStore in
257+
Picker(
258+
selection: viewStore.$isFullURL,
259+
content: {
260+
Text("Base URL").tag(false)
261+
Text("Full URL").tag(true)
262+
},
263+
label: { Text("URL") }
264+
)
265+
.pickerStyle(.segmented)
266+
}
267+
268+
WithViewStore(store, observe: \.isFullURL) { viewStore in
269+
baseURLTextField(
270+
title: "",
271+
prompt: viewStore.state
272+
? Text("https://api.openai.com/v1/embeddings")
273+
: Text("https://api.openai.com")
274+
) {
275+
if !viewStore.state {
276+
Text("/v1/embeddings")
277+
}
278+
}
279+
}
280+
227281
apiKeyNamePicker
228282

229283
WithViewStore(

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelManagementView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct EmbeddingModelManagementView: View {
1212
action: EmbeddingModelManagement.Action.embeddingModelItem
1313
)) { store in
1414
EmbeddingModelEditView(store: store)
15-
.frame(minWidth: 400)
15+
.frame(width: 800)
1616
}
1717
}
1818
}
Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,59 @@
11
import ComposableArchitecture
22
import SwiftUI
33

4-
struct BaseURLPicker: View {
4+
struct BaseURLPicker<TrailingContent: View>: View {
5+
let title: String
56
let prompt: Text?
67
let store: StoreOf<BaseURLSelection>
7-
8+
@ViewBuilder let trailingContent: () -> TrailingContent
9+
810
var body: some View {
911
WithViewStore(store) { viewStore in
10-
TextField("Base URL", text: viewStore.$baseURL, prompt: prompt)
11-
.overlay(alignment: .trailing) {
12-
Picker(
13-
"",
14-
selection: viewStore.$baseURL,
15-
content: {
16-
if !viewStore.state.availableBaseURLs
17-
.contains(viewStore.state.baseURL),
18-
!viewStore.state.baseURL.isEmpty
19-
{
20-
Text("Custom Value").tag(viewStore.state.baseURL)
21-
}
22-
23-
Text("Empty (Default Value)").tag("")
24-
25-
ForEach(viewStore.state.availableBaseURLs, id: \.self) { baseURL in
26-
Text(baseURL).tag(baseURL)
12+
HStack {
13+
TextField(title, text: viewStore.$baseURL, prompt: prompt)
14+
.overlay(alignment: .trailing) {
15+
Picker(
16+
"",
17+
selection: viewStore.$baseURL,
18+
content: {
19+
if !viewStore.state.availableBaseURLs
20+
.contains(viewStore.state.baseURL),
21+
!viewStore.state.baseURL.isEmpty
22+
{
23+
Text("Custom Value").tag(viewStore.state.baseURL)
24+
}
25+
26+
Text("Empty (Default Value)").tag("")
27+
28+
ForEach(viewStore.state.availableBaseURLs, id: \.self) { baseURL in
29+
Text(baseURL).tag(baseURL)
30+
}
2731
}
28-
}
29-
)
30-
.frame(width: 20)
31-
}
32-
.onAppear {
33-
viewStore.send(.appear)
34-
}
32+
)
33+
.frame(width: 20)
34+
}
35+
36+
trailingContent()
37+
.foregroundStyle(.secondary)
38+
}
39+
.onAppear {
40+
viewStore.send(.appear)
41+
}
3542
}
3643
}
3744
}
3845

46+
extension BaseURLPicker where TrailingContent == EmptyView {
47+
init(
48+
title: String,
49+
prompt: Text? = nil,
50+
store: StoreOf<BaseURLSelection>
51+
) {
52+
self.init(
53+
title: title,
54+
prompt: prompt,
55+
store: store,
56+
trailingContent: { EmptyView() }
57+
)
58+
}
59+
}

Core/Sources/HostApp/AccountSettings/SharedModelManagement/BaseURLSelection.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import SwiftUI
66
struct BaseURLSelection: ReducerProtocol {
77
struct State: Equatable {
88
@BindingState var baseURL: String = ""
9+
@BindingState var isFullURL: Bool = false
910
var availableBaseURLs: [String] = []
1011
}
1112

0 commit comments

Comments
 (0)