-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* test Google Gemini * perf: use Task to wrap gemini async stream * feat: initiate support for Google Gemini * fix: build error * fix: missing GeminiTranslateType file * perf: restore CaiyunService * perf: improve gemini prompt from openai translate * refractor: define static property for translation prompt Co-Authored-By: Kyle <kyle201817146@gmail.com> * perf: bump debug version * fix: resolve deprecated warnings * refractor: remove GeminiTranslateType * perf: change min version back to 11.0 * perf: resolve xcode warning Xcode Autofix * perf: adjust gemini safety level #297 (comment) * refractor: manual error handling * perf: improve error message for Gemini * fix: content streaming on macOS 12+ * perf: improve code, remove logs * perf: update generative-ai-swift to 0.4.7 * perf: rename variable --------- Co-authored-by: tisfeng <tisfeng@gmail.com> Co-authored-by: Kyle <kyle201817146@gmail.com>
- Loading branch information
1 parent
896d04d
commit 1a89fb9
Showing
13 changed files
with
261 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
Easydict/App/Assets.xcassets/service-icon/Gemini.imageset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"idiom" : "universal", | ||
"scale" : "1x" | ||
}, | ||
{ | ||
"filename" : "Gemini.png", | ||
"idiom" : "universal", | ||
"scale" : "2x" | ||
}, | ||
{ | ||
"idiom" : "universal", | ||
"scale" : "3x" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// | ||
// GeminiService.swift | ||
// Easydict | ||
// | ||
// Created by Jerry on 2024-01-02. | ||
// Copyright © 2024 izual. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import GoogleGenerativeAI | ||
|
||
@objc(EZGeminiService) | ||
public final class GeminiService: QueryService { | ||
override public func serviceType() -> ServiceType { | ||
.gemini | ||
} | ||
|
||
override public func link() -> String? { | ||
"https://bard.google.com/chat" | ||
} | ||
|
||
override public func name() -> String { | ||
NSLocalizedString("gemini_translate", comment: "The name of Gemini Translate") | ||
} | ||
|
||
// https://ai.google.dev/available_regions | ||
private static let unsupportedLanguages: [Language] = [.persian, .filipino, .khmer, .lao, .malay, .mongolian, .burmese, .telugu, .tamil, .urdu] | ||
|
||
override public func supportLanguagesDictionary() -> MMOrderedDictionary<AnyObject, AnyObject> { | ||
// TODO: Replace MMOrderedDictionary. | ||
let orderedDict = MMOrderedDictionary<AnyObject, AnyObject>() | ||
for language in EZLanguageManager.shared().allLanguages { | ||
let value = language.rawValue | ||
if !GeminiService.unsupportedLanguages.contains(language) { | ||
orderedDict.setObject(value as NSString, forKey: language.rawValue as NSString) | ||
} | ||
} | ||
|
||
return orderedDict | ||
} | ||
|
||
override public func ocr(_: EZQueryModel) async throws -> EZOCRResult { | ||
NSLog("Gemini Translate does not support OCR") | ||
throw QueryServiceError.notSupported | ||
} | ||
|
||
override public func needPrivateAPIKey() -> Bool { | ||
true | ||
} | ||
|
||
override public func hasPrivateAPIKey() -> Bool { | ||
if apiKey == defaultAPIKey { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
private let defaultAPIKey = "" /* .decryptAES() */ | ||
|
||
// easydict://writeKeyValue?EZGeminiAPIKey=xxx | ||
private var apiKey: String { | ||
let apiKey = UserDefaults.standard.string(forKey: EZGeminiAPIKey) | ||
if let apiKey, !apiKey.isEmpty { | ||
return apiKey | ||
} else { | ||
return defaultAPIKey | ||
} | ||
} | ||
|
||
// Set Gemini safety level to BLOCK_NONE | ||
private static let harassmentSafety = SafetySetting(harmCategory: .harassment, threshold: .blockNone) | ||
private static let hateSpeechSafety = SafetySetting(harmCategory: .hateSpeech, threshold: .blockNone) | ||
private static let sexuallyExplicitSafety = SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockNone) | ||
private static let dangerousContentSafety = SafetySetting(harmCategory: .dangerousContent, threshold: .blockNone) | ||
|
||
private static let translationPrompt = "You are a translation expert proficient in various languages that can only translate text and cannot interpret it. You are able to accurately understand the meaning of proper nouns, idioms, metaphors, allusions or other obscure words in sentences and translate them into appropriate words by combining the context and language environment. The result of the translation should be natural and fluent, you can only return the translated text, do not show additional information and notes." | ||
|
||
override public func autoConvertTraditionalChinese() -> Bool { | ||
true | ||
} | ||
|
||
override public func translate(_ text: String, from: Language, to: Language, completion: @escaping (EZQueryResult, Error?) -> Void) { | ||
Task { | ||
do { | ||
let prompt = GeminiService.translationPrompt + "Translate the following \(from.rawValue) text into \(to.rawValue): \(text)" | ||
print("gemini prompt: \(prompt)") | ||
let model = GenerativeModel( | ||
name: "gemini-pro", | ||
apiKey: apiKey, | ||
safetySettings: [ | ||
GeminiService.harassmentSafety, | ||
GeminiService.hateSpeechSafety, | ||
GeminiService.sexuallyExplicitSafety, | ||
GeminiService.dangerousContentSafety, | ||
] | ||
) | ||
|
||
if #available(macOS 12.0, *) { | ||
var resultString = "" | ||
let outputContentStream = model.generateContentStream(prompt) | ||
|
||
// stream response | ||
for try await outputContent in outputContentStream { | ||
guard let line = outputContent.text else { | ||
return | ||
} | ||
|
||
resultString += line | ||
result.translatedResults = [resultString] | ||
completion(result, nil) | ||
} | ||
|
||
} else { | ||
let outputContent = try await model.generateContent(prompt) | ||
guard let resultString = outputContent.text else { | ||
return | ||
} | ||
|
||
result.translatedResults = [resultString] | ||
completion(result, nil) | ||
} | ||
} catch { | ||
/** | ||
https://github.com/google/generative-ai-swift/issues/89 | ||
|
||
String(describing: error) | ||
|
||
"internalError(underlying: GoogleGenerativeAI.RPCError(httpResponseCode: 400, message: \"API key not valid. Please pass a valid API key.\", status: GoogleGenerativeAI.RPCStatus.invalidArgument))" | ||
*/ | ||
let ezError = EZError(nsError: error) | ||
let errorString = String(describing: error) | ||
let errorMessage = errorString.extract(withPattern: "message: \"([^\"]*)\"") ?? errorString | ||
ezError?.errorDataMessage = errorMessage | ||
|
||
completion(result, ezError) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.