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

740 lines
22 KiB
TypeScript
Raw Normal View History

import { useState, useEffect, useMemo } from "react";
2023-03-13 16:25:07 +00:00
import styles from "./settings.module.scss";
import ResetIcon from "../icons/reload.svg";
2023-05-01 18:26:43 +00:00
import AddIcon from "../icons/add.svg";
2023-03-14 17:44:42 +00:00
import CloseIcon from "../icons/close.svg";
2023-04-17 17:34:12 +00:00
import CopyIcon from "../icons/copy.svg";
2023-03-19 15:13:10 +00:00
import ClearIcon from "../icons/clear.svg";
import LoadingIcon from "../icons/three-dots.svg";
2023-03-28 17:30:11 +00:00
import EditIcon from "../icons/edit.svg";
2023-05-01 18:26:43 +00:00
import EyeIcon from "../icons/eye.svg";
import DownloadIcon from "../icons/download.svg";
import UploadIcon from "../icons/upload.svg";
import {
Input,
List,
ListItem,
Modal,
PasswordInput,
Popover,
Select,
showConfirm,
2023-09-10 16:34:51 +00:00
showToast,
} from "./ui-lib";
2023-04-22 17:27:15 +00:00
import { ModelConfigList } from "./model-config";
2023-03-13 16:25:07 +00:00
import { IconButton } from "./button";
import {
SubmitKey,
useChatStore,
Theme,
useUpdateStore,
useAccessStore,
2023-04-21 16:12:07 +00:00
useAppConfig,
} from "../store";
2023-03-13 16:25:07 +00:00
import Locale, {
AllLangs,
ALL_LANG_OPTIONS,
changeLang,
getLang,
} from "../locales";
2023-04-22 17:27:15 +00:00
import { copyToClipboard } from "../utils";
2023-03-23 16:01:00 +00:00
import Link from "next/link";
import { Path, RELEASE_URL, UPDATE_URL } from "../constant";
2023-04-17 17:34:12 +00:00
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
2023-04-03 17:05:33 +00:00
import { ErrorBoundary } from "./error";
import { InputRange } from "./input-range";
2023-04-20 17:12:39 +00:00
import { useNavigate } from "react-router-dom";
2023-04-22 17:27:15 +00:00
import { Avatar, AvatarPicker } from "./emoji";
import { getClientConfig } from "../config/client";
2023-06-27 16:34:01 +00:00
import { useSyncStore } from "../store/sync";
2023-07-09 11:37:42 +00:00
import { nanoid } from "nanoid";
import { useMaskStore } from "../store/mask";
2023-03-21 16:20:32 +00:00
2023-07-09 11:37:42 +00:00
function EditPromptModal(props: { id: string; onClose: () => void }) {
2023-05-01 18:26:43 +00:00
const promptStore = usePromptStore();
const prompt = promptStore.get(props.id);
return prompt ? (
<div className="modal-mask">
<Modal
title={Locale.Settings.Prompt.EditModal.Title}
onClose={props.onClose}
actions={[
<IconButton
key=""
onClick={props.onClose}
text={Locale.UI.Confirm}
bordered
/>,
]}
>
<div className={styles["edit-prompt-modal"]}>
<input
type="text"
value={prompt.title}
readOnly={!prompt.isUser}
className={styles["edit-prompt-title"]}
onInput={(e) =>
promptStore.updatePrompt(
2023-05-01 18:26:43 +00:00
props.id,
(prompt) => (prompt.title = e.currentTarget.value),
)
}
></input>
<Input
value={prompt.content}
readOnly={!prompt.isUser}
className={styles["edit-prompt-content"]}
rows={10}
onInput={(e) =>
promptStore.updatePrompt(
2023-05-01 18:26:43 +00:00
props.id,
(prompt) => (prompt.content = e.currentTarget.value),
)
}
></Input>
</div>
</Modal>
</div>
) : null;
}
2023-04-17 17:34:12 +00:00
function UserPromptModal(props: { onClose?: () => void }) {
2023-04-17 15:12:27 +00:00
const promptStore = usePromptStore();
2023-04-17 17:34:12 +00:00
const userPrompts = promptStore.getUserPrompts();
const builtinPrompts = SearchService.builtinPrompts;
const allPrompts = userPrompts.concat(builtinPrompts);
const [searchInput, setSearchInput] = useState("");
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
2023-07-09 11:37:42 +00:00
const [editingPromptId, setEditingPromptId] = useState<string>();
2023-05-01 18:26:43 +00:00
2023-04-17 17:34:12 +00:00
useEffect(() => {
if (searchInput.length > 0) {
const searchResult = SearchService.search(searchInput);
setSearchPrompts(searchResult);
} else {
setSearchPrompts([]);
}
}, [searchInput]);
2023-04-17 15:12:27 +00:00
2023-04-17 17:34:12 +00:00
return (
<div className="modal-mask">
<Modal
title={Locale.Settings.Prompt.Modal.Title}
onClose={() => props.onClose?.()}
actions={[
<IconButton
key="add"
onClick={() => {
const promptId = promptStore.add({
2023-07-09 11:37:42 +00:00
id: nanoid(),
createdAt: Date.now(),
2023-05-01 18:26:43 +00:00
title: "Empty Prompt",
content: "Empty Prompt Content",
});
setEditingPromptId(promptId);
}}
2023-05-01 18:26:43 +00:00
icon={<AddIcon />}
2023-04-17 17:34:12 +00:00
bordered
text={Locale.Settings.Prompt.Modal.Add}
/>,
]}
>
<div className={styles["user-prompt-modal"]}>
<input
type="text"
className={styles["user-prompt-search"]}
placeholder={Locale.Settings.Prompt.Modal.Search}
value={searchInput}
onInput={(e) => setSearchInput(e.currentTarget.value)}
></input>
<div className={styles["user-prompt-list"]}>
{prompts.map((v, _) => (
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
<div className={styles["user-prompt-header"]}>
2023-05-01 18:26:43 +00:00
<div className={styles["user-prompt-title"]}>{v.title}</div>
<div className={styles["user-prompt-content"] + " one-line"}>
{v.content}
</div>
</div>
2023-04-17 17:34:12 +00:00
2023-05-01 18:26:43 +00:00
<div className={styles["user-prompt-buttons"]}>
{v.isUser && (
2023-04-17 17:34:12 +00:00
<IconButton
2023-05-01 18:26:43 +00:00
icon={<ClearIcon />}
2023-04-17 17:34:12 +00:00
className={styles["user-prompt-button"]}
2023-05-01 18:26:43 +00:00
onClick={() => promptStore.remove(v.id!)}
2023-04-17 17:34:12 +00:00
/>
2023-05-01 18:26:43 +00:00
)}
{v.isUser ? (
<IconButton
icon={<EditIcon />}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
) : (
<IconButton
icon={<EyeIcon />}
className={styles["user-prompt-button"]}
onClick={() => setEditingPromptId(v.id)}
/>
)}
<IconButton
icon={<CopyIcon />}
className={styles["user-prompt-button"]}
onClick={() => copyToClipboard(v.content)}
/>
2023-04-17 17:34:12 +00:00
</div>
</div>
))}
</div>
</div>
</Modal>
2023-05-01 18:26:43 +00:00
{editingPromptId !== undefined && (
<EditPromptModal
id={editingPromptId!}
onClose={() => setEditingPromptId(undefined)}
/>
)}
2023-04-17 17:34:12 +00:00
</div>
);
2023-04-17 15:12:27 +00:00
}
2023-06-28 17:09:51 +00:00
function DangerItems() {
const chatStore = useChatStore();
const appConfig = useAppConfig();
return (
<List>
<ListItem
title={Locale.Settings.Danger.Reset.Title}
subTitle={Locale.Settings.Danger.Reset.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Reset.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Reset.Confirm)) {
appConfig.reset();
}
}}
type="danger"
/>
</ListItem>
<ListItem
title={Locale.Settings.Danger.Clear.Title}
subTitle={Locale.Settings.Danger.Clear.SubTitle}
>
<IconButton
text={Locale.Settings.Danger.Clear.Action}
onClick={async () => {
if (await showConfirm(Locale.Settings.Danger.Clear.Confirm)) {
chatStore.clearAllData();
}
}}
type="danger"
/>
</ListItem>
</List>
);
}
2023-06-27 16:34:01 +00:00
function SyncItems() {
const syncStore = useSyncStore();
const webdav = syncStore.webDavConfig;
const chatStore = useChatStore();
const promptStore = usePromptStore();
const maskStore = useMaskStore();
const stateOverview = useMemo(() => {
const sessions = chatStore.sessions;
const messageCount = sessions.reduce((p, c) => p + c.messages.length, 0);
2023-06-27 16:34:01 +00:00
return {
chat: sessions.length,
message: messageCount,
prompt: Object.keys(promptStore.prompts).length,
mask: Object.keys(maskStore.masks).length,
};
}, [chatStore.sessions, maskStore.masks, promptStore.prompts]);
2023-06-27 16:34:01 +00:00
return (
<List>
<ListItem
title={Locale.Settings.Sync.LastUpdate}
subTitle={new Date().toLocaleString()}
2023-06-27 16:34:01 +00:00
>
<IconButton
icon={<ResetIcon />}
text={Locale.UI.Sync}
2023-06-27 16:34:01 +00:00
onClick={() => {
2023-09-10 16:34:51 +00:00
showToast(Locale.WIP);
2023-06-27 16:34:01 +00:00
}}
/>
</ListItem>
<ListItem
title={Locale.Settings.Sync.LocalState}
subTitle={Locale.Settings.Sync.Overview(stateOverview)}
2023-06-27 16:34:01 +00:00
>
<div style={{ display: "flex" }}>
<IconButton
icon={<UploadIcon />}
text={Locale.UI.Export}
onClick={() => {
syncStore.export();
}}
/>
<IconButton
icon={<DownloadIcon />}
text={Locale.UI.Import}
onClick={() => {
syncStore.import();
}}
/>
</div>
2023-06-27 16:34:01 +00:00
</ListItem>
</List>
);
}
2023-04-20 17:12:39 +00:00
export function Settings() {
const navigate = useNavigate();
2023-03-13 16:25:07 +00:00
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
2023-04-21 16:12:07 +00:00
const config = useAppConfig();
const updateConfig = config.update;
2023-03-13 16:25:07 +00:00
const updateStore = useUpdateStore();
2023-03-23 16:01:00 +00:00
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentVersion = updateStore.formatVersion(updateStore.version);
const remoteId = updateStore.formatVersion(updateStore.remoteVersion);
2023-04-10 17:21:34 +00:00
const hasNewVersion = currentVersion !== remoteId;
const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;
2023-03-23 16:01:00 +00:00
function checkUpdate(force = false) {
setCheckingUpdate(true);
2023-04-10 18:54:31 +00:00
updateStore.getLatestVersion(force).then(() => {
2023-03-23 16:01:00 +00:00
setCheckingUpdate(false);
});
2023-05-03 16:12:00 +00:00
console.log("[Update] local version ", updateStore.version);
console.log("[Update] remote version ", updateStore.remoteVersion);
2023-03-23 16:01:00 +00:00
}
2023-04-17 15:12:27 +00:00
const usage = {
used: updateStore.used,
subscription: updateStore.subscription,
};
const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage(force = false) {
if (accessStore.hideBalanceQuery) {
return;
}
setLoadingUsage(true);
updateStore.updateUsage(force).finally(() => {
2023-04-17 15:12:27 +00:00
setLoadingUsage(false);
});
}
const accessStore = useAccessStore();
const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(),
2023-04-03 17:05:33 +00:00
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
2023-03-28 17:30:11 +00:00
const promptStore = usePromptStore();
const builtinCount = SearchService.count.builtin;
2023-04-17 17:34:12 +00:00
const customCount = promptStore.getUserPrompts().length ?? 0;
const [shouldShowPromptModal, setShowPromptModal] = useState(false);
2023-03-28 17:30:11 +00:00
2023-04-09 15:51:12 +00:00
const showUsage = accessStore.isAuthorized();
2023-04-03 12:18:04 +00:00
useEffect(() => {
2023-04-17 15:12:27 +00:00
// checks per minutes
2023-04-05 19:56:54 +00:00
checkUpdate();
showUsage && checkUsage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
2023-04-03 12:18:04 +00:00
useEffect(() => {
const keydownEvent = (e: KeyboardEvent) => {
if (e.key === "Escape") {
2023-04-20 17:12:39 +00:00
navigate(Path.Home);
}
};
document.addEventListener("keydown", keydownEvent);
return () => {
document.removeEventListener("keydown", keydownEvent);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const clientConfig = useMemo(() => getClientConfig(), []);
const showAccessCode = enabledAccessControl && !clientConfig?.isApp;
2023-03-13 16:25:07 +00:00
return (
2023-04-03 17:05:33 +00:00
<ErrorBoundary>
2023-06-15 15:20:14 +00:00
<div className="window-header" data-tauri-drag-region>
2023-04-24 16:49:27 +00:00
<div className="window-header-title">
<div className="window-header-main-title">
2023-03-21 16:20:32 +00:00
{Locale.Settings.Title}
</div>
2023-04-24 16:49:27 +00:00
<div className="window-header-sub-title">
2023-03-21 16:20:32 +00:00
{Locale.Settings.SubTitle}
</div>
2023-03-13 16:25:07 +00:00
</div>
2023-04-24 16:49:27 +00:00
<div className="window-actions">
2023-06-28 17:09:51 +00:00
<div className="window-action-button"></div>
<div className="window-action-button"></div>
2023-04-24 16:49:27 +00:00
<div className="window-action-button">
2023-03-19 15:13:10 +00:00
<IconButton
icon={<CloseIcon />}
2023-04-20 17:12:39 +00:00
onClick={() => navigate(Path.Home)}
2023-03-19 15:13:10 +00:00
bordered
/>
</div>
2023-03-13 16:25:07 +00:00
</div>
</div>
<div className={styles["settings"]}>
<List>
2023-04-22 17:27:15 +00:00
<ListItem title={Locale.Settings.Avatar}>
2023-03-13 16:25:07 +00:00
<Popover
onClose={() => setShowEmojiPicker(false)}
content={
2023-04-22 17:27:15 +00:00
<AvatarPicker
onEmojiClick={(avatar: string) => {
updateConfig((config) => (config.avatar = avatar));
2023-03-13 16:25:07 +00:00
setShowEmojiPicker(false);
}}
/>
}
open={showEmojiPicker}
>
<div
className={styles.avatar}
onClick={() => setShowEmojiPicker(true)}
>
2023-04-22 17:27:15 +00:00
<Avatar avatar={config.avatar} />
2023-03-13 16:25:07 +00:00
</div>
</Popover>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-13 16:25:07 +00:00
2023-04-22 17:27:15 +00:00
<ListItem
2023-04-10 17:21:34 +00:00
title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
2023-03-23 16:01:00 +00:00
subTitle={
checkingUpdate
? Locale.Settings.Update.IsChecking
: hasNewVersion
? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
: Locale.Settings.Update.IsLatest
}
>
{checkingUpdate ? (
<LoadingIcon />
2023-03-23 16:01:00 +00:00
) : hasNewVersion ? (
<Link href={updateUrl} target="_blank" className="link">
2023-03-23 16:01:00 +00:00
{Locale.Settings.Update.GoToUpdate}
</Link>
) : (
<IconButton
icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Update.CheckUpdate}
onClick={() => checkUpdate(true)}
/>
)}
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-23 16:01:00 +00:00
2023-04-22 17:27:15 +00:00
<ListItem title={Locale.Settings.SendKey}>
<Select
2023-03-21 16:20:32 +00:00
value={config.submitKey}
onChange={(e) => {
updateConfig(
(config) =>
(config.submitKey = e.target.value as any as SubmitKey),
2023-03-21 16:20:32 +00:00
);
}}
>
{Object.values(SubmitKey).map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</Select>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-13 16:25:07 +00:00
2023-04-22 17:27:15 +00:00
<ListItem title={Locale.Settings.Theme}>
<Select
2023-03-21 16:20:32 +00:00
value={config.theme}
onChange={(e) => {
updateConfig(
(config) => (config.theme = e.target.value as any as Theme),
2023-03-21 16:20:32 +00:00
);
}}
>
{Object.values(Theme).map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</Select>
2023-03-13 16:25:07 +00:00
</ListItem>
2023-04-22 17:27:15 +00:00
<ListItem title={Locale.Settings.Lang.Name}>
<Select
2023-03-28 17:39:14 +00:00
value={getLang()}
onChange={(e) => {
changeLang(e.target.value as any);
}}
>
{AllLangs.map((lang) => (
<option value={lang} key={lang}>
{ALL_LANG_OPTIONS[lang]}
2023-03-20 16:17:45 +00:00
</option>
2023-03-28 17:39:14 +00:00
))}
</Select>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-20 16:17:45 +00:00
2023-04-22 17:27:15 +00:00
<ListItem
2023-03-28 06:37:44 +00:00
title={Locale.Settings.FontSize.Title}
subTitle={Locale.Settings.FontSize.SubTitle}
>
<InputRange
2023-03-29 08:06:22 +00:00
title={`${config.fontSize ?? 14}px`}
2023-03-28 06:37:44 +00:00
value={config.fontSize}
min="12"
max="18"
step="1"
onChange={(e) =>
updateConfig(
(config) =>
(config.fontSize = Number.parseInt(e.currentTarget.value)),
2023-03-28 06:37:44 +00:00
)
}
></InputRange>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-21 17:16:36 +00:00
<ListItem
title={Locale.Settings.AutoGenerateTitle.Title}
subTitle={Locale.Settings.AutoGenerateTitle.SubTitle}
>
<input
type="checkbox"
checked={config.enableAutoGenerateTitle}
onChange={(e) =>
updateConfig(
(config) =>
(config.enableAutoGenerateTitle = e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.SendPreviewBubble.Title}
subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
>
2023-03-30 16:20:47 +00:00
<input
type="checkbox"
checked={config.sendPreviewBubble}
2023-03-30 16:20:47 +00:00
onChange={(e) =>
updateConfig(
(config) =>
(config.sendPreviewBubble = e.currentTarget.checked),
2023-03-30 16:20:47 +00:00
)
}
></input>
2023-04-22 17:27:15 +00:00
</ListItem>
</List>
<SyncItems />
<List>
<ListItem
title={Locale.Settings.Mask.Splash.Title}
subTitle={Locale.Settings.Mask.Splash.SubTitle}
>
<input
type="checkbox"
checked={!config.dontShowMaskSplashScreen}
onChange={(e) =>
updateConfig(
(config) =>
(config.dontShowMaskSplashScreen =
!e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Mask.Builtin.Title}
subTitle={Locale.Settings.Mask.Builtin.SubTitle}
>
<input
type="checkbox"
2023-07-05 14:39:25 +00:00
checked={config.hideBuiltinMasks}
onChange={(e) =>
updateConfig(
(config) =>
2023-07-05 14:39:25 +00:00
(config.hideBuiltinMasks = e.currentTarget.checked),
)
}
></input>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-03-13 16:25:07 +00:00
</List>
2023-03-28 17:30:11 +00:00
<List>
<ListItem
title={Locale.Settings.Prompt.Disable.Title}
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
>
<input
type="checkbox"
checked={config.disablePromptHint}
onChange={(e) =>
updateConfig(
(config) =>
(config.disablePromptHint = e.currentTarget.checked),
)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Prompt.List}
subTitle={Locale.Settings.Prompt.ListCount(
builtinCount,
customCount,
)}
>
<IconButton
icon={<EditIcon />}
text={Locale.Settings.Prompt.Edit}
onClick={() => setShowPromptModal(true)}
/>
</ListItem>
</List>
2023-03-13 16:25:07 +00:00
<List>
{showAccessCode ? (
2023-04-22 17:27:15 +00:00
<ListItem
title={Locale.Settings.AccessCode.Title}
subTitle={Locale.Settings.AccessCode.SubTitle}
>
2023-04-03 12:18:04 +00:00
<PasswordInput
value={accessStore.accessCode}
type="text"
placeholder={Locale.Settings.AccessCode.Placeholder}
onChange={(e) => {
accessStore.updateCode(e.currentTarget.value);
}}
2023-04-03 12:18:04 +00:00
/>
2023-04-22 17:27:15 +00:00
</ListItem>
) : (
<></>
)}
{!accessStore.hideUserApiKey ? (
<>
<ListItem
title={Locale.Settings.Endpoint.Title}
subTitle={Locale.Settings.Endpoint.SubTitle}
>
<input
type="text"
value={accessStore.openaiUrl}
placeholder="https://api.openai.com/"
onChange={(e) =>
accessStore.updateOpenAiUrl(e.currentTarget.value)
}
></input>
</ListItem>
<ListItem
title={Locale.Settings.Token.Title}
subTitle={Locale.Settings.Token.SubTitle}
>
<PasswordInput
value={accessStore.token}
type="text"
placeholder={Locale.Settings.Token.Placeholder}
onChange={(e) => {
accessStore.updateToken(e.currentTarget.value);
}}
/>
</ListItem>
</>
) : null}
2023-03-26 11:58:25 +00:00
{!accessStore.hideBalanceQuery ? (
<ListItem
title={Locale.Settings.Usage.Title}
subTitle={
showUsage
? loadingUsage
? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle(
usage?.used ?? "[?]",
usage?.subscription ?? "[?]",
)
: Locale.Settings.Usage.NoAccess
}
>
{!showUsage || loadingUsage ? (
<div />
) : (
<IconButton
icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Usage.Check}
onClick={() => checkUsage(true)}
/>
)}
</ListItem>
) : null}
2023-03-21 16:20:32 +00:00
2023-04-22 17:27:15 +00:00
<ListItem
title={Locale.Settings.CustomModel.Title}
subTitle={Locale.Settings.CustomModel.SubTitle}
2023-04-09 15:51:12 +00:00
>
<input
type="text"
value={config.customModels}
placeholder="model1,model2,model3"
2023-04-09 15:51:12 +00:00
onChange={(e) =>
config.update(
(config) => (config.customModels = e.currentTarget.value),
2023-04-09 15:51:12 +00:00
)
}
></input>
2023-04-22 17:27:15 +00:00
</ListItem>
2023-04-09 15:51:12 +00:00
</List>
2023-04-22 17:37:47 +00:00
<List>
<ModelConfigList
modelConfig={config.modelConfig}
updateConfig={(updater) => {
2023-04-22 17:37:47 +00:00
const modelConfig = { ...config.modelConfig };
updater(modelConfig);
2023-04-22 17:37:47 +00:00
config.update((config) => (config.modelConfig = modelConfig));
}}
/>
</List>
2023-04-17 17:34:12 +00:00
{shouldShowPromptModal && (
<UserPromptModal onClose={() => setShowPromptModal(false)} />
)}
2023-06-28 17:09:51 +00:00
<DangerItems />
2023-03-13 16:25:07 +00:00
</div>
2023-04-03 17:05:33 +00:00
</ErrorBoundary>
2023-03-13 16:25:07 +00:00
);
}