ChatGPT-Next-Web/app/components/settings.tsx

392 lines
12 KiB
TypeScript

import { useState, useEffect, useRef, useMemo } from "react";
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
import styles from "./settings.module.scss";
import ResetIcon from "../icons/reload.svg";
import CloseIcon from "../icons/close.svg";
import ClearIcon from "../icons/clear.svg";
import { List, ListItem, Popover } from "./ui-lib";
import { IconButton } from "./button";
import {
SubmitKey,
useChatStore,
Theme,
ALL_MODELS,
useUpdateStore,
useAccessStore,
} from "../store";
import { Avatar } from "./home";
import Locale, { changeLang, getLang } from "../locales";
import { getCurrentCommitId } from "../utils";
import Link from "next/link";
import { UPDATE_URL } from "../constant";
function SettingItem(props: {
title: string;
subTitle?: string;
children: JSX.Element;
}) {
return (
<ListItem>
<div className={styles["settings-title"]}>
<div>{props.title}</div>
{props.subTitle && (
<div className={styles["settings-sub-title"]}>{props.subTitle}</div>
)}
</div>
{props.children}
</ListItem>
);
}
export function Settings(props: { closeSettings: () => void }) {
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData] = useChatStore(
(state) => [
state.config,
state.updateConfig,
state.resetConfig,
state.clearAllData,
]
);
const updateStore = useUpdateStore();
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentId = getCurrentCommitId();
const remoteId = updateStore.remoteId;
const hasNewVersion = currentId !== remoteId;
function checkUpdate(force = false) {
setCheckingUpdate(true);
updateStore.getLatestCommitId(force).then(() => {
setCheckingUpdate(false);
});
}
useEffect(() => {
checkUpdate();
}, []);
const accessStore = useAccessStore();
const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(),
[]
);
return (
<>
<div className={styles["window-header"]}>
<div className={styles["window-header-title"]}>
<div className={styles["window-header-main-title"]}>
{Locale.Settings.Title}
</div>
<div className={styles["window-header-sub-title"]}>
{Locale.Settings.SubTitle}
</div>
</div>
<div className={styles["window-actions"]}>
<div className={styles["window-action-button"]}>
<IconButton
icon={<ClearIcon />}
onClick={clearAllData}
bordered
title={Locale.Settings.Actions.ClearAll}
/>
</div>
<div className={styles["window-action-button"]}>
<IconButton
icon={<ResetIcon />}
onClick={resetConfig}
bordered
title={Locale.Settings.Actions.ResetAll}
/>
</div>
<div className={styles["window-action-button"]}>
<IconButton
icon={<CloseIcon />}
onClick={props.closeSettings}
bordered
title={Locale.Settings.Actions.Close}
/>
</div>
</div>
</div>
<div className={styles["settings"]}>
<List>
<SettingItem title={Locale.Settings.Avatar}>
<Popover
onClose={() => setShowEmojiPicker(false)}
content={
<EmojiPicker
lazyLoadEmojis
theme={EmojiTheme.AUTO}
onEmojiClick={(e) => {
updateConfig((config) => (config.avatar = e.unified));
setShowEmojiPicker(false);
}}
/>
}
open={showEmojiPicker}
>
<div
className={styles.avatar}
onClick={() => setShowEmojiPicker(true)}
>
<Avatar role="user" />
</div>
</Popover>
</SettingItem>
<SettingItem
title={Locale.Settings.Update.Version(currentId)}
subTitle={
checkingUpdate
? Locale.Settings.Update.IsChecking
: hasNewVersion
? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
: Locale.Settings.Update.IsLatest
}
>
{checkingUpdate ? (
<div />
) : hasNewVersion ? (
<Link href={UPDATE_URL} target="_blank" className="link">
{Locale.Settings.Update.GoToUpdate}
</Link>
) : (
<IconButton
icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Update.CheckUpdate}
onClick={() => checkUpdate(true)}
/>
)}
</SettingItem>
<SettingItem title={Locale.Settings.SendKey}>
<select
value={config.submitKey}
onChange={(e) => {
updateConfig(
(config) =>
(config.submitKey = e.target.value as any as SubmitKey)
);
}}
>
{Object.values(SubmitKey).map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</select>
</SettingItem>
<ListItem>
<div className={styles["settings-title"]}>
{Locale.Settings.Theme}
</div>
<select
value={config.theme}
onChange={(e) => {
updateConfig(
(config) => (config.theme = e.target.value as any as Theme)
);
}}
>
{Object.values(Theme).map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</select>
</ListItem>
<SettingItem title={Locale.Settings.Lang.Name}>
<div className="">
<select
value={getLang()}
onChange={(e) => {
changeLang(e.target.value as any);
}}
>
<option value="en" key="en">
{Locale.Settings.Lang.Options.en}
</option>
<option value="cn" key="cn">
{Locale.Settings.Lang.Options.cn}
</option>
</select>
</div>
</SettingItem>
<div className="no-mobile">
<SettingItem title={Locale.Settings.TightBorder}>
<input
type="checkbox"
checked={config.tightBorder}
onChange={(e) =>
updateConfig(
(config) => (config.tightBorder = e.currentTarget.checked)
)
}
></input>
</SettingItem>
</div>
</List>
<List>
{enabledAccessControl ? (
<SettingItem
title={Locale.Settings.AccessCode.Title}
subTitle={Locale.Settings.AccessCode.SubTitle}
>
<input
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.AccessCode.Placeholder}
onChange={(e) => {
accessStore.updateCode(e.currentTarget.value);
}}
></input>
</SettingItem>
) : (
<></>
)}
<SettingItem
title={Locale.Settings.Token.Title}
subTitle={Locale.Settings.Token.SubTitle}
>
<input
value={accessStore.token}
type="text"
placeholder={Locale.Settings.Token.Placeholder}
onChange={(e) => {
accessStore.updateToken(e.currentTarget.value);
}}
></input>
</SettingItem>
<SettingItem
title={Locale.Settings.HistoryCount.Title}
subTitle={Locale.Settings.HistoryCount.SubTitle}
>
<input
type="range"
title={config.historyMessageCount.toString()}
value={config.historyMessageCount}
min="2"
max="25"
step="2"
onChange={(e) =>
updateConfig(
(config) =>
(config.historyMessageCount = e.target.valueAsNumber)
)
}
></input>
</SettingItem>
<SettingItem
title={Locale.Settings.CompressThreshold.Title}
subTitle={Locale.Settings.CompressThreshold.SubTitle}
>
<input
type="number"
min={500}
max={4000}
value={config.compressMessageLengthThreshold}
onChange={(e) =>
updateConfig(
(config) =>
(config.compressMessageLengthThreshold =
e.currentTarget.valueAsNumber)
)
}
></input>
</SettingItem>
</List>
<List>
<SettingItem title={Locale.Settings.Model}>
<select
value={config.modelConfig.model}
onChange={(e) => {
updateConfig(
(config) => (config.modelConfig.model = e.currentTarget.value)
);
}}
>
{ALL_MODELS.map((v) => (
<option value={v.name} key={v.name} disabled={!v.available}>
{v.name}
</option>
))}
</select>
</SettingItem>
<SettingItem
title={Locale.Settings.Temperature.Title}
subTitle={Locale.Settings.Temperature.SubTitle}
>
<input
type="range"
value={config.modelConfig.temperature.toFixed(1)}
min="0"
max="1"
step="0.1"
onChange={(e) => {
updateConfig(
(config) =>
(config.modelConfig.temperature =
e.currentTarget.valueAsNumber)
);
}}
></input>
</SettingItem>
<SettingItem
title={Locale.Settings.MaxTokens.Title}
subTitle={Locale.Settings.MaxTokens.SubTitle}
>
<input
type="number"
min={100}
max={4096}
value={config.modelConfig.max_tokens}
onChange={(e) =>
updateConfig(
(config) =>
(config.modelConfig.max_tokens =
e.currentTarget.valueAsNumber)
)
}
></input>
</SettingItem>
<SettingItem
title={Locale.Settings.PresencePenlty.Title}
subTitle={Locale.Settings.PresencePenlty.SubTitle}
>
<input
type="range"
value={config.modelConfig.presence_penalty.toFixed(1)}
min="-2"
max="2"
step="0.5"
onChange={(e) => {
updateConfig(
(config) =>
(config.modelConfig.presence_penalty =
e.currentTarget.valueAsNumber)
);
}}
></input>
</SettingItem>
</List>
</div>
</>
);
}