From b1896836921573f94f83a00ebd5e6337fa035a89 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 18:07:21 +0800 Subject: [PATCH 01/30] perf: update gemini package --- Easydict.xcodeproj/project.pbxproj | 2 +- Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index de0bac069..60cc6eac8 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -3573,7 +3573,7 @@ repositoryURL = "https://github.com/google/generative-ai-swift"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.4.4; + minimumVersion = 0.5.3; }; }; 03022F1A2B35DEBA00B63209 /* XCRemoteSwiftPackageReference "Hue" */ = { diff --git a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved index b579e007d..323e32f13 100644 --- a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "1cdad83f58bb3467937e3ac7250e920b2a1fea08918ee2d945f05853d37eb573", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -77,8 +78,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/generative-ai-swift", "state" : { - "revision" : "dcbdb5e591e1aa2bb68851dc7515f6b0a59026cd", - "version" : "0.4.7" + "revision" : "5d750b80651da9721c37c5eb1fc0b6750d1884d3", + "version" : "0.5.3" } }, { @@ -316,5 +317,5 @@ } } ], - "version" : 2 + "version" : 3 } From 9661e35f20e73f59f688d95361aed794c6ea568b Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:10:37 +0800 Subject: [PATCH 02/30] feat: add new models for gemini translation --- Easydict/App/Localizable.xcstrings | 8 +- .../Configuration+Defaults.swift | 25 ++ .../Swift/Service/Gemini/GeminiService.swift | 24 +- .../GeminiService+ConfigurableService.swift | 222 +++++++++++++++++- Easydict/objc/Service/Model/EZConstKey.h | 11 +- .../Utility/EZLinkParser/EZSchemeParser.m | 10 +- 6 files changed, 286 insertions(+), 14 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0ecc4103d..0168a0b36 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -2410,18 +2410,18 @@ } } }, - "service.configuration.gemini.api_key.title" : { + "service.configuration.gemini.api_key.placeholder" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "API Key" + "value" : "xxxxxxxxxxxxx" } }, "zh-Hans" : { "stringUnit" : { - "state" : "translated", - "value" : "API Key" + "state" : "needs_review", + "value" : "xxxxxxxxxxxxx" } } } diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index 55317599d..4a4e5c31d 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -312,6 +312,31 @@ extension Defaults.Keys { // Gemni static let geminiAPIKey = Key(EZGeminiAPIKey) + static let geminiTranslation = Key( + translationStoredKey(.gemini), + default: "1" + ) + static let geminiDictionary = Key( + dictionaryStoredKey(.gemini), + default: "1" + ) + static let geminiSentence = Key( + sentenceStoredKey(.gemini), + default: "1" + ) + static let geminiServiceUsageStatus = Key( + serviceUsageStatusStoredKey(.gemini), + default: .default + ) + static let geminiModel = Key(EZGeminiModelKey, default: GeminiModel.gemini1_5_flash.rawValue) + static let geminiAvailableModels = Key( + EZGeminiAvailableModelsKey, + default: GeminiModel.allCases.map { $0.rawValue }.joined(separator: ",") + ) + static let geminiVaildModels = Key( + EZGeminiValidModelsKey, + default: GeminiModel.allCases.map { $0.rawValue } + ) } /// shortcut diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 85c07e3f8..1a8faa517 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -10,7 +10,6 @@ import Defaults import Foundation import GoogleGenerativeAI -// TODO: add a LLM stream service base class, make both OpenAI and Gemini inherit from it. @objc(EZGeminiService) public final class GeminiService: LLMStreamService { // MARK: Public @@ -28,7 +27,7 @@ public final class GeminiService: LLMStreamService { } override public func queryTextType() -> EZQueryTextType { - [.translation] + [.translation, .dictionary, .sentence] } override public func translate( @@ -39,11 +38,14 @@ public final class GeminiService: LLMStreamService { ) { Task { do { + result.from = from + result.to = to + result.isStreamFinished = false let translationPrompt = translationPrompt(text: text, from: from, to: to) let prompt = LLMStreamService.translationSystemPrompt + "\n" + translationPrompt let model = GenerativeModel( - name: "gemini-pro", + name: model, apiKey: apiKey, safetySettings: [ harassmentBlockNone, @@ -53,8 +55,6 @@ public final class GeminiService: LLMStreamService { ] ) - result.isStreamFinished = false - var resultString = "" // Gemini Docs: https://github.com/google/generative-ai-swift @@ -123,6 +123,20 @@ public final class GeminiService: LLMStreamService { Defaults[.geminiAPIKey] ?? "" } + override var availableModels: [String] { + Defaults[.geminiVaildModels] + } + + override var model: String { + get { + Defaults[.geminiModel] + } + set { + // easydict://writeKeyValue?EZGeminiModelKey=gemini-1.5-flash + Defaults[.geminiModel] = newValue + } + } + // MARK: Private // Set Gemini safety level to BLOCK_NONE diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift index b6a6e75d2..365754194 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift @@ -6,16 +6,232 @@ // Copyright © 2024 izual. All rights reserved. // +import Combine +import Defaults import Foundation import SwiftUI +// MARK: - GeminiService + ConfigurableService + extension GeminiService: ConfigurableService { func configurationListItems() -> some View { - ServiceConfigurationSecretSectionView(service: self, observeKeys: [.geminiAPIKey]) { + GeminiServiceConfigurationView(service: self) + } +} + +// MARK: - GeminiServiceConfigurationView + +private struct GeminiServiceConfigurationView: View { + // MARK: Lifecycle + + init(service: GeminiService) { + self.service = service + self.viewModel = GeminiViewModel(service: service) + } + + // MARK: Internal + + let service: GeminiService + + var body: some View { + ServiceConfigurationSecretSectionView( + service: service, + observeKeys: [.geminiAPIKey, .geminiAvailableModels] + ) { ServiceConfigurationSecureInputCell( - textFieldTitleKey: "service.configuration.gemini.api_key.title", - key: .geminiAPIKey + textFieldTitleKey: "service.configuration.openai.api_key.title", + key: .geminiAPIKey, + placeholder: "service.configuration.gemini.api_key.placeholder" + ) + // supported models + TextField( + "service.configuration.custom_openai.supported_models.title", + text: viewModel.$availableModels ?? "", + prompt: Text("service.configuration.custom_openai.model.placeholder") + ) + .padding(10.0) + Picker( + "service.configuration.openai.model.title", + selection: viewModel.$model + ) { + ForEach(viewModel.validModels, id: \.self) { value in + Text(value) + } + } + .padding(10.0) + + ServiceConfigurationToggleCell( + titleKey: "service.configuration.openai.translation.title", + key: .geminiTranslation + ) + ServiceConfigurationToggleCell( + titleKey: "service.configuration.openai.sentence.title", + key: .geminiSentence + ) + ServiceConfigurationToggleCell( + titleKey: "service.configuration.openai.dictionary.title", + key: .geminiDictionary + ) + ServiceConfigurationPickerCell( + titleKey: "service.configuration.openai.usage_status.title", + key: .geminiServiceUsageStatus, + values: GeminiUsageStatus.allCases + ) + } + .onDisappear { + viewModel.invalidate() + } + } + + // MARK: Private + + @ObservedObject private var viewModel: GeminiViewModel +} + +// MARK: - GeminiViewModel + +private class GeminiViewModel: ObservableObject { + // MARK: Lifecycle + + init(service: GeminiService) { + self.service = service + Defaults.publisher(.geminiModel, options: []) + .removeDuplicates() + .sink { _ in + self.modelChanged() + } + .store(in: &cancellables) + Defaults.publisher(.geminiAvailableModels) + .removeDuplicates() + .throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true) + .sink { _ in + self.modelsTextChanged() + } + .store(in: &cancellables) + } + + // MARK: Internal + + let service: GeminiService + + @Default(.geminiModel) var model + @Default(.geminiAvailableModels) var availableModels + + @Published var validModels: [String] = [] + + func invalidate() { + cancellables.forEach { $0.cancel() } + cancellables.removeAll() + } + + // MARK: Private + + private var cancellables: Set = [] + + private func modelChanged() { + if !validModels.contains(model) { + if model.isEmpty { + availableModels = "" + } else { + if availableModels?.isEmpty == true { + availableModels = model + } else { + availableModels = /* "\(model), " + */ availableModels ?? "" + } + } + } + serviceConfigChanged() + } + + private func modelsTextChanged() { + guard let availableModels else { return } + + validModels = availableModels.components(separatedBy: ",") + .map { $0.trim() }.filter { !$0.isEmpty } + + if validModels.isEmpty { + model = "" + } else if !validModels.contains(model) { + model = validModels[0] + } + + Defaults[.geminiVaildModels] = validModels + } + + private func serviceConfigChanged() { + objectWillChange.send() + + let userInfo: [String: Any] = [ + EZWindowTypeKey: service.windowType.rawValue, + EZServiceTypeKey: service.serviceType().rawValue, + ] + let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) + NotificationCenter.default.post(notification) + } +} + +// MARK: - GeminiModel + +// swiftlint:disable identifier_name +enum GeminiModel: String, CaseIterable { + // Docs: https://ai.google.dev/gemini-api/docs/models/gemini + + // RPM: Requests per minute, TPM: Tokens per minute + // RPD: Requests per day, TPD: Tokens per day + case gemini1_0_pro = "gemini-1.0-pro" // Free 15 RPM/32,000 TPM, 1,500 RPD/46,080,000 TPD (n/a context length) + case gemini1_5_flash = "gemini-1.5-flash" // Free 15 RPM/100million TPM, 1500 RPD/ n/a TPD (1048k context length) + case gemini1_5_pro = "gemini-1.5-pro" // Free 2 RPM/32,000 TPM, 50 RPD/46,080,000 TPD (1048k context length) +} + +// MARK: EnumLocalizedStringConvertible + +// swiftlint:enable identifier_name + +extension GeminiModel: EnumLocalizedStringConvertible { + var title: String { + rawValue + } +} + +// MARK: Defaults.Serializable + +extension GeminiModel: Defaults.Serializable {} + +// MARK: - GeminiUsageStatus + +enum GeminiUsageStatus: String, CaseIterable { + case `default` = "0" + case alwaysOff = "1" + case alwaysOn = "2" +} + +// MARK: EnumLocalizedStringConvertible + +extension GeminiUsageStatus: EnumLocalizedStringConvertible { + var title: String { // Use same xcstring with openai for title + switch self { + case .default: + NSLocalizedString( + "service.configuration.openai.usage_status_default.title", + bundle: .main, + comment: "" + ) + case .alwaysOff: + NSLocalizedString( + "service.configuration.openai.usage_status_always_off.title", + bundle: .main, + comment: "" + ) + case .alwaysOn: + NSLocalizedString( + "service.configuration.openai.usage_status_always_on.title", + bundle: .main, + comment: "" ) } } } + +// MARK: Defaults.Serializable + +extension GeminiUsageStatus: Defaults.Serializable {} diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index 988b90de1..0737d7d84 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -44,6 +44,15 @@ static NSString *const EZCustomOpenAIValidModelsKey = @"EZCustomOpenAIValidModel // // Built-in AI static NSString *const EZBuiltInAIModelKey = @"EZBuiltInAIModelKey"; +// Gemini +static NSString *const EZGeminiAPIKey = @"EZGeminiAPIKey"; +static NSString *const EZGeminiTranslationKey = @"EZGeminiTranslationKey"; +static NSString *const EZGeminiDictionaryKey = @"EZGeminiDictionaryKey"; +static NSString *const EZGeminiSentenceKey = @"EZGeminiSentenceKey"; +static NSString *const EZGeminiServiceUsageStatusKey = @"EZGeminiServiceUsageStatusKey"; +static NSString *const EZGeminiModelKey = @"EZGeminiModelKey"; +static NSString *const EZGeminiAvailableModelsKey = @"EZGeminiAvailableModelsKey"; +static NSString *const EZGeminiValidModelsKey = @"EZGeminiValidModelsKey"; static NSString *const EZDeepLAuthKey = @"EZDeepLAuthKey"; static NSString *const EZDeepLTranslateEndPointKey = @"EZDeepLTranslateEndPointKey"; @@ -53,7 +62,7 @@ static NSString *const EZNiuTransAPIKey = @"EZNiuTransAPIKey"; static NSString *const EZCaiyunToken = @"EZCaiyunToken"; static NSString *const EZTencentSecretId = @"EZTencentSecretId"; static NSString *const EZTencentSecretKey = @"EZTencentSecretKey"; -static NSString *const EZGeminiAPIKey = @"EZGeminiAPIKey"; + static NSString *const EZAliAccessKeyId = @"EZAliAccessKeyId"; static NSString *const EZAliAccessKeySecret = @"EZAliAccessKeySecret"; diff --git a/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m b/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m index b3a4b3c8e..fc325dad4 100644 --- a/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m +++ b/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m @@ -211,6 +211,15 @@ - (NSArray *)allowedReadWriteKeys { EZCustomOpenAISentenceKey, EZCustomOpenAIServiceUsageStatusKey, + EZGeminiAPIKey, + EZGeminiTranslationKey, + EZGeminiDictionaryKey, + EZGeminiSentenceKey, + EZGeminiServiceUsageStatusKey, + EZGeminiModelKey, + EZGeminiAvailableModelsKey, + EZGeminiValidModelsKey, + EZYoudaoTranslationKey, EZYoudaoDictionaryKey, @@ -225,7 +234,6 @@ - (NSArray *)allowedReadWriteKeys { EZAliAccessKeyId, EZAliAccessKeySecret, - EZGeminiAPIKey, EZIntelligentQueryModeKey, ]; From 6d2e66bd0a62a86fd8beeb26017e51dbd148a149 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:17:13 +0800 Subject: [PATCH 03/30] fix: type in openai service --- .../Feature/Configuration/Configuration+Defaults.swift | 6 +++--- .../BuiltInAIService+ConfigurableService.swift | 2 +- .../CustomOpenAIService+ConfigurableService.swift | 2 +- .../OpenAIService+ConfigurableService.swift | 10 +++++----- .../ServiceConfigurationCells.swift | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index 55317599d..46618c67f 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -221,7 +221,7 @@ extension Defaults.Keys { sentenceStoredKey(.openAI), default: "1" ) - static let openAIServiceUsageStatus = Key( + static let openAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.openAI), default: .default ) @@ -254,7 +254,7 @@ extension Defaults.Keys { sentenceStoredKey(.customOpenAI), default: "0" ) - static let customOpenAIServiceUsageStatus = Key( + static let customOpenAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.builtInAI), default: .default ) @@ -280,7 +280,7 @@ extension Defaults.Keys { sentenceStoredKey(.builtInAI), default: "0" ) - static let builtInAIServiceUsageStatus = Key( + static let builtInAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.builtInAI), default: .default ) diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift index e4fff4428..a876825e1 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift @@ -31,7 +31,7 @@ extension BuiltInAIService: ConfigurableService { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .builtInAIServiceUsageStatus, - values: OpenAIUsageStats.allCases + values: OpenAIUsageStatus.allCases ) } } diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift index 26bf87719..f18da4605 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift @@ -89,7 +89,7 @@ private struct CustomOpenAIServiceConfigurationView: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .customOpenAIServiceUsageStatus, - values: OpenAIUsageStats.allCases + values: OpenAIUsageStatus.allCases ) } .onDisappear { diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift index f4e8e2c2c..74ecaca1a 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift @@ -81,7 +81,7 @@ private struct OpenAIServiceConfigurationView: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .openAIServiceUsageStatus, - values: OpenAIUsageStats.allCases + values: OpenAIUsageStatus.allCases ) } .onDisappear { @@ -211,9 +211,9 @@ extension OpenAIModel: EnumLocalizedStringConvertible { extension OpenAIModel: Defaults.Serializable {} -// MARK: - OpenAIUsageStats +// MARK: - OpenAIUsageStatus -enum OpenAIUsageStats: String, CaseIterable { +enum OpenAIUsageStatus: String, CaseIterable { case `default` = "0" case alwaysOff = "1" case alwaysOn = "2" @@ -221,7 +221,7 @@ enum OpenAIUsageStats: String, CaseIterable { // MARK: EnumLocalizedStringConvertible -extension OpenAIUsageStats: EnumLocalizedStringConvertible { +extension OpenAIUsageStatus: EnumLocalizedStringConvertible { var title: String { switch self { case .default: @@ -248,4 +248,4 @@ extension OpenAIUsageStats: EnumLocalizedStringConvertible { // MARK: Defaults.Serializable -extension OpenAIUsageStats: Defaults.Serializable {} +extension OpenAIUsageStatus: Defaults.Serializable {} diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift index f39795a69..b89d898fe 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift @@ -155,7 +155,7 @@ struct ServiceConfigurationToggleCell: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .openAIServiceUsageStatus, - values: OpenAIUsageStats.allCases + values: OpenAIUsageStatus.allCases ) ServiceConfigurationToggleCell( From 93fd77b6018bb4bdda71fb6780701b3055fca763 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:17:28 +0800 Subject: [PATCH 04/30] fix: type in ezconstkey annotation --- Easydict/objc/Service/Model/EZConstKey.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index 988b90de1..42ad1bb1e 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -41,7 +41,7 @@ static NSString *const EZCustomOpenAIAvailableModelsKey = @"EZCustomOpenAIAvaila static NSString *const EZCustomOpenAIModelKey = @"EZCustomOpenAIModelKey"; static NSString *const EZCustomOpenAIValidModelsKey = @"EZCustomOpenAIValidModelsKey"; -// // Built-in AI +// Built-in AI static NSString *const EZBuiltInAIModelKey = @"EZBuiltInAIModelKey"; From e32fa3086984ae3b4080de81c01026326f0d1d38 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:22:11 +0800 Subject: [PATCH 05/30] fix: type in a few files --- .../Swift/Service/OpenAI/BaseOpenAIService.swift | 4 ++-- .../Swift/Service/OpenAI/LLMStreamService.swift | 6 +++--- Easydict/Swift/Service/OpenAI/Prompt.swift | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift index 1bd63677b..44cd99830 100644 --- a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift +++ b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift @@ -151,7 +151,7 @@ extension BaseOpenAIService { typealias Role = ChatCompletionMessageParam.Role var chats: [ChatCompletionMessageParam] = [] - let messages = translatioMessages(text: text, from: from, to: to) + let messages = translationMessages(text: text, from: from, to: to) for message in messages { if let roleRawValue = message["role"], let role = Role(rawValue: roleRawValue), @@ -183,7 +183,7 @@ extension BaseOpenAIService { case .translation: fallthrough default: - messages = translatioMessages(text: text, from: from, to: to) + messages = translationMessages(text: text, from: from, to: to) } var chats: [ChatCompletionMessageParam] = [] diff --git a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift index c85f49038..96b21361d 100644 --- a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift +++ b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift @@ -24,8 +24,8 @@ public class LLMStreamService: QueryService { } override public func supportLanguagesDictionary() -> MMOrderedDictionary { - let allLangauges = EZLanguageManager.shared().allLanguages - let supportedLanguages = allLangauges.filter { language in + let allLanguages = EZLanguageManager.shared().allLanguages + let supportedLanguages = allLanguages.filter { language in !unsupportedLanguages.contains(language) } @@ -105,7 +105,7 @@ public class LLMStreamService: QueryService { return resultText } - /// Get query type by text and from && to langauge. + /// Get query type by text and from && to language. func queryType(text: String, from: Language, to _: Language) -> EZQueryTextType { let enableDictionary = queryTextType().contains(.dictionary) var isQueryDictionary = false diff --git a/Easydict/Swift/Service/OpenAI/Prompt.swift b/Easydict/Swift/Service/OpenAI/Prompt.swift index 45b94e87c..b4f8e3805 100644 --- a/Easydict/Swift/Service/OpenAI/Prompt.swift +++ b/Easydict/Swift/Service/OpenAI/Prompt.swift @@ -16,10 +16,10 @@ extension LLMStreamService { """ func translationPrompt(text: String, from sourceLanguage: Language, to targetLanguage: Language) -> String { - "Translate the following \(sourceLanguage.queryLangaugeName) text into \(targetLanguage.queryLangaugeName) text: \"\"\"\(text)\"\"\"" + "Translate the following \(sourceLanguage.queryLanguageName) text into \(targetLanguage.queryLanguageName) text: \"\"\"\(text)\"\"\"" } - func translatioMessages(text: String, from: Language, to: Language) -> [[String: String]] { + func translationMessages(text: String, from: Language, to: Language) -> [[String: String]] { // Use """ %@ """ to wrap user input, Ref: https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-openai-api#h_21d4f4dc3d // let prompt = "Translate the following \(from.rawValue) text into \(to.rawValue) text: \"\"\"\(text)\"\"\"" @@ -138,7 +138,7 @@ extension LLMStreamService { ], ] - let fromClassicalChinseseFewShot = [ + let fromClassicalChineseFewShot = [ // wyw --> zh [ "role": "user", @@ -211,7 +211,7 @@ extension LLMStreamService { ], ] - let toClassicalChinseseFewShot = [ + let toClassicalChineseFewShot = [ // --> wyw [ "role": "user", @@ -256,10 +256,10 @@ extension LLMStreamService { messages.append(contentsOf: chineseFewShot) if from == .classicalChinese { - messages.append(contentsOf: fromClassicalChinseseFewShot) + messages.append(contentsOf: fromClassicalChineseFewShot) } if to == .classicalChinese { - messages.append(contentsOf: toClassicalChinseseFewShot) + messages.append(contentsOf: toClassicalChineseFewShot) } let userMessages = [ @@ -852,7 +852,7 @@ extension LLMStreamService { } extension Language { - var queryLangaugeName: String { + var queryLanguageName: String { let languageName = switch self { case .classicalChinese: "简体中文文言文" From 659fa9c9cf3d5268d155c4a345b279b91dd3ad80 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:22:53 +0800 Subject: [PATCH 06/30] perf: update chatgpt link --- Easydict/Swift/Service/OpenAI/OpenAIService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/Swift/Service/OpenAI/OpenAIService.swift b/Easydict/Swift/Service/OpenAI/OpenAIService.swift index 94a20532d..396da9ce0 100644 --- a/Easydict/Swift/Service/OpenAI/OpenAIService.swift +++ b/Easydict/Swift/Service/OpenAI/OpenAIService.swift @@ -24,7 +24,7 @@ class OpenAIService: BaseOpenAIService { } override public func link() -> String? { - "https://chat.openai.com" + "https://chatgpt.com" } // MARK: Internal From 930e7e5836163fdae7be20ac4636e2b2a9ac7cda Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 22:36:29 +0800 Subject: [PATCH 07/30] fix: build error --- Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved index 323e32f13..2e0e0074b 100644 --- a/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Easydict.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "1cdad83f58bb3467937e3ac7250e920b2a1fea08918ee2d945f05853d37eb573", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -317,5 +316,5 @@ } } ], - "version" : 3 + "version" : 2 } From a9b784e81a24631d01acd70d04a96bd2732ad3f8 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 23:06:54 +0800 Subject: [PATCH 08/30] perf: remove gemini keys in ezschemeparser --- .../Configuration/Configuration+Defaults.swift | 8 ++++---- Easydict/objc/Service/Model/EZConstKey.h | 11 +---------- Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m | 10 +--------- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index 4a4e5c31d..5d7d18841 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -311,7 +311,7 @@ extension Defaults.Keys { static let aliAccessKeySecret = Key(EZAliAccessKeySecret) // Gemni - static let geminiAPIKey = Key(EZGeminiAPIKey) + static let geminiAPIKey = Key("EZGeminiAPIKey") static let geminiTranslation = Key( translationStoredKey(.gemini), default: "1" @@ -328,13 +328,13 @@ extension Defaults.Keys { serviceUsageStatusStoredKey(.gemini), default: .default ) - static let geminiModel = Key(EZGeminiModelKey, default: GeminiModel.gemini1_5_flash.rawValue) + static let geminiModel = Key("geminiModel", default: GeminiModel.gemini1_5_flash.rawValue) static let geminiAvailableModels = Key( - EZGeminiAvailableModelsKey, + "geminiAvailableModels", default: GeminiModel.allCases.map { $0.rawValue }.joined(separator: ",") ) static let geminiVaildModels = Key( - EZGeminiValidModelsKey, + "geminiVaildModels", default: GeminiModel.allCases.map { $0.rawValue } ) } diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index 0737d7d84..d21ffbd53 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -44,16 +44,6 @@ static NSString *const EZCustomOpenAIValidModelsKey = @"EZCustomOpenAIValidModel // // Built-in AI static NSString *const EZBuiltInAIModelKey = @"EZBuiltInAIModelKey"; -// Gemini -static NSString *const EZGeminiAPIKey = @"EZGeminiAPIKey"; -static NSString *const EZGeminiTranslationKey = @"EZGeminiTranslationKey"; -static NSString *const EZGeminiDictionaryKey = @"EZGeminiDictionaryKey"; -static NSString *const EZGeminiSentenceKey = @"EZGeminiSentenceKey"; -static NSString *const EZGeminiServiceUsageStatusKey = @"EZGeminiServiceUsageStatusKey"; -static NSString *const EZGeminiModelKey = @"EZGeminiModelKey"; -static NSString *const EZGeminiAvailableModelsKey = @"EZGeminiAvailableModelsKey"; -static NSString *const EZGeminiValidModelsKey = @"EZGeminiValidModelsKey"; - static NSString *const EZDeepLAuthKey = @"EZDeepLAuthKey"; static NSString *const EZDeepLTranslateEndPointKey = @"EZDeepLTranslateEndPointKey"; @@ -62,6 +52,7 @@ static NSString *const EZNiuTransAPIKey = @"EZNiuTransAPIKey"; static NSString *const EZCaiyunToken = @"EZCaiyunToken"; static NSString *const EZTencentSecretId = @"EZTencentSecretId"; static NSString *const EZTencentSecretKey = @"EZTencentSecretKey"; +static NSString *const EZGeminiAPIKey = @"EZGeminiAPIKey"; static NSString *const EZAliAccessKeyId = @"EZAliAccessKeyId"; diff --git a/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m b/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m index fc325dad4..b3a4b3c8e 100644 --- a/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m +++ b/Easydict/objc/Utility/EZLinkParser/EZSchemeParser.m @@ -211,15 +211,6 @@ - (NSArray *)allowedReadWriteKeys { EZCustomOpenAISentenceKey, EZCustomOpenAIServiceUsageStatusKey, - EZGeminiAPIKey, - EZGeminiTranslationKey, - EZGeminiDictionaryKey, - EZGeminiSentenceKey, - EZGeminiServiceUsageStatusKey, - EZGeminiModelKey, - EZGeminiAvailableModelsKey, - EZGeminiValidModelsKey, - EZYoudaoTranslationKey, EZYoudaoDictionaryKey, @@ -234,6 +225,7 @@ - (NSArray *)allowedReadWriteKeys { EZAliAccessKeyId, EZAliAccessKeySecret, + EZGeminiAPIKey, EZIntelligentQueryModeKey, ]; From 53f7ada27cc7a84b4e299323e2313fecc53fd10a Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 23:08:27 +0800 Subject: [PATCH 09/30] format: add back empty line --- Easydict/objc/Service/Model/EZConstKey.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index d21ffbd53..988b90de1 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -44,6 +44,7 @@ static NSString *const EZCustomOpenAIValidModelsKey = @"EZCustomOpenAIValidModel // // Built-in AI static NSString *const EZBuiltInAIModelKey = @"EZBuiltInAIModelKey"; + static NSString *const EZDeepLAuthKey = @"EZDeepLAuthKey"; static NSString *const EZDeepLTranslateEndPointKey = @"EZDeepLTranslateEndPointKey"; @@ -54,7 +55,6 @@ static NSString *const EZTencentSecretId = @"EZTencentSecretId"; static NSString *const EZTencentSecretKey = @"EZTencentSecretKey"; static NSString *const EZGeminiAPIKey = @"EZGeminiAPIKey"; - static NSString *const EZAliAccessKeyId = @"EZAliAccessKeyId"; static NSString *const EZAliAccessKeySecret = @"EZAliAccessKeySecret"; From 8df4ebb505d267d2bfd0acc9349e9985d765633e Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 23:31:03 +0800 Subject: [PATCH 10/30] perf: rename opanaiusagestatus to serviceusagestatus --- .../Configuration+Defaults.swift | 8 ++-- .../Service/OpenAI/LLMStreamService.swift | 40 ++++++++++++++++++ ...BuiltInAIService+ConfigurableService.swift | 2 +- ...tomOpenAIService+ConfigurableService.swift | 2 +- .../GeminiService+ConfigurableService.swift | 41 +------------------ .../OpenAIService+ConfigurableService.swift | 41 +------------------ .../ServiceConfigurationCells.swift | 2 +- 7 files changed, 49 insertions(+), 87 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index 18ea3d282..cac83c0af 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -221,7 +221,7 @@ extension Defaults.Keys { sentenceStoredKey(.openAI), default: "1" ) - static let openAIServiceUsageStatus = Key( + static let openAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.openAI), default: .default ) @@ -254,7 +254,7 @@ extension Defaults.Keys { sentenceStoredKey(.customOpenAI), default: "0" ) - static let customOpenAIServiceUsageStatus = Key( + static let customOpenAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.builtInAI), default: .default ) @@ -280,7 +280,7 @@ extension Defaults.Keys { sentenceStoredKey(.builtInAI), default: "0" ) - static let builtInAIServiceUsageStatus = Key( + static let builtInAIServiceUsageStatus = Key( serviceUsageStatusStoredKey(.builtInAI), default: .default ) @@ -324,7 +324,7 @@ extension Defaults.Keys { sentenceStoredKey(.gemini), default: "1" ) - static let geminiServiceUsageStatus = Key( + static let geminiServiceUsageStatus = Key( serviceUsageStatusStoredKey(.gemini), default: .default ) diff --git a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift index 96b21361d..69061b126 100644 --- a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift +++ b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift @@ -6,6 +6,7 @@ // Copyright © 2024 izual. All rights reserved. // +import Defaults import Foundation // MARK: - LLMStreamService @@ -131,3 +132,42 @@ public class LLMStreamService: QueryService { return .translation } } + +// MARK: - ServiceUsageStatus + +enum ServiceUsageStatus: String, CaseIterable { + case `default` = "0" + case alwaysOff = "1" + case alwaysOn = "2" +} + +// MARK: EnumLocalizedStringConvertible + +extension ServiceUsageStatus: EnumLocalizedStringConvertible { + var title: String { + switch self { + case .default: + NSLocalizedString( + "service.configuration.openai.usage_status_default.title", + bundle: .main, + comment: "" + ) + case .alwaysOff: + NSLocalizedString( + "service.configuration.openai.usage_status_always_off.title", + bundle: .main, + comment: "" + ) + case .alwaysOn: + NSLocalizedString( + "service.configuration.openai.usage_status_always_on.title", + bundle: .main, + comment: "" + ) + } + } +} + +// MARK: Defaults.Serializable + +extension ServiceUsageStatus: Defaults.Serializable {} diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift index a876825e1..7895a4965 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/BuiltInAIService+ConfigurableService.swift @@ -31,7 +31,7 @@ extension BuiltInAIService: ConfigurableService { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .builtInAIServiceUsageStatus, - values: OpenAIUsageStatus.allCases + values: ServiceUsageStatus.allCases ) } } diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift index f18da4605..7dc1db47e 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/CustomOpenAIService+ConfigurableService.swift @@ -89,7 +89,7 @@ private struct CustomOpenAIServiceConfigurationView: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .customOpenAIServiceUsageStatus, - values: OpenAIUsageStatus.allCases + values: ServiceUsageStatus.allCases ) } .onDisappear { diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift index 365754194..c1297f104 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift @@ -75,7 +75,7 @@ private struct GeminiServiceConfigurationView: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .geminiServiceUsageStatus, - values: GeminiUsageStatus.allCases + values: ServiceUsageStatus.allCases ) } .onDisappear { @@ -196,42 +196,3 @@ extension GeminiModel: EnumLocalizedStringConvertible { // MARK: Defaults.Serializable extension GeminiModel: Defaults.Serializable {} - -// MARK: - GeminiUsageStatus - -enum GeminiUsageStatus: String, CaseIterable { - case `default` = "0" - case alwaysOff = "1" - case alwaysOn = "2" -} - -// MARK: EnumLocalizedStringConvertible - -extension GeminiUsageStatus: EnumLocalizedStringConvertible { - var title: String { // Use same xcstring with openai for title - switch self { - case .default: - NSLocalizedString( - "service.configuration.openai.usage_status_default.title", - bundle: .main, - comment: "" - ) - case .alwaysOff: - NSLocalizedString( - "service.configuration.openai.usage_status_always_off.title", - bundle: .main, - comment: "" - ) - case .alwaysOn: - NSLocalizedString( - "service.configuration.openai.usage_status_always_on.title", - bundle: .main, - comment: "" - ) - } - } -} - -// MARK: Defaults.Serializable - -extension GeminiUsageStatus: Defaults.Serializable {} diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift index 74ecaca1a..8274c0659 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift @@ -81,7 +81,7 @@ private struct OpenAIServiceConfigurationView: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .openAIServiceUsageStatus, - values: OpenAIUsageStatus.allCases + values: ServiceUsageStatus.allCases ) } .onDisappear { @@ -210,42 +210,3 @@ extension OpenAIModel: EnumLocalizedStringConvertible { // MARK: Defaults.Serializable extension OpenAIModel: Defaults.Serializable {} - -// MARK: - OpenAIUsageStatus - -enum OpenAIUsageStatus: String, CaseIterable { - case `default` = "0" - case alwaysOff = "1" - case alwaysOn = "2" -} - -// MARK: EnumLocalizedStringConvertible - -extension OpenAIUsageStatus: EnumLocalizedStringConvertible { - var title: String { - switch self { - case .default: - NSLocalizedString( - "service.configuration.openai.usage_status_default.title", - bundle: .main, - comment: "" - ) - case .alwaysOff: - NSLocalizedString( - "service.configuration.openai.usage_status_always_off.title", - bundle: .main, - comment: "" - ) - case .alwaysOn: - NSLocalizedString( - "service.configuration.openai.usage_status_always_on.title", - bundle: .main, - comment: "" - ) - } - } -} - -// MARK: Defaults.Serializable - -extension OpenAIUsageStatus: Defaults.Serializable {} diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift index b89d898fe..d9cd9a55a 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/ServiceConfigurationCells.swift @@ -155,7 +155,7 @@ struct ServiceConfigurationToggleCell: View { ServiceConfigurationPickerCell( titleKey: "service.configuration.openai.usage_status.title", key: .openAIServiceUsageStatus, - values: OpenAIUsageStatus.allCases + values: ServiceUsageStatus.allCases ) ServiceConfigurationToggleCell( From a0d3fa7ddca288c3f9ee7f21dcc6e81f7a91407c Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 23:33:56 +0800 Subject: [PATCH 11/30] perf: remove queryTextType since gemini supports all type --- Easydict/Swift/Service/Gemini/GeminiService.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 1a8faa517..daed2d04a 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -26,10 +26,6 @@ public final class GeminiService: LLMStreamService { NSLocalizedString("gemini_translate", comment: "The name of Gemini Translate") } - override public func queryTextType() -> EZQueryTextType { - [.translation, .dictionary, .sentence] - } - override public func translate( _ text: String, from: Language, From cff774a339bcf10a99e80d265c558acc785ea4dc Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Tue, 28 May 2024 23:34:33 +0800 Subject: [PATCH 12/30] perf: update xcstring state --- Easydict/App/Localizable.xcstrings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0168a0b36..bb0b58633 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -2420,7 +2420,7 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "xxxxxxxxxxxxx" } } From cc781170d4bc5216fa96f6451209e7b0983b5233 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Tue, 28 May 2024 23:35:59 +0800 Subject: [PATCH 13/30] perf: enable LLMStreamService to change models quickly --- .../ViewController/View/ResultView/EZResultView.m | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Easydict/objc/ViewController/View/ResultView/EZResultView.m b/Easydict/objc/ViewController/View/ResultView/EZResultView.m index 96657c438..957533a72 100644 --- a/Easydict/objc/ViewController/View/ResultView/EZResultView.m +++ b/Easydict/objc/ViewController/View/ResultView/EZResultView.m @@ -267,8 +267,8 @@ - (void)setResult:(EZQueryResult *)result { mm_weakify(self); - if ([self isBaseOpenAIService:result.service]) { - EZBaseOpenAIService *service = (EZBaseOpenAIService *)result.service; + if ([self isLLLStreamService:result.service]) { + EZLLMStreamService *service = (EZLLMStreamService *)result.service; NSString *model = service.model; self.serviceModelButton.title = model; // hoverTitle may be different from normalTitle, fix https://github.com/tisfeng/Easydict/pull/516#issuecomment-2064164503 @@ -314,7 +314,7 @@ - (void)updateConstraints { }]; CGFloat modelButtonWidth = 0; - if ([self isBaseOpenAIService:self.result.service]) { + if ([self isLLLStreamService:self.result.service]) { [self.serviceModelButton sizeToFit]; // 105 is the length of "gpt-4-turbo-preview" modelButtonWidth = MIN(self.serviceModelButton.width, 105 * [self windowWidthRatio]); @@ -429,12 +429,12 @@ - (void)updateArrowButton { }]; } -- (BOOL)isBaseOpenAIService:(EZQueryService *)service { - return [service isKindOfClass:[EZBaseOpenAIService class]]; +- (BOOL)isLLLStreamService:(EZQueryService *)service { + return [service isKindOfClass:[EZLLMStreamService class]]; } - (void)showModelSelectionMenu:(EZButton *)sender { - EZBaseOpenAIService *service = (EZBaseOpenAIService *)self.result.service; + EZLLMStreamService *service = (EZLLMStreamService *)self.result.service; NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Menu"]; for (NSString *model in service.availableModels) { NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:model action:@selector(modelDidSelected:) keyEquivalent:@""]; @@ -445,7 +445,7 @@ - (void)showModelSelectionMenu:(EZButton *)sender { } - (void)modelDidSelected:(NSMenuItem *)sender { - EZBaseOpenAIService *service = (EZBaseOpenAIService *)self.result.service; + EZLLMStreamService *service = (EZLLMStreamService *)self.result.service; if (![service.model isEqualToString:sender.title]) { service.model = sender.title; self.serviceModelButton.title = service.model; From 8446f1159f5780ba83e1b2e0ce549f82560cc51c Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Wed, 29 May 2024 01:51:17 +0800 Subject: [PATCH 14/30] fix: typo in gemini service --- .../Swift/Feature/Configuration/Configuration+Defaults.swift | 4 ++-- Easydict/Swift/Service/Gemini/GeminiService.swift | 2 +- .../GeminiService+ConfigurableService.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index cac83c0af..a34d84a59 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -333,8 +333,8 @@ extension Defaults.Keys { "geminiAvailableModels", default: GeminiModel.allCases.map { $0.rawValue }.joined(separator: ",") ) - static let geminiVaildModels = Key( - "geminiVaildModels", + static let geminiValidModels = Key( + "geminiValidModels", default: GeminiModel.allCases.map { $0.rawValue } ) } diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index daed2d04a..72d4762d2 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -120,7 +120,7 @@ public final class GeminiService: LLMStreamService { } override var availableModels: [String] { - Defaults[.geminiVaildModels] + Defaults[.geminiValidModels] } override var model: String { diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift index c1297f104..72bc1e33b 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift @@ -155,7 +155,7 @@ private class GeminiViewModel: ObservableObject { model = validModels[0] } - Defaults[.geminiVaildModels] = validModels + Defaults[.geminiValidModels] = validModels } private func serviceConfigChanged() { From 2d5b9ae7801d4f07c70d55de3311c3a5327e24d4 Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Wed, 29 May 2024 02:17:51 +0800 Subject: [PATCH 15/30] perf: support dictionary and sentence query --- .../Swift/Service/Gemini/GeminiService.swift | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 72d4762d2..17b3dae78 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -37,7 +37,9 @@ public final class GeminiService: LLMStreamService { result.from = from result.to = to result.isStreamFinished = false - let translationPrompt = translationPrompt(text: text, from: from, to: to) + + let queryType = queryType(text: text, from: from, to: to) + let translationPrompt = promptMessage(queryType: queryType, text: text, from: from, to: to) let prompt = LLMStreamService.translationSystemPrompt + "\n" + translationPrompt let model = GenerativeModel( @@ -140,4 +142,32 @@ public final class GeminiService: LLMStreamService { private let hateSpeechBlockNone = SafetySetting(harmCategory: .hateSpeech, threshold: .blockNone) private let sexuallyExplicitBlockNone = SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockNone) private let dangerousContentBlockNone = SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone) + + private func promptMessage( + queryType: EZQueryTextType, + text: String, + from sourceLanguage: Language, + to targetLanguage: Language + ) + -> String { + var prompt = [[String: String]]() + + switch queryType { + case .dictionary: + prompt = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) + case .sentence: + prompt = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) + case .translation: + fallthrough + default: + prompt = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) + } + + let finalPrompt = messagesToString(prompt) + return finalPrompt + } + + private func messagesToString(_ messages: [[String: String]]) -> String { + messages.compactMap { $0["content"] }.joined(separator: "\n") + } } From e65b2f744955c8728c93590ba59c196fa73ddb3a Mon Sep 17 00:00:00 2001 From: Jerry <89069957+Jerry23011@users.noreply.github.com> Date: Wed, 29 May 2024 02:48:14 +0800 Subject: [PATCH 16/30] perf: implement results handle for gemini --- .../Swift/Service/Gemini/GeminiService.swift | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 17b3dae78..6b7549b93 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -68,7 +68,12 @@ public final class GeminiService: LLMStreamService { result.translatedResults = [resultString] await MainActor.run { throttler.throttle { [unowned self] in - completion(result, nil) + handleResult( + queryType: queryType, + resultString: concatenateStrings(from: result.translatedResults ?? []), + error: nil, + completion: completion + ) } } } @@ -100,6 +105,8 @@ public final class GeminiService: LLMStreamService { // MARK: Internal + var updateCompletion: ((EZQueryResult, Error?) -> ())? + // https://ai.google.dev/available_regions override var unsupportedLanguages: [Language] { [ @@ -170,4 +177,50 @@ public final class GeminiService: LLMStreamService { private func messagesToString(_ messages: [[String: String]]) -> String { messages.compactMap { $0["content"] }.joined(separator: "\n") } + + private func handleResult( + queryType: EZQueryTextType, + resultString: String?, + error: Error?, + completion: @escaping (EZQueryResult, Error?) -> () + ) { + var normalResults: [String]? + if let resultString { + normalResults = [resultString.trim()] + } + + result.isStreamFinished = error != nil + result.translatedResults = normalResults + + let updateCompletion = { + self.throttler.throttle { [unowned self] in + self.updateCompletion?(result, error) + } + } + + switch queryType { + case .sentence, .translation: + updateCompletion() + + case .dictionary: + if error != nil { + result.showBigWord = false + result.translateResultsTopInset = 0 + updateCompletion() + return + } + + result.showBigWord = true + result.queryText = queryModel.queryText + result.translateResultsTopInset = 6 + updateCompletion() + + default: + updateCompletion() + } + } + + private func concatenateStrings(from array: [String]) -> String { + array.joined() + } } From cbbda542a0a3963001d99947fe0e7a3bee5178f2 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Wed, 29 May 2024 11:15:57 +0800 Subject: [PATCH 17/30] docs: update sponsor list --- README.md | 1 + README_EN.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index ecf573de7..3c3564395 100644 --- a/README.md +++ b/README.md @@ -859,6 +859,7 @@ Easydict 作为一个免费开源的非盈利项目,目前主要是作者个 | 2024-04-12 | 奥雷里亚诺 | 50 | 界面精致,而且帮我节约了不少时间 | | 2024-04-15 | | 5 | 谢谢你的 Easydict!! | | 2024-05-11 | | 35 | 感谢开源和持续更新! | +| 2024-05-29 | 天色晚晚 | 10 | 项目很用心!感谢!!! |

diff --git a/README_EN.md b/README_EN.md index 5d92beb56..130bfa2e0 100644 --- a/README_EN.md +++ b/README_EN.md @@ -851,6 +851,7 @@ If you don't want your username to be displayed in the list, please choose anony | 2024-04-12 | 奥雷里亚诺 | 50 | 界面精致,而且帮我节约了不少时间 | | 2024-04-15 | | 5 | 谢谢你的 Easydict!! | | 2024-05-11 | | 35 | 感谢开源和持续更新! | +| 2024-05-29 | 天色晚晚 | 10 | 项目很用心!感谢!!! |

From 7e6c7a6f171534ed7c482faf8ee2ec047975e674 Mon Sep 17 00:00:00 2001 From: Jerry Date: Wed, 29 May 2024 23:53:24 +0800 Subject: [PATCH 18/30] perf: remove unused annotation Co-Authored-By: Tisfeng <25194972+tisfeng@users.noreply.github.com> --- .../GeminiService+ConfigurableService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift index 72bc1e33b..a0669d15d 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift @@ -136,7 +136,7 @@ private class GeminiViewModel: ObservableObject { if availableModels?.isEmpty == true { availableModels = model } else { - availableModels = /* "\(model), " + */ availableModels ?? "" + availableModels = availableModels ?? "" } } } From ad879a43d1d3520fd71d68971ca8da95a7bb931e Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 30 May 2024 00:42:55 +0800 Subject: [PATCH 19/30] perf: remove unused code --- .../GeminiService+ConfigurableService.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift index a0669d15d..0568af977 100644 --- a/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift +++ b/Easydict/Swift/View/SettingView/Tabs/ServiceConfigurationView/QueryService+ConfigurableService/GeminiService+ConfigurableService.swift @@ -183,16 +183,4 @@ enum GeminiModel: String, CaseIterable { case gemini1_5_pro = "gemini-1.5-pro" // Free 2 RPM/32,000 TPM, 50 RPD/46,080,000 TPD (1048k context length) } -// MARK: EnumLocalizedStringConvertible - // swiftlint:enable identifier_name - -extension GeminiModel: EnumLocalizedStringConvertible { - var title: String { - rawValue - } -} - -// MARK: Defaults.Serializable - -extension GeminiModel: Defaults.Serializable {} From fcde1373e43c9987d05067ae62206d759541c3dd Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 30 May 2024 14:09:08 +0800 Subject: [PATCH 20/30] perf: implement systemInstruction and role/model prompt --- .../Swift/Service/Gemini/GeminiService.swift | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 6b7549b93..a3766710e 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -39,9 +39,8 @@ public final class GeminiService: LLMStreamService { result.isStreamFinished = false let queryType = queryType(text: text, from: from, to: to) - let translationPrompt = promptMessage(queryType: queryType, text: text, from: from, to: to) - let prompt = LLMStreamService.translationSystemPrompt + - "\n" + translationPrompt + let translationPrompt = promptContent(queryType: queryType, text: text, from: from, to: to) + let systemInstruction = LLMStreamService.translationSystemPrompt let model = GenerativeModel( name: model, apiKey: apiKey, @@ -50,14 +49,15 @@ public final class GeminiService: LLMStreamService { hateSpeechBlockNone, sexuallyExplicitBlockNone, dangerousContentBlockNone, - ] + ], + systemInstruction: systemInstruction ) var resultString = "" // Gemini Docs: https://github.com/google/generative-ai-swift - let outputContentStream = model.generateContentStream(prompt) + let outputContentStream = model.generateContentStream(translationPrompt) for try await outputContent in outputContentStream { guard let line = outputContent.text else { return @@ -150,28 +150,50 @@ public final class GeminiService: LLMStreamService { private let sexuallyExplicitBlockNone = SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockNone) private let dangerousContentBlockNone = SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone) - private func promptMessage( + private func promptContent( queryType: EZQueryTextType, text: String, from sourceLanguage: Language, to targetLanguage: Language ) - -> String { - var prompt = [[String: String]]() + -> [ModelContent] { + var prompts = [[String: String]]() switch queryType { case .dictionary: - prompt = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) + prompts = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) case .sentence: - prompt = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) + prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) case .translation: fallthrough default: - prompt = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) + prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) } - let finalPrompt = messagesToString(prompt) - return finalPrompt + var chats: [ModelContent] = [] + for prompt in prompts { + if let roleRaw = prompt["role"], + let parts = prompt["content"] { + let role = getCorrectParts(from: roleRaw) + let chat = ModelContent(role: role, parts: parts) + chats.append(chat) + } + } + guard !chats.isEmpty else { + return chats + } + // removing first element in [ModelContent] since it's system instruction + chats.removeFirst() + return chats + } + + /// Given a roleRaw, replace "assistant" with "model" + private func getCorrectParts(from roleRaw: String) -> String { + if roleRaw.lowercased() == "assistant" { + "model" + } else { + roleRaw + } } private func messagesToString(_ messages: [[String: String]]) -> String { From f6e92b70be7d11fbdcab4c72933fb05d668f3b87 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 30 May 2024 16:40:01 +0800 Subject: [PATCH 21/30] perf: remove redundant code --- Easydict/Swift/Service/Gemini/GeminiService.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index a3766710e..3c9af6ea1 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -34,8 +34,6 @@ public final class GeminiService: LLMStreamService { ) { Task { do { - result.from = from - result.to = to result.isStreamFinished = false let queryType = queryType(text: text, from: from, to: to) @@ -196,10 +194,6 @@ public final class GeminiService: LLMStreamService { } } - private func messagesToString(_ messages: [[String: String]]) -> String { - messages.compactMap { $0["content"] }.joined(separator: "\n") - } - private func handleResult( queryType: EZQueryTextType, resultString: String?, From f26ec5cd4efbb725b526a160143a164ae162fe6d Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 30 May 2024 17:04:40 +0800 Subject: [PATCH 22/30] fix: gemini stream ui --- .../Swift/Service/Gemini/GeminiService.swift | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 3c9af6ea1..25d4a900e 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -65,14 +65,12 @@ public final class GeminiService: LLMStreamService { result.translatedResults = [resultString] await MainActor.run { - throttler.throttle { [unowned self] in - handleResult( - queryType: queryType, - resultString: concatenateStrings(from: result.translatedResults ?? []), - error: nil, - completion: completion - ) - } + handleResult( + queryType: queryType, + resultString: concatenateStrings(from: result.translatedResults ?? []), + error: nil, + completion: completion + ) } } } @@ -103,8 +101,6 @@ public final class GeminiService: LLMStreamService { // MARK: Internal - var updateCompletion: ((EZQueryResult, Error?) -> ())? - // https://ai.google.dev/available_regions override var unsupportedLanguages: [Language] { [ @@ -207,10 +203,9 @@ public final class GeminiService: LLMStreamService { result.isStreamFinished = error != nil result.translatedResults = normalResults - let updateCompletion = { self.throttler.throttle { [unowned self] in - self.updateCompletion?(result, error) + completion(result, error) } } From 74d919d449113c0dc9118513e0846782dbc9b554 Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 2 Jun 2024 20:35:48 +0800 Subject: [PATCH 23/30] perf: move promptContent to extension GeminiService --- .../Swift/Service/Gemini/GeminiService.swift | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 25d4a900e..075663810 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -10,6 +10,8 @@ import Defaults import Foundation import GoogleGenerativeAI +// MARK: - GeminiService + @objc(EZGeminiService) public final class GeminiService: LLMStreamService { // MARK: Public @@ -144,43 +146,6 @@ public final class GeminiService: LLMStreamService { private let sexuallyExplicitBlockNone = SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockNone) private let dangerousContentBlockNone = SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone) - private func promptContent( - queryType: EZQueryTextType, - text: String, - from sourceLanguage: Language, - to targetLanguage: Language - ) - -> [ModelContent] { - var prompts = [[String: String]]() - - switch queryType { - case .dictionary: - prompts = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) - case .sentence: - prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) - case .translation: - fallthrough - default: - prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) - } - - var chats: [ModelContent] = [] - for prompt in prompts { - if let roleRaw = prompt["role"], - let parts = prompt["content"] { - let role = getCorrectParts(from: roleRaw) - let chat = ModelContent(role: role, parts: parts) - chats.append(chat) - } - } - guard !chats.isEmpty else { - return chats - } - // removing first element in [ModelContent] since it's system instruction - chats.removeFirst() - return chats - } - /// Given a roleRaw, replace "assistant" with "model" private func getCorrectParts(from roleRaw: String) -> String { if roleRaw.lowercased() == "assistant" { @@ -235,3 +200,42 @@ public final class GeminiService: LLMStreamService { array.joined() } } + +extension GeminiService { + func promptContent( + queryType: EZQueryTextType, + text: String, + from sourceLanguage: Language, + to targetLanguage: Language + ) + -> [ModelContent] { + var prompts = [[String: String]]() + + switch queryType { + case .dictionary: + prompts = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) + case .sentence: + prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) + case .translation: + fallthrough + default: + prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) + } + + var chats: [ModelContent] = [] + for prompt in prompts { + if let roleRaw = prompt["role"], + let parts = prompt["content"] { + let role = getCorrectParts(from: roleRaw) + let chat = ModelContent(role: role, parts: parts) + chats.append(chat) + } + } + guard !chats.isEmpty else { + return chats + } + // removing first element in [ModelContent] since it's system instruction + chats.removeFirst() + return chats + } +} From 9c8c34b8c1f3f60ee7dd5c0c4664e268c2b4ea0e Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 2 Jun 2024 20:52:46 +0800 Subject: [PATCH 24/30] perf: optimize usage of handleResult --- .../Swift/Service/Gemini/GeminiService.swift | 43 +-------------- .../Service/OpenAI/BaseOpenAIService.swift | 52 ------------------- .../Service/OpenAI/LLMStreamService.swift | 44 ++++++++++++++++ 3 files changed, 45 insertions(+), 94 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 075663810..51ea64a6b 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -69,7 +69,7 @@ public final class GeminiService: LLMStreamService { await MainActor.run { handleResult( queryType: queryType, - resultString: concatenateStrings(from: result.translatedResults ?? []), + resultText: concatenateStrings(from: result.translatedResults ?? []), error: nil, completion: completion ) @@ -155,47 +155,6 @@ public final class GeminiService: LLMStreamService { } } - private func handleResult( - queryType: EZQueryTextType, - resultString: String?, - error: Error?, - completion: @escaping (EZQueryResult, Error?) -> () - ) { - var normalResults: [String]? - if let resultString { - normalResults = [resultString.trim()] - } - - result.isStreamFinished = error != nil - result.translatedResults = normalResults - let updateCompletion = { - self.throttler.throttle { [unowned self] in - completion(result, error) - } - } - - switch queryType { - case .sentence, .translation: - updateCompletion() - - case .dictionary: - if error != nil { - result.showBigWord = false - result.translateResultsTopInset = 0 - updateCompletion() - return - } - - result.showBigWord = true - result.queryText = queryModel.queryText - result.translateResultsTopInset = 6 - updateCompletion() - - default: - updateCompletion() - } - } - private func concatenateStrings(from array: [String]) -> String { array.joined() } diff --git a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift index 44cd99830..d6d12efb3 100644 --- a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift +++ b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift @@ -16,8 +16,6 @@ import OpenAI @objcMembers @objc(EZBaseOpenAIService) public class BaseOpenAIService: LLMStreamService { - // MARK: Public - override public func translate( _ text: String, from: Language, @@ -31,8 +29,6 @@ public class BaseOpenAIService: LLMStreamService { return } - updateCompletion = completion - var resultText = "" result.from = from @@ -92,54 +88,6 @@ public class BaseOpenAIService: LLMStreamService { } } } - - // MARK: Internal - - var updateCompletion: ((EZQueryResult, Error?) -> ())? - - // MARK: Private - - private func handleResult( - queryType: EZQueryTextType, - resultText: String?, - error: Error?, - completion: @escaping (EZQueryResult, Error?) -> () - ) { - var normalResults: [String]? - if let resultText { - normalResults = [resultText.trim()] - } - - result.isStreamFinished = error != nil - result.translatedResults = normalResults - - let updateCompletion = { - self.throttler.throttle { [unowned self] in - self.updateCompletion?(result, error) - } - } - - switch queryType { - case .sentence, .translation: - updateCompletion() - - case .dictionary: - if error != nil { - result.showBigWord = false - result.translateResultsTopInset = 0 - updateCompletion() - return - } - - result.showBigWord = true - result.queryText = queryModel.queryText - result.translateResultsTopInset = 6 - updateCompletion() - - default: - updateCompletion() - } - } } // MARK: OpenAI chat messages diff --git a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift index 69061b126..7f198152b 100644 --- a/Easydict/Swift/Service/OpenAI/LLMStreamService.swift +++ b/Easydict/Swift/Service/OpenAI/LLMStreamService.swift @@ -171,3 +171,47 @@ extension ServiceUsageStatus: EnumLocalizedStringConvertible { // MARK: Defaults.Serializable extension ServiceUsageStatus: Defaults.Serializable {} + +extension LLMStreamService { + func handleResult( + queryType: EZQueryTextType, + resultText: String?, + error: Error?, + completion: @escaping (EZQueryResult, Error?) -> () + ) { + var normalResults: [String]? + if let resultText { + normalResults = [resultText.trim()] + } + + result.isStreamFinished = error != nil + result.translatedResults = normalResults + + let updateCompletion = { + self.throttler.throttle { [unowned self] in + completion(result, error) + } + } + + switch queryType { + case .sentence, .translation: + updateCompletion() + + case .dictionary: + if error != nil { + result.showBigWord = false + result.translateResultsTopInset = 0 + updateCompletion() + return + } + + result.showBigWord = true + result.queryText = queryModel.queryText + result.translateResultsTopInset = 6 + updateCompletion() + + default: + updateCompletion() + } + } +} From adff63821420716a0f3b6e4377058c3cffd1962a Mon Sep 17 00:00:00 2001 From: Jerry Date: Sun, 2 Jun 2024 21:57:20 +0800 Subject: [PATCH 25/30] perf: add bool argument to opt out LLM systemPrompt --- .../Swift/Service/Gemini/GeminiService.swift | 13 ++-- .../Service/OpenAI/BaseOpenAIService.swift | 8 +-- Easydict/Swift/Service/OpenAI/Prompt.swift | 61 ++++++++++++------- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 51ea64a6b..2fb89b5e5 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -172,13 +172,18 @@ extension GeminiService { switch queryType { case .dictionary: - prompts = dictMessages(word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage) + prompts = dictMessages( + word: text, + sourceLanguage: sourceLanguage, + targetLanguage: targetLanguage, + systemPrompt: false + ) case .sentence: - prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage) + prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage, systemPrompt: false) case .translation: fallthrough default: - prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage) + prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage, systemPrompt: false) } var chats: [ModelContent] = [] @@ -193,8 +198,6 @@ extension GeminiService { guard !chats.isEmpty else { return chats } - // removing first element in [ModelContent] since it's system instruction - chats.removeFirst() return chats } } diff --git a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift index d6d12efb3..d60c61d2a 100644 --- a/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift +++ b/Easydict/Swift/Service/OpenAI/BaseOpenAIService.swift @@ -99,7 +99,7 @@ extension BaseOpenAIService { typealias Role = ChatCompletionMessageParam.Role var chats: [ChatCompletionMessageParam] = [] - let messages = translationMessages(text: text, from: from, to: to) + let messages = translationMessages(text: text, from: from, to: to, systemPrompt: false) for message in messages { if let roleRawValue = message["role"], let role = Role(rawValue: roleRawValue), @@ -125,13 +125,13 @@ extension BaseOpenAIService { switch queryType { case .sentence: - messages = sentenceMessages(sentence: text, from: from, to: to) + messages = sentenceMessages(sentence: text, from: from, to: to, systemPrompt: true) case .dictionary: - messages = dictMessages(word: text, sourceLanguage: from, targetLanguage: to) + messages = dictMessages(word: text, sourceLanguage: from, targetLanguage: to, systemPrompt: true) case .translation: fallthrough default: - messages = translationMessages(text: text, from: from, to: to) + messages = translationMessages(text: text, from: from, to: to, systemPrompt: true) } var chats: [ChatCompletionMessageParam] = [] diff --git a/Easydict/Swift/Service/OpenAI/Prompt.swift b/Easydict/Swift/Service/OpenAI/Prompt.swift index b4f8e3805..032266097 100644 --- a/Easydict/Swift/Service/OpenAI/Prompt.swift +++ b/Easydict/Swift/Service/OpenAI/Prompt.swift @@ -19,7 +19,7 @@ extension LLMStreamService { "Translate the following \(sourceLanguage.queryLanguageName) text into \(targetLanguage.queryLanguageName) text: \"\"\"\(text)\"\"\"" } - func translationMessages(text: String, from: Language, to: Language) -> [[String: String]] { + func translationMessages(text: String, from: Language, to: Language, systemPrompt: Bool) -> [[String: String]] { // Use """ %@ """ to wrap user input, Ref: https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-openai-api#h_21d4f4dc3d // let prompt = "Translate the following \(from.rawValue) text into \(to.rawValue) text: \"\"\"\(text)\"\"\"" @@ -245,12 +245,16 @@ extension LLMStreamService { ], ] - let systemMessages = [ - [ - "role": "system", - "content": LLMStreamService.translationSystemPrompt, - ], - ] + let systemMessages: [[String: String]] = { + if systemPrompt { + [[ + "role": "system", + "content": LLMStreamService.translationSystemPrompt, + ]] + } else { + [] + } + }() var messages = systemMessages messages.append(contentsOf: chineseFewShot) @@ -276,7 +280,8 @@ extension LLMStreamService { func sentenceMessages( sentence: String, from sourceLanguage: Language, - to targetLanguage: Language + to targetLanguage: Language, + systemPrompt: Bool ) -> [[String: String]] { let answerLanguage = Configuration.shared.firstLanguage @@ -469,12 +474,16 @@ extension LLMStreamService { ], ] - let systemMessages = [ - [ - "role": "system", - "content": LLMStreamService.translationSystemPrompt, - ], - ] + let systemMessages: [[String: String]] = { + if systemPrompt { + [[ + "role": "system", + "content": LLMStreamService.translationSystemPrompt, + ]] + } else { + [] + } + }() var messages = systemMessages @@ -496,7 +505,13 @@ extension LLMStreamService { return messages } - func dictMessages(word: String, sourceLanguage: Language, targetLanguage: Language) -> [[String: String]] { + func dictMessages( + word: String, + sourceLanguage: Language, + targetLanguage: Language, + systemPrompt: Bool + ) + -> [[String: String]] { var prompt = "" let answerLanguage = Configuration.shared.firstLanguage @@ -823,12 +838,16 @@ extension LLMStreamService { ], ] - let systemMessages = [ - [ - "role": "system", - "content": dictSystemPrompt, - ], - ] + let systemMessages: [[String: String]] = { + if systemPrompt { + [[ + "role": "system", + "content": LLMStreamService.translationSystemPrompt, + ]] + } else { + [] + } + }() var messages = systemMessages From 5c0082d3a0307c388d48a23252e6259afd8d6be1 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Mon, 3 Jun 2024 23:59:19 +0800 Subject: [PATCH 26/30] fix: use dict system prompt when querying a word --- Easydict/Swift/Service/Gemini/GeminiService.swift | 3 ++- Easydict/Swift/Service/OpenAI/Prompt.swift | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 2fb89b5e5..4a648f4c5 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -40,7 +40,8 @@ public final class GeminiService: LLMStreamService { let queryType = queryType(text: text, from: from, to: to) let translationPrompt = promptContent(queryType: queryType, text: text, from: from, to: to) - let systemInstruction = LLMStreamService.translationSystemPrompt + let systemInstruction = queryType == .dictionary ? LLMStreamService.dictSystemPrompt : LLMStreamService + .translationSystemPrompt let model = GenerativeModel( name: model, apiKey: apiKey, diff --git a/Easydict/Swift/Service/OpenAI/Prompt.swift b/Easydict/Swift/Service/OpenAI/Prompt.swift index 032266097..24c51bfce 100644 --- a/Easydict/Swift/Service/OpenAI/Prompt.swift +++ b/Easydict/Swift/Service/OpenAI/Prompt.swift @@ -15,6 +15,10 @@ extension LLMStreamService { You are a translation expert proficient in various languages, focusing solely on translating text without interpretation. You accurately understand the meanings of proper nouns, idioms, metaphors, allusions, and other obscure words in sentences, translating them appropriately based on the context and language environment. The translation should be natural and fluent. Only return the translated text, without including redundant quotes or additional notes. """ + static let dictSystemPrompt = """ + You are a word search assistant skilled in multiple languages and knowledgeable in etymology. You can help search for words, phrases, slang, abbreviations, and other information. Prioritize queries from authoritative dictionary databases, such as the Oxford Dictionary, Cambridge Dictionary, and Wikipedia. If a word or abbreviation has multiple meanings, look up the most commonly used ones. + """ + func translationPrompt(text: String, from sourceLanguage: Language, to targetLanguage: Language) -> String { "Translate the following \(sourceLanguage.queryLanguageName) text into \(targetLanguage.queryLanguageName) text: \"\"\"\(text)\"\"\"" } @@ -537,10 +541,6 @@ extension LLMStreamService { let sourceLanguageString = sourceLanguage.rawValue - let dictSystemPrompt = """ - You are a word search assistant skilled in multiple languages and knowledgeable in etymology. You can help search for words, phrases, slang, abbreviations, and other information. Prioritize queries from authoritative dictionary databases, such as the Oxford Dictionary, Cambridge Dictionary, and Wikipedia. For Chinese words, use Baidu Baike. If a word or abbreviation has multiple meanings, look up the most commonly used ones. - """ - let answerLanguagePrompt = "Using \(answerLanguage.rawValue): \n" prompt.append(answerLanguagePrompt) @@ -842,7 +842,7 @@ extension LLMStreamService { if systemPrompt { [[ "role": "system", - "content": LLMStreamService.translationSystemPrompt, + "content": LLMStreamService.dictSystemPrompt, ]] } else { [] From 6301f085f7bbb2ab4a6637d4e5720868bc737ffd Mon Sep 17 00:00:00 2001 From: Jerry Date: Tue, 4 Jun 2024 13:48:54 +0800 Subject: [PATCH 27/30] perf: add dynamic variable for gemini available models --- .../Feature/Configuration/Configuration+Defaults.swift | 6 ++++-- .../Swift/Feature/Configuration/DefaultsStoredKey.swift | 4 ++++ Easydict/objc/Service/Model/EZConstKey.h | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index a34d84a59..bc1fb6ffa 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -328,9 +328,11 @@ extension Defaults.Keys { serviceUsageStatusStoredKey(.gemini), default: .default ) - static let geminiModel = Key("geminiModel", default: GeminiModel.gemini1_5_flash.rawValue) + static let geminiModel = Key( + "geminiModel", default: GeminiModel.gemini1_5_flash.rawValue + ) static let geminiAvailableModels = Key( - "geminiAvailableModels", + availableModelStoredKey(.gemini), default: GeminiModel.allCases.map { $0.rawValue }.joined(separator: ",") ) static let geminiValidModels = Key( diff --git a/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift b/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift index a5ed08daa..6a99a3016 100644 --- a/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift +++ b/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift @@ -31,6 +31,10 @@ func dictionaryStoredKey(_ serviceType: ServiceType) -> String { storedKey(EZDictionaryKey, serviceType: serviceType) } +func availableModelStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZAvailableModelKey, serviceType: serviceType) +} + extension UserDefaults { static func bool(forKey key: String, serviceType: ServiceType) -> Bool { let key = storedKey(key, serviceType: serviceType) diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index d2acdf309..9089e1a8f 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -17,6 +17,7 @@ static NSString *const EZServiceUsageStatusKey = @"ServiceUsageStatus"; static NSString *const EZTranslationKey = @"Translation"; static NSString *const EZDictionaryKey = @"Dictionary"; static NSString *const EZSentenceKey = @"Sentence"; +static NSString *const EZAvailableModelKey = @"AvailableModel"; // OpenAI static NSString *const EZOpenAIAPIKey = @"EZOpenAIAPIKey"; From a6dcd5d388ac18f020ec46c598beacaed5da9fc6 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Tue, 4 Jun 2024 23:45:58 +0800 Subject: [PATCH 28/30] refactor: replace all AI const stored keys with dynamic variables --- .../Configuration+Defaults.swift | 95 +++++++++++-------- .../Configuration/DefaultsStoredKey.swift | 25 ++++- Easydict/objc/Service/Model/EZConstKey.h | 7 +- 3 files changed, 83 insertions(+), 44 deletions(-) diff --git a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift index bc1fb6ffa..629799283 100644 --- a/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift +++ b/Easydict/Swift/Feature/Configuration/Configuration+Defaults.swift @@ -208,7 +208,7 @@ class ShortcutWrapper { // Service Configuration extension Defaults.Keys { // OpenAI - static let openAIAPIKey = Key(EZOpenAIAPIKey) + static let openAIAPIKey = Key(apiStoredKey(.openAI)) // EZOpenAIAPIKey static let openAITranslation = Key( translationStoredKey(.openAI), default: "1" @@ -225,23 +225,26 @@ extension Defaults.Keys { serviceUsageStatusStoredKey(.openAI), default: .default ) - static let openAIEndPoint = Key(EZOpenAIEndPointKey) - static let openAIModel = Key(EZOpenAIModelKey, default: OpenAIModel.gpt3_5_turbo.rawValue) + static let openAIEndPoint = Key(endpointStoredKey(.openAI)) + static let openAIModel = Key( + modelStoredKey(.openAI), + default: OpenAIModel.gpt3_5_turbo.rawValue + ) static let openAIAvailableModels = Key( - EZOpenAIAvailableModelsKey, + availableModelsStoredKey(.openAI), default: OpenAIModel.allCases.map { $0.rawValue }.joined(separator: ",") ) static let openAIVaildModels = Key( - EZOpenAIValidModelsKey, + validModelsStoredKey(.openAI), default: OpenAIModel.allCases.map { $0.rawValue } ) // Custom OpenAI static let customOpenAINameKey = Key( - EZCustomOpenAINameKey, + nameStoredKey(.customOpenAI), default: NSLocalizedString("custom_openai", comment: "") ) - static let customOpenAIAPIKey = Key(EZCustomOpenAIAPIKey, default: "") + static let customOpenAIAPIKey = Key(apiStoredKey(.customOpenAI)) static let customOpenAITranslation = Key( translationStoredKey(.customOpenAI), default: "1" @@ -258,16 +261,25 @@ extension Defaults.Keys { serviceUsageStatusStoredKey(.builtInAI), default: .default ) - static let customOpenAIEndPoint = Key(EZCustomOpenAIEndPointKey, default: "") - static let customOpenAIModel = Key(EZCustomOpenAIModelKey, default: "") - static let customOpenAIAvailableModels = Key(EZCustomOpenAIAvailableModelsKey, default: "") + static let customOpenAIEndPoint = Key(endpointStoredKey(.customOpenAI)) + static let customOpenAIModel = Key( + modelStoredKey(.customOpenAI), + default: "" + ) + static let customOpenAIAvailableModels = Key( + availableModelsStoredKey(.customOpenAI), + default: "" + ) static let customOpenAIVaildModels = Key( - EZCustomOpenAIValidModelsKey, + validModelsStoredKey(.customOpenAI), default: [""] ) // Built-in AI - static let builtInAIModel = Key(EZBuiltInAIModelKey, default: "") + static let builtInAIModel = Key( + modelStoredKey(.builtInAI), + default: "" + ) // EZBuiltInAIModelKey static let builtInAITranslation = Key( translationStoredKey(.builtInAI), default: "1" @@ -285,33 +297,8 @@ extension Defaults.Keys { default: .default ) - // DeepL - static let deepLAuth = Key(EZDeepLAuthKey) - static let deepLTranslation = Key( - EZDeepLTranslationAPIKey, - default: DeepLAPIUsagePriority.webFirst - ) - static let deepLTranslateEndPointKey = Key(EZDeepLTranslateEndPointKey) - - // Bing - static let bingCookieKey = Key(EZBingCookieKey) - - // niu - static let niuTransAPIKey = Key(EZNiuTransAPIKey) - - // Caiyun - static let caiyunToken = Key(EZCaiyunToken) - - // tencent - static let tencentSecretId = Key(EZTencentSecretId) - static let tencentSecretKey = Key(EZTencentSecretKey) - - // Ali - static let aliAccessKeyId = Key(EZAliAccessKeyId) - static let aliAccessKeySecret = Key(EZAliAccessKeySecret) - // Gemni - static let geminiAPIKey = Key("EZGeminiAPIKey") + static let geminiAPIKey = Key(apiStoredKey(.gemini)) // EZGeminiAPIKey static let geminiTranslation = Key( translationStoredKey(.gemini), default: "1" @@ -329,16 +316,42 @@ extension Defaults.Keys { default: .default ) static let geminiModel = Key( - "geminiModel", default: GeminiModel.gemini1_5_flash.rawValue + modelStoredKey(.gemini), + default: GeminiModel.gemini1_5_flash.rawValue ) static let geminiAvailableModels = Key( - availableModelStoredKey(.gemini), + availableModelsStoredKey(.gemini), default: GeminiModel.allCases.map { $0.rawValue }.joined(separator: ",") ) static let geminiValidModels = Key( - "geminiValidModels", + validModelsStoredKey(.gemini), default: GeminiModel.allCases.map { $0.rawValue } ) + + // DeepL + static let deepLAuth = Key(EZDeepLAuthKey) + static let deepLTranslation = Key( + EZDeepLTranslationAPIKey, + default: DeepLAPIUsagePriority.webFirst + ) + static let deepLTranslateEndPointKey = Key(EZDeepLTranslateEndPointKey) + + // Bing + static let bingCookieKey = Key(EZBingCookieKey) + + // niu + static let niuTransAPIKey = Key(EZNiuTransAPIKey) + + // Caiyun + static let caiyunToken = Key(EZCaiyunToken) + + // tencent + static let tencentSecretId = Key(EZTencentSecretId) + static let tencentSecretKey = Key(EZTencentSecretKey) + + // Ali + static let aliAccessKeyId = Key(EZAliAccessKeyId) + static let aliAccessKeySecret = Key(EZAliAccessKeySecret) } /// shortcut diff --git a/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift b/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift index 6a99a3016..203ea793a 100644 --- a/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift +++ b/Easydict/Swift/Feature/Configuration/DefaultsStoredKey.swift @@ -8,6 +8,7 @@ import Foundation +// TODO: refactor key with enum key type. func storedKey(_ key: String, serviceType: ServiceType) -> String { // This key should be compatible with existing OpenAI config keys // EZOpenAIServiceUsageStatusKey @@ -31,8 +32,28 @@ func dictionaryStoredKey(_ serviceType: ServiceType) -> String { storedKey(EZDictionaryKey, serviceType: serviceType) } -func availableModelStoredKey(_ serviceType: ServiceType) -> String { - storedKey(EZAvailableModelKey, serviceType: serviceType) +func availableModelsStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZAvailableModelsKey, serviceType: serviceType) +} + +func validModelsStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZValidModelsKey, serviceType: serviceType) +} + +func modelStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZModelKey, serviceType: serviceType) +} + +func apiStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZAPIKey, serviceType: serviceType) +} + +func endpointStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZEndpointKey, serviceType: serviceType) +} + +func nameStoredKey(_ serviceType: ServiceType) -> String { + storedKey(EZNameKey, serviceType: serviceType) } extension UserDefaults { diff --git a/Easydict/objc/Service/Model/EZConstKey.h b/Easydict/objc/Service/Model/EZConstKey.h index 9089e1a8f..4a94c7ed9 100644 --- a/Easydict/objc/Service/Model/EZConstKey.h +++ b/Easydict/objc/Service/Model/EZConstKey.h @@ -17,7 +17,12 @@ static NSString *const EZServiceUsageStatusKey = @"ServiceUsageStatus"; static NSString *const EZTranslationKey = @"Translation"; static NSString *const EZDictionaryKey = @"Dictionary"; static NSString *const EZSentenceKey = @"Sentence"; -static NSString *const EZAvailableModelKey = @"AvailableModel"; +static NSString *const EZAvailableModelsKey = @"AvailableModels"; +static NSString *const EZValidModelsKey = @"ValidModels"; +static NSString *const EZModelKey = @"Model"; +static NSString *const EZAPIKey = @"API"; +static NSString *const EZEndpointKey = @"EndPoint"; +static NSString *const EZNameKey = @"Name"; // OpenAI static NSString *const EZOpenAIAPIKey = @"EZOpenAIAPIKey"; From 22bf492d0c6231509b56199be406906ccb7ad870 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Wed, 5 Jun 2024 00:23:02 +0800 Subject: [PATCH 29/30] fix: gemini-1.0-pro model cannot use system instruction --- .../Swift/Service/Gemini/GeminiService.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index 4a648f4c5..c52846731 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -39,9 +39,18 @@ public final class GeminiService: LLMStreamService { result.isStreamFinished = false let queryType = queryType(text: text, from: from, to: to) - let translationPrompt = promptContent(queryType: queryType, text: text, from: from, to: to) - let systemInstruction = queryType == .dictionary ? LLMStreamService.dictSystemPrompt : LLMStreamService + let chatHistory = promptContent(queryType: queryType, text: text, from: from, to: to) + let systemPrompt = queryType == .dictionary ? LLMStreamService + .dictSystemPrompt : LLMStreamService .translationSystemPrompt + + var systemInstruction: ModelContent? = try ModelContent(role: "system", systemPrompt) + + // !!!: gemini-1.0-pro model does not support system instruction https://github.com/google-gemini/generative-ai-python/issues/328 + if model == GeminiModel.gemini1_0_pro.rawValue { + systemInstruction = nil + } + let model = GenerativeModel( name: model, apiKey: apiKey, @@ -58,7 +67,7 @@ public final class GeminiService: LLMStreamService { // Gemini Docs: https://github.com/google/generative-ai-swift - let outputContentStream = model.generateContentStream(translationPrompt) + let outputContentStream = model.generateContentStream(chatHistory) for try await outputContent in outputContentStream { guard let line = outputContent.text else { return @@ -196,9 +205,7 @@ extension GeminiService { chats.append(chat) } } - guard !chats.isEmpty else { - return chats - } + return chats } } From 67a63f15d04735e559ba49f38de705f237cd473e Mon Sep 17 00:00:00 2001 From: tisfeng Date: Wed, 5 Jun 2024 00:44:39 +0800 Subject: [PATCH 30/30] perf: change system prompt to user prompt for gemini-1.0-pro model --- .../Swift/Service/Gemini/GeminiService.swift | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Easydict/Swift/Service/Gemini/GeminiService.swift b/Easydict/Swift/Service/Gemini/GeminiService.swift index c52846731..e27700adb 100644 --- a/Easydict/Swift/Service/Gemini/GeminiService.swift +++ b/Easydict/Swift/Service/Gemini/GeminiService.swift @@ -39,18 +39,27 @@ public final class GeminiService: LLMStreamService { result.isStreamFinished = false let queryType = queryType(text: text, from: from, to: to) - let chatHistory = promptContent(queryType: queryType, text: text, from: from, to: to) let systemPrompt = queryType == .dictionary ? LLMStreamService .dictSystemPrompt : LLMStreamService .translationSystemPrompt + var enableSystemPromptInChats = false var systemInstruction: ModelContent? = try ModelContent(role: "system", systemPrompt) // !!!: gemini-1.0-pro model does not support system instruction https://github.com/google-gemini/generative-ai-python/issues/328 if model == GeminiModel.gemini1_0_pro.rawValue { systemInstruction = nil + enableSystemPromptInChats = true } + let chatHistory = promptContent( + queryType: queryType, + text: text, + from: from, + to: to, + systemPrompt: enableSystemPromptInChats + ) + let model = GenerativeModel( name: model, apiKey: apiKey, @@ -156,10 +165,12 @@ public final class GeminiService: LLMStreamService { private let sexuallyExplicitBlockNone = SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockNone) private let dangerousContentBlockNone = SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone) - /// Given a roleRaw, replace "assistant" with "model" + /// Given a roleRaw, currently only support "user" and "model", "model" is equal to "assistant". https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=swift&hl=zh-cn#multi-turn-conversations-chat private func getCorrectParts(from roleRaw: String) -> String { - if roleRaw.lowercased() == "assistant" { + if roleRaw == "assistant" { "model" + } else if roleRaw == "system" { + "user" } else { roleRaw } @@ -175,7 +186,8 @@ extension GeminiService { queryType: EZQueryTextType, text: String, from sourceLanguage: Language, - to targetLanguage: Language + to targetLanguage: Language, + systemPrompt: Bool ) -> [ModelContent] { var prompts = [[String: String]]() @@ -186,14 +198,24 @@ extension GeminiService { word: text, sourceLanguage: sourceLanguage, targetLanguage: targetLanguage, - systemPrompt: false + systemPrompt: systemPrompt ) case .sentence: - prompts = sentenceMessages(sentence: text, from: sourceLanguage, to: targetLanguage, systemPrompt: false) + prompts = sentenceMessages( + sentence: text, + from: sourceLanguage, + to: targetLanguage, + systemPrompt: systemPrompt + ) case .translation: fallthrough default: - prompts = translationMessages(text: text, from: sourceLanguage, to: targetLanguage, systemPrompt: false) + prompts = translationMessages( + text: text, + from: sourceLanguage, + to: targetLanguage, + systemPrompt: systemPrompt + ) } var chats: [ModelContent] = []