customize api...
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
OPT_TRANS_MICROSOFT,
|
OPT_TRANS_MICROSOFT,
|
||||||
OPT_TRANS_DEEPL,
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_CUSTOMIZE,
|
||||||
URL_MICROSOFT_TRANS,
|
URL_MICROSOFT_TRANS,
|
||||||
OPT_LANGS_SPECIAL,
|
OPT_LANGS_SPECIAL,
|
||||||
PROMPT_PLACE_FROM,
|
PROMPT_PLACE_FROM,
|
||||||
@@ -49,7 +50,7 @@ export const apiFetchRules = (url, isBg = false) =>
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
||||||
const { googleUrl } = setting;
|
const { url, key } = setting;
|
||||||
const params = {
|
const params = {
|
||||||
client: "gtx",
|
client: "gtx",
|
||||||
dt: "t",
|
dt: "t",
|
||||||
@@ -59,7 +60,7 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
|||||||
tl: to,
|
tl: to,
|
||||||
q: text,
|
q: text,
|
||||||
};
|
};
|
||||||
const input = `${googleUrl}?${queryString.stringify(params)}`;
|
const input = `${url}?${queryString.stringify(params)}`;
|
||||||
return fetchPolyfill(input, {
|
return fetchPolyfill(input, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
@@ -67,6 +68,7 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
|
|||||||
useCache: true,
|
useCache: true,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
translator,
|
||||||
|
token: key,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
||||||
const { deeplUrl, deeplKey } = setting;
|
const { url, key } = setting;
|
||||||
const data = {
|
const data = {
|
||||||
text: [text],
|
text: [text],
|
||||||
target_lang: to,
|
target_lang: to,
|
||||||
@@ -113,7 +115,7 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
|||||||
if (from) {
|
if (from) {
|
||||||
data.source_lang = from;
|
data.source_lang = from;
|
||||||
}
|
}
|
||||||
return fetchPolyfill(deeplUrl, {
|
return fetchPolyfill(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
},
|
},
|
||||||
@@ -122,7 +124,7 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
|||||||
useCache: true,
|
useCache: true,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
translator,
|
||||||
token: deeplKey,
|
token: key,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,17 +136,17 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
||||||
const { openaiUrl, openaiKey, openaiModel, openaiPrompt } = setting;
|
let { url, key, model, prompt } = setting;
|
||||||
let prompt = openaiPrompt
|
prompt = prompt
|
||||||
.replaceAll(PROMPT_PLACE_FROM, from)
|
.replaceAll(PROMPT_PLACE_FROM, from)
|
||||||
.replaceAll(PROMPT_PLACE_TO, to);
|
.replaceAll(PROMPT_PLACE_TO, to);
|
||||||
return fetchPolyfill(openaiUrl, {
|
return fetchPolyfill(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-type": "application/json",
|
"Content-type": "application/json",
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: openaiModel,
|
model: model,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
@@ -161,7 +163,34 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
|
|||||||
useCache: true,
|
useCache: true,
|
||||||
usePool: true,
|
usePool: true,
|
||||||
translator,
|
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) {
|
} else if (translator === OPT_TRANS_MICROSOFT) {
|
||||||
const res = await apiMicrosoftTranslate(translator, q, to, from);
|
const res = await apiMicrosoftTranslate(translator, q, to, from);
|
||||||
trText = res[0].translations[0].text;
|
trText = res[0].translations[0].text;
|
||||||
isSame = to === res[0].detectedLanguage.language;
|
isSame = to === res[0].detectedLanguage?.language;
|
||||||
} else if (translator === OPT_TRANS_DEEPL) {
|
} else if (translator === OPT_TRANS_DEEPL) {
|
||||||
const res = await apiDeepLTranslate(translator, q, to, from, setting);
|
const res = await apiDeepLTranslate(translator, q, to, from, setting);
|
||||||
trText = res.translations.map((item) => item.text).join(" ");
|
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) {
|
} else if (translator === OPT_TRANS_OPENAI) {
|
||||||
const res = await apiOpenaiTranslate(translator, q, to, from, setting);
|
const res = await apiOpenaiTranslate(translator, q, to, from, setting);
|
||||||
trText = res?.choices?.[0].message.content;
|
trText = res?.choices?.[0].message.content;
|
||||||
const sLang = await tryDetectLang(q);
|
const sLang = await tryDetectLang(q);
|
||||||
const tLang = await tryDetectLang(trText);
|
const tLang = await tryDetectLang(trText);
|
||||||
isSame = q === trText || (sLang && tLang && sLang === tLang);
|
isSame = q === trText || (sLang && tLang && sLang === tLang);
|
||||||
|
} else if (translator === OPT_TRANS_CUSTOMIZE) {
|
||||||
|
// todo
|
||||||
}
|
}
|
||||||
|
|
||||||
return [trText, isSame];
|
return [trText, isSame];
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ export const I18N = {
|
|||||||
zh: `规则设置`,
|
zh: `规则设置`,
|
||||||
en: `Rules Setting`,
|
en: `Rules Setting`,
|
||||||
},
|
},
|
||||||
|
apis_setting: {
|
||||||
|
zh: `接口设置`,
|
||||||
|
en: `Apis Setting`,
|
||||||
|
},
|
||||||
sync_setting: {
|
sync_setting: {
|
||||||
zh: `同步设置`,
|
zh: `同步设置`,
|
||||||
en: `Sync Setting`,
|
en: `Sync Setting`,
|
||||||
@@ -360,4 +364,8 @@ export const I18N = {
|
|||||||
zh: `求助`,
|
zh: `求助`,
|
||||||
en: `Help`,
|
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_MICROSOFT = "Microsoft";
|
||||||
export const OPT_TRANS_DEEPL = "DeepL";
|
export const OPT_TRANS_DEEPL = "DeepL";
|
||||||
export const OPT_TRANS_OPENAI = "OpenAI";
|
export const OPT_TRANS_OPENAI = "OpenAI";
|
||||||
|
export const OPT_TRANS_CUSTOMIZE = "Customize";
|
||||||
export const OPT_TRANS_ALL = [
|
export const OPT_TRANS_ALL = [
|
||||||
OPT_TRANS_GOOGLE,
|
OPT_TRANS_GOOGLE,
|
||||||
OPT_TRANS_MICROSOFT,
|
OPT_TRANS_MICROSOFT,
|
||||||
OPT_TRANS_DEEPL,
|
OPT_TRANS_DEEPL,
|
||||||
OPT_TRANS_OPENAI,
|
OPT_TRANS_OPENAI,
|
||||||
|
OPT_TRANS_CUSTOMIZE,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const OPT_LANGS_TO = [
|
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_MIN_LENGTH = 5; // 最短翻译长度
|
||||||
export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度
|
export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度
|
||||||
export const TRANS_NEWLINE_LENGTH = 40; // 换行字符数
|
export const TRANS_NEWLINE_LENGTH = 40; // 换行字符数
|
||||||
@@ -214,6 +242,7 @@ export const DEFAULT_SETTING = {
|
|||||||
injectRules: true, // 是否注入订阅规则
|
injectRules: true, // 是否注入订阅规则
|
||||||
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
|
||||||
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
|
||||||
|
transApis: DEFAULT_TRANS_APIS, // 翻译接口
|
||||||
googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
|
googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
|
||||||
deeplUrl: "https://api-free.deepl.com/v2/translate",
|
deeplUrl: "https://api-free.deepl.com/v2/translate",
|
||||||
deeplKey: "",
|
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,
|
q,
|
||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
setting,
|
setting: setting[translator],
|
||||||
});
|
});
|
||||||
setText(trText);
|
setText(trText);
|
||||||
setSamelang(isSame);
|
setSamelang(isSame);
|
||||||
|
|||||||
@@ -67,13 +67,15 @@ const newCacheReq = async (request) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
const fetchApi = async ({ input, init = {}, translator, token }) => {
|
||||||
if (translator === OPT_TRANS_MICROSOFT) {
|
if (token) {
|
||||||
init.headers["Authorization"] = `Bearer ${token}`; // Microsoft
|
if (translator === OPT_TRANS_DEEPL) {
|
||||||
} else if (translator === OPT_TRANS_DEEPL) {
|
|
||||||
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
|
init.headers["Authorization"] = `DeepL-Auth-Key ${token}`; // DeepL
|
||||||
} else if (translator === OPT_TRANS_OPENAI) {
|
} else if (translator === OPT_TRANS_OPENAI) {
|
||||||
init.headers["Authorization"] = `Bearer ${token}`; // OpenAI
|
init.headers["Authorization"] = `Bearer ${token}`; // OpenAI
|
||||||
init.headers["api-key"] = token; // Azure OpenAI
|
init.headers["api-key"] = token; // Azure OpenAI
|
||||||
|
} else {
|
||||||
|
init.headers["Authorization"] = `Bearer ${token}`; // Microsoft & others
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGm) {
|
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 DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import SyncIcon from "@mui/icons-material/Sync";
|
import SyncIcon from "@mui/icons-material/Sync";
|
||||||
|
import ApiIcon from "@mui/icons-material/Api";
|
||||||
|
|
||||||
function LinkItem({ label, url, icon }) {
|
function LinkItem({ label, url, icon }) {
|
||||||
const match = useMatch(url);
|
const match = useMatch(url);
|
||||||
@@ -36,6 +37,12 @@ export default function Navigator(props) {
|
|||||||
url: "/rules",
|
url: "/rules",
|
||||||
icon: <DesignServicesIcon />,
|
icon: <DesignServicesIcon />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "apis_setting",
|
||||||
|
label: i18n("apis_setting"),
|
||||||
|
url: "/apis",
|
||||||
|
icon: <ApiIcon />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "sync",
|
id: "sync",
|
||||||
label: i18n("sync_setting"),
|
label: i18n("sync_setting"),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Divider from "@mui/material/Divider";
|
|||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import { adaptScript } from "../../libs/gm";
|
import { adaptScript } from "../../libs/gm";
|
||||||
import Alert from "@mui/material/Alert";
|
import Alert from "@mui/material/Alert";
|
||||||
|
import Apis from "./Apis";
|
||||||
|
|
||||||
export default function Options() {
|
export default function Options() {
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -124,6 +125,7 @@ export default function Options() {
|
|||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
<Route index element={<Setting />} />
|
<Route index element={<Setting />} />
|
||||||
<Route path="rules" element={<Rules />} />
|
<Route path="rules" element={<Rules />} />
|
||||||
|
<Route path="apis" element={<Apis />} />
|
||||||
<Route path="sync" element={<SyncSetting />} />
|
<Route path="sync" element={<SyncSetting />} />
|
||||||
<Route path="about" element={<About />} />
|
<Route path="about" element={<About />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
Reference in New Issue
Block a user