Merge branch 'main' b0c6f6d 20230616 into pro

This commit is contained in:
2023-06-22 13:20:21 +08:00
31 changed files with 794 additions and 735 deletions
+1 -3
View File
@@ -72,9 +72,7 @@ export function ChatItem(props: {
<div className={styles["chat-item-count"]}>
{Locale.ChatItem.ChatItemCount(props.count)}
</div>
<div className={styles["chat-item-date"]}>
{new Date(props.time).toLocaleString()}
</div>
<div className={styles["chat-item-date"]}>{props.time}</div>
</div>
</>
)}
+28
View File
@@ -17,10 +17,38 @@
transition: all ease 0.3s;
margin-bottom: 10px;
align-items: center;
height: 16px;
width: var(--icon-width);
&:not(:last-child) {
margin-right: 5px;
}
.text {
white-space: nowrap;
padding-left: 5px;
opacity: 0;
transform: translateX(-5px);
transition: all ease 0.3s;
transition-delay: 0.1s;
pointer-events: none;
}
&:hover {
width: var(--full-width);
.text {
opacity: 1;
transform: translate(0);
}
}
.text,
.icon {
display: flex;
align-items: center;
justify-content: center;
}
}
}
+102 -43
View File
@@ -1,5 +1,11 @@
import { useDebouncedCallback } from "use-debounce";
import { useState, useRef, useEffect, useLayoutEffect } from "react";
import React, {
useState,
useRef,
useEffect,
useLayoutEffect,
useMemo,
} from "react";
import SendWhiteIcon from "../icons/send-white.svg";
import BrainIcon from "../icons/brain.svg";
@@ -61,6 +67,7 @@ import { useMaskStore } from "../store/mask";
import { useCommand } from "../command";
import { prettyObject } from "../utils/format";
import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />,
@@ -279,6 +286,57 @@ function ClearContextDivider() {
);
}
function ChatAction(props: {
text: string;
icon: JSX.Element;
onClick: () => void;
}) {
const iconRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState({
full: 20,
icon: 20,
});
function updateWidth() {
if (!iconRef.current || !textRef.current) return;
const getWidth = (dom: HTMLDivElement) => dom.getBoundingClientRect().width;
const textWidth = getWidth(textRef.current);
const iconWidth = getWidth(iconRef.current);
setWidth({
full: textWidth + iconWidth,
icon: iconWidth,
});
}
useEffect(() => {
updateWidth();
}, []);
return (
<div
className={`${chatStyle["chat-input-action"]} clickable`}
onClick={() => {
props.onClick();
setTimeout(updateWidth, 1);
}}
style={
{
"--icon-width": `${width.icon}px`,
"--full-width": `${width.full}px`,
} as React.CSSProperties
}
>
<div ref={iconRef} className={chatStyle["icon"]}>
{props.icon}
</div>
<div className={chatStyle["text"]} ref={textRef}>
{props.text}
</div>
</div>
);
}
function useScrollToBottom() {
// for auto-scroll
const scrollRef = useRef<HTMLDivElement>(null);
@@ -330,61 +388,60 @@ export function ChatActions(props: {
return (
<div className={chatStyle["chat-input-actions"]}>
{couldStop && (
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={stopAll}
>
<StopIcon />
</div>
text={Locale.Chat.InputActions.Stop}
icon={<StopIcon />}
/>
)}
{!props.hitBottom && (
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={props.scrollToBottom}
>
<BottomIcon />
</div>
text={Locale.Chat.InputActions.ToBottom}
icon={<BottomIcon />}
/>
)}
{props.hitBottom && (
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={props.showPromptModal}
>
<SettingsIcon />
</div>
text={Locale.Chat.InputActions.Settings}
icon={<SettingsIcon />}
/>
)}
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={nextTheme}
>
{theme === Theme.Auto ? (
<AutoIcon />
) : theme === Theme.Light ? (
<LightIcon />
) : theme === Theme.Dark ? (
<DarkIcon />
) : null}
</div>
text={Locale.Chat.InputActions.Theme[theme]}
icon={
<>
{theme === Theme.Auto ? (
<AutoIcon />
) : theme === Theme.Light ? (
<LightIcon />
) : theme === Theme.Dark ? (
<DarkIcon />
) : null}
</>
}
/>
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={props.showPromptHints}
>
<PromptIcon />
</div>
text={Locale.Chat.InputActions.Prompt}
icon={<PromptIcon />}
/>
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
onClick={() => {
navigate(Path.Masks);
}}
>
<MaskIcon />
</div>
text={Locale.Chat.InputActions.Masks}
icon={<MaskIcon />}
/>
<div
className={`${chatStyle["chat-input-action"]} clickable`}
<ChatAction
text={Locale.Chat.InputActions.Clear}
icon={<BreakIcon />}
onClick={() => {
chatStore.updateCurrentSession((session) => {
if (session.clearContextIndex === session.messages.length) {
@@ -395,9 +452,7 @@ export function ChatActions(props: {
}
});
}}
>
<ClearIcon />
</div>
/>
</div>
);
}
@@ -656,9 +711,13 @@ export function Chat() {
}
};
const clientConfig = useMemo(() => getClientConfig(), []);
const location = useLocation();
const isChat = location.pathname === Path.Chat;
const autoFocus = !isMobileScreen || isChat; // only focus in chat page
const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
useCommand({
fill: setUserInput,
@@ -707,7 +766,7 @@ export function Chat() {
}}
/>
</div>
{!isMobileScreen && (
{showMaxIcon && (
<div className="window-action-button">
<IconButton
icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
+11 -1
View File
@@ -24,6 +24,7 @@ import {
import { SideBar } from "./sidebar";
import { useAppConfig } from "../store/config";
import { AuthPage } from "./auth";
import { getClientConfig } from "../config/client";
export function Loading(props: { noLogo?: boolean }) {
return (
@@ -93,9 +94,14 @@ const useHasHydrated = () => {
const loadAsyncGoogleFont = () => {
const linkEl = document.createElement("link");
const proxyFontUrl = "/google-fonts";
const remoteFontUrl = "https://fonts.googleapis.com";
const googleFontUrl =
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
linkEl.rel = "stylesheet";
linkEl.href =
"/google-fonts/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
googleFontUrl +
"/css2?family=Noto+Sans+SC:wght@300;400;700;900&display=swap";
document.head.appendChild(linkEl);
};
@@ -147,6 +153,10 @@ function Screen() {
export function Home() {
useSwitchTheme();
useEffect(() => {
console.log("[Config] got config from build time", getClientConfig());
}, []);
if (!useHasHydrated()) {
return <Loading />;
}
+42 -27
View File
@@ -11,6 +11,7 @@ import mermaid from "mermaid";
import LoadingIcon from "../icons/three-dots.svg";
import React from "react";
import { useThrottledCallback } from "use-debounce";
export function Mermaid(props: { code: string; onError: () => void }) {
const ref = useRef<HTMLDivElement>(null);
@@ -127,43 +128,57 @@ export function Markdown(
) {
const mdRef = useRef<HTMLDivElement>(null);
const renderedHeight = useRef(0);
const renderedWidth = useRef(0);
const inView = useRef(!!props.defaultShow);
const [_, triggerRender] = useState(0);
const checkInView = useThrottledCallback(
() => {
const parent = props.parentRef?.current;
const md = mdRef.current;
if (parent && md && !props.defaultShow) {
const parentBounds = parent.getBoundingClientRect();
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
const mdBounds = md.getBoundingClientRect();
const parentTop = parentBounds.top - twoScreenHeight;
const parentBottom = parentBounds.bottom + twoScreenHeight;
const isOverlap =
Math.max(parentTop, mdBounds.top) <=
Math.min(parentBottom, mdBounds.bottom);
inView.current = isOverlap;
triggerRender(Date.now());
}
const parent = props.parentRef?.current;
const md = mdRef.current;
if (inView.current && md) {
const rect = md.getBoundingClientRect();
renderedHeight.current = Math.max(renderedHeight.current, rect.height);
renderedWidth.current = Math.max(renderedWidth.current, rect.width);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},
300,
{
leading: true,
trailing: true,
},
);
const checkInView = () => {
if (parent && md) {
const parentBounds = parent.getBoundingClientRect();
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
const mdBounds = md.getBoundingClientRect();
const parentTop = parentBounds.top - twoScreenHeight;
const parentBottom = parentBounds.bottom + twoScreenHeight;
const isOverlap =
Math.max(parentTop, mdBounds.top) <=
Math.min(parentBottom, mdBounds.bottom);
inView.current = isOverlap;
}
useEffect(() => {
props.parentRef?.current?.addEventListener("scroll", checkInView);
checkInView();
return () =>
props.parentRef?.current?.removeEventListener("scroll", checkInView);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (inView.current && md) {
renderedHeight.current = Math.max(
renderedHeight.current,
md.getBoundingClientRect().height,
);
}
};
setTimeout(() => checkInView(), 1);
const getSize = (x: number) => (!inView.current && x > 0 ? x : "auto");
return (
<div
className="markdown-body"
style={{
fontSize: `${props.fontSize ?? 14}px`,
height:
!inView.current && renderedHeight.current > 0
? renderedHeight.current
: "auto",
height: getSize(renderedHeight.current),
width: getSize(renderedWidth.current),
}}
ref={mdRef}
onContextMenu={props.onContextMenu}
+6 -1
View File
@@ -185,7 +185,12 @@ function ContextPromptItem(props: {
className={chatStyle["context-content"]}
rows={focusingInput ? 5 : 1}
onFocus={() => setFocusingInput(true)}
onBlur={() => setFocusingInput(false)}
onBlur={() => {
setFocusingInput(false);
// If the selection is not removed when the user loses focus, some
// extensions like "Translate" will always display a floating bar
window?.getSelection()?.removeAllRanges();
}}
onInput={(e) =>
props.update({
...props.prompt,
-595
View File
@@ -1,596 +1 @@
export {};
// import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
// import styles from "./settings.module.scss";
// import ResetIcon from "../icons/reload.svg";
// import AddIcon from "../icons/add.svg";
// import CloseIcon from "../icons/close.svg";
// import CopyIcon from "../icons/copy.svg";
// import ClearIcon from "../icons/clear.svg";
// import LoadingIcon from "../icons/three-dots.svg";
// import EditIcon from "../icons/edit.svg";
// import EyeIcon from "../icons/eye.svg";
// import {
// Input,
// List,
// ListItem,
// Modal,
// PasswordInput,
// Popover,
// Select,
// } from "./ui-lib";
// import { ModelConfigList } from "./model-config";
// import { IconButton } from "./button";
// import {
// SubmitKey,
// useChatStore,
// Theme,
// useUpdateStore,
// useAccessStore,
// useAppConfig,
// } from "../store";
// import Locale, {
// AllLangs,
// ALL_LANG_OPTIONS,
// 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 EditPromptModal(props: { id: number; onClose: () => void }) {
// 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.update(
// 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.update(
// props.id,
// (prompt) => (prompt.content = e.currentTarget.value),
// )
// }
// ></Input>
// </div>
// </Modal>
// </div>
// ) : null;
// }
// 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<Prompt[]>([]);
// const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
// const [editingPromptId, setEditingPromptId] = useState<number>();
// useEffect(() => {
// if (searchInput.length > 0) {
// const searchResult = SearchService.search(searchInput);
// setSearchPrompts(searchResult);
// } else {
// setSearchPrompts([]);
// }
// }, [searchInput]);
// return (
// <div className="modal-mask">
// <Modal
// title={Locale.Settings.Prompt.Modal.Title}
// onClose={() => props.onClose?.()}
// actions={[
// <IconButton
// key="add"
// onClick={() =>
// promptStore.add({
// title: "Empty Prompt",
// content: "Empty Prompt Content",
// })
// }
// icon={<AddIcon />}
// 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"]}>
// <div className={styles["user-prompt-title"]}>{v.title}</div>
// <div className={styles["user-prompt-content"] + " one-line"}>
// {v.content}
// </div>
// </div>
// <div className={styles["user-prompt-buttons"]}>
// {v.isUser && (
// <IconButton
// icon={<ClearIcon />}
// className={styles["user-prompt-button"]}
// onClick={() => promptStore.remove(v.id!)}
// />
// )}
// {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)}
// />
// </div>
// </div>
// ))}
// </div>
// </div>
// </Modal>
// {editingPromptId !== undefined && (
// <EditPromptModal
// id={editingPromptId!}
// onClose={() => setEditingPromptId(undefined)}
// />
// )}
// </div>
// );
// }
// function formatVersionDate(t: string) {
// const d = new Date(+t);
// const year = d.getUTCFullYear();
// const month = d.getUTCMonth() + 1;
// const day = d.getUTCDate();
// return [
// year.toString(),
// month.toString().padStart(2, "0"),
// day.toString().padStart(2, "0"),
// ].join("");
// }
// export function Settings() {
// const navigate = useNavigate();
// const [showEmojiPicker, setShowEmojiPicker] = useState(false);
// const config = useAppConfig();
// const updateConfig = config.update;
// const resetConfig = config.reset;
// const chatStore = useChatStore();
// const updateStore = useUpdateStore();
// const [checkingUpdate, setCheckingUpdate] = useState(false);
// const currentVersion = formatVersionDate(updateStore.version);
// const remoteId = formatVersionDate(updateStore.remoteVersion);
// const hasNewVersion = currentVersion !== remoteId;
// function checkUpdate(force = false) {
// setCheckingUpdate(true);
// updateStore.getLatestVersion(force).then(() => {
// setCheckingUpdate(false);
// });
// console.log(
// "[Update] local version ",
// new Date(+updateStore.version).toLocaleString(),
// );
// console.log(
// "[Update] remote version ",
// new Date(+updateStore.remoteVersion).toLocaleString(),
// );
// }
// const usage = {
// used: updateStore.used,
// subscription: updateStore.subscription,
// };
// const [loadingUsage, setLoadingUsage] = useState(false);
// function checkUsage(force = false) {
// setLoadingUsage(true);
// updateStore.updateUsage(force).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 (
// <ErrorBoundary>
// <div className="window-header">
// <div className="window-header-title">
// <div className="window-header-main-title">
// {Locale.Settings.Title}
// </div>
// <div className="window-header-sub-title">
// {Locale.Settings.SubTitle}
// </div>
// </div>
// <div className="window-actions">
// <div className="window-action-button">
// <IconButton
// icon={<ClearIcon />}
// onClick={() => {
// if (confirm(Locale.Settings.Actions.ConfirmClearAll)) {
// chatStore.clearAllData();
// }
// }}
// bordered
// title={Locale.Settings.Actions.ClearAll}
// />
// </div>
// <div className="window-action-button">
// <IconButton
// icon={<ResetIcon />}
// onClick={() => {
// if (confirm(Locale.Settings.Actions.ConfirmResetAll)) {
// resetConfig();
// }
// }}
// bordered
// title={Locale.Settings.Actions.ResetAll}
// />
// </div>
// <div className="window-action-button">
// <IconButton
// icon={<CloseIcon />}
// onClick={() => navigate(Path.Home)}
// bordered
// title={Locale.Settings.Actions.Close}
// />
// </div>
// </div>
// </div>
// <div className={styles["settings"]}>
// <List>
// <ListItem title={Locale.Settings.Avatar}>
// <Popover
// onClose={() => setShowEmojiPicker(false)}
// content={
// <AvatarPicker
// onEmojiClick={(avatar: string) => {
// updateConfig((config) => (config.avatar = avatar));
// setShowEmojiPicker(false);
// }}
// />
// }
// open={showEmojiPicker}
// >
// <div
// className={styles.avatar}
// onClick={() => setShowEmojiPicker(true)}
// >
// <Avatar avatar={config.avatar} />
// </div>
// </Popover>
// </ListItem>
// <ListItem
// title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
// subTitle={
// checkingUpdate
// ? Locale.Settings.Update.IsChecking
// : hasNewVersion
// ? Locale.Settings.Update.FoundUpdate(remoteId ?? "ERROR")
// : Locale.Settings.Update.IsLatest
// }
// >
// {checkingUpdate ? (
// <LoadingIcon />
// ) : 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)}
// />
// )}
// </ListItem>
// <ListItem 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>
// </ListItem>
// <ListItem title={Locale.Settings.Theme}>
// <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>
// <ListItem title={Locale.Settings.Lang.Name}>
// <Select
// value={getLang()}
// onChange={(e) => {
// changeLang(e.target.value as any);
// }}
// >
// {AllLangs.map((lang) => (
// <option value={lang} key={lang}>
// {ALL_LANG_OPTIONS[lang]}
// </option>
// ))}
// </Select>
// </ListItem>
// <ListItem
// title={Locale.Settings.FontSize.Title}
// subTitle={Locale.Settings.FontSize.SubTitle}
// >
// <InputRange
// title={`${config.fontSize ?? 14}px`}
// value={config.fontSize}
// min="12"
// max="18"
// step="1"
// onChange={(e) =>
// updateConfig(
// (config) =>
// (config.fontSize = Number.parseInt(e.currentTarget.value)),
// )
// }
// ></InputRange>
// </ListItem>
// <ListItem
// title={Locale.Settings.SendPreviewBubble.Title}
// subTitle={Locale.Settings.SendPreviewBubble.SubTitle}
// >
// <input
// type="checkbox"
// checked={config.sendPreviewBubble}
// onChange={(e) =>
// updateConfig(
// (config) =>
// (config.sendPreviewBubble = e.currentTarget.checked),
// )
// }
// ></input>
// </ListItem>
// <ListItem
// title={Locale.Settings.Mask.Title}
// subTitle={Locale.Settings.Mask.SubTitle}
// >
// <input
// type="checkbox"
// checked={!config.dontShowMaskSplashScreen}
// onChange={(e) =>
// updateConfig(
// (config) =>
// (config.dontShowMaskSplashScreen =
// !e.currentTarget.checked),
// )
// }
// ></input>
// </ListItem>
// </List>
// <List>
// {enabledAccessControl ? (
// <ListItem
// title={Locale.Settings.AccessCode.Title}
// subTitle={Locale.Settings.AccessCode.SubTitle}
// >
// <PasswordInput
// value={accessStore.accessCode}
// type="text"
// placeholder={Locale.Settings.AccessCode.Placeholder}
// onChange={(e) => {
// accessStore.updateCode(e.currentTarget.value);
// }}
// />
// </ListItem>
// ) : (
// <></>
// )}
// {!accessStore.hideUserApiKey ? (
// <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}
// <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>
// </List>
// <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>
// <List>
// <ModelConfigList
// modelConfig={config.modelConfig}
// updateConfig={(updater) => {
// const modelConfig = { ...config.modelConfig };
// updater(modelConfig);
// config.update((config) => (config.modelConfig = modelConfig));
// }}
// />
// </List>
// {shouldShowPromptModal && (
// <UserPromptModal onClose={() => setShowPromptModal(false)} />
// )}
// </div>
// </ErrorBoundary>
// );
// }