Skip to content

Commit 7e1950e

Browse files
committed
Merge branch 'release/0.31.3'
2 parents 205bbd0 + eb30a93 commit 7e1950e

28 files changed

+722
-96
lines changed

Core/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ let package = Package(
275275
.product(name: "AppMonitoring", package: "Tool"),
276276
.product(name: "ChatTab", package: "Tool"),
277277
.product(name: "Logger", package: "Tool"),
278+
.product(name: "CustomAsyncAlgorithms", package: "Tool"),
278279
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
279280
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
280281
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),

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

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AIModel
2+
import Toast
23
import ComposableArchitecture
34
import Dependencies
45
import Keychain
@@ -40,7 +41,12 @@ struct ChatModelEdit: ReducerProtocol {
4041
case baseURLSelection(BaseURLSelection.Action)
4142
}
4243

43-
@Dependency(\.toast) var toast
44+
var toast: (String, ToastType) -> Void {
45+
@Dependency(\.namespacedToast) var toast
46+
return {
47+
toast($0, $1, "ChatModelEdit")
48+
}
49+
}
4450
@Dependency(\.apiKeyKeychain) var keychain
4551

4652
var body: some ReducerProtocol<State, Action> {
@@ -86,27 +92,35 @@ struct ChatModelEdit: ReducerProtocol {
8692
)
8793
return .run { send in
8894
do {
89-
let reply =
90-
try await ChatGPTService(
91-
configuration: UserPreferenceChatGPTConfiguration()
92-
.overriding {
93-
$0.model = model
94-
}
95-
).sendAndWait(content: "Hello")
95+
let service = ChatGPTService(
96+
configuration: UserPreferenceChatGPTConfiguration()
97+
.overriding {
98+
$0.model = model
99+
}
100+
)
101+
let reply = try await service
102+
.sendAndWait(content: "Respond with \"Test succeeded\"")
96103
await send(.testSucceeded(reply ?? "No Message"))
104+
let stream = try await service
105+
.send(content: "Respond with \"Stream response is working\"")
106+
var streamReply = ""
107+
for try await chunk in stream {
108+
streamReply += chunk
109+
}
110+
await send(.testSucceeded(streamReply))
97111
} catch {
98112
await send(.testFailed(error.localizedDescription))
99113
}
100114
}
101115

102116
case let .testSucceeded(message):
103117
state.isTesting = false
104-
toast(message, .info)
118+
toast(message.trimmingCharacters(in: .whitespacesAndNewlines), .info)
105119
return .none
106120

107121
case let .testFailed(message):
108122
state.isTesting = false
109-
toast(message, .error)
123+
toast(message.trimmingCharacters(in: .whitespacesAndNewlines), .error)
110124
return .none
111125

112126
case .refreshAvailableModelNames:
@@ -132,6 +146,15 @@ struct ChatModelEdit: ReducerProtocol {
132146
state.suggestedMaxTokens = nil
133147
}
134148
return .none
149+
case .claude:
150+
if let knownModel = ClaudeChatCompletionsService
151+
.KnownModel(rawValue: state.modelName)
152+
{
153+
state.suggestedMaxTokens = knownModel.contextWindow
154+
} else {
155+
state.suggestedMaxTokens = nil
156+
}
157+
return .none
135158
default:
136159
state.suggestedMaxTokens = nil
137160
return .none
@@ -192,13 +215,12 @@ extension ChatModel {
192215
isFullURL: state.isFullURL,
193216
maxTokens: state.maxTokens,
194217
supportsFunctionCalling: {
195-
if case .googleAI = state.format {
196-
return false
197-
}
198-
if case .ollama = state.format {
218+
switch state.format {
219+
case .googleAI, .ollama, .claude:
199220
return false
221+
case .azureOpenAI, .openAI, .openAICompatible:
222+
return state.supportsFunctionCalling
200223
}
201-
return state.supportsFunctionCalling
202224
}(),
203225
modelName: state.modelName.trimmingCharacters(in: .whitespacesAndNewlines),
204226
ollamaInfo: .init(keepAlive: state.ollamaKeepAlive)

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AIModel
22
import ComposableArchitecture
3+
import OpenAIService
34
import Preferences
45
import SwiftUI
56

@@ -26,6 +27,8 @@ struct ChatModelEditView: View {
2627
googleAI
2728
case .ollama:
2829
ollama
30+
case .claude:
31+
claude
2932
}
3033
}
3134
}
@@ -68,6 +71,7 @@ struct ChatModelEditView: View {
6871
store.send(.appear)
6972
}
7073
.fixedSize(horizontal: false, vertical: true)
74+
.handleToast(namespace: "ChatModelEdit")
7175
}
7276

