diff --git a/liltr.xcodeproj/project.pbxproj b/liltr.xcodeproj/project.pbxproj index 46a8e91..3c382a0 100644 --- a/liltr.xcodeproj/project.pbxproj +++ b/liltr.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 12F1730A2C4B9566009FD444 /* Ollama.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F173092C4B9566009FD444 /* Ollama.swift */; }; 2B0437D22B79D5D800792E8B /* AppleScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0437D12B79D5D800792E8B /* AppleScript.swift */; }; 2B0437D42B79E2F000792E8B /* Pasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0437D32B79E2F000792E8B /* Pasteboard.swift */; }; 2B0437D62B79E34900792E8B /* SelectedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B0437D52B79E34900792E8B /* SelectedText.swift */; }; @@ -59,6 +60,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 12F173092C4B9566009FD444 /* Ollama.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ollama.swift; sourceTree = ""; }; 2B0437D12B79D5D800792E8B /* AppleScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScript.swift; sourceTree = ""; }; 2B0437D32B79E2F000792E8B /* Pasteboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pasteboard.swift; sourceTree = ""; }; 2B0437D52B79E34900792E8B /* SelectedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedText.swift; sourceTree = ""; }; @@ -228,6 +230,7 @@ 2B7CF3002B75BCB400BFA0BA /* Volcengine.swift */, 2B7CF2FE2B75BCB400BFA0BA /* AppleDictionary.swift */, 2B7CF2FF2B75BCB400BFA0BA /* ProviderManager.swift */, + 12F173092C4B9566009FD444 /* Ollama.swift */, ); path = Provider; sourceTree = ""; @@ -415,6 +418,7 @@ 2B0437D62B79E34900792E8B /* SelectedText.swift in Sources */, 2B7CF38E2B75C00400BFA0BA /* TranslateFieldView.swift in Sources */, 2B7CF31C2B75BCB400BFA0BA /* NiuTrans.swift in Sources */, + 12F1730A2C4B9566009FD444 /* Ollama.swift in Sources */, 2B7CF31F2B75BCB400BFA0BA /* ProviderManager.swift in Sources */, 2B7CF31A2B75BCB400BFA0BA /* BigHugeThesaurus.swift in Sources */, 2B7CF3242B75BCB400BFA0BA /* extensions.swift in Sources */, diff --git a/liltr/Settings/ProvidersView.swift b/liltr/Settings/ProvidersView.swift index 62c2841..282d07c 100644 --- a/liltr/Settings/ProvidersView.swift +++ b/liltr/Settings/ProvidersView.swift @@ -62,6 +62,9 @@ struct ProvidersView: View { @Default(\.BigHugeThesaurusSK) var bigHugeThesaurusSK + @Default(\.OllamaAPI) var ollamaApi + @Default(\.OllamaModel) var ollamaModel + @Default(\.dictionary) var dictionary private let _gapSize: CGFloat = 8 @@ -102,6 +105,9 @@ struct ProvidersView: View { ProviderKeyField(label: "Baidu", icon: "4.square", ak: $baiduAK, sk: $baiduSK) ProviderKeyField(label: "BigHugeThesaurus", icon: "5.square", sk: $bigHugeThesaurusSK) + + ProviderKeyField(label: "Ollama", icon: "6.square", ak: $ollamaApi, sk: $ollamaModel) + }).padding(EdgeInsets(top: 0, leading: _gapSize * 2, bottom: 0, trailing: _gapSize * 2)) } .frame(width: 500, height: 220) diff --git a/liltr/Utils/Provider/Ollama.swift b/liltr/Utils/Provider/Ollama.swift new file mode 100644 index 0000000..e6564bd --- /dev/null +++ b/liltr/Utils/Provider/Ollama.swift @@ -0,0 +1,70 @@ +import Alamofire +import Foundation + +struct OllamaResult: Decodable { + let role: String + let content: String +} + +struct OllamaResponse: BaseResponse { + let model: String + let created_at: String + let message: OllamaResult + let done: Bool + + var target: String? { + return message.content + } + + var errorMessage: String? { + return nil + } +} + +let OllamaProviderName = "Ollama" + +class OllamaProvider: BaseProvider { + static let shared = OllamaProvider() + let delay: DispatchTimeInterval = .seconds(1) + let name = OllamaProviderName + + var apiUrl: String { + return Defaults.shared.OllamaAPI.isEmpty ? "http://localhost:11434/api/chat" : Defaults.shared.OllamaAPI + } + + var model: String { + return Defaults.shared.OllamaModel.isEmpty ? "qwen2" : Defaults.shared.OllamaModel + } + + func translate(source: String, from: Language, to: Language, cb: @escaping (_ target: String, _ _sourceLanguage: Language?, _ _targetLanguage: Language?) -> Void) { + let prompt = "Assuming you are a seasoned translator, please translate the following source text to \(to.name) as target language, ensuring accuracy while trying to retain emotion and natural flow. Your response should ONLY contain the translated result.\n The source text is: ```\(source)```" + let parameters: [String: Any] = [ + "model": model, + "stream": false, + "messages": [ + [ + "role": "user", + "content": prompt + ] + ], + ] + + debugPrint("[OllamaProvider] parameters:", parameters) + + AF.request(apiUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default) + .cacheResponse(using: .cache) + .cURLDescription(calling: { curl in + print(curl) + }) + .responseDecodable(of: OllamaResponse.self) { response in + if response.error != nil { + cb(response.error!.errorDescription!, nil, nil) + } else if response.value?.errorMessage != nil { + cb(response.value!.errorMessage!, nil, nil) + } else { + cb(response.value!.target!, from, to) + } + } + } + +} diff --git a/liltr/Utils/Provider/ProviderManager.swift b/liltr/Utils/Provider/ProviderManager.swift index 88b0466..a6e735b 100644 --- a/liltr/Utils/Provider/ProviderManager.swift +++ b/liltr/Utils/Provider/ProviderManager.swift @@ -12,7 +12,7 @@ protocol BaseResponse: Decodable { var errorMessage: String? { get } } -let PROVIDER_ARRAY: [any BaseProvider] = [NiuTransProvider.shared, BaiduProvider.shared, VolcengineProvider.shared, AliProvider.shared, AppleDictionaryProvider.shared, BigHugeThesaurusProvider.shared] +let PROVIDER_ARRAY: [any BaseProvider] = [NiuTransProvider.shared, BaiduProvider.shared, VolcengineProvider.shared, AliProvider.shared, AppleDictionaryProvider.shared, BigHugeThesaurusProvider.shared, OllamaProvider.shared] let PROVIDER_DICT: [String: any BaseProvider] = Dictionary(uniqueKeysWithValues: PROVIDER_ARRAY.map { ($0.name, $0) }) struct ProviderCallbackData { @@ -70,7 +70,14 @@ class ProviderManager: ObservableObject { if query == curQuery { let data = ProviderCallbackData(target, source, sourceLanguage: _sourceLanguage, targetLanguage: _targetLanguage?.name == _sourceLanguage?.name ? nil : _targetLanguage, providerName: cur.name) cb(data) - resultCache[query] = data + + if _sourceLanguage == nil && _targetLanguage == nil { + // if is error + } else { + // if not error + resultCache[query] = data + } + isTranslating = false curQuery = "" } diff --git a/liltr/userDefaults.swift b/liltr/userDefaults.swift index 1c9f32f..d5f8adc 100644 --- a/liltr/userDefaults.swift +++ b/liltr/userDefaults.swift @@ -31,6 +31,9 @@ public class Defaults: ObservableObject { @AppStorage("\(AliProviderName)SK") public var AliSK = "" // big huge @AppStorage("\(BigHugeThesaurusProviderName)SK") public var BigHugeThesaurusSK = "" + // ollama + @AppStorage("\(OllamaProviderName)API") public var OllamaAPI = "" + @AppStorage("\(OllamaProviderName)Model") public var OllamaModel = "" // MARK: Dictionary @AppStorage("dictionary") public var dictionary = DCSOxfordDictionaryOfEnglish