customize api...
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_DEEPL,
|
||||
OPT_TRANS_OPENAI,
|
||||
OPT_TRANS_CUSTOMIZE,
|
||||
URL_MICROSOFT_TRANS,
|
||||
OPT_LANGS_SPECIAL,
|
||||
PROMPT_PLACE_FROM,
|
||||
@@ -49,7 +50,7 @@ export const apiFetchRules = (url, isBg = false) =>
|
||||
* @returns
|
||||
*/
|
||||
const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
||||
const { googleUrl } = setting;
|
||||
const { url, key } = setting;
|
||||
const params = {
|
||||
client: "gtx",
|
||||
dt: "t",
|
||||
@@ -59,7 +60,7 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
||||
tl: to,
|
||||
q: text,
|
||||
};
|
||||
const input = `${googleUrl}?${queryString.stringify(params)}`;
|
||||
const input = `${url}?${queryString.stringify(params)}`;
|
||||
return fetchPolyfill(input, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
@@ -67,6 +68,7 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
||||
useCache: true,
|
||||
usePool: true,
|
||||
translator,
|
||||
token: key,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -104,7 +106,7 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
|
||||
* @returns
|
||||
*/
|
||||
const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
||||
const { deeplUrl, deeplKey } = setting;
|
||||
const { url, key } = setting;
|
||||
const data = {
|
||||
text: [text],
|
||||
target_lang: to,
|
||||
@@ -113,7 +115,7 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
||||
if (from) {
|
||||
data.source_lang = from;
|
||||
}
|
||||
return fetchPolyfill(deeplUrl, {
|
||||
return fetchPolyfill(url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
},
|
||||
@@ -122,7 +124,7 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
||||
useCache: true,
|
||||
usePool: true,
|
||||
translator,
|
||||
token: deeplKey,
|
||||
token: key,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -134,17 +136,17 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
||||
* @returns
|
||||
*/
|
||||
const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
||||
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = setting;
|
||||
let prompt = openaiPrompt
|
||||
let { url, key, model, prompt } = setting;
|
||||
prompt = prompt
|
||||
.replaceAll(PROMPT_PLACE_FROM, from)
|
||||
.replaceAll(PROMPT_PLACE_TO, to);
|
||||
return fetchPolyfill(openaiUrl, {
|
||||
return fetchPolyfill(url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
model: openaiModel,
|
||||
model: model,
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
@@ -161,7 +163,34 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
||||
useCache: true,
|
||||
usePool: true,
|
||||
translator,
|
||||
token: openaiKey,
|
||||
token: key,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 自定义接口 翻译
|
||||
* @param {*} text
|
||||
* @param {*} to
|
||||
* @param {*} from
|
||||
* @returns
|
||||
*/
|
||||
const apiCustomizeTranslate = async (translator, text, to, from, setting) => {
|
||||
let { url, key, headers } = setting;
|
||||
return fetchPolyfill(url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
...JSON.parse(headers),
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
from,
|
||||
to,
|
||||
}),
|
||||
useCache: true,
|
||||
usePool: true,
|
||||
translator,
|
||||
token: key,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -190,17 +219,19 @@ export const apiTranslate = async ({
|
||||
} else if (translator === OPT_TRANS_MICROSOFT) {
|
||||
const res = await apiMicrosoftTranslate(translator, q, to, from);
|
||||
trText = res[0].translations[0].text;
|
||||
isSame = to === res[0].detectedLanguage.language;
|
||||
isSame = to === res[0].detectedLanguage?.language;
|
||||
} else if (translator === OPT_TRANS_DEEPL) {
|
||||
const res = await apiDeepLTranslate(translator, q, to, from, setting);
|
||||
trText = res.translations.map((item) => item.text).join(" ");
|
||||
isSame = to === res.translations[0].detected_source_language;
|
||||
isSame = to === (from || res.translations[0].detected_source_language);
|
||||
} else if (translator === OPT_TRANS_OPENAI) {
|
||||
const res = await apiOpenaiTranslate(translator, q, to, from, setting);
|
||||
trText = res?.choices?.[0].message.content;
|
||||
const sLang = await tryDetectLang(q);
|
||||
const tLang = await tryDetectLang(trText);
|
||||
isSame = q === trText || (sLang && tLang && sLang === tLang);
|
||||
} else if (translator === OPT_TRANS_CUSTOMIZE) {
|
||||
// todo
|
||||
}
|
||||
|
||||
return [trText, isSame];
|
||||
|
||||
@@ -24,6 +24,10 @@ export const I18N = {
|
||||
zh: `规则设置`,
|
||||
en: `Rules Setting`,
|
||||
},
|
||||
apis_setting: {
|
||||
zh: `接口设置`,
|
||||
en: `Apis Setting`,
|
||||
},
|
||||
sync_setting: {
|
||||
zh: `同步设置`,
|
||||
en: `Sync Setting`,
|
||||
@@ -360,4 +364,8 @@ export const I18N = {
|
||||
zh: `求助`,
|
||||
en: `Help`,
|
||||
},
|
||||
restore_default: {
|
||||
zh: `恢复默认`,
|
||||
en: `Restore Default`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -71,11 +71,13 @@ export const OPT_TRANS_GOOGLE = "Google";
|
||||
export const OPT_TRANS_MICROSOFT = "Microsoft";
|
||||
export const OPT_TRANS_DEEPL = "DeepL";
|
||||
export const OPT_TRANS_OPENAI = "OpenAI";
|
||||
export const OPT_TRANS_CUSTOMIZE = "Customize";
|
||||
export const OPT_TRANS_ALL = [
|
||||
OPT_TRANS_GOOGLE,
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_DEEPL,
|
||||
OPT_TRANS_OPENAI,
|
||||
OPT_TRANS_CUSTOMIZE,
|
||||
];
|
||||
|
||||
export const OPT_LANGS_TO = [
|
||||
@@ -198,6 +200,32 @@ export const DEFAULT_SUBRULES_LIST = [
|
||||
},
|
||||
];
|
||||
|
||||
// 翻译接口
|
||||
export const DEFAULT_TRANS_APIS = {
|
||||
[OPT_TRANS_GOOGLE]: {
|
||||
url: "https://translate.googleapis.com/translate_a/single",
|
||||
},
|
||||
[OPT_TRANS_MICROSOFT]: {
|
||||
url: "https://api-edge.cognitive.microsofttranslator.com/translate",
|
||||
authUrl: "https://edge.microsoft.com/translate/auth",
|
||||
},
|
||||
[OPT_TRANS_DEEPL]: {
|
||||
url: "https://api-free.deepl.com/v2/translate",
|
||||
key: "",
|
||||
},
|
||||
[OPT_TRANS_OPENAI]: {
|
||||
url: "https://api.openai.com/v1/chat/completion",
|
||||
key: "",
|
||||
model: "gpt-4",
|
||||
prompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`,
|
||||
},
|
||||
[OPT_TRANS_CUSTOMIZE]: {
|
||||
url: "",
|
||||
key: "",
|
||||
headers: "",
|
||||
},
|
||||
};
|
||||
|
||||
export const TRANS_MIN_LENGTH = 5; // 最短翻译长度
|
||||
export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度
|
||||
export const TRANS_NEWLINE_LENGTH = 40; // 换行字符数
|
||||
@@ -214,6 +242,7 @@ export const DEFAULT_SETTING = {
|
||||
injectRules: true, // 是否注入订阅规则
|
||||
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
||||
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
||||
transApis: DEFAULT_TRANS_APIS, // 翻译接口
|
||||
googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
|
||||
deeplUrl: "https://api-free.deepl.com/v2/translate",
|
||||
deeplKey: "",
|
||||
|
||||
26
src/hooks/Api.js
Normal file
26
src/hooks/Api.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useCallback } from "react";
|
||||
import { DEFAULT_TRANS_APIS } from "../config";
|
||||
import { useSetting } from "./Setting";
|
||||
|
||||
export function useApi(translator) {
|
||||
const { setting, updateSetting } = useSetting();
|
||||
const apis = setting?.transApis || DEFAULT_TRANS_APIS;
|
||||
const api = apis[translator] || {};
|
||||
console.log("apis", translator, apis);
|
||||
|
||||
const updateApi = useCallback(
|
||||
async (obj) => {
|
||||
const api = apis[translator] || {};
|
||||
const transApis = { ...apis, [translator]: { ...api, ...obj } };
|
||||
await updateSetting({ transApis });
|
||||
},
|
||||
[translator, apis, updateSetting]
|
||||
);
|
||||
|
||||
const resetApi = useCallback(async () => {
|
||||
const transApis = { ...apis, [translator]: DEFAULT_TRANS_APIS[translator] };
|
||||
await updateSetting({ transApis });
|
||||
}, [translator, apis, updateSetting]);
|
||||
|
||||
return { api, updateApi, resetApi };
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export function useTranslate(q, rule, setting) {
|
||||
q,
|
||||
fromLang,
|
||||
toLang,
|
||||
setting,
|
||||
setting: setting[translator],
|
||||
});
|
||||
setText(trText);
|
||||
setSamelang(isSame);
|
||||
|
||||
@@ -67,13 +67,15 @@ const newCacheReq = async (request) => {
|
||||
* @returns
|
||||
*/
|
||||
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
||||
if (translator === OPT_TRANS_MICROSOFT) {
|
||||
init.headers["Authorization"] = `Bearer ${token}`; // Microsoft
|
||||
} else if (translator === OPT_TRANS_DEEPL) {
|
||||
if (token) {
|
||||
if (translator === OPT_TRANS_DEEPL) {
|
||||
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
|
||||
} else if (translator === OPT_TRANS_OPENAI) {
|
||||
init.headers["Authorization"] = `Bearer ${token}`; // OpenAI
|
||||
init.headers["api-key"] = token; // Azure OpenAI
|
||||
} else {
|
||||
init.headers["Authorization"] = `Bearer ${token}`; // Microsoft & others
|
||||
}
|
||||
}
|
||||
|
||||
if (isGm) {
|
||||
|
||||
161
src/views/Options/Apis.js
Normal file
161
src/views/Options/Apis.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import {
|
||||
OPT_TRANS_ALL,
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_OPENAI,
|
||||
OPT_TRANS_CUSTOMIZE,
|
||||
} from "../../config";
|
||||
import { useState } from "react";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { useAlert } from "../../hooks/Alert";
|
||||
import { useApi } from "../../hooks/Api";
|
||||
import { apiTranslate } from "../../apis";
|
||||
|
||||
function TestButton({ translator, api }) {
|
||||
const i18n = useI18n();
|
||||
const alert = useAlert();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleApiTest = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [text] = await apiTranslate({
|
||||
translator,
|
||||
q: "hello world",
|
||||
fromLang: "auto",
|
||||
toLang: "zh-CN",
|
||||
setting: api,
|
||||
});
|
||||
if (!text) {
|
||||
throw new Error("empty reault");
|
||||
}
|
||||
alert.success(i18n("test_success"));
|
||||
} catch (err) {
|
||||
alert.error(`${i18n("test_failed")}: ${err.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress sx={{ marginLeft: "2em" }} size={16} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button size="small" variant="contained" onClick={handleApiTest}>
|
||||
{i18n("click_test")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiFields({ translator }) {
|
||||
const i18n = useI18n();
|
||||
const { api, updateApi, resetApi } = useApi(translator);
|
||||
const { url = "", key = "", model = "", prompt = "", headers = "" } = api;
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
updateApi({
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
{translator !== OPT_TRANS_MICROSOFT && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"URL"}
|
||||
name="url"
|
||||
value={url}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"KEY"}
|
||||
name="key"
|
||||
value={key}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{translator === OPT_TRANS_OPENAI && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"MODEL"}
|
||||
name="model"
|
||||
value={model}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"PROMPT"}
|
||||
name="prompt"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{translator === OPT_TRANS_CUSTOMIZE && (
|
||||
<TextField
|
||||
size="small"
|
||||
label={"HEADERS"}
|
||||
name="headers"
|
||||
value={headers}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TestButton translator={translator} api={api} />
|
||||
{translator !== OPT_TRANS_MICROSOFT && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
resetApi();
|
||||
}}
|
||||
>
|
||||
{i18n("restore_default")}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiAccordion({ translator }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setExpanded((pre) => !pre);
|
||||
};
|
||||
|
||||
return (
|
||||
<Accordion expanded={expanded} onChange={handleChange}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>{translator}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{expanded && <ApiFields translator={translator} />}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Apis() {
|
||||
return OPT_TRANS_ALL.map((translator) => (
|
||||
<ApiAccordion key={translator} translator={translator} />
|
||||
));
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import InfoIcon from "@mui/icons-material/Info";
|
||||
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import SyncIcon from "@mui/icons-material/Sync";
|
||||
import ApiIcon from "@mui/icons-material/Api";
|
||||
|
||||
function LinkItem({ label, url, icon }) {
|
||||
const match = useMatch(url);
|
||||
@@ -36,6 +37,12 @@ export default function Navigator(props) {
|
||||
url: "/rules",
|
||||
icon: <DesignServicesIcon />,
|
||||
},
|
||||
{
|
||||
id: "apis_setting",
|
||||
label: i18n("apis_setting"),
|
||||
url: "/apis",
|
||||
icon: <ApiIcon />,
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
label: i18n("sync_setting"),
|
||||
|
||||
@@ -17,6 +17,7 @@ import Divider from "@mui/material/Divider";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { adaptScript } from "../../libs/gm";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Apis from "./Apis";
|
||||
|
||||
export default function Options() {
|
||||
const [error, setError] = useState("");
|
||||
@@ -124,6 +125,7 @@ export default function Options() {
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Setting />} />
|
||||
<Route path="rules" element={<Rules />} />
|
||||
<Route path="apis" element={<Apis />} />
|
||||
<Route path="sync" element={<SyncSetting />} />
|
||||
<Route path="about" element={<About />} />
|
||||
</Route>
|
||||
|
||||
Reference in New Issue
Block a user