7377
var nameTextField: some View {
@@ -96,6 +100,8 @@ struct ChatModelEditView: View {
96100
Text("Google Generative AI").tag(format)
97101
case .ollama:
98102
Text("Ollama").tag(format)
103+
case .claude:
104+
Text("Claude").tag(format)
99105
}
100106
}
101107
},
@@ -348,7 +354,7 @@ struct ChatModelEditView: View {
348354

349355
maxTokensTextField
350356
}
351-
357+
352358
@ViewBuilder
353359
var ollama: some View {
354360
baseURLTextField(prompt: Text("http://127.0.0.1:11434")) {
@@ -363,7 +369,7 @@ struct ChatModelEditView: View {
363369
}
364370

365371
maxTokensTextField
366-
372+
367373
WithViewStore(
368374
store,
369375
removeDuplicates: { $0.ollamaKeepAlive == $1.ollamaKeepAlive }
@@ -380,6 +386,51 @@ struct ChatModelEditView: View {
380386
}
381387
.padding(.vertical)
382388
}
389+
390+
@ViewBuilder
391+
var claude: some View {
392+
baseURLTextField(prompt: Text("https://api.anthropic.com")) {
393+
Text("/v1/messages")
394+
}
395+
396+
apiKeyNamePicker
397+
398+
WithViewStore(
399+
store,
400+
removeDuplicates: { $0.modelName == $1.modelName }
401+
) { viewStore in
402+
TextField("Model Name", text: viewStore.$modelName)
403+
.overlay(alignment: .trailing) {
404+
Picker(
405+
"",
406+
selection: viewStore.$modelName,
407+
content: {
408+
if ClaudeChatCompletionsService
409+
.KnownModel(rawValue: viewStore.state.modelName) == nil
410+
{
411+
Text("Custom Model").tag(viewStore.state.modelName)
412+
}
413+
ForEach(
414+
ClaudeChatCompletionsService.KnownModel.allCases,
415+
id: \.self
416+
) { model in
417+
Text(model.rawValue).tag(model.rawValue)
418+
}
419+
}
420+
)
421+
.frame(width: 20)
422+
}
423+
}
424+
425+
maxTokensTextField
426+
427+
VStack(alignment: .leading, spacing: 8) {
428+
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
429+
" For more details, please visit [https://anthropic.com](https://anthropic.com)."
430+
)
431+
}
432+
.padding(.vertical)
433+
}
383434
}
384435

385436
#Preview("OpenAI") {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extension ChatModel: ManageableAIModel {
1212
case .openAICompatible: return "OpenAI Compatible"
1313
case .googleAI: return "Google Generative AI"
1414
case .ollama: return "Ollama"
15+
case .claude: return "Claude"
1516
}
1617
}
1718

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AIModel
2+
import Toast
23
import ComposableArchitecture
34
import Dependencies
45
import Keychain
@@ -39,7 +40,12 @@ struct EmbeddingModelEdit: ReducerProtocol {
3940
case baseURLSelection(BaseURLSelection.Action)
4041
}
4142

42-
@Dependency(\.toast) var toast
43+
var toast: (String, ToastType) -> Void {
44+
@Dependency(\.namespacedToast) var toast
45+
return {
46+
toast($0, $1, "EmbeddingModelEdit")
47+
}
48+
}
4349
@Dependency(\.apiKeyKeychain) var keychain
4450

4551
var body: some ReducerProtocol<State, Action> {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ struct EmbeddingModelEditView: View {
6666
store.send(.appear)
6767
}
6868
.fixedSize(horizontal: false, vertical: true)
69+
.handleToast(namespace: "EmbeddingModelEdit")
6970
}
7071

