2024-03-04 10:13:34 +08:00

382 lines
11 KiB
TypeScript

import React, { useEffect, useState } from "react";
import {
Title,
Box,
TextInput,
Tabs,
PasswordInput,
Radio,
Group,
Stack,
Select,
Button,
Drawer,
NumberInput,
LoadingOverlay,
} 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 { 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-input-border)",
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 = function () {
const [loading, { open: startLoading, close: closeLoading }] =
useDisclosure(false);
const { config } = useMst();
const router = useRouter();
const { i18n, t } = useTranslation();
const form = useForm({
initialValues: {
providers: {},
models: {},
...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",
},
},
},
});
const [models, setModels] = useState<any[]>([]);
const [current, setCurrent] = useState("");
useEffect(() => {
MessageUtil.registerHandler("updateSetting", (data) => {
// 保存后的回调
MessageUtil.sendMessage({ command: "readConfig" });
});
if (router.currentRoute !== "config") return;
const modelName = config.getModelList();
const modelArray = modelName.map((item) => ({
value: item,
label: getModelShowName(item),
}));
setModels(modelArray);
setCurrent(modelArray[0].value);
}, [router.currentRoute]);
useEffect(() => {
if (router.currentRoute !== "config") return;
if (config.settle && loading) {
setTimeout(() => {
router.updateRoute("chat");
closeLoading();
}, 1000);
}
}, [config.settle]);
const onSave = (values) => {
if (!isEqual(values, config.config)) {
config.updateSettle(false);
startLoading();
MessageUtil.sendMessage({ command: "saveConfig", data: values });
}
};
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);
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}
>
<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>
<TextInput
styles={commonInputStyle}
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")}
/>
<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="Access Key of OpenAI"
placeholder="Your Access Key"
description="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="Current model"
placeholder="Pick one"
description="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="Max input tokens"
description="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)}
/>
{current.toLowerCase().startsWith("gpt") && (
<Select
label="Provider"
placeholder="Pick one"
description="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="Language"
description="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="Python for chat"
placeholder="/xxx/xxx"
description="Please enter the path of your python"
{...form.getInputProps("python_for_chat")}
/>
<TextInput
styles={commonInputStyle}
label="Python for commands"
placeholder="/xxx/xxx"
description="Please enter the path of your python"
{...form.getInputProps("python_for_command")}
/>
</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}>
Save
</Button>
<Button
variant="outline"
color="gray"
onClick={() => {
router.updateRoute("chat");
}}
>
Cancel
</Button>
</Group>
</form>
</Drawer>
);
};
export default observer(Config);