feat: Extensive refactoring and modification to support any number of interfaces
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { limitNumber } from "../../libs/utils";
|
||||
import { isMobile } from "../../libs/mobile";
|
||||
import { updateFab } from "../../libs/storage";
|
||||
import { putFab } from "../../libs/storage";
|
||||
import { debounce } from "../../libs/utils";
|
||||
import Paper from "@mui/material/Paper";
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function Draggable({
|
||||
const [hover, setHover] = useState(false);
|
||||
const [origin, setOrigin] = useState(null);
|
||||
const [position, setPosition] = useState({ x: left, y: top });
|
||||
const setFabPosition = useMemo(() => debounce(updateFab, 500), []);
|
||||
const setFabPosition = useMemo(() => debounce(putFab, 500), []);
|
||||
|
||||
const handlePointerDown = (e) => {
|
||||
!isMobile && e.target.setPointerCapture(e.pointerId);
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function Action({ translator, fab }) {
|
||||
});
|
||||
};
|
||||
} catch (err) {
|
||||
kissLog(err, "registerMenuCommand");
|
||||
kissLog("registerMenuCommand", err);
|
||||
}
|
||||
}, [translator]);
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { loadingSvg } from "../../libs/svg";
|
||||
|
||||
export default function LoadingIcon() {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "1.2em",
|
||||
height: "1em",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: loadingSvg }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import LoadingIcon from "./LoadingIcon";
|
||||
import {
|
||||
OPT_STYLE_LINE,
|
||||
OPT_STYLE_DOTLINE,
|
||||
OPT_STYLE_DASHLINE,
|
||||
OPT_STYLE_WAVYLINE,
|
||||
OPT_STYLE_DASHBOX,
|
||||
OPT_STYLE_FUZZY,
|
||||
OPT_STYLE_HIGHLIGHT,
|
||||
OPT_STYLE_BLOCKQUOTE,
|
||||
OPT_STYLE_DIY,
|
||||
DEFAULT_COLOR,
|
||||
MSG_TRANS_CURRULE,
|
||||
} from "../../config";
|
||||
import { useTranslate } from "../../hooks/Translate";
|
||||
import { styled, css } from "@mui/material/styles";
|
||||
import { APP_LCNAME } from "../../config";
|
||||
import interpreter from "../../libs/interpreter";
|
||||
|
||||
const LINE_STYLES = {
|
||||
[OPT_STYLE_LINE]: "solid",
|
||||
[OPT_STYLE_DOTLINE]: "dotted",
|
||||
[OPT_STYLE_DASHLINE]: "dashed",
|
||||
[OPT_STYLE_WAVYLINE]: "wavy",
|
||||
};
|
||||
|
||||
const StyledSpan = styled("span")`
|
||||
${({ textStyle, textDiyStyle, bgColor }) => {
|
||||
switch (textStyle) {
|
||||
case OPT_STYLE_LINE: // 下划线
|
||||
case OPT_STYLE_DOTLINE: // 点状线
|
||||
case OPT_STYLE_DASHLINE: // 虚线
|
||||
case OPT_STYLE_WAVYLINE: // 波浪线
|
||||
return css`
|
||||
opacity: 0.6;
|
||||
-webkit-opacity: 0.6;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: ${LINE_STYLES[textStyle]};
|
||||
text-decoration-color: ${bgColor};
|
||||
text-decoration-thickness: 2px;
|
||||
text-underline-offset: 0.3em;
|
||||
-webkit-text-decoration-line: underline;
|
||||
-webkit-text-decoration-style: ${LINE_STYLES[textStyle]};
|
||||
-webkit-text-decoration-color: ${bgColor};
|
||||
-webkit-text-decoration-thickness: 2px;
|
||||
-webkit-text-underline-offset: 0.3em;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
-webkit-opacity: 1;
|
||||
}
|
||||
`;
|
||||
case OPT_STYLE_DASHBOX: // 虚线框
|
||||
return css`
|
||||
color: ${bgColor || DEFAULT_COLOR};
|
||||
border: 1px dashed ${bgColor || DEFAULT_COLOR};
|
||||
background: transparent;
|
||||
display: block;
|
||||
padding: 0.2em;
|
||||
box-sizing: border-box;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
`;
|
||||
case OPT_STYLE_FUZZY: // 模糊
|
||||
return css`
|
||||
filter: blur(0.2em);
|
||||
-webkit-filter: blur(0.2em);
|
||||
&:hover {
|
||||
filter: none;
|
||||
-webkit-filter: none;
|
||||
}
|
||||
`;
|
||||
case OPT_STYLE_HIGHLIGHT: // 高亮
|
||||
return css`
|
||||
color: #fff;
|
||||
background-color: ${bgColor || DEFAULT_COLOR};
|
||||
`;
|
||||
case OPT_STYLE_BLOCKQUOTE: // 引用
|
||||
return css`
|
||||
opacity: 0.6;
|
||||
-webkit-opacity: 0.6;
|
||||
display: block;
|
||||
padding: 0 0.75em;
|
||||
border-left: 0.25em solid ${bgColor || DEFAULT_COLOR};
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
-webkit-opacity: 1;
|
||||
}
|
||||
`;
|
||||
case OPT_STYLE_DIY: // 自定义
|
||||
return textDiyStyle;
|
||||
default:
|
||||
return ``;
|
||||
}
|
||||
}}
|
||||
`;
|
||||
|
||||
export default function Content({ q, keeps, translator, $el }) {
|
||||
const [rule, setRule] = useState(translator.rule);
|
||||
const { text, sameLang, loading } = useTranslate(
|
||||
q,
|
||||
rule,
|
||||
translator.setting,
|
||||
translator.docInfo
|
||||
);
|
||||
const {
|
||||
transOpen,
|
||||
textStyle,
|
||||
bgColor,
|
||||
textDiyStyle,
|
||||
transOnly,
|
||||
transTag,
|
||||
transEndHook,
|
||||
} = rule;
|
||||
|
||||
const { newlineLength } = translator.setting;
|
||||
|
||||
const handleKissEvent = (e) => {
|
||||
const { action, args } = e.detail;
|
||||
switch (action) {
|
||||
case MSG_TRANS_CURRULE:
|
||||
setRule(args);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener(translator.eventName, handleKissEvent);
|
||||
return () => {
|
||||
window.removeEventListener(translator.eventName, handleKissEvent);
|
||||
};
|
||||
}, [translator.eventName]);
|
||||
|
||||
const gap = useMemo(() => {
|
||||
if (transOnly === "true") {
|
||||
return "";
|
||||
}
|
||||
return q.length >= newlineLength ? <br /> : " ";
|
||||
}, [q, transOnly, newlineLength]);
|
||||
|
||||
const styles = useMemo(
|
||||
() => ({
|
||||
textStyle,
|
||||
textDiyStyle,
|
||||
bgColor,
|
||||
as: transTag,
|
||||
}),
|
||||
[textStyle, textDiyStyle, bgColor, transTag]
|
||||
);
|
||||
|
||||
const trText = useMemo(() => {
|
||||
if (loading || !transEndHook?.trim()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// 翻译完成钩子函数
|
||||
interpreter.run(`exports.transEndHook = ${transEndHook}`);
|
||||
return interpreter.exports.transEndHook($el, text, q, keeps);
|
||||
}, [loading, $el, q, text, keeps, transEndHook]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<>
|
||||
{gap}
|
||||
<LoadingIcon />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!trText || sameLang) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
transOnly === "true" &&
|
||||
transOpen === "true" &&
|
||||
$el.querySelector(APP_LCNAME)
|
||||
) {
|
||||
Array.from($el.childNodes).forEach((el) => {
|
||||
if (el.localName !== APP_LCNAME) {
|
||||
el.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (keeps.length > 0) {
|
||||
return (
|
||||
<>
|
||||
{gap}
|
||||
<StyledSpan
|
||||
{...styles}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: trText.replace(/\[(\d+)\]/g, (_, p) => keeps[parseInt(p)]),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{gap}
|
||||
<StyledSpan {...styles}>{trText}</StyledSpan>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Button from "@mui/material/Button";
|
||||
@@ -5,58 +6,44 @@ import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import {
|
||||
OPT_TRANS_ALL,
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_DEEPL,
|
||||
OPT_TRANS_DEEPLX,
|
||||
OPT_TRANS_DEEPLFREE,
|
||||
OPT_TRANS_BAIDU,
|
||||
OPT_TRANS_TENCENT,
|
||||
OPT_TRANS_VOLCENGINE,
|
||||
OPT_TRANS_OPENAI,
|
||||
OPT_TRANS_OPENAI_2,
|
||||
OPT_TRANS_OPENAI_3,
|
||||
OPT_TRANS_GEMINI,
|
||||
OPT_TRANS_GEMINI_2,
|
||||
OPT_TRANS_CLAUDE,
|
||||
OPT_TRANS_CLOUDFLAREAI,
|
||||
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,
|
||||
OPT_TRANS_CUSTOMIZE_4,
|
||||
OPT_TRANS_CUSTOMIZE_5,
|
||||
OPT_TRANS_NIUTRANS,
|
||||
DEFAULT_FETCH_LIMIT,
|
||||
DEFAULT_FETCH_INTERVAL,
|
||||
DEFAULT_HTTP_TIMEOUT,
|
||||
OPT_TRANS_BATCH,
|
||||
OPT_TRANS_CONTEXT,
|
||||
DEFAULT_BATCH_INTERVAL,
|
||||
DEFAULT_BATCH_SIZE,
|
||||
DEFAULT_BATCH_LENGTH,
|
||||
DEFAULT_CONTEXT_SIZE,
|
||||
} 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 AddIcon from "@mui/icons-material/Add";
|
||||
import Alert from "@mui/material/Alert";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import { useAlert } from "../../hooks/Alert";
|
||||
import { useApi } from "../../hooks/Api";
|
||||
import { useApiList, useApiItem } from "../../hooks/Api";
|
||||
import { useConfirm } from "../../hooks/Confirm";
|
||||
import { apiTranslate } from "../../apis";
|
||||
import Box from "@mui/material/Box";
|
||||
import Link from "@mui/material/Link";
|
||||
import { limitNumber, limitFloat } from "../../libs/utils";
|
||||
import ReusableAutocomplete from "./ReusableAutocomplete";
|
||||
import {
|
||||
OPT_TRANS_DEEPLX,
|
||||
OPT_TRANS_OLLAMA,
|
||||
OPT_TRANS_CUSTOMIZE,
|
||||
OPT_TRANS_NIUTRANS,
|
||||
DEFAULT_FETCH_LIMIT,
|
||||
DEFAULT_FETCH_INTERVAL,
|
||||
DEFAULT_HTTP_TIMEOUT,
|
||||
DEFAULT_BATCH_INTERVAL,
|
||||
DEFAULT_BATCH_SIZE,
|
||||
DEFAULT_BATCH_LENGTH,
|
||||
DEFAULT_CONTEXT_SIZE,
|
||||
OPT_ALL_TYPES,
|
||||
API_SPE_TYPES,
|
||||
BUILTIN_STONES,
|
||||
// BUILTIN_PLACEHOULDERS,
|
||||
// BUILTIN_TAG_NAMES,
|
||||
} from "../../config";
|
||||
|
||||
function TestButton({ translator, api }) {
|
||||
function TestButton({ apiSlug, api }) {
|
||||
const i18n = useI18n();
|
||||
const alert = useAlert();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -64,7 +51,7 @@ function TestButton({ translator, api }) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [text] = await apiTranslate({
|
||||
translator,
|
||||
apiSlug,
|
||||
text: "hello world",
|
||||
fromLang: "en",
|
||||
toLang: "zh-CN",
|
||||
@@ -114,7 +101,7 @@ function TestButton({ translator, api }) {
|
||||
return (
|
||||
<LoadingButton
|
||||
size="small"
|
||||
variant="contained"
|
||||
variant="outlined"
|
||||
onClick={handleApiTest}
|
||||
loading={loading}
|
||||
>
|
||||
@@ -123,39 +110,34 @@ function TestButton({ translator, api }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
function ApiFields({ apiSlug, isUserApi, deleteApi }) {
|
||||
const { api, update, reset } = useApiItem(apiSlug);
|
||||
const i18n = useI18n();
|
||||
const {
|
||||
url = "",
|
||||
key = "",
|
||||
model = "",
|
||||
systemPrompt = "",
|
||||
userPrompt = "",
|
||||
customHeader = "",
|
||||
customBody = "",
|
||||
think = false,
|
||||
thinkIgnore = "",
|
||||
fetchLimit = DEFAULT_FETCH_LIMIT,
|
||||
fetchInterval = DEFAULT_FETCH_INTERVAL,
|
||||
httpTimeout = DEFAULT_HTTP_TIMEOUT,
|
||||
dictNo = "",
|
||||
memoryNo = "",
|
||||
reqHook = "",
|
||||
resHook = "",
|
||||
temperature = 0,
|
||||
maxTokens = 256,
|
||||
apiName = "",
|
||||
isDisabled = false,
|
||||
useBatchFetch = false,
|
||||
batchInterval = DEFAULT_BATCH_INTERVAL,
|
||||
batchSize = DEFAULT_BATCH_SIZE,
|
||||
batchLength = DEFAULT_BATCH_LENGTH,
|
||||
useContext = false,
|
||||
contextSize = DEFAULT_CONTEXT_SIZE,
|
||||
} = api;
|
||||
const [formData, setFormData] = useState({});
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const confirm = useConfirm();
|
||||
|
||||
useEffect(() => {
|
||||
if (api) {
|
||||
setFormData(api);
|
||||
}
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!api) return;
|
||||
const hasChanged = JSON.stringify(api) !== JSON.stringify(formData);
|
||||
setIsModified(hasChanged);
|
||||
}, [api, formData]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
let { name, value } = e.target;
|
||||
let { name, value, type, checked } = e.target;
|
||||
|
||||
if (type === "checkbox" || type === "switch") {
|
||||
value = checked;
|
||||
}
|
||||
// if (value === "true") value = true;
|
||||
// if (value === "false") value = false;
|
||||
|
||||
switch (name) {
|
||||
case "fetchLimit":
|
||||
value = limitNumber(value, 1, 100);
|
||||
@@ -186,56 +168,78 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
break;
|
||||
default:
|
||||
}
|
||||
updateApi({
|
||||
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
const builtinTranslators = [
|
||||
OPT_TRANS_MICROSOFT,
|
||||
OPT_TRANS_DEEPLFREE,
|
||||
OPT_TRANS_BAIDU,
|
||||
OPT_TRANS_TENCENT,
|
||||
OPT_TRANS_VOLCENGINE,
|
||||
];
|
||||
const handleSave = () => {
|
||||
// 过滤掉 api 对象中不存在的字段
|
||||
// const updatedFields = Object.keys(formData).reduce((acc, key) => {
|
||||
// if (api && Object.keys(api).includes(key)) {
|
||||
// acc[key] = formData[key];
|
||||
// }
|
||||
// return acc;
|
||||
// }, {});
|
||||
// update(updatedFields);
|
||||
update(formData);
|
||||
};
|
||||
|
||||
const mulkeysTranslators = [
|
||||
OPT_TRANS_DEEPL,
|
||||
OPT_TRANS_OPENAI,
|
||||
OPT_TRANS_OPENAI_2,
|
||||
OPT_TRANS_OPENAI_3,
|
||||
OPT_TRANS_GEMINI,
|
||||
OPT_TRANS_GEMINI_2,
|
||||
OPT_TRANS_CLAUDE,
|
||||
OPT_TRANS_CLOUDFLAREAI,
|
||||
OPT_TRANS_OLLAMA,
|
||||
OPT_TRANS_OLLAMA_2,
|
||||
OPT_TRANS_OLLAMA_3,
|
||||
OPT_TRANS_OPENROUTER,
|
||||
OPT_TRANS_NIUTRANS,
|
||||
OPT_TRANS_CUSTOMIZE,
|
||||
OPT_TRANS_CUSTOMIZE_2,
|
||||
OPT_TRANS_CUSTOMIZE_3,
|
||||
OPT_TRANS_CUSTOMIZE_4,
|
||||
OPT_TRANS_CUSTOMIZE_5,
|
||||
];
|
||||
const handleReset = () => {
|
||||
reset();
|
||||
};
|
||||
|
||||
const keyHelper =
|
||||
translator === OPT_TRANS_NIUTRANS ? (
|
||||
<>
|
||||
{i18n("mulkeys_help")}
|
||||
<Link
|
||||
href="https://niutrans.com/login?active=3&userSource=kiss-translator"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n("reg_niutrans")}
|
||||
</Link>
|
||||
</>
|
||||
) : mulkeysTranslators.includes(translator) ? (
|
||||
i18n("mulkeys_help")
|
||||
) : (
|
||||
""
|
||||
);
|
||||
const handleDelete = async () => {
|
||||
const isConfirmed = await confirm({
|
||||
confirmText: i18n("delete"),
|
||||
cancelText: i18n("cancel"),
|
||||
});
|
||||
|
||||
if (isConfirmed) {
|
||||
deleteApi(apiSlug);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
url = "",
|
||||
key = "",
|
||||
model = "",
|
||||
apiType,
|
||||
systemPrompt = "",
|
||||
// userPrompt = "",
|
||||
customHeader = "",
|
||||
customBody = "",
|
||||
think = false,
|
||||
thinkIgnore = "",
|
||||
fetchLimit = DEFAULT_FETCH_LIMIT,
|
||||
fetchInterval = DEFAULT_FETCH_INTERVAL,
|
||||
httpTimeout = DEFAULT_HTTP_TIMEOUT,
|
||||
dictNo = "",
|
||||
memoryNo = "",
|
||||
reqHook = "",
|
||||
resHook = "",
|
||||
temperature = 0,
|
||||
maxTokens = 256,
|
||||
apiName = "",
|
||||
isDisabled = false,
|
||||
useBatchFetch = false,
|
||||
batchInterval = DEFAULT_BATCH_INTERVAL,
|
||||
batchSize = DEFAULT_BATCH_SIZE,
|
||||
batchLength = DEFAULT_BATCH_LENGTH,
|
||||
useContext = false,
|
||||
contextSize = DEFAULT_CONTEXT_SIZE,
|
||||
tone = "neutral",
|
||||
// placeholder = "{ }",
|
||||
// tagName = "i",
|
||||
// aiTerms = false,
|
||||
} = formData;
|
||||
|
||||
const keyHelper = useMemo(
|
||||
() => (API_SPE_TYPES.mulkeys.has(apiType) ? i18n("mulkeys_help") : ""),
|
||||
[apiType, i18n]
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
@@ -247,7 +251,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
{!builtinTranslators.includes(translator) && (
|
||||
{!API_SPE_TYPES.machine.has(apiType) && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
@@ -255,10 +259,10 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
name="url"
|
||||
value={url}
|
||||
onChange={handleChange}
|
||||
multiline={translator === OPT_TRANS_DEEPLX}
|
||||
multiline={apiType === OPT_TRANS_DEEPLX}
|
||||
maxRows={10}
|
||||
helperText={
|
||||
translator === OPT_TRANS_DEEPLX ? i18n("mulkeys_help") : ""
|
||||
apiType === OPT_TRANS_DEEPLX ? i18n("mulkeys_help") : ""
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
@@ -267,26 +271,66 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
name="key"
|
||||
value={key}
|
||||
onChange={handleChange}
|
||||
multiline={mulkeysTranslators.includes(translator)}
|
||||
multiline={API_SPE_TYPES.mulkeys.has(apiType)}
|
||||
maxRows={10}
|
||||
helperText={keyHelper}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{(translator.startsWith(OPT_TRANS_OPENAI) ||
|
||||
translator.startsWith(OPT_TRANS_OLLAMA) ||
|
||||
translator === OPT_TRANS_CLAUDE ||
|
||||
translator === OPT_TRANS_OPENROUTER ||
|
||||
translator.startsWith(OPT_TRANS_GEMINI)) && (
|
||||
{API_SPE_TYPES.ai.has(apiType) && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"MODEL"}
|
||||
name="model"
|
||||
value={model}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
{/* todo: 改成 ReusableAutocomplete 可选择和填写模型 */}
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={"MODEL"}
|
||||
name="model"
|
||||
value={model}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<ReusableAutocomplete
|
||||
freeSolo
|
||||
size="small"
|
||||
fullWidth
|
||||
options={BUILTIN_STONES}
|
||||
name="tone"
|
||||
label={i18n("translation_style")}
|
||||
value={tone}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={"Temperature"}
|
||||
type="number"
|
||||
name="temperature"
|
||||
value={temperature}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={"Max Tokens"}
|
||||
type="number"
|
||||
name="maxTokens"
|
||||
value={maxTokens}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}></Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={"SYSTEM PROMPT"}
|
||||
@@ -295,8 +339,9 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
maxRows={10}
|
||||
helperText={i18n("system_prompt_helper")}
|
||||
/>
|
||||
<TextField
|
||||
{/* <TextField
|
||||
size="small"
|
||||
label={"USER PROMPT"}
|
||||
name="userPrompt"
|
||||
@@ -304,7 +349,51 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
maxRows={10}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* <Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<ReusableAutocomplete
|
||||
freeSolo
|
||||
size="small"
|
||||
fullWidth
|
||||
options={BUILTIN_PLACEHOULDERS}
|
||||
name="placeholder"
|
||||
label={i18n("placeholder")}
|
||||
value={placeholder}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<ReusableAutocomplete
|
||||
freeSolo
|
||||
size="small"
|
||||
fullWidth
|
||||
options={BUILTIN_TAG_NAMES}
|
||||
name="tagName"
|
||||
label={i18n("tag_name")}
|
||||
value={tagName}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="aiTerms"
|
||||
value={aiTerms}
|
||||
label={i18n("ai_terms")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box> */}
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("custom_header")}
|
||||
@@ -328,7 +417,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{translator.startsWith(OPT_TRANS_OLLAMA) && (
|
||||
{apiType === OPT_TRANS_OLLAMA && (
|
||||
<>
|
||||
<TextField
|
||||
select
|
||||
@@ -351,32 +440,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{(translator.startsWith(OPT_TRANS_OPENAI) ||
|
||||
translator === OPT_TRANS_CLAUDE ||
|
||||
translator === OPT_TRANS_OPENROUTER ||
|
||||
translator === OPT_TRANS_GEMINI ||
|
||||
translator === OPT_TRANS_GEMINI_2) && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"Temperature"}
|
||||
type="number"
|
||||
name="temperature"
|
||||
value={temperature}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
size="small"
|
||||
label={"Max Tokens"}
|
||||
type="number"
|
||||
name="maxTokens"
|
||||
value={maxTokens}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{translator === OPT_TRANS_NIUTRANS && (
|
||||
{apiType === OPT_TRANS_NIUTRANS && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
@@ -395,7 +459,7 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{translator.startsWith(OPT_TRANS_CUSTOMIZE) && (
|
||||
{apiType === OPT_TRANS_CUSTOMIZE && (
|
||||
<>
|
||||
<TextField
|
||||
size="small"
|
||||
@@ -418,140 +482,180 @@ function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
</>
|
||||
)}
|
||||
|
||||
{OPT_TRANS_BATCH.has(translator) && (
|
||||
<>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
name="useBatchFetch"
|
||||
value={useBatchFetch}
|
||||
label={i18n("use_batch_fetch")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||
</TextField>
|
||||
{useBatchFetch && (
|
||||
<>
|
||||
{API_SPE_TYPES.batch.has(api.apiType) && (
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
size="small"
|
||||
name="useBatchFetch"
|
||||
value={useBatchFetch}
|
||||
label={i18n("use_batch_fetch")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("batch_interval")}
|
||||
type="number"
|
||||
name="batchInterval"
|
||||
value={batchInterval}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("batch_size")}
|
||||
type="number"
|
||||
name="batchSize"
|
||||
value={batchSize}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("batch_length")}
|
||||
type="number"
|
||||
name="batchLength"
|
||||
value={batchLength}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{API_SPE_TYPES.context.has(api.apiType) && (
|
||||
<>
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
{" "}
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="useContext"
|
||||
value={useContext}
|
||||
label={i18n("use_context")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
{" "}
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("context_size")}
|
||||
type="number"
|
||||
name="contextSize"
|
||||
value={contextSize}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{OPT_TRANS_CONTEXT.has(translator) && (
|
||||
<>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
name="useContext"
|
||||
value={useContext}
|
||||
label={i18n("use_context")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={false}>{i18n("disable")}</MenuItem>
|
||||
<MenuItem value={true}>{i18n("enable")}</MenuItem>
|
||||
</TextField>
|
||||
{useBatchFetch && (
|
||||
<Box>
|
||||
<Grid container spacing={2} columns={12}>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("context_size")}
|
||||
fullWidth
|
||||
label={i18n("fetch_limit")}
|
||||
type="number"
|
||||
name="contextSize"
|
||||
value={contextSize}
|
||||
name="fetchLimit"
|
||||
value={fetchLimit}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("fetch_limit")}
|
||||
type="number"
|
||||
name="fetchLimit"
|
||||
value={fetchLimit}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("fetch_interval")}
|
||||
type="number"
|
||||
name="fetchInterval"
|
||||
value={fetchInterval}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
label={i18n("http_timeout")}
|
||||
type="number"
|
||||
name="httpTimeout"
|
||||
defaultValue={httpTimeout}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
name="isDisabled"
|
||||
checked={isDisabled}
|
||||
onChange={() => {
|
||||
updateApi({ isDisabled: !isDisabled });
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={i18n("is_disabled")}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("fetch_interval")}
|
||||
type="number"
|
||||
name="fetchInterval"
|
||||
value={fetchInterval}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
label={i18n("http_timeout")}
|
||||
type="number"
|
||||
name="httpTimeout"
|
||||
value={httpTimeout}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={6} lg={3}></Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TestButton translator={translator} api={api} />
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
resetApi();
|
||||
}}
|
||||
variant="contained"
|
||||
onClick={handleSave}
|
||||
disabled={!isModified}
|
||||
>
|
||||
{i18n("save")}
|
||||
</Button>
|
||||
<TestButton apiSlug={apiSlug} api={api} />
|
||||
<Button size="small" variant="outlined" onClick={handleReset}>
|
||||
{i18n("restore_default")}
|
||||
</Button>
|
||||
{isUserApi && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{i18n("delete")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
fullWidth
|
||||
name="isDisabled"
|
||||
checked={isDisabled}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
}
|
||||
label={i18n("is_disabled")}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{translator.startsWith(OPT_TRANS_CUSTOMIZE) && (
|
||||
<pre>{i18n("custom_api_help")}</pre>
|
||||
)}
|
||||
{apiType === OPT_TRANS_CUSTOMIZE && <pre>{i18n("custom_api_help")}</pre>}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function ApiAccordion({ translator }) {
|
||||
function ApiAccordion({ api, isUserApi, deleteApi }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { api, updateApi, resetApi } = useApi(translator);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setExpanded((pre) => !pre);
|
||||
@@ -566,16 +670,15 @@ function ApiAccordion({ translator }) {
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
>
|
||||
{api.apiName ? `${translator} (${api.apiName})` : translator}
|
||||
{`[${api.apiType}] ${api.apiName}`}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{expanded && (
|
||||
<ApiFields
|
||||
translator={translator}
|
||||
api={api}
|
||||
updateApi={updateApi}
|
||||
resetApi={resetApi}
|
||||
apiSlug={api.apiSlug}
|
||||
isUserApi={isUserApi}
|
||||
deleteApi={deleteApi}
|
||||
/>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
@@ -585,14 +688,85 @@ function ApiAccordion({ translator }) {
|
||||
|
||||
export default function Apis() {
|
||||
const i18n = useI18n();
|
||||
const { userApis, builtinApis, addApi, deleteApi } = useApiList();
|
||||
|
||||
const apiTypes = useMemo(
|
||||
() =>
|
||||
OPT_ALL_TYPES.map((type) => ({
|
||||
type,
|
||||
label: type,
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (apiType) => {
|
||||
addApi(apiType);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack spacing={3}>
|
||||
<Alert severity="info">{i18n("about_api")}</Alert>
|
||||
|
||||
<Box>
|
||||
{OPT_TRANS_ALL.map((translator) => (
|
||||
<ApiAccordion key={translator} translator={translator} />
|
||||
<Button
|
||||
size="small"
|
||||
id="add-api-button"
|
||||
variant="contained"
|
||||
onClick={handleClick}
|
||||
aria-controls={open ? "add-api-menu" : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
endIcon={<KeyboardArrowDownIcon />}
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
{i18n("add")}
|
||||
</Button>
|
||||
<Menu
|
||||
id="add-api-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
"aria-labelledby": "add-api-button",
|
||||
}}
|
||||
>
|
||||
{apiTypes.map((apiOption) => (
|
||||
<MenuItem
|
||||
key={apiOption.type}
|
||||
onClick={() => handleMenuItemClick(apiOption.type)}
|
||||
>
|
||||
{apiOption.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
{userApis.map((api) => (
|
||||
<ApiAccordion
|
||||
key={api.apiSlug}
|
||||
api={api}
|
||||
isUserApi={true}
|
||||
deleteApi={deleteApi}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box>
|
||||
{builtinApis.map((api) => (
|
||||
<ApiAccordion key={api.apiSlug} api={api} />
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function DownloadButton({ handleData, text, fileName }) {
|
||||
link.click();
|
||||
link.remove();
|
||||
} catch (err) {
|
||||
kissLog(err, "download");
|
||||
kissLog("download", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 CircularProgress from "@mui/material/CircularProgress";
|
||||
// import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useFavWords } from "../../hooks/FavWords";
|
||||
@@ -49,29 +49,25 @@ function FavAccordion({ word, index }) {
|
||||
|
||||
export default function FavWords() {
|
||||
const i18n = useI18n();
|
||||
const { loading, favWords, mergeWords, clearWords } = useFavWords();
|
||||
const favList = Object.entries(favWords).sort((a, b) =>
|
||||
a[0].localeCompare(b[0])
|
||||
);
|
||||
const downloadList = favList.map(([word]) => word);
|
||||
const { favList, wordList, mergeWords, clearWords } = useFavWords();
|
||||
|
||||
const handleImport = async (data) => {
|
||||
const handleImport = (data) => {
|
||||
try {
|
||||
const newWords = data
|
||||
.split("\n")
|
||||
.map((line) => line.split(",")[0].trim())
|
||||
.filter(isValidWord);
|
||||
await mergeWords(newWords);
|
||||
mergeWords(newWords);
|
||||
} catch (err) {
|
||||
kissLog(err, "import rules");
|
||||
kissLog("import rules", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTranslation = async () => {
|
||||
const tranList = [];
|
||||
for (const text of downloadList) {
|
||||
for (const text of wordList) {
|
||||
try {
|
||||
// todo
|
||||
// todo: 修复
|
||||
const dictRes = await apiTranslate({
|
||||
text,
|
||||
translator: OPT_TRANS_BAIDU,
|
||||
@@ -122,7 +118,7 @@ export default function FavWords() {
|
||||
fileExts={[".txt", ".csv"]}
|
||||
/>
|
||||
<DownloadButton
|
||||
handleData={() => downloadList.join("\n")}
|
||||
handleData={() => wordList.join("\n")}
|
||||
text={i18n("export")}
|
||||
fileName={`kiss-words_${Date.now()}.txt`}
|
||||
/>
|
||||
@@ -144,18 +140,14 @@ export default function FavWords() {
|
||||
</Stack>
|
||||
|
||||
<Box>
|
||||
{loading ? (
|
||||
<CircularProgress size={24} />
|
||||
) : (
|
||||
favList.map(([word, { createdAt }], index) => (
|
||||
<FavAccordion
|
||||
key={word}
|
||||
index={index}
|
||||
word={word}
|
||||
createdAt={createdAt}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
{favList.map(([word, { createdAt }], index) => (
|
||||
<FavAccordion
|
||||
key={word}
|
||||
index={index}
|
||||
word={word}
|
||||
createdAt={createdAt}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -4,7 +4,6 @@ import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import {
|
||||
OPT_TRANS_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_INPUT_TRANS_SIGNS,
|
||||
@@ -16,10 +15,12 @@ import { useInputRule } from "../../hooks/InputRule";
|
||||
import { useCallback } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { limitNumber } from "../../libs/utils";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
|
||||
export default function InputSetting() {
|
||||
const i18n = useI18n();
|
||||
const { inputRule, updateInputRule } = useInputRule();
|
||||
const { enabledApis } = useApiList();
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -44,7 +45,7 @@ export default function InputSetting() {
|
||||
|
||||
const {
|
||||
transOpen,
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
triggerShortcut,
|
||||
@@ -73,14 +74,14 @@ export default function InputSetting() {
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
name="translator"
|
||||
value={translator}
|
||||
name="apiSlug"
|
||||
value={apiSlug}
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_TRANS_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{item}
|
||||
{enabledApis.map((api) => (
|
||||
<MenuItem key={api.apiSlug} value={api.apiSlug}>
|
||||
{api.apiName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
@@ -166,7 +167,7 @@ export default function InputSetting() {
|
||||
label={i18n("combo_timeout")}
|
||||
type="number"
|
||||
name="triggerTime"
|
||||
defaultValue={triggerTime}
|
||||
value={triggerTime}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Switch from "@mui/material/Switch";
|
||||
import { useMouseHoverSetting } from "../../hooks/MouseHover";
|
||||
import { useCallback } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { DEFAULT_MOUSEHOVER_KEY } from "../../config";
|
||||
|
||||
export default function MouseHoverSetting() {
|
||||
const i18n = useI18n();
|
||||
@@ -19,7 +20,7 @@ export default function MouseHoverSetting() {
|
||||
[updateMouseHoverSetting]
|
||||
);
|
||||
|
||||
const { useMouseHover = true, mouseHoverKey = ["ControlLeft"] } =
|
||||
const { useMouseHover = true, mouseHoverKey = DEFAULT_MOUSEHOVER_KEY } =
|
||||
mouseHoverSetting;
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
REMAIN_KEY,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_TRANS_ALL,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_STYLE_DIY,
|
||||
OPT_STYLE_USE_COLOR,
|
||||
@@ -15,10 +14,12 @@ import { useI18n } from "../../hooks/I18n";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { useOwSubRule } from "../../hooks/SubRules";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
|
||||
export default function OwSubRule() {
|
||||
const i18n = useI18n();
|
||||
const { owSubrule, updateOwSubrule } = useOwSubRule();
|
||||
const { enabledApis } = useApiList();
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -27,7 +28,7 @@ export default function OwSubRule() {
|
||||
};
|
||||
|
||||
const {
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
textStyle,
|
||||
@@ -73,16 +74,16 @@ export default function OwSubRule() {
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="translator"
|
||||
value={translator}
|
||||
name="apiSlug"
|
||||
value={apiSlug}
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{RemainItem}
|
||||
{GlobalItem}
|
||||
{OPT_TRANS_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{item}
|
||||
{enabledApis.map((api) => (
|
||||
<MenuItem key={api.apiSlug} value={api.apiSlug}>
|
||||
{api.apiName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
74
src/views/Options/ReusableAutocomplete.js
Normal file
74
src/views/Options/ReusableAutocomplete.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
/**
|
||||
* 一个可复用的 Autocomplete 组件,增加了 name 属性和标准化的 onChange 事件
|
||||
* @param {object} props - 组件的 props
|
||||
* @param {string} props.name - 表单字段的名称,会包含在 onChange 的 event.target 中
|
||||
* @param {string} props.label - TextField 的标签
|
||||
* @param {any} props.value - 受控组件的当前值
|
||||
* @param {function} props.onChange - 值改变时的回调函数 (event) => {}
|
||||
* @param {Array} props.options - Autocomplete 的选项列表
|
||||
*/
|
||||
export default function ReusableAutocomplete({
|
||||
name,
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
...rest
|
||||
}) {
|
||||
const [inputValue, setInputValue] = useState(value || "");
|
||||
const isChangeCommitted = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value || "");
|
||||
}, [value]);
|
||||
|
||||
const triggerOnChange = (newValue) => {
|
||||
if (onChange) {
|
||||
const syntheticEvent = {
|
||||
target: {
|
||||
name: name,
|
||||
value: newValue,
|
||||
},
|
||||
};
|
||||
onChange(syntheticEvent);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
if (isChangeCommitted.current) {
|
||||
isChangeCommitted.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputValue !== value) {
|
||||
triggerOnChange(inputValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
isChangeCommitted.current = true;
|
||||
triggerOnChange(newValue);
|
||||
};
|
||||
|
||||
const handleInputChange = (event, newInputValue) => {
|
||||
isChangeCommitted.current = false;
|
||||
setInputValue(newInputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
inputValue={inputValue}
|
||||
onInputChange={handleInputChange}
|
||||
onBlur={handleBlur}
|
||||
{...rest}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} name={name} label={label} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
GLOBLA_RULE,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_TRANS_ALL,
|
||||
OPT_STYLE_ALL,
|
||||
OPT_STYLE_DIY,
|
||||
OPT_STYLE_USE_COLOR,
|
||||
@@ -58,19 +57,26 @@ import EditIcon from "@mui/icons-material/Edit";
|
||||
import CancelIcon from "@mui/icons-material/Cancel";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import { kissLog } from "../../libs/log";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
|
||||
function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
const initFormValues = {
|
||||
...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE),
|
||||
...(rule || {}),
|
||||
};
|
||||
const editMode = !!rule;
|
||||
const initFormValues = useMemo(
|
||||
() => ({
|
||||
...(rule?.pattern === "*" ? GLOBLA_RULE : DEFAULT_RULE),
|
||||
...(rule || {}),
|
||||
}),
|
||||
[rule]
|
||||
);
|
||||
const editMode = useMemo(() => !!rule, [rule]);
|
||||
|
||||
const i18n = useI18n();
|
||||
const [disabled, setDisabled] = useState(editMode);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [formValues, setFormValues] = useState(initFormValues);
|
||||
const [showMore, setShowMore] = useState(!rules);
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
const { enabledApis } = useApiList();
|
||||
|
||||
const {
|
||||
pattern,
|
||||
selector,
|
||||
@@ -82,7 +88,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
parentStyle = "",
|
||||
injectJs = "",
|
||||
injectCss = "",
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
textStyle,
|
||||
@@ -106,6 +112,13 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
// transRemoveHook = "",
|
||||
} = formValues;
|
||||
|
||||
useEffect(() => {
|
||||
if (!initFormValues) return;
|
||||
const hasChanged =
|
||||
JSON.stringify(initFormValues) !== JSON.stringify(formValues);
|
||||
setIsModified(hasChanged);
|
||||
}, [initFormValues, formValues]);
|
||||
|
||||
const hasSamePattern = (str) => {
|
||||
for (const item of rules.list) {
|
||||
if (item.pattern === str && rule?.pattern !== str) {
|
||||
@@ -417,16 +430,16 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
select
|
||||
size="small"
|
||||
fullWidth
|
||||
name="translator"
|
||||
value={translator}
|
||||
name="apiSlug"
|
||||
value={apiSlug}
|
||||
label={i18n("translate_service")}
|
||||
disabled={disabled}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{GlobalItem}
|
||||
{OPT_TRANS_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{item}
|
||||
{enabledApis.map((api) => (
|
||||
<MenuItem key={api.apiSlug} value={api.apiSlug}>
|
||||
{api.apiName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
@@ -738,6 +751,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
||||
variant="contained"
|
||||
type="submit"
|
||||
startIcon={<SaveIcon />}
|
||||
disabled={!isModified}
|
||||
>
|
||||
{i18n("save")}
|
||||
</Button>
|
||||
@@ -842,7 +856,7 @@ function ShareButton({ rules, injectRules, selectedUrl }) {
|
||||
window.open(url, "_blank");
|
||||
} catch (err) {
|
||||
alert.warning(i18n("error_got_some_wrong"));
|
||||
kissLog(err, "share rules");
|
||||
kissLog("share rules", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -871,7 +885,7 @@ function UserRules({ subRules, rules }) {
|
||||
try {
|
||||
await rules.merge(JSON.parse(data));
|
||||
} catch (err) {
|
||||
kissLog(err, "import rules");
|
||||
kissLog("import rules", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1004,7 +1018,7 @@ function SubRulesItem({
|
||||
await delSubRules(url);
|
||||
await deleteDataCache(url);
|
||||
} catch (err) {
|
||||
kissLog(err, "del subrules");
|
||||
kissLog("del subrules", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1017,7 +1031,7 @@ function SubRulesItem({
|
||||
}
|
||||
await updateDataCache(url);
|
||||
} catch (err) {
|
||||
kissLog(err, "sync sub rules");
|
||||
kissLog("sync sub rules", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -1096,7 +1110,7 @@ function SubRulesEdit({ subList, addSub, updateDataCache }) {
|
||||
setShowInput(false);
|
||||
setInputText("");
|
||||
} catch (err) {
|
||||
kissLog(err, "fetch rules");
|
||||
kissLog("fetch rules", err);
|
||||
setInputError(i18n("error_fetch_url"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -96,7 +96,7 @@ export default function Settings() {
|
||||
caches.delete(CACHE_NAME);
|
||||
alert.success(i18n("clear_success"));
|
||||
} catch (err) {
|
||||
kissLog(err, "clear cache");
|
||||
kissLog("clear cache", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function Settings() {
|
||||
try {
|
||||
await updateSetting(JSON.parse(data));
|
||||
} catch (err) {
|
||||
kissLog(err, "import setting");
|
||||
kissLog("import setting", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,10 +119,10 @@ export default function Settings() {
|
||||
touchTranslate = 2,
|
||||
blacklist = DEFAULT_BLACKLIST.join(",\n"),
|
||||
csplist = DEFAULT_CSPLIST.join(",\n"),
|
||||
transInterval = 200,
|
||||
transInterval = 100,
|
||||
langDetector = OPT_TRANS_MICROSOFT,
|
||||
} = setting;
|
||||
const { isHide = false, fabClickAction = 0 } = fab || {};
|
||||
const { isHide = false, fabClickAction = 0 } = fab || {};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -163,7 +163,7 @@ export default function Settings() {
|
||||
label={i18n("min_translate_length")}
|
||||
type="number"
|
||||
name="minLength"
|
||||
defaultValue={minLength}
|
||||
value={minLength}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function Settings() {
|
||||
label={i18n("max_translate_length")}
|
||||
type="number"
|
||||
name="maxLength"
|
||||
defaultValue={maxLength}
|
||||
value={maxLength}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -181,7 +181,7 @@ export default function Settings() {
|
||||
label={i18n("num_of_newline_characters")}
|
||||
type="number"
|
||||
name="newlineLength"
|
||||
defaultValue={newlineLength}
|
||||
value={newlineLength}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function Settings() {
|
||||
label={i18n("translate_interval")}
|
||||
type="number"
|
||||
name="transInterval"
|
||||
defaultValue={transInterval}
|
||||
value={transInterval}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
@@ -198,7 +198,7 @@ export default function Settings() {
|
||||
label={i18n("http_timeout")}
|
||||
type="number"
|
||||
name="httpTimeout"
|
||||
defaultValue={httpTimeout}
|
||||
value={httpTimeout}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<FormControl size="small">
|
||||
@@ -236,9 +236,9 @@ export default function Settings() {
|
||||
<InputLabel>{i18n("fab_click_action")}</InputLabel>
|
||||
<Select
|
||||
name="fabClickAction"
|
||||
value={fabClickAction}
|
||||
value={fabClickAction}
|
||||
label={i18n("fab_click_action")}
|
||||
onChange= {(e) => updateFab({ fabClickAction: e.target.value })}
|
||||
onChange={(e) => updateFab({ fabClickAction: e.target.value })}
|
||||
>
|
||||
<MenuItem value={0}>{i18n("fab_click_menu")}</MenuItem>
|
||||
<MenuItem value={1}>{i18n("fab_click_translate")}</MenuItem>
|
||||
@@ -302,7 +302,7 @@ export default function Settings() {
|
||||
i18n("pattern_helper") + " " + i18n("disabled_csplist_helper")
|
||||
}
|
||||
name="csplist"
|
||||
defaultValue={csplist}
|
||||
value={csplist}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
/>
|
||||
@@ -345,7 +345,7 @@ export default function Settings() {
|
||||
label={i18n("translate_blacklist")}
|
||||
helperText={i18n("pattern_helper")}
|
||||
name="blacklist"
|
||||
defaultValue={blacklist}
|
||||
value={blacklist}
|
||||
onChange={handleChange}
|
||||
maxRows={10}
|
||||
multiline
|
||||
|
||||
@@ -2,32 +2,61 @@ import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { shortcutListener } from "../../libs/shortcut";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
|
||||
export default function ShortcutInput({ value, onChange, label, helperText }) {
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
export default function ShortcutInput({
|
||||
value: keys,
|
||||
onChange,
|
||||
label,
|
||||
helperText,
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editingKeys, setEditingKeys] = useState([]);
|
||||
const inputRef = useRef(null);
|
||||
const i18n = useI18n();
|
||||
|
||||
const commitChanges = () => {
|
||||
if (editingKeys.length > 0) {
|
||||
onChange(editingKeys);
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
commitChanges();
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
setEditingKeys([]);
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
if (!isEditing) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputRef.current.focus();
|
||||
onChange([]);
|
||||
|
||||
const clearShortcut = shortcutListener((curkeys, allkeys) => {
|
||||
onChange(allkeys);
|
||||
if (curkeys.length === 0) {
|
||||
setDisabled(true);
|
||||
}
|
||||
}, inputRef.current);
|
||||
const inputElement = inputRef.current;
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
const clearShortcut = shortcutListener((pressedKeys, event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setEditingKeys([...pressedKeys]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
clearShortcut();
|
||||
};
|
||||
}, [disabled, onChange]);
|
||||
}, [isEditing]);
|
||||
|
||||
const displayValue = isEditing ? editingKeys : keys;
|
||||
const formattedValue = displayValue
|
||||
.map((item) => (item === " " ? "Space" : item))
|
||||
.join(" + ");
|
||||
|
||||
return (
|
||||
<Stack direction="row" alignItems="flex-start">
|
||||
@@ -35,22 +64,22 @@ export default function ShortcutInput({ value, onChange, label, helperText }) {
|
||||
size="small"
|
||||
label={label}
|
||||
name={label}
|
||||
value={value.map((item) => (item === " " ? "Space" : item)).join(" + ")}
|
||||
value={formattedValue}
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
disabled={disabled}
|
||||
onBlur={() => {
|
||||
setDisabled(true);
|
||||
}}
|
||||
helperText={helperText}
|
||||
disabled={!isEditing}
|
||||
onBlur={handleBlur}
|
||||
helperText={isEditing ? i18n("pls_press_shortcut") : helperText}
|
||||
/>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDisabled(false);
|
||||
}}
|
||||
>
|
||||
{<EditIcon />}
|
||||
</IconButton>
|
||||
{isEditing ? (
|
||||
<IconButton onClick={commitChanges} color="primary">
|
||||
<CheckIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton onClick={handleEditClick}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import { useAlert } from "../../hooks/Alert";
|
||||
import { useSetting } from "../../hooks/Setting";
|
||||
import { kissLog } from "../../libs/log";
|
||||
import SyncIcon from "@mui/icons-material/Sync";
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||
import ContentPasteIcon from "@mui/icons-material/ContentPaste";
|
||||
|
||||
export default function SyncSetting() {
|
||||
const i18n = useI18n();
|
||||
@@ -44,10 +44,10 @@ export default function SyncSetting() {
|
||||
try {
|
||||
setLoading(true);
|
||||
await syncSettingAndRules();
|
||||
await reloadSetting();
|
||||
reloadSetting();
|
||||
alert.success(i18n("sync_success"));
|
||||
} catch (err) {
|
||||
kissLog(err, "sync all");
|
||||
kissLog("sync all", err);
|
||||
alert.error(i18n("sync_failed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -56,37 +56,37 @@ export default function SyncSetting() {
|
||||
|
||||
const handleGenerateShareString = async () => {
|
||||
try {
|
||||
const base64Config = btoa(JSON.stringify({
|
||||
syncType: syncType,
|
||||
syncUrl: syncUrl,
|
||||
syncUser: syncUser,
|
||||
syncKey: syncKey,
|
||||
}));
|
||||
const base64Config = btoa(
|
||||
JSON.stringify({
|
||||
syncType: syncType,
|
||||
syncUrl: syncUrl,
|
||||
syncUser: syncUser,
|
||||
syncKey: syncKey,
|
||||
})
|
||||
);
|
||||
const shareString = `${OPT_SYNCTOKEN_PERFIX}${base64Config}`;
|
||||
await navigator.clipboard.writeText(shareString);
|
||||
console.debug("Share string copied to clipboard", shareString);
|
||||
kissLog("Share string copied to clipboard", shareString);
|
||||
} catch (error) {
|
||||
console.error("Failed to copy share string to clipboard", error);
|
||||
kissLog("Failed to copy share string to clipboard", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportFromClipboard = async () => {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
console.debug('read_clipboard', text)
|
||||
kissLog("read_clipboard", text);
|
||||
if (text.startsWith(OPT_SYNCTOKEN_PERFIX)) {
|
||||
const base64Config = text.slice(OPT_SYNCTOKEN_PERFIX.length);
|
||||
const jsonString = atob(base64Config);
|
||||
const updatedConfig = JSON.parse(jsonString);
|
||||
|
||||
if (!OPT_SYNCTYPE_ALL.includes(updatedConfig.syncType)) {
|
||||
console.error('error syncType', updatedConfig.syncType)
|
||||
kissLog("error syncType", updatedConfig.syncType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
updatedConfig.syncUrl
|
||||
) {
|
||||
if (updatedConfig.syncUrl) {
|
||||
updateSync({
|
||||
syncType: updatedConfig.syncType,
|
||||
syncUrl: updatedConfig.syncUrl,
|
||||
@@ -94,17 +94,16 @@ export default function SyncSetting() {
|
||||
syncKey: updatedConfig.syncKey,
|
||||
});
|
||||
} else {
|
||||
console.error("Invalid config structure");
|
||||
kissLog("Invalid config structure");
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid share string", text);
|
||||
kissLog("Invalid share string", text);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to read from clipboard or parse JSON", error);
|
||||
kissLog("Failed to read from clipboard or parse JSON", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (!sync) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import {
|
||||
OPT_TRANS_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_TRANBOX_TRIGGER_CLICK,
|
||||
@@ -16,11 +15,13 @@ import { useCallback } from "react";
|
||||
import { limitNumber } from "../../libs/utils";
|
||||
import { useTranbox } from "../../hooks/Tranbox";
|
||||
import { isExt } from "../../libs/client";
|
||||
import { useApiList } from "../../hooks/Api";
|
||||
import Alert from "@mui/material/Alert";
|
||||
|
||||
export default function Tranbox() {
|
||||
const i18n = useI18n();
|
||||
const { tranboxSetting, updateTranbox } = useTranbox();
|
||||
const { enabledApis } = useApiList();
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -47,7 +48,7 @@ export default function Tranbox() {
|
||||
);
|
||||
|
||||
const {
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
toLang2 = "en",
|
||||
@@ -72,14 +73,14 @@ export default function Tranbox() {
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
name="translator"
|
||||
value={translator}
|
||||
name="apiSlug"
|
||||
value={apiSlug}
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{OPT_TRANS_ALL.map((item) => (
|
||||
<MenuItem key={item} value={item}>
|
||||
{item}
|
||||
{enabledApis.map((api) => (
|
||||
<MenuItem key={api.apiSlug} value={api.apiSlug}>
|
||||
{api.apiName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
@@ -147,7 +148,7 @@ export default function Tranbox() {
|
||||
label={i18n("tranbtn_offset_x")}
|
||||
type="number"
|
||||
name="btnOffsetX"
|
||||
defaultValue={btnOffsetX}
|
||||
value={btnOffsetX}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -156,7 +157,7 @@ export default function Tranbox() {
|
||||
label={i18n("tranbtn_offset_y")}
|
||||
type="number"
|
||||
name="btnOffsetY"
|
||||
defaultValue={btnOffsetY}
|
||||
value={btnOffsetY}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -165,7 +166,7 @@ export default function Tranbox() {
|
||||
label={i18n("tranbox_offset_x")}
|
||||
type="number"
|
||||
name="boxOffsetX"
|
||||
defaultValue={boxOffsetX}
|
||||
value={boxOffsetX}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -174,7 +175,7 @@ export default function Tranbox() {
|
||||
label={i18n("tranbox_offset_y")}
|
||||
type="number"
|
||||
name="boxOffsetY"
|
||||
defaultValue={boxOffsetY}
|
||||
value={boxOffsetY}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
@@ -245,7 +246,7 @@ export default function Tranbox() {
|
||||
size="small"
|
||||
label={i18n("extend_styles")}
|
||||
name="extStyles"
|
||||
defaultValue={extStyles}
|
||||
value={extStyles}
|
||||
onChange={handleChange}
|
||||
maxRows={10}
|
||||
multiline
|
||||
|
||||
@@ -9,9 +9,9 @@ import ThemeProvider from "../../hooks/Theme";
|
||||
import { useEffect, useState } from "react";
|
||||
import { isGm } from "../../libs/client";
|
||||
import { sleep } from "../../libs/utils";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import { trySyncSettingAndRules } from "../../libs/sync";
|
||||
import { AlertProvider } from "../../hooks/Alert";
|
||||
import { ConfirmProvider } from "../../hooks/Confirm";
|
||||
import Link from "@mui/material/Link";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import Stack from "@mui/material/Stack";
|
||||
@@ -22,6 +22,7 @@ import InputSetting from "./InputSetting";
|
||||
import Tranbox from "./Tranbox";
|
||||
import FavWords from "./FavWords";
|
||||
import MouseHoverSetting from "./MouseHover";
|
||||
import Loading from "../../hooks/Loading";
|
||||
|
||||
export default function Options() {
|
||||
const [error, setError] = useState("");
|
||||
@@ -91,37 +92,30 @@ export default function Options() {
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
<center>
|
||||
<Divider>
|
||||
<Link
|
||||
href={process.env.REACT_APP_HOMEPAGE}
|
||||
>{`KISS Translator v${process.env.REACT_APP_VERSION}`}</Link>
|
||||
</Divider>
|
||||
<CircularProgress />
|
||||
</center>
|
||||
);
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingProvider>
|
||||
<ThemeProvider>
|
||||
<AlertProvider>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Setting />} />
|
||||
<Route path="rules" element={<Rules />} />
|
||||
<Route path="input" element={<InputSetting />} />
|
||||
<Route path="tranbox" element={<Tranbox />} />
|
||||
<Route path="mousehover" element={<MouseHoverSetting />} />
|
||||
<Route path="apis" element={<Apis />} />
|
||||
<Route path="sync" element={<SyncSetting />} />
|
||||
<Route path="words" element={<FavWords />} />
|
||||
<Route path="about" element={<About />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
<ConfirmProvider>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Setting />} />
|
||||
<Route path="rules" element={<Rules />} />
|
||||
<Route path="input" element={<InputSetting />} />
|
||||
<Route path="tranbox" element={<Tranbox />} />
|
||||
<Route path="mousehover" element={<MouseHoverSetting />} />
|
||||
<Route path="apis" element={<Apis />} />
|
||||
<Route path="sync" element={<SyncSetting />} />
|
||||
<Route path="words" element={<FavWords />} />
|
||||
<Route path="about" element={<About />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</ConfirmProvider>
|
||||
</AlertProvider>
|
||||
</ThemeProvider>
|
||||
</SettingProvider>
|
||||
|
||||
@@ -20,11 +20,9 @@ import {
|
||||
MSG_OPEN_OPTIONS,
|
||||
MSG_SAVE_RULE,
|
||||
MSG_COMMAND_SHORTCUTS,
|
||||
OPT_TRANS_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
OPT_STYLE_ALL,
|
||||
DEFAULT_TRANS_APIS,
|
||||
} from "../../config";
|
||||
import { sendIframeMsg } from "../../libs/iframe";
|
||||
import { saveRule } from "../../libs/rules";
|
||||
@@ -33,14 +31,14 @@ import { kissLog } from "../../libs/log";
|
||||
|
||||
// 插件popup没有参数
|
||||
// 网页弹框有
|
||||
export default function Popup({ setShowPopup, translator: tran }) {
|
||||
export default function Popup({ setShowPopup, translator }) {
|
||||
const i18n = useI18n();
|
||||
const [rule, setRule] = useState(tran?.rule);
|
||||
const [transApis, setTransApis] = useState(tran?.setting?.transApis || []);
|
||||
const [rule, setRule] = useState(translator?.rule);
|
||||
const [transApis, setTransApis] = useState(translator?.setting?.transApis || []);
|
||||
const [commands, setCommands] = useState({});
|
||||
|
||||
const handleOpenSetting = () => {
|
||||
if (!tran) {
|
||||
if (!translator) {
|
||||
browser?.runtime.openOptionsPage();
|
||||
} else if (isExt) {
|
||||
sendBgMsg(MSG_OPEN_OPTIONS);
|
||||
@@ -54,14 +52,14 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
try {
|
||||
setRule({ ...rule, transOpen: e.target.checked ? "true" : "false" });
|
||||
|
||||
if (!tran) {
|
||||
if (!translator) {
|
||||
await sendTabMsg(MSG_TRANS_TOGGLE);
|
||||
} else {
|
||||
tran.toggle();
|
||||
translator.toggle();
|
||||
sendIframeMsg(MSG_TRANS_TOGGLE);
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog(err, "toggle trans");
|
||||
kissLog("toggle trans", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,14 +68,14 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
const { name, value } = e.target;
|
||||
setRule((pre) => ({ ...pre, [name]: value }));
|
||||
|
||||
if (!tran) {
|
||||
if (!translator) {
|
||||
await sendTabMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
||||
} else {
|
||||
tran.updateRule({ [name]: value });
|
||||
translator.updateRule({ [name]: value });
|
||||
sendIframeMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog(err, "update rule");
|
||||
kissLog("update rule", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,23 +86,23 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
const handleSaveRule = async () => {
|
||||
try {
|
||||
let href = window.location.href;
|
||||
if (!tran) {
|
||||
if (!translator) {
|
||||
const tab = await getCurTab();
|
||||
href = tab.url;
|
||||
}
|
||||
const newRule = { ...rule, pattern: href.split("/")[2] };
|
||||
if (isExt && tran) {
|
||||
if (isExt && translator) {
|
||||
sendBgMsg(MSG_SAVE_RULE, newRule);
|
||||
} else {
|
||||
saveRule(newRule);
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog(err, "save rule");
|
||||
kissLog("save rule", err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (tran) {
|
||||
if (translator) {
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
@@ -115,10 +113,10 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
setTransApis(res.setting.transApis);
|
||||
}
|
||||
} catch (err) {
|
||||
kissLog(err, "query rule");
|
||||
kissLog("query rule", err);
|
||||
}
|
||||
})();
|
||||
}, [tran]);
|
||||
}, [translator]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -130,7 +128,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
commands[name] = shortcut;
|
||||
});
|
||||
} else {
|
||||
const shortcuts = tran.setting.shortcuts;
|
||||
const shortcuts = translator.setting.shortcuts;
|
||||
if (shortcuts) {
|
||||
Object.entries(shortcuts).forEach(([key, val]) => {
|
||||
commands[key] = val.join("+");
|
||||
@@ -139,21 +137,18 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
}
|
||||
setCommands(commands);
|
||||
} catch (err) {
|
||||
kissLog(err, "query cmds");
|
||||
kissLog("query cmds", err);
|
||||
}
|
||||
})();
|
||||
}, [tran]);
|
||||
}, [translator]);
|
||||
|
||||
const optApis = useMemo(
|
||||
() =>
|
||||
OPT_TRANS_ALL.map((key) => ({
|
||||
...(transApis[key] || DEFAULT_TRANS_APIS[key]),
|
||||
apiKey: key,
|
||||
}))
|
||||
.filter((item) => !item.isDisabled)
|
||||
.map(({ apiKey, apiName }) => ({
|
||||
key: apiKey,
|
||||
name: apiName?.trim() || apiKey,
|
||||
transApis
|
||||
.filter((api) => !api.isDisabled)
|
||||
.map((api) => ({
|
||||
key: api.apiSlug,
|
||||
name: api.apiName || api.apiSlug,
|
||||
})),
|
||||
[transApis]
|
||||
);
|
||||
@@ -161,7 +156,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
if (!rule) {
|
||||
return (
|
||||
<Box minWidth={300}>
|
||||
{!tran && (
|
||||
{!translator && (
|
||||
<>
|
||||
<Header />
|
||||
<Divider />
|
||||
@@ -178,7 +173,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
|
||||
const {
|
||||
transOpen,
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
textStyle,
|
||||
@@ -190,7 +185,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
|
||||
return (
|
||||
<Box width={320}>
|
||||
{!tran && (
|
||||
{!translator && (
|
||||
<>
|
||||
<Header />
|
||||
<Divider />
|
||||
@@ -275,8 +270,8 @@ export default function Popup({ setShowPopup, translator: tran }) {
|
||||
select
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
size="small"
|
||||
value={translator}
|
||||
name="translator"
|
||||
value={apiSlug}
|
||||
name="apiSlug"
|
||||
label={i18n("translate_service")}
|
||||
onChange={handleChange}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import FavBtn from "./FavBtn";
|
||||
import Typography from "@mui/material/Typography";
|
||||
@@ -26,10 +26,10 @@ export default function DictCont({ text }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo
|
||||
// todo: 修复
|
||||
const dictRes = await apiTranslate({
|
||||
text,
|
||||
translator: OPT_TRANS_BAIDU,
|
||||
apiSlug: OPT_TRANS_BAIDU,
|
||||
fromLang: "en",
|
||||
toLang: "zh-CN",
|
||||
});
|
||||
@@ -45,70 +45,74 @@ export default function DictCont({ text }) {
|
||||
})();
|
||||
}, [text]);
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
const copyText = useMemo(() => {
|
||||
if (!dictResult) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return [
|
||||
dictResult.src,
|
||||
dictResult.voice
|
||||
?.map(Object.entries)
|
||||
.map((item) => item[0])
|
||||
.map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||
.join(" "),
|
||||
dictResult.content[0].mean
|
||||
.map(({ pre, cont }) => {
|
||||
return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
|
||||
})
|
||||
.join("\n"),
|
||||
].join("\n");
|
||||
}, [text, dictResult]);
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress size={16} />;
|
||||
}
|
||||
|
||||
if (!text || !dictResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
const copyText = [
|
||||
dictResult.src,
|
||||
dictResult.voice
|
||||
?.map(Object.entries)
|
||||
.map((item) => item[0])
|
||||
.map(([key, val]) => `${PHONIC_MAP[key]?.[0] || key} ${val}`)
|
||||
.join(" "),
|
||||
dictResult.content[0].mean
|
||||
.map(({ pre, cont }) => {
|
||||
return `${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
|
||||
})
|
||||
.join("\n"),
|
||||
].join("\n");
|
||||
|
||||
return (
|
||||
<Stack className="KT-transbox-dict" spacing={1}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography variant="subtitle1" style={{ fontWeight: "bold" }}>
|
||||
{dictResult.src}
|
||||
</Typography>
|
||||
{text && (
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<CopyBtn text={copyText} />
|
||||
<FavBtn word={dictResult.src} />
|
||||
<Typography variant="subtitle1" style={{ fontWeight: "bold" }}>
|
||||
{dictResult?.src || text}
|
||||
</Typography>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<CopyBtn text={copyText} />
|
||||
<FavBtn word={dictResult?.src || text} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Typography component="div">
|
||||
{error && <Alert severity="error">{error}</Alert>}
|
||||
|
||||
{dictResult && (
|
||||
<Typography component="div">
|
||||
{dictResult.voice
|
||||
?.map(Object.entries)
|
||||
.map((item) => item[0])
|
||||
.map(([key, val]) => (
|
||||
<Typography
|
||||
component="div"
|
||||
key={key}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
<Typography component="span">{`${PHONIC_MAP[key]?.[0] || key} ${val}`}</Typography>
|
||||
<AudioBtn text={dictResult.src} lan={PHONIC_MAP[key]?.[1]} />
|
||||
<Typography component="div">
|
||||
{dictResult.voice
|
||||
?.map(Object.entries)
|
||||
.map((item) => item[0])
|
||||
.map(([key, val]) => (
|
||||
<Typography
|
||||
component="div"
|
||||
key={key}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
<Typography component="span">{`${PHONIC_MAP[key]?.[0] || key} ${val}`}</Typography>
|
||||
<AudioBtn text={dictResult.src} lan={PHONIC_MAP[key]?.[1]} />
|
||||
</Typography>
|
||||
))}
|
||||
</Typography>
|
||||
|
||||
<Typography component="ul">
|
||||
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
|
||||
<Typography component="li" key={idx}>
|
||||
{pre && `[${pre}] `}
|
||||
{Object.keys(cont).join("; ")}
|
||||
</Typography>
|
||||
))}
|
||||
</Typography>
|
||||
</Typography>
|
||||
|
||||
<Typography component="ul">
|
||||
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
|
||||
<Typography component="li" key={idx}>
|
||||
{pre && `[${pre}] `}
|
||||
{Object.keys(cont).join("; ")}
|
||||
</Typography>
|
||||
))}
|
||||
</Typography>
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ export default function FavBtn({ word }) {
|
||||
const { favWords, toggleFav } = useFavWords();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleClick = async () => {
|
||||
const handleClick = () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await toggleFav(word);
|
||||
toggleFav(word);
|
||||
} catch (err) {
|
||||
kissLog(err, "set fav");
|
||||
kissLog("set fav", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,7 @@ import LockIcon from "@mui/icons-material/Lock";
|
||||
import LockOpenIcon from "@mui/icons-material/LockOpen";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import {
|
||||
OPT_TRANS_ALL,
|
||||
OPT_LANGS_FROM,
|
||||
OPT_LANGS_TO,
|
||||
DEFAULT_TRANS_APIS,
|
||||
} from "../../config";
|
||||
import { OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
|
||||
import { useState, useRef, useMemo } from "react";
|
||||
import TranCont from "./TranCont";
|
||||
import DictCont from "./DictCont";
|
||||
@@ -119,21 +114,18 @@ function TranForm({
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editText, setEditText] = useState("");
|
||||
const [translator, setTranslator] = useState(tranboxSetting.translator);
|
||||
const [apiSlug, setApiSlug] = useState(tranboxSetting.apiSlug);
|
||||
const [fromLang, setFromLang] = useState(tranboxSetting.fromLang);
|
||||
const [toLang, setToLang] = useState(tranboxSetting.toLang);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const optApis = useMemo(
|
||||
() =>
|
||||
OPT_TRANS_ALL.map((key) => ({
|
||||
...(transApis[key] || DEFAULT_TRANS_APIS[key]),
|
||||
apiKey: key,
|
||||
}))
|
||||
.filter((item) => !item.isDisabled)
|
||||
.map(({ apiKey, apiName }) => ({
|
||||
key: apiKey,
|
||||
name: apiName?.trim() || apiKey,
|
||||
transApis
|
||||
.filter((api) => !api.isDisabled)
|
||||
.map((api) => ({
|
||||
key: api.apiSlug,
|
||||
name: api.apiName || api.apiSlug,
|
||||
})),
|
||||
[transApis]
|
||||
);
|
||||
@@ -194,11 +186,11 @@ function TranForm({
|
||||
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||
fullWidth
|
||||
size="small"
|
||||
value={translator}
|
||||
name="translator"
|
||||
value={apiSlug}
|
||||
name="apiSlug"
|
||||
label={i18n("translate_service")}
|
||||
onChange={(e) => {
|
||||
setTranslator(e.target.value);
|
||||
setApiSlug(e.target.value);
|
||||
}}
|
||||
>
|
||||
{optApis.map(({ key, name }) => (
|
||||
@@ -266,7 +258,7 @@ function TranForm({
|
||||
enDict === "-") && (
|
||||
<TranCont
|
||||
text={text}
|
||||
translator={translator}
|
||||
apiSlug={apiSlug}
|
||||
fromLang={fromLang}
|
||||
toLang={toLang}
|
||||
toLang2={tranboxSetting.toLang2}
|
||||
@@ -307,6 +299,7 @@ export default function TranBox({
|
||||
enDict,
|
||||
}) {
|
||||
const [mouseHover, setMouseHover] = useState(false);
|
||||
// todo: 这里的 SettingProvider 不应和 background 的共用
|
||||
return (
|
||||
<SettingProvider>
|
||||
<ThemeProvider styles={extStyles}>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Box from "@mui/material/Box";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { useI18n } from "../../hooks/I18n";
|
||||
import { DEFAULT_TRANS_APIS } from "../../config";
|
||||
import { DEFAULT_API_SETTING } from "../../config";
|
||||
import { useEffect, useState } from "react";
|
||||
import { apiTranslate } from "../../apis";
|
||||
import CopyBtn from "./CopyBtn";
|
||||
@@ -13,7 +13,7 @@ import { tryDetectLang } from "../../libs";
|
||||
|
||||
export default function TranCont({
|
||||
text,
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang,
|
||||
toLang2 = "en",
|
||||
@@ -42,10 +42,11 @@ export default function TranCont({
|
||||
}
|
||||
|
||||
const apiSetting =
|
||||
transApis[translator] || DEFAULT_TRANS_APIS[translator];
|
||||
transApis.find((api) => api.apiSlug === apiSlug) ||
|
||||
DEFAULT_API_SETTING;
|
||||
const [trText] = await apiTranslate({
|
||||
text,
|
||||
translator,
|
||||
apiSlug,
|
||||
fromLang,
|
||||
toLang: to,
|
||||
apiSetting,
|
||||
@@ -57,7 +58,7 @@ export default function TranCont({
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [text, translator, fromLang, toLang, toLang2, transApis, langDetector]);
|
||||
}, [text, apiSlug, fromLang, toLang, toLang2, transApis, langDetector]);
|
||||
|
||||
if (simpleStyle) {
|
||||
return (
|
||||
|
||||
@@ -201,7 +201,7 @@ export default function Slection({
|
||||
});
|
||||
};
|
||||
} catch (err) {
|
||||
kissLog(err, "registerMenuCommand");
|
||||
kissLog("registerMenuCommand", err);
|
||||
}
|
||||
}, [handleTranbox, contextMenuType, langMap]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user