7172
var nameTextField: some View {

Core/Sources/HostApp/FeatureSettings/PromptToCodeSettingsView.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import ProHostApp
88

99
struct PromptToCodeSettingsView: View {
1010
final class Settings: ObservableObject {
11-
@AppStorage(\.hideCommonPrecedingSpacesInSuggestion)
12-
var hideCommonPrecedingSpacesInSuggestion
13-
@AppStorage(\.suggestionCodeFontSize)
14-
var suggestionCodeFontSize
11+
@AppStorage(\.hideCommonPrecedingSpacesInPromptToCode)
12+
var hideCommonPrecedingSpaces
13+
@AppStorage(\.promptToCodeCodeFontSize)
14+
var fontSize
1515
@AppStorage(\.promptToCodeGenerateDescription)
1616
var promptToCodeGenerateDescription
1717
@AppStorage(\.promptToCodeGenerateDescriptionInUserPreferredLanguage)
@@ -84,25 +84,25 @@ struct PromptToCodeSettingsView: View {
8484
}
8585
}
8686

87-
SettingsDivider("Mirroring Settings of Suggestion Feature")
87+
SettingsDivider("UI")
8888

8989
Form {
90-
Toggle(isOn: $settings.hideCommonPrecedingSpacesInSuggestion) {
90+
Toggle(isOn: $settings.hideCommonPrecedingSpaces) {
9191
Text("Hide Common Preceding Spaces")
92-
}.disabled(true)
92+
}
9393

9494
HStack {
9595
TextField(text: .init(get: {
96-
"\(Int(settings.suggestionCodeFontSize))"
96+
"\(Int(settings.fontSize))"
9797
}, set: {
98-
settings.suggestionCodeFontSize = Double(Int($0) ?? 0)
98+
settings.fontSize = Double(Int($0) ?? 0)
9999
})) {
100100
Text("Font size of suggestion code")
101101
}
102102
.textFieldStyle(.roundedBorder)
103103

104104
Text("pt")
105-
}.disabled(true)
105+
}
106106
}
107107

108108
ScopeForm()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Dependencies
2+
import SwiftUI
3+
import Toast
4+
5+
struct ToastHandler: View {
6+
@ObservedObject var toastController: ToastController
7+
let namespace: String?
8+
9+
init(toastController: ToastController, namespace: String?) {
10+
_toastController = .init(wrappedValue: toastController)
11+
self.namespace = namespace
12+
}
13+
14+
var body: some View {
15+
VStack(spacing: 4) {
16+
ForEach(toastController.messages) { message in
17+
if let n = message.namespace, n != namespace {
18+
EmptyView()
19+
} else {
20+
message.content
21+
.foregroundColor(.white)
22+
.padding(8)
23+
.background({
24+
switch message.type {
25+
case .info: return Color.accentColor
26+
case .error: return Color(nsColor: .systemRed)
27+
case .warning: return Color(nsColor: .systemOrange)
28+
}
29+
}() as Color, in: RoundedRectangle(cornerRadius: 8))
30+
.shadow(color: Color.black.opacity(0.2), radius: 4)
31+
}
32+
}
33+
}
34+
.padding()
35+
.allowsHitTesting(false)
36+
}
37+
}
38+
39+
extension View {
40+
func handleToast(namespace: String? = nil) -> some View {
41+
@Dependency(\.toastController) var toastController
42+
return overlay(alignment: .bottom) {
43+
ToastHandler(toastController: toastController, namespace: namespace)
44+
}.environment(\.toast) { [toastController] content, type in
45+
toastController.toast(content: content, type: type, namespace: namespace)
46+
}
47+
}
48+
}
49+

Core/Sources/HostApp/TabContainer.swift

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,33 +75,12 @@ public struct TabContainer: View {
7575
}
7676
.environment(\.tabBarTabTag, tag)
7777
.frame(minHeight: 400)
78-
.overlay(alignment: .bottom) {
79-
VStack(spacing: 4) {
80-
ForEach(toastController.messages) { message in
81-
message.content
82-
.foregroundColor(.white)
83-
.padding(8)
84-
.background({
85-
switch message.type {
86-
case .info: return Color.accentColor
87-
case .error: return Color(nsColor: .systemRed)
88-
case .warning: return Color(nsColor: .systemOrange)
89-
}
90-
}() as Color, in: RoundedRectangle(cornerRadius: 8))
91-
.shadow(color: Color.black.opacity(0.2), radius: 4)
92-
}
93-
}
94-
.padding()
95-
.allowsHitTesting(false)
96-
}
9778
}
9879
.focusable(false)
9980
.padding(.top, 8)
10081
.background(.ultraThinMaterial.opacity(0.01))
10182
.background(Color(nsColor: .controlBackgroundColor).opacity(0.4))
102-
.environment(\.toast) { [toastController] content, type in
103-
toastController.toast(content: content, type: type)
104-
}
83+
.handleToast()
10584
.onPreferenceChange(TabBarItemPreferenceKey.self) { items in
10685
tabBarItems = items
10786
}

0 commit comments

Comments
 (0)