bobo b9432b6ef6 feat: Add proxy setting to Config page
This commit adds the proxy setting to the Config page. It includes the necessary changes to the zh.json and Config.tsx files to display and handle the proxy URL and port input. Users can now enter their proxy URL and port in the Config page to configure the proxy settings for the application.
2024-05-30 11:14:33 +08:00

484 lines
15 KiB
TypeScript

import React, { useEffect, useState, forwardRef } from "react";
import {
Title,
Box,
TextInput,
Tabs,
PasswordInput,
Radio,
Group,
Stack,
Select,
Button,
Drawer,
NumberInput,
LoadingOverlay,
Switch,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useRouter } from "@/views/router";
import MessageUtil from "@/util/MessageUtil";
import { useMst } from "@/views/stores/RootStore";
import getModelShowName from "@/util/getModelShowName";
import isEqual from "lodash.isequal";
import cloneDeep from "lodash.clonedeep";
import { useTranslation } from "react-i18next";
import { useDisclosure } from "@mantine/hooks";
import { observer } from "mobx-react-lite";
const commonInputStyle = {
label: {
color: "var(--vscode-editor-foreground)",
fontSize: "var(--vscode-editor-font-size)",
},
input: {
fontSize: "var(--vscode-editor-font-size)",
backgroundColor: "var(--vscode-sideBar-background)",
borderColor: "var(--vscode-editor-foreground)",
color: "var(--vscode-editor-foreground)",
"&[data-disabled]": {
color: "var(--vscode-disabledForeground)",
},
},
dropdown: {
backgroundColor: "var(--vscode-sideBar-background)",
borderColor: "var(--vscode-input-border)",
},
};
const selectStyle = {
dropdown: {
backgroundColor: "var(--vscode-sideBar-background)",
borderColor: "var(--vscode-input-border)",
},
item: {
color: "var(--vscode-editor-foreground)",
"&:hover,&[data-hovered=true], &[data-selected=true], &[data-selected=true]:hover":
{
color: "var(--vscode-editor-foreground)",
borderColor: "var(--vscode-commandCenter-border)",
backgroundColor: "var(--vscode-commandCenter-activeBackground)",
},
},
};
const Config = observer(() => {
const [loading, { open: startLoading, close: closeLoading }] =
useDisclosure(false);
const { config } = useMst();
const router = useRouter();
const { i18n, t } = useTranslation();
const apiSelection = [
{
value: "https://api.devchat.ai/v1",
label: t("Singapore Node"),
},
{
value: "https://api.devchat-ai.cn/v1",
label: t("China Node"),
},
{
value: "custom",
label: t("Custom"),
},
];
const form = useForm({
initialValues: {
providers: {},
models: {},
...cloneDeep(config.config),
},
validate: {
providers: {
devchat: {
// api_key: (value) =>
// value.length > 0 ? null : "Please enter access key",
api_base: (value) =>
value.length > 0 ? null : "Please enter api base",
cumstom_api_base: (value, values) =>
values.providers?.devchat?.api_base === "custom" && value?.length <= 0
? "Please enter custom api base"
: null,
},
},
},
});
const [models, setModels] = useState<any[]>([]);
const [codeModels, setCodeModels] = useState<any[]>([]);
const [current, setCurrent] = useState("");
useEffect(() => {
MessageUtil.registerHandler("updateSetting", (data) => {
// 保存后的回调
MessageUtil.sendMessage({ command: "readConfig" });
});
if (router.currentRoute !== "config") {return;}
const modelArray = config.modelsTemplate.map((item) => ({
value: item.name,
label: getModelShowName(item.name),
}));
setModels(modelArray);
const codeModelArray = config.modelsTemplate.filter(model => model.category === "code").map((item) => ({
value: item.name,
label: getModelShowName(item.name),
}));
setCodeModels(codeModelArray);
if (modelArray.length > 0) {
setCurrent(modelArray[0].value);
}
}, [router.currentRoute]);
useEffect(() => {
const cloneConfig = cloneDeep(config.config);
form.setValues(cloneConfig);
if (router.currentRoute !== "config") {return;}
if (config.settle && loading) {
setTimeout(() => {
router.updateRoute("chat");
closeLoading();
}, 1000);
}
}, [config.config]);
const onSave = (values) => {
config.updateSettle(false);
startLoading();
const writeConfig = cloneDeep(values);
if (
writeConfig.providers.devchat.api_base === "custom" &&
writeConfig.providers.devchat.cumstom_api_base
) {
writeConfig.providers.devchat.api_base =
writeConfig.providers.devchat.cumstom_api_base;
}
delete writeConfig.providers.devchat.cumstom_api_base;
MessageUtil.sendMessage({
command: "writeConfig",
value: writeConfig,
key: "",
});
setTimeout(() => {
MessageUtil.sendMessage({ command: "readConfig", key: "" });
}, 1000);
};
const changeModelDetail = (key: string, value: number | string) => {
const newModel = { ...form.values.models[current], [key]: value };
form.setFieldValue("models", {
...form.values.models,
[current]: newModel,
});
};
const languageChange = (value: string) => {
i18n.changeLanguage(value);
form.setFieldValue("language", value);
};
const disabledSubmit = isEqual(form.values, config.config);
const showProvider =
current.toLowerCase().startsWith("gpt") &&
form.values.providers?.openai?.api_key;
return (
<Drawer
opened={router.currentRoute === "config"}
onClose={() => {
router.updateRoute("chat");
}}
position="right"
size="100%"
transitionProps={{
duration: 0,
}}
sx={{
height: "100%",
"& .mantine-Paper-root": {
color: "var(--vscode-editor-foreground)",
background: "var(--vscode-sideBar-background)",
},
"& .mantine-Drawer-inner": {
top: 40,
paddingBottom: 57,
},
"& section": {
flex: 1,
},
}}
withCloseButton={false}
withOverlay={false}
zIndex={99}
>
<LoadingOverlay visible={loading} overlayBlur={2} />
<Title order={2} mb={20}>
{t("Config")}
</Title>
<form
onSubmit={form.onSubmit((values) => {
onSave(values);
})}
>
<Stack>
<Tabs
defaultValue="Devchat"
variant="outline"
sx={{
".mantine-UnstyledButton-root::before": {
backgroundColor: "var(--vscode-sideBar-background)!important",
},
}}
>
<Tabs.List>
<Tabs.Tab
value="Devchat"
sx={{
color: "var(--vscode-editor-foreground)",
}}
>
Devchat
</Tabs.Tab>
<Tabs.Tab
value="OpenAI"
sx={{
color: "var(--vscode-editor-foreground)",
}}
>
OpenAI
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel
value="Devchat"
pt="xs"
p={10}
sx={{
border: "1px solid #ced4da",
borderTop: "none",
borderRadius: "0 0 4px 4px",
}}
>
<Stack>
<Select
data={apiSelection}
styles={{
...commonInputStyle,
...selectStyle,
}}
placeholder="https://xxxx.xx"
label={t("API Base of Devchat")}
withAsterisk
description={t("the base URL for the API")}
{...form.getInputProps("providers.devchat.api_base")}
/>
{form.values.providers?.devchat?.api_base === "custom" && (
<TextInput
styles={commonInputStyle}
label={t("Custom API Base of Devchat")}
withAsterisk
description={t("the base URL for the API")}
{...form.getInputProps(
"providers.devchat.cumstom_api_base"
)}
/>
)}
<PasswordInput
styles={commonInputStyle}
sx={{
"& .mantine-PasswordInput-innerInput": {
fontSize: "var(--vscode-editor-font-size)",
color: "var(--vscode-editor-foreground)",
},
}}
withAsterisk
label={t("Access Key of Devchat")}
placeholder={t("Your Access Key")}
description={t("please keep this secret")}
{...form.getInputProps("providers.devchat.api_key")}
/>
</Stack>
</Tabs.Panel>
<Tabs.Panel
value="OpenAI"
pt="xs"
p={10}
sx={{
border: "1px solid #ced4da",
borderTop: "none",
borderRadius: "4px",
}}
>
<Stack>
<TextInput
styles={commonInputStyle}
placeholder={t("API Base of OpenAI")}
label={t("API Base of OpenAI")}
withAsterisk
description={t("the base URL for the API")}
{...form.getInputProps("providers.openai.api_base")}
/>
<PasswordInput
styles={commonInputStyle}
withAsterisk
label={t("Access Key of OpenAI")}
description={t("please keep this secret")}
{...form.getInputProps("providers.openai.api_key")}
/>
</Stack>
</Tabs.Panel>
</Tabs>
<Box
sx={{
border: "1px solid #ced4da",
borderRadius: "4px",
}}
p={10}
>
<Select
label={t("Model Config")}
description={t("Leave it blank if you won't use this llm model")}
withAsterisk
styles={{
...commonInputStyle,
...selectStyle,
}}
data={models}
value={current}
onChange={(value: string) => setCurrent(value)}
/>
<NumberInput
label={t("Max input tokens")}
description={t(
"the maximum number of tokens that can be used in the input"
)}
styles={commonInputStyle}
value={form.values?.models[current]?.max_input_tokens}
onChange={(value) => changeModelDetail("max_input_tokens", value)}
/>
<NumberInput
label={t("Max output tokens")}
description={t(
"the maximum number of tokens output"
)}
styles={commonInputStyle}
value={form.values?.models[current]?.max_tokens}
onChange={(value) => changeModelDetail("max_tokens", value)}
/>
{showProvider && (
<Select
label={t("Provider")}
description={t("select the provider for the model")}
styles={{
...commonInputStyle,
...selectStyle,
}}
data={[
{ value: "devchat", label: "Devchat" },
{ value: "openai", label: "OpenAI" },
]}
value={form.values?.models[current]?.provider}
onChange={(value) =>
changeModelDetail("provider", value as string)
}
/>
)}
</Box>
<Radio.Group
label={t("Language")}
description={t("Select your preferred language")}
withAsterisk
styles={commonInputStyle}
sx={{
label: {
color: "var(--vscode-editor-foreground)",
fontSize: "var(--vscode-editor-font-size)",
},
}}
{...form.getInputProps("language")}
onChange={languageChange}
>
<Group mt="xs">
<Radio value="en" label="EN" />
<Radio value="zh" label="中文" />
</Group>
</Radio.Group>
<TextInput
styles={commonInputStyle}
label={t("Python for chat")}
placeholder="/xxx/xxx"
description={t("Please enter the path of your python")}
{...form.getInputProps("python_for_chat")}
/>
<TextInput
styles={commonInputStyle}
label={t("Python for commands")}
placeholder="/xxx/xxx"
description={t("Please enter the path of your python")}
{...form.getInputProps("python_for_commands")}
/>
<TextInput
styles={commonInputStyle}
label={t("Proxy setting")}
placeholder="http://127.0.0.1:7890"
description={t("Please enter the proxy url and port")}
{...form.getInputProps("DEVCHAT_PROXY")}
/>
{codeModels.length > 0 && (
<>
<Switch
styles={commonInputStyle}
label={t("Code Completion Enable")}
{...form.getInputProps('complete_enable', { type: 'checkbox' })}
/>
<Switch
styles={commonInputStyle}
label={t("Codebase Index Enable")}
{...form.getInputProps('complete_index_enable', { type: 'checkbox' })}
/>
<Select
styles={{
...commonInputStyle,
...selectStyle,
}}
label={t("Code Completion Model")}
placeholder={t("Select a model")}
data={codeModels}
{...form.getInputProps('complete_model')}
disabled={!form.values.complete_enable}
/>
</>
)}
</Stack>
<Group
grow
p={10}
sx={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
borderTop: "1px solid #ced4da",
background: "var(--vscode-sideBar-background)",
}}
>
<Button type="submit" disabled={disabledSubmit}>
{t("Save")}
</Button>
<Button
variant="outline"
color="gray"
onClick={() => {
router.updateRoute("chat");
}}
>
{t("Cancel")}
</Button>
</Group>
</form>
</Drawer>
);
});
export default Config;