Browse Source

feat: add settings ui

Yidadaa 2 years ago
parent
commit
a9940cb05e
9 changed files with 326 additions and 218 deletions
  1. 23 5
      app/components/home.module.css
  2. 111 9
      app/components/home.tsx
  3. 56 0
      app/components/ui-lib.module.css
  4. 38 0
      app/components/ui-lib.tsx
  5. 7 0
      app/globals.css
  6. 27 10
      app/requests.ts
  7. 49 5
      app/store.ts
  8. 1 0
      package.json
  9. 14 189
      yarn.lock

+ 23 - 5
app/components/home.module.css

@@ -14,7 +14,7 @@
 }
 
 .sidebar {
-  max-width: 300px;
+  width: 300px;
   padding: 20px;
   background-color: var(--second);
   display: flex;
@@ -141,14 +141,19 @@
   margin-left: 15px;
 }
 
+.window-content {
+  width: 100%;
+  height: 100%;
+}
+
 .chat {
   display: flex;
   flex-direction: column;
-  width: 100%;
   position: relative;
+  height: 100%;
 }
 
-.chat-header {
+.window-header {
   padding: 14px 20px;
   border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
 
@@ -157,7 +162,7 @@
   align-items: center;
 }
 
-.chat-header-title {
+.window-header-title {
   font-size: 20px;
   font-weight: bolder;
   overflow: hidden;
@@ -167,7 +172,7 @@
   -webkit-box-orient: vertical;
 }
 
-.chat-header-sub-title {
+.window-header-sub-title {
   font-size: 14px;
   margin-top: 5px;
 }
@@ -303,3 +308,16 @@
   right: 30px;
   bottom: 10px;
 }
+
+.settings {
+  padding: 20px;
+}
+
+.settings-title {
+  font-size: 14px;
+  font-weight: bolder;
+}
+
+.avatar {
+  cursor: pointer;
+}

+ 111 - 9
app/components/home.tsx

@@ -6,6 +6,8 @@ import "katex/dist/katex.min.css";
 import RemarkMath from "remark-math";
 import RehypeKatex from "rehype-katex";
 
+import EmojiPicker, { Emoji, EmojiClickData } from "emoji-picker-react";
+
 import { IconButton } from "./button";
 import styles from "./home.module.css";
 
@@ -20,7 +22,8 @@ import AddIcon from "../icons/add.svg";
 import DeleteIcon from "../icons/delete.svg";
 import LoadingIcon from "../icons/three-dots.svg";
 
-import { Message, useChatStore } from "../store";
+import { Message, SubmitKey, useChatStore } from "../store";
+import { Card, List, ListItem, Popover } from "./ui-lib";
 
 export function Markdown(props: { content: string }) {
   return (
@@ -31,11 +34,17 @@ export function Markdown(props: { content: string }) {
 }
 
 export function Avatar(props: { role: Message["role"] }) {
+  const config = useChatStore((state) => state.config);
+
   if (props.role === "assistant") {
     return <BotIcon className={styles["user-avtar"]} />;
   }
 
-  return <div className={styles["user-avtar"]}>🤣</div>;
+  return (
+    <div className={styles["user-avtar"]}>
+      <Emoji unified={config.avatar} size={18} />
+    </div>
+  );
 }
 
 export function ChatItem(props: {
@@ -148,11 +157,11 @@ export function Chat() {
   });
 
   return (
-    <div className={styles.chat} key={session.topic}>
-      <div className={styles["chat-header"]}>
+    <div className={styles.chat} key={session.id}>
+      <div className={styles["window-header"]}>
         <div>
-          <div className={styles["chat-header-title"]}>{session.topic}</div>
-          <div className={styles["chat-header-sub-title"]}>
+          <div className={styles["window-header-title"]}>{session.topic}</div>
+          <div className={styles["window-header-sub-title"]}>
             与 ChatGPT 的 {session.messages.length} 条对话
           </div>
         </div>
@@ -181,7 +190,7 @@ export function Chat() {
                 <div className={styles["chat-message-avatar"]}>
                   <Avatar role={message.role} />
                 </div>
-                {message.preview && (
+                {(message.preview || message.streaming) && (
                   <div className={styles["chat-message-status"]}>正在输入…</div>
                 )}
                 <div className={styles["chat-message-item"]}>
@@ -235,6 +244,9 @@ export function Chat() {
 export function Home() {
   const [createNewSession] = useChatStore((state) => [state.newSession]);
 
+  // settings
+  const [openSettings, setOpenSettings] = useState(false);
+
   return (
     <div className={styles.container}>
       <div className={styles.sidebar}>
@@ -255,7 +267,10 @@ export function Home() {
         <div className={styles["sidebar-tail"]}>
           <div className={styles["sidebar-actions"]}>
             <div className={styles["sidebar-action"]}>
-              <IconButton icon={<SettingsIcon />} />
+              <IconButton
+                icon={<SettingsIcon />}
+                onClick={() => setOpenSettings(!openSettings)}
+              />
             </div>
             <div className={styles["sidebar-action"]}>
               <a href="https://github.com/Yidadaa" target="_blank">
@@ -273,7 +288,94 @@ export function Home() {
         </div>
       </div>
 
-      <Chat key="chat" />
+      <div className={styles["window-content"]}>
+        {openSettings ? <Settings /> : <Chat key="chat" />}
+      </div>
     </div>
   );
 }
+
+export function EmojiPickerModal(props: {
+  show: boolean;
+  onClose: (_: boolean) => void;
+}) {
+  return <div className=""></div>;
+}
+
+export function Settings() {
+  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
+  const [config, updateConfig] = useChatStore((state) => [
+    state.config,
+    state.updateConfig,
+  ]);
+
+  return (
+    <>
+      <div className={styles["window-header"]}>
+        <div>
+          <div className={styles["window-header-title"]}>设置</div>
+          <div className={styles["window-header-sub-title"]}>设置选项</div>
+        </div>
+      </div>
+      <div className={styles["settings"]}>
+        <List>
+          <ListItem>
+            <div className={styles["settings-title"]}>头像</div>
+            <Popover
+              onClose={() => setShowEmojiPicker(false)}
+              content={
+                <EmojiPicker
+                  lazyLoadEmojis
+                  onEmojiClick={(e) => {
+                    updateConfig((config) => (config.avatar = e.unified));
+                    setShowEmojiPicker(false);
+                  }}
+                />
+              }
+              open={showEmojiPicker}
+            >
+              <div
+                className={styles.avatar}
+                onClick={() => setShowEmojiPicker(true)}
+              >
+                <Avatar role="user" />
+              </div>
+            </Popover>
+          </ListItem>
+
+          <ListItem>
+            <div className={styles["settings-title"]}>发送键</div>
+            <div className="">
+              <select
+                value={config.submitKey}
+                onChange={(e) => {
+                  updateConfig(
+                    (config) =>
+                      (config.submitKey = e.target.value as any as SubmitKey)
+                  );
+                }}
+              >
+                {Object.entries(SubmitKey).map(([k, v]) => (
+                  <option value={k} key={v}>
+                    {v}
+                  </option>
+                ))}
+              </select>
+            </div>
+          </ListItem>
+        </List>
+        <List>
+          <ListItem>
+            <div className={styles["settings-title"]}>最大记忆历史消息数</div>
+            <div className="">{config.historyMessageCount}</div>
+          </ListItem>
+
+          <ListItem>
+            <div className={styles["settings-title"]}>发送机器人回复消息</div>
+            <div className="">{config.sendBotMessages ? "是" : "否"}</div>
+          </ListItem>
+        </List>
+      </div>
+    </>
+  );
+}

+ 56 - 0
app/components/ui-lib.module.css

@@ -0,0 +1,56 @@
+.card {
+  background-color: var(--white);
+  border-radius: 10px;
+  box-shadow: var(--card-shadow);
+  padding: 10px;
+}
+
+.popover {
+  position: relative;
+}
+
+.popover-content {
+  position: absolute;
+  animation: slide-in 0.3s ease;
+  right: 0;
+  top: calc(100% + 10px);
+}
+
+.popover-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+}
+
+@keyframes slide-in {
+  from {
+    transform: translateY(10px);
+    opacity: 0;
+  }
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
+
+.list-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  min-height: 40px;
+  border-bottom: var(--border-in-light);
+  padding: 10px 20px;
+}
+
+.list {
+  border: var(--border-in-light);
+  border-radius: 10px;
+  box-shadow: var(--card-shadow);
+  margin-bottom: 20px;
+}
+
+.list .list-item:last-child {
+  border: 0;
+}

+ 38 - 0
app/components/ui-lib.tsx

@@ -0,0 +1,38 @@
+import styles from "./ui-lib.module.css";
+
+export function Popover(props: {
+  children: JSX.Element;
+  content: JSX.Element;
+  open?: boolean;
+  onClose?: () => void;
+}) {
+  return (
+    <div className={styles.popover}>
+      {props.children}
+      {props.open && (
+        <div className={styles["popover-content"]}>
+          <div className={styles["popover-mask"]} onClick={props.onClose}></div>
+          {props.content}
+        </div>
+      )}
+    </div>
+  );
+}
+
+export function Card(props: { children: JSX.Element[]; className?: string }) {
+  return (
+    <div className={styles.card + " " + props.className}>{props.children}</div>
+  );
+}
+
+export function ListItem(props: { children: JSX.Element[] }) {
+  if (props.children.length > 2) {
+    throw Error("Only Support Two Children");
+  }
+
+  return <div className={styles["list-item"]}>{props.children}</div>;
+}
+
+export function List(props: { children: JSX.Element[] }) {
+  return <div className={styles.list}>{props.children}</div>;
+}

+ 7 - 0
app/globals.css

@@ -1,6 +1,7 @@
 :root {
   /* color */
   --white: white;
+  --black: --black;
   --gray: rgb(250, 250, 250);
   --primary: rgb(29, 147, 171);
   --second: rgb(231, 248, 255);
@@ -40,3 +41,9 @@ body {
   border: 6px solid transparent;
   background-clip: content-box;
 }
+
+select {
+  border: var(--border-in-light);
+  padding: 10px;
+  border-radius: 10px;
+}

+ 27 - 10
app/requests.ts

@@ -1,21 +1,31 @@
 import type { ChatRequest, ChatReponse } from "./api/chat/typing";
 import { Message } from "./store";
 
-const makeRequestParam = (messages: Message[], stream = false): ChatRequest => {
+const makeRequestParam = (
+  messages: Message[],
+  options?: {
+    filterBot?: boolean;
+    stream?: boolean;
+  }
+): ChatRequest => {
+  let sendMessages = messages.map((v) => ({
+    role: v.role,
+    content: v.content,
+  }));
+
+  if (options?.filterBot) {
+    sendMessages = sendMessages.filter((m) => m.role !== "assistant");
+  }
+
   return {
     model: "gpt-3.5-turbo",
-    messages: messages
-      .map((v) => ({
-        role: v.role,
-        content: v.content,
-      }))
-      .filter((m) => m.role !== "assistant"),
-    stream,
+    messages: sendMessages,
+    stream: options?.stream,
   };
 };
 
 export async function requestChat(messages: Message[]) {
-  const req: ChatRequest = makeRequestParam(messages);
+  const req: ChatRequest = makeRequestParam(messages, { filterBot: true });
 
   const res = await fetch("/api/chat", {
     method: "POST",
@@ -31,10 +41,15 @@ export async function requestChat(messages: Message[]) {
 export async function requestChatStream(
   messages: Message[],
   options?: {
+    filterBot?: boolean;
     onMessage: (message: string, done: boolean) => void;
+    onError: (error: Error) => void;
   }
 ) {
-  const req = makeRequestParam(messages, true);
+  const req = makeRequestParam(messages, {
+    stream: true,
+    filterBot: options?.filterBot,
+  });
 
   const res = await fetch("/api/chat-stream", {
     method: "POST",
@@ -64,6 +79,8 @@ export async function requestChatStream(
     }
 
     options?.onMessage(responseText, true);
+  } else {
+    options?.onError(new Error("NetWork Error"));
   }
 }
 

+ 49 - 5
app/store.ts

@@ -10,8 +10,19 @@ export type Message = ChatCompletionResponseMessage & {
   streaming?: boolean;
 };
 
+export enum SubmitKey {
+  Enter = "Enter",
+  CtrlEnter = "Ctrl + Enter",
+  ShiftEnter = "Shift + Enter",
+  AltEnter = "Alt + Enter",
+}
+
 interface ChatConfig {
-  maxToken: number;
+  maxToken?: number;
+  historyMessageCount: number; // -1 means all
+  sendBotMessages: boolean; // send bot's message or not
+  submitKey: SubmitKey;
+  avatar: string;
 }
 
 interface ChatStat {
@@ -21,6 +32,7 @@ interface ChatStat {
 }
 
 interface ChatSession {
+  id: number;
   topic: string;
   memoryPrompt: string;
   messages: Message[];
@@ -35,6 +47,7 @@ function createEmptySession(): ChatSession {
   const createDate = new Date().toLocaleString();
 
   return {
+    id: Date.now(),
     topic: DEFAULT_TOPIC,
     memoryPrompt: "",
     messages: [
@@ -54,6 +67,7 @@ function createEmptySession(): ChatSession {
 }
 
 interface ChatStore {
+  config: ChatConfig;
   sessions: ChatSession[];
   currentSessionIndex: number;
   removeSession: (index: number) => void;
@@ -71,6 +85,9 @@ interface ChatStore {
     messageIndex: number,
     updater: (message?: Message) => void
   ) => void;
+
+  getConfig: () => ChatConfig;
+  updateConfig: (updater: (config: ChatConfig) => void) => void;
 }
 
 export const useChatStore = create<ChatStore>()(
@@ -78,6 +95,22 @@ export const useChatStore = create<ChatStore>()(
     (set, get) => ({
       sessions: [createEmptySession()],
       currentSessionIndex: 0,
+      config: {
+        historyMessageCount: 5,
+        sendBotMessages: false as boolean,
+        submitKey: SubmitKey.CtrlEnter,
+        avatar: "1fae0",
+      },
+
+      getConfig() {
+        return get().config;
+      },
+
+      updateConfig(updater) {
+        const config = get().config;
+        updater(config);
+        set(() => ({ config }));
+      },
 
       selectSession(index: number) {
         set({
@@ -126,7 +159,9 @@ export const useChatStore = create<ChatStore>()(
           set(() => ({ currentSessionIndex: index }));
         }
 
-        return sessions[index];
+        const session = sessions[index];
+
+        return session;
       },
 
       onNewMessage(message) {
@@ -144,6 +179,7 @@ export const useChatStore = create<ChatStore>()(
           date: new Date().toLocaleString(),
         };
 
+        // get last five messges
         const messages = get().currentSession().messages.concat(message);
         get().onNewMessage(message);
 
@@ -158,17 +194,25 @@ export const useChatStore = create<ChatStore>()(
           session.messages.push(botMessage);
         });
 
-        requestChatStream(messages, {
+        const fiveMessages = messages.slice(-5);
+
+        requestChatStream(fiveMessages, {
           onMessage(content, done) {
             if (done) {
-              get().updateStat(message);
+              botMessage.streaming = false;
+              get().updateStat(botMessage);
               get().summarizeSession();
             } else {
               botMessage.content = content;
-              botMessage.streaming = false;
               set(() => ({}));
             }
           },
+          onError(error) {
+            botMessage.content = "出错了,稍后重试吧";
+            botMessage.streaming = false;
+            set(() => ({}));
+          },
+          filterBot: !get().config.sendBotMessages,
         });
       },
 

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "@types/react": "^18.0.28",
     "@types/react-dom": "^18.0.11",
     "@types/react-katex": "^3.0.0",
+    "emoji-picker-react": "^4.4.7",
     "eslint": "8.35.0",
     "eslint-config-next": "13.2.3",
     "eventsource-parser": "^0.1.0",

+ 14 - 189
yarn.lock

@@ -1313,11 +1313,6 @@
   dependencies:
     "@types/ms" "*"
 
-"@types/global-tunnel-ng@^2.1.1":
-  version "2.1.1"
-  resolved "https://registry.npmmirror.com/@types/global-tunnel-ng/-/global-tunnel-ng-2.1.1.tgz#4f01cf93f7e51a6f97bdea5a11f49c742993f865"
-  integrity sha512-TikApqV8CkUsI1GGUgVydkJFrq9sYCBWv4fc/r3zvl6Oqe2YU1ASeWBrG5bw1D2XvS07YS3s05hCor/lEtIoYw==
-
 "@types/hast@^2.0.0":
   version "2.3.4"
   resolved "https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
@@ -1610,11 +1605,6 @@ boolbase@^1.0.0:
   resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
 
-boolean@^3.0.1:
-  version "3.2.0"
-  resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
-  integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
-
 brace-expansion@^1.1.7:
   version "1.1.11"
   resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -1695,6 +1685,11 @@ client-only@0.0.1:
   resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
   integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
 
+clsx@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+  integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
 color-convert@^1.9.0:
   version "1.9.3"
   resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -1746,14 +1741,6 @@ concat-map@0.0.1:
   resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
-config-chain@^1.1.11:
-  version "1.1.13"
-  resolved "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
-  integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
-  dependencies:
-    ini "^1.3.4"
-    proto-list "~1.2.1"
-
 convert-source-map@^1.7.0:
   version "1.9.0"
   resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
@@ -1766,11 +1753,6 @@ core-js-compat@^3.25.1:
   dependencies:
     browserslist "^4.21.5"
 
-core-js@^3.6.5:
-  version "3.29.0"
-  resolved "https://registry.npmmirror.com/core-js/-/core-js-3.29.0.tgz#0273e142b67761058bcde5615c503c7406b572d6"
-  integrity sha512-VG23vuEisJNkGl6XQmFJd3rEG/so/CNatqeE+7uZAwTSwFeB/qaO0be8xZYUNWprJ/GIwL8aMt9cj1kvbpTZhg==
-
 cosmiconfig@^7.0.1:
   version "7.1.0"
   resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
@@ -1909,11 +1891,6 @@ dequal@^2.0.0:
   resolved "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
   integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
 
-detect-node@^2.0.4:
-  version "2.1.0"
-  resolved "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
-  integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
-
 diff@^5.0.0:
   version "5.1.0"
   resolved "https://registry.npmmirror.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
@@ -1975,16 +1952,18 @@ electron-to-chromium@^1.4.284:
   resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.325.tgz#7b97238a61192d85d055d97f3149832b3617d37b"
   integrity sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==
 
+emoji-picker-react@^4.4.7:
+  version "4.4.7"
+  resolved "https://registry.npmmirror.com/emoji-picker-react/-/emoji-picker-react-4.4.7.tgz#faff798cf7acad41c5eb6b103c93480e777f2222"
+  integrity sha512-HcB1Fr57W6M+gLLm/W4YhbAnUQqYBUAhZBOHmPlSQODLtsDI8vecyqNIF9RrStZHfZwzUSLlpZMfY07AA6gxmA==
+  dependencies:
+    clsx "^1.2.1"
+
 emoji-regex@^9.2.2:
   version "9.2.2"
   resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
   integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
 
-encodeurl@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-  integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
-
 enhanced-resolve@^5.10.0:
   version "5.12.0"
   resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
@@ -2089,11 +2068,6 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es6-error@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
-  integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
-
 escalade@^3.1.1:
   version "3.1.1"
   resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -2515,29 +2489,6 @@ glob@^7.1.3:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-agent@^2.1.12:
-  version "2.2.0"
-  resolved "https://registry.npmmirror.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc"
-  integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==
-  dependencies:
-    boolean "^3.0.1"
-    core-js "^3.6.5"
-    es6-error "^4.1.1"
-    matcher "^3.0.0"
-    roarr "^2.15.3"
-    semver "^7.3.2"
-    serialize-error "^7.0.1"
-
-global-tunnel-ng@^2.7.1:
-  version "2.7.1"
-  resolved "https://registry.npmmirror.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f"
-  integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==
-  dependencies:
-    encodeurl "^1.0.2"
-    lodash "^4.17.10"
-    npm-conf "^1.1.3"
-    tunnel "^0.0.6"
-
 globals@^11.1.0:
   version "11.12.0"
   resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -2550,7 +2501,7 @@ globals@^13.19.0:
   dependencies:
     type-fest "^0.20.2"
 
-globalthis@^1.0.1, globalthis@^1.0.3:
+globalthis@^1.0.3:
   version "1.0.3"
   resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
   integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
@@ -2738,16 +2689,6 @@ inherits@2:
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
-inherits@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-  integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
-
-ini@^1.3.4:
-  version "1.3.8"
-  resolved "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
-  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
-
 inline-style-parser@0.1.1:
   version "0.1.1"
   resolved "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
@@ -2996,11 +2937,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
   resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 
-json-stringify-safe@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-  integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
-
 json5@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
@@ -3035,13 +2971,6 @@ katex@^0.15.0:
   dependencies:
     commander "^8.0.0"
 
-katex@^0.16.0:
-  version "0.16.4"
-  resolved "https://registry.npmmirror.com/katex/-/katex-0.16.4.tgz#87021bc3bbd80586ef715aeb476794cba6a49ad4"
-  integrity sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==
-  dependencies:
-    commander "^8.0.0"
-
 kleur@^4.0.3:
   version "4.1.5"
   resolved "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
@@ -3089,11 +3018,6 @@ lodash.merge@^4.6.2:
   resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
-lodash@^4.17.10:
-  version "4.17.21"
-  resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
-  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-
 longest-streak@^3.0.0:
   version "3.1.0"
   resolved "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
@@ -3120,13 +3044,6 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
-matcher@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
-  integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==
-  dependencies:
-    escape-string-regexp "^4.0.0"
-
 mdast-util-definitions@^5.0.0:
   version "5.1.2"
   resolved "https://registry.npmmirror.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7"
@@ -3506,27 +3423,11 @@ next@^13.2.3:
     "@next/swc-win32-ia32-msvc" "13.2.4"
     "@next/swc-win32-x64-msvc" "13.2.4"
 
-node-global-proxy@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.npmmirror.com/node-global-proxy/-/node-global-proxy-1.0.1.tgz#6d1f66ed60427cb3f70066f7da0b70c78a1547a4"
-  integrity sha512-pKGD42p81TFbrUudDBtApwri2Ho1xjm3ozmXhHLIRpwgXk18KvPMv0ubDZLD8gNI6q/Jzc2zK6jNbga8j+FRSw==
-  dependencies:
-    global-agent "^2.1.12"
-    global-tunnel-ng "^2.7.1"
-
 node-releases@^2.0.8:
   version "2.0.10"
   resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
   integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
 
-npm-conf@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.npmmirror.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
-  integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==
-  dependencies:
-    config-chain "^1.1.11"
-    pify "^3.0.0"
-
 nth-check@^2.0.1:
   version "2.1.1"
   resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
@@ -3699,14 +3600,6 @@ path-type@^4.0.0:
   resolved "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
-path@^0.12.7:
-  version "0.12.7"
-  resolved "https://registry.npmmirror.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
-  integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
-  dependencies:
-    process "^0.11.1"
-    util "^0.10.3"
-
 picocolors@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -3717,11 +3610,6 @@ picomatch@^2.3.1:
   resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-pify@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
-  integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
-
 postcss@8.4.14:
   version "8.4.14"
   resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
@@ -3736,11 +3624,6 @@ prelude-ls@^1.2.1:
   resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-process@^0.11.1:
-  version "0.11.10"
-  resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
-  integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
-
 prop-types@^15.0.0, prop-types@^15.8.1:
   version "15.8.1"
   resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
@@ -3755,11 +3638,6 @@ property-information@^6.0.0:
   resolved "https://registry.npmmirror.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d"
   integrity sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==
 
-proto-list@~1.2.1:
-  version "1.2.4"
-  resolved "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
-  integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
-
 punycode@^2.1.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
@@ -3788,13 +3666,6 @@ react-is@^18.0.0:
   resolved "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
   integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
 
-react-katex@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.npmmirror.com/react-katex/-/react-katex-3.0.1.tgz#262b44f49c5fa727f1d13cbab595f791318e5083"
-  integrity sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==
-  dependencies:
-    katex "^0.16.0"
-
 react-markdown@^8.0.5:
   version "8.0.5"
   resolved "https://registry.npmmirror.com/react-markdown/-/react-markdown-8.0.5.tgz#c9a70a33ca9aeeafb769c6582e7e38843b9d70ad"
@@ -3968,18 +3839,6 @@ rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
-roarr@^2.15.3:
-  version "2.15.4"
-  resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
-  integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==
-  dependencies:
-    boolean "^3.0.1"
-    detect-node "^2.0.4"
-    globalthis "^1.0.1"
-    json-stringify-safe "^5.0.1"
-    semver-compare "^1.0.0"
-    sprintf-js "^1.1.2"
-
 run-parallel@^1.1.9:
   version "1.2.0"
   resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -4010,30 +3869,18 @@ scheduler@^0.23.0:
   dependencies:
     loose-envify "^1.1.0"
 
-semver-compare@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
-  integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
-
 semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.3.2, semver@^7.3.7:
+semver@^7.3.7:
   version "7.3.8"
   resolved "https://registry.npmmirror.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
   integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
   dependencies:
     lru-cache "^6.0.0"
 
-serialize-error@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
-  integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==
-  dependencies:
-    type-fest "^0.13.1"
-
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -4080,11 +3927,6 @@ space-separated-tokens@^2.0.0:
   resolved "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
   integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
 
-sprintf-js@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
-  integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
-
 stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
@@ -4272,11 +4114,6 @@ tsutils@^3.21.0:
   dependencies:
     tslib "^1.8.1"
 
-tunnel@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.npmmirror.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
-  integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
-
 type-check@^0.4.0, type-check@~0.4.0:
   version "0.4.0"
   resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -4284,11 +4121,6 @@ type-check@^0.4.0, type-check@~0.4.0:
   dependencies:
     prelude-ls "^1.2.1"
 
-type-fest@^0.13.1:
-  version "0.13.1"
-  resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
-  integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
-
 type-fest@^0.20.2:
   version "0.20.2"
   resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
@@ -4433,13 +4265,6 @@ use-sync-external-store@1.2.0:
   resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
   integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
 
-util@^0.10.3:
-  version "0.10.4"
-  resolved "https://registry.npmmirror.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
-  integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
-  dependencies:
-    inherits "2.0.3"
-
 uvu@^0.5.0:
   version "0.5.6"
   resolved "https://registry.npmmirror.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"