customize api...

This commit is contained in:
Gabe Yuan
2023-09-06 00:25:46 +08:00
parent 93fd82fcd9
commit f772fa000c
9 changed files with 286 additions and 20 deletions

View File

@@ -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];

View File

@@ -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`,
},
};

View File

@@ -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
View 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 };
}

View File

@@ -31,7 +31,7 @@ export function useTranslate(q, rule, setting) {
q,
fromLang,
toLang,
setting,
setting: setting[translator],
});
setText(trText);
setSamelang(isSame);

View File

@@ -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
View 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} />
));
}

View File

@@ -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"),

View File

@@ -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>