diff --git a/src/apis/index.js b/src/apis/index.js index 48e6452..09aea94 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -21,6 +21,7 @@ import { OPT_TRANS_OLLAMA, OPT_TRANS_OLLAMA_2, OPT_TRANS_OLLAMA_3, + OPT_TRANS_OPENROUTER, OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE_2, OPT_TRANS_CUSTOMIZE_3, @@ -316,6 +317,7 @@ export const apiTranslate = async ({ case OPT_TRANS_OPENAI_2: case OPT_TRANS_OPENAI_3: case OPT_TRANS_GEMINI_2: + case OPT_TRANS_OPENROUTER: trText = res?.choices?.map((item) => item.message.content).join(" "); isSame = text === trText; break; diff --git a/src/apis/trans.js b/src/apis/trans.js index 24be8d4..c779185 100644 --- a/src/apis/trans.js +++ b/src/apis/trans.js @@ -20,6 +20,7 @@ import { OPT_TRANS_OLLAMA, OPT_TRANS_OLLAMA_2, OPT_TRANS_OLLAMA_3, + OPT_TRANS_OPENROUTER, OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE_2, OPT_TRANS_CUSTOMIZE_3, @@ -465,6 +466,62 @@ const genClaude = ({ return [url, init]; }; +const genOpenRouter = ({ + text, + from, + to, + url, + key, + systemPrompt, + userPrompt, + model, + temperature, + maxTokens, + customHeader, + customBody, +}) => { + systemPrompt = systemPrompt + .replaceAll(INPUT_PLACE_FROM, from) + .replaceAll(INPUT_PLACE_TO, to) + .replaceAll(INPUT_PLACE_TEXT, text); + userPrompt = userPrompt + .replaceAll(INPUT_PLACE_FROM, from) + .replaceAll(INPUT_PLACE_TO, to) + .replaceAll(INPUT_PLACE_TEXT, text); + + customHeader = parseJsonObj(customHeader); + customBody = parseJsonObj(customBody); + + const data = { + model, + messages: [ + { + role: "system", + content: systemPrompt, + }, + { + role: "user", + content: userPrompt, + }, + ], + temperature, + max_tokens: maxTokens, + ...customBody, + }; + + const init = { + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${key}`, + ...customHeader, + }, + method: "POST", + body: JSON.stringify(data), + }; + + return [url, init]; +}; + const genOllama = ({ text, from, @@ -587,6 +644,7 @@ export const genTransReq = ({ translator, text, from, to }, apiSetting) => { case OPT_TRANS_OLLAMA: case OPT_TRANS_OLLAMA_2: case OPT_TRANS_OLLAMA_3: + case OPT_TRANS_OPENROUTER: case OPT_TRANS_NIUTRANS: case OPT_TRANS_CUSTOMIZE: case OPT_TRANS_CUSTOMIZE_2: @@ -638,6 +696,8 @@ export const genTransReq = ({ translator, text, from, to }, apiSetting) => { case OPT_TRANS_OLLAMA_2: case OPT_TRANS_OLLAMA_3: return genOllama(args); + case OPT_TRANS_OPENROUTER: + return genOpenRouter(args); case OPT_TRANS_CUSTOMIZE: case OPT_TRANS_CUSTOMIZE_2: case OPT_TRANS_CUSTOMIZE_3: diff --git a/src/config/index.js b/src/config/index.js index e131ca5..c9864f4 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -136,6 +136,7 @@ export const OPT_TRANS_CLOUDFLAREAI = "CloudflareAI"; export const OPT_TRANS_OLLAMA = "Ollama"; export const OPT_TRANS_OLLAMA_2 = "Ollama2"; export const OPT_TRANS_OLLAMA_3 = "Ollama3"; +export const OPT_TRANS_OPENROUTER = "OpenRouter"; export const OPT_TRANS_CUSTOMIZE = "Custom"; export const OPT_TRANS_CUSTOMIZE_2 = "Custom2"; export const OPT_TRANS_CUSTOMIZE_3 = "Custom3"; @@ -162,6 +163,7 @@ export const OPT_TRANS_ALL = [ OPT_TRANS_OLLAMA, OPT_TRANS_OLLAMA_2, OPT_TRANS_OLLAMA_3, + OPT_TRANS_OPENROUTER, OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE_2, OPT_TRANS_CUSTOMIZE_3, @@ -331,6 +333,9 @@ export const OPT_LANGS_SPECIAL = { [OPT_TRANS_OLLAMA_3]: new Map( OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) ), + [OPT_TRANS_OPENROUTER]: new Map( + OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]]) + ), [OPT_TRANS_CLOUDFLAREAI]: new Map([ ["auto", ""], ["zh-CN", "chinese"], @@ -738,6 +743,22 @@ export const DEFAULT_TRANS_APIS = { [OPT_TRANS_OLLAMA]: defaultOllamaApi, [OPT_TRANS_OLLAMA_2]: defaultOllamaApi, [OPT_TRANS_OLLAMA_3]: defaultOllamaApi, + [OPT_TRANS_OPENROUTER]: { + url: "https://openrouter.ai/api/v1/chat/completions", + key: "", + model: "openai/gpt-4o", + systemPrompt: `You are a professional, authentic machine translation engine.`, + userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`, + customHeader: "", + customBody: "", + temperature: 0, + maxTokens: 256, + fetchLimit: 1, + fetchInterval: 500, + apiName: OPT_TRANS_OPENROUTER, + isDisabled: false, + httpTimeout: DEFAULT_HTTP_TIMEOUT * 2, + }, [OPT_TRANS_CUSTOMIZE]: defaultCustomApi, [OPT_TRANS_CUSTOMIZE_2]: defaultCustomApi, [OPT_TRANS_CUSTOMIZE_3]: defaultCustomApi, diff --git a/src/views/Options/Apis.js b/src/views/Options/Apis.js index fe7bacf..4832153 100644 --- a/src/views/Options/Apis.js +++ b/src/views/Options/Apis.js @@ -24,6 +24,7 @@ import { OPT_TRANS_OLLAMA, OPT_TRANS_OLLAMA_2, OPT_TRANS_OLLAMA_3, + OPT_TRANS_OPENROUTER, OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE_2, OPT_TRANS_CUSTOMIZE_3, @@ -187,6 +188,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) { OPT_TRANS_OLLAMA, OPT_TRANS_OLLAMA_2, OPT_TRANS_OLLAMA_3, + OPT_TRANS_OPENROUTER, OPT_TRANS_NIUTRANS, OPT_TRANS_CUSTOMIZE, OPT_TRANS_CUSTOMIZE_2, @@ -249,6 +251,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) { {(translator.startsWith(OPT_TRANS_OPENAI) || translator.startsWith(OPT_TRANS_OLLAMA) || translator === OPT_TRANS_CLAUDE || + translator === OPT_TRANS_OPENROUTER || translator.startsWith(OPT_TRANS_GEMINI)) && ( <>