import { useState, useEffect, useMemo, HTMLProps, useRef } from "react"; import styles from "./settings.module.scss"; import ResetIcon from "../icons/reload.svg"; import CloseIcon from "../icons/close.svg"; import CopyIcon from "../icons/copy.svg"; import ClearIcon from "../icons/clear.svg"; import EditIcon from "../icons/edit.svg"; import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib"; import { ModelConfigList } from "./model-config"; import { IconButton } from "./button"; import { SubmitKey, useChatStore, Theme, useUpdateStore, useAccessStore, useAppConfig, } from "../store"; import Locale, { AllLangs, changeLang, getLang } from "../locales"; import { copyToClipboard } from "../utils"; import Link from "next/link"; import { Path, UPDATE_URL } from "../constant"; import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { ErrorBoundary } from "./error"; import { InputRange } from "./input-range"; import { useNavigate } from "react-router-dom"; import { Avatar, AvatarPicker } from "./emoji"; function UserPromptModal(props: { onClose?: () => void }) { const promptStore = usePromptStore(); const userPrompts = promptStore.getUserPrompts(); const builtinPrompts = SearchService.builtinPrompts; const allPrompts = userPrompts.concat(builtinPrompts); const [searchInput, setSearchInput] = useState(""); const [searchPrompts, setSearchPrompts] = useState([]); const prompts = searchInput.length > 0 ? searchPrompts : allPrompts; useEffect(() => { if (searchInput.length > 0) { const searchResult = SearchService.search(searchInput); setSearchPrompts(searchResult); } else { setSearchPrompts([]); } }, [searchInput]); return (
props.onClose?.()} actions={[ promptStore.add({ title: "", content: "" })} icon={} bordered text={Locale.Settings.Prompt.Modal.Add} />, ]} >
setSearchInput(e.currentTarget.value)} >
{prompts.map((v, _) => (
{ if (v.isUser) { promptStore.updateUserPrompts( v.id!, (prompt) => (prompt.title = e.currentTarget.value), ); } }} >
{v.isUser && ( } bordered className={styles["user-prompt-button"]} onClick={() => promptStore.remove(v.id!)} /> )} } bordered className={styles["user-prompt-button"]} onClick={() => copyToClipboard(v.content)} />
{ if (v.isUser) { promptStore.updateUserPrompts( v.id!, (prompt) => (prompt.content = e.currentTarget.value), ); } }} />
))}
); } export function Settings() { const navigate = useNavigate(); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const config = useAppConfig(); const updateConfig = config.update; const resetConfig = config.reset; const [clearAllData, clearSessions] = useChatStore((state) => [ state.clearAllData, state.clearSessions, ]); const updateStore = useUpdateStore(); const [checkingUpdate, setCheckingUpdate] = useState(false); const currentVersion = updateStore.version; const remoteId = updateStore.remoteVersion; const hasNewVersion = currentVersion !== remoteId; function checkUpdate(force = false) { setCheckingUpdate(true); updateStore.getLatestVersion(force).then(() => { setCheckingUpdate(false); }); } const usage = { used: updateStore.used, subscription: updateStore.subscription, }; const [loadingUsage, setLoadingUsage] = useState(false); function checkUsage() { setLoadingUsage(true); updateStore.updateUsage().finally(() => { setLoadingUsage(false); }); } const accessStore = useAccessStore(); const enabledAccessControl = useMemo( () => accessStore.enabledAccessControl(), // eslint-disable-next-line react-hooks/exhaustive-deps [], ); const promptStore = usePromptStore(); const builtinCount = SearchService.count.builtin; const customCount = promptStore.getUserPrompts().length ?? 0; const [shouldShowPromptModal, setShowPromptModal] = useState(false); const showUsage = accessStore.isAuthorized(); useEffect(() => { // checks per minutes checkUpdate(); showUsage && checkUsage(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { const keydownEvent = (e: KeyboardEvent) => { if (e.key === "Escape") { navigate(Path.Home); } }; document.addEventListener("keydown", keydownEvent); return () => { document.removeEventListener("keydown", keydownEvent); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
{Locale.Settings.Title}
{Locale.Settings.SubTitle}
} onClick={() => { const confirmed = window.confirm( `${Locale.Settings.Actions.ConfirmClearAll.Confirm}`, ); if (confirmed) { clearSessions(); } }} bordered title={Locale.Settings.Actions.ClearAll} />
} onClick={() => { const confirmed = window.confirm( `${Locale.Settings.Actions.ConfirmResetAll.Confirm}`, ); if (confirmed) { resetConfig(); } }} bordered title={Locale.Settings.Actions.ResetAll} />
} onClick={() => navigate(Path.Home)} bordered title={Locale.Settings.Actions.Close} />
setShowEmojiPicker(false)} content={ { updateConfig((config) => (config.avatar = avatar)); setShowEmojiPicker(false); }} /> } open={showEmojiPicker} >
setShowEmojiPicker(true)} >
{checkingUpdate ? (
) : hasNewVersion ? ( {Locale.Settings.Update.GoToUpdate} ) : ( } text={Locale.Settings.Update.CheckUpdate} onClick={() => checkUpdate(true)} /> )} updateConfig( (config) => (config.fontSize = Number.parseInt(e.currentTarget.value)), ) } > updateConfig( (config) => (config.tightBorder = e.currentTarget.checked), ) } > updateConfig( (config) => (config.sendPreviewBubble = e.currentTarget.checked), ) } > {enabledAccessControl ? ( { accessStore.updateCode(e.currentTarget.value); }} /> ) : ( <> )} { accessStore.updateToken(e.currentTarget.value); }} /> {!showUsage || loadingUsage ? (
) : ( } text={Locale.Settings.Usage.Check} onClick={checkUsage} /> )} updateConfig( (config) => (config.disablePromptHint = e.currentTarget.checked), ) } > } text={Locale.Settings.Prompt.Edit} onClick={() => setShowPromptModal(true)} /> { const modelConfig = { ...config.modelConfig }; upater(modelConfig); config.update((config) => (config.modelConfig = modelConfig)); }} /> {shouldShowPromptModal && ( setShowPromptModal(false)} /> )}
); }