Browse Source

feat: session-level model config

Yidadaa 2 years ago
parent
commit
4cdb2f0fa3

+ 1 - 3
app/components/home.module.scss

@@ -138,9 +138,7 @@
 .sidebar-body {
   flex: 1;
   overflow: auto;
-}
-
-.chat-list {
+  overflow-x: hidden;
 }
 
 .chat-item {

+ 5 - 2
app/components/home.tsx

@@ -2,7 +2,7 @@
 
 require("../polyfill");
 
-import { useState, useEffect, StyleHTMLAttributes } from "react";
+import { useState, useEffect } from "react";
 
 import styles from "./home.module.scss";
 
@@ -10,7 +10,6 @@ import BotIcon from "../icons/bot.svg";
 import LoadingIcon from "../icons/three-dots.svg";
 
 import { getCSSVar, useMobileScreen } from "../utils";
-import { Chat } from "./chat";
 
 import dynamic from "next/dynamic";
 import { Path } from "../constant";
@@ -38,6 +37,10 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
   loading: () => <Loading noLogo />,
 });
 
+const Chat = dynamic(async () => (await import("./chat")).Chat, {
+  loading: () => <Loading noLogo />,
+});
+
 export function useSwitchTheme() {
   const config = useAppConfig();
 

+ 0 - 14
app/components/settings.module.scss

@@ -19,20 +19,6 @@
   cursor: pointer;
 }
 
-.password-input-container {
-  max-width: 50%;
-  display: flex;
-  justify-content: flex-end;
-
-  .password-eye {
-    margin-right: 4px;
-  }
-
-  .password-input {
-    min-width: 80%;
-  }
-}
-
 .user-prompt-modal {
   min-height: 40vh;
 

+ 131 - 140
app/components/settings.tsx

@@ -9,10 +9,7 @@ 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 EyeIcon from "../icons/eye.svg";
-import EyeOffIcon from "../icons/eye-off.svg";
-
-import { Input, List, ListItem, Modal, Popover } from "./ui-lib";
+import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
 
 import { IconButton } from "./button";
 import {
@@ -24,6 +21,8 @@ import {
   useAccessStore,
   ModalConfigValidator,
   useAppConfig,
+  ChatConfig,
+  ModelConfig,
 } from "../store";
 import { Avatar } from "./chat";
 
@@ -155,26 +154,127 @@ function SettingItem(props: {
   );
 }
 
-function PasswordInput(props: HTMLProps<HTMLInputElement>) {
-  const [visible, setVisible] = useState(false);
-
-  function changeVisibility() {
-    setVisible(!visible);
-  }
-
+export function ModelConfigList(props: {
+  modelConfig: ModelConfig;
+  updateConfig: (updater: (config: ModelConfig) => void) => void;
+}) {
   return (
-    <div className={styles["password-input-container"]}>
-      <IconButton
-        icon={visible ? <EyeIcon /> : <EyeOffIcon />}
-        onClick={changeVisibility}
-        className={styles["password-eye"]}
-      />
-      <input
-        {...props}
-        type={visible ? "text" : "password"}
-        className={styles["password-input"]}
-      />
-    </div>
+    <>
+      <SettingItem title={Locale.Settings.Model}>
+        <select
+          value={props.modelConfig.model}
+          onChange={(e) => {
+            props.updateConfig(
+              (config) =>
+                (config.model = ModalConfigValidator.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}
+      >
+        <InputRange
+          value={props.modelConfig.temperature?.toFixed(1)}
+          min="0"
+          max="2"
+          step="0.1"
+          onChange={(e) => {
+            props.updateConfig(
+              (config) =>
+                (config.temperature = ModalConfigValidator.temperature(
+                  e.currentTarget.valueAsNumber,
+                )),
+            );
+          }}
+        ></InputRange>
+      </SettingItem>
+      <SettingItem
+        title={Locale.Settings.MaxTokens.Title}
+        subTitle={Locale.Settings.MaxTokens.SubTitle}
+      >
+        <input
+          type="number"
+          min={100}
+          max={32000}
+          value={props.modelConfig.max_tokens}
+          onChange={(e) =>
+            props.updateConfig(
+              (config) =>
+                (config.max_tokens = ModalConfigValidator.max_tokens(
+                  e.currentTarget.valueAsNumber,
+                )),
+            )
+          }
+        ></input>
+      </SettingItem>
+      <SettingItem
+        title={Locale.Settings.PresencePenlty.Title}
+        subTitle={Locale.Settings.PresencePenlty.SubTitle}
+      >
+        <InputRange
+          value={props.modelConfig.presence_penalty?.toFixed(1)}
+          min="-2"
+          max="2"
+          step="0.1"
+          onChange={(e) => {
+            props.updateConfig(
+              (config) =>
+                (config.presence_penalty =
+                  ModalConfigValidator.presence_penalty(
+                    e.currentTarget.valueAsNumber,
+                  )),
+            );
+          }}
+        ></InputRange>
+      </SettingItem>
+
+      <SettingItem
+        title={Locale.Settings.HistoryCount.Title}
+        subTitle={Locale.Settings.HistoryCount.SubTitle}
+      >
+        <InputRange
+          title={props.modelConfig.historyMessageCount.toString()}
+          value={props.modelConfig.historyMessageCount}
+          min="0"
+          max="25"
+          step="1"
+          onChange={(e) =>
+            props.updateConfig(
+              (config) => (config.historyMessageCount = e.target.valueAsNumber),
+            )
+          }
+        ></InputRange>
+      </SettingItem>
+
+      <SettingItem
+        title={Locale.Settings.CompressThreshold.Title}
+        subTitle={Locale.Settings.CompressThreshold.SubTitle}
+      >
+        <input
+          type="number"
+          min={500}
+          max={4000}
+          value={props.modelConfig.compressMessageLengthThreshold}
+          onChange={(e) =>
+            props.updateConfig(
+              (config) =>
+                (config.compressMessageLengthThreshold =
+                  e.currentTarget.valueAsNumber),
+            )
+          }
+        ></input>
+      </SettingItem>
+    </>
   );
 }
 
@@ -505,44 +605,6 @@ export function Settings() {
               />
             )}
           </SettingItem>
-
-          <SettingItem
-            title={Locale.Settings.HistoryCount.Title}
-            subTitle={Locale.Settings.HistoryCount.SubTitle}
-          >
-            <InputRange
-              title={config.historyMessageCount.toString()}
-              value={config.historyMessageCount}
-              min="0"
-              max="25"
-              step="1"
-              onChange={(e) =>
-                updateConfig(
-                  (config) =>
-                    (config.historyMessageCount = e.target.valueAsNumber),
-                )
-              }
-            ></InputRange>
-          </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>
@@ -578,85 +640,14 @@ export function Settings() {
         </List>
 
         <List>
-          <SettingItem title={Locale.Settings.Model}>
-            <select
-              value={config.modelConfig.model}
-              onChange={(e) => {
-                updateConfig(
-                  (config) =>
-                    (config.modelConfig.model = ModalConfigValidator.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}
-          >
-            <InputRange
-              value={config.modelConfig.temperature?.toFixed(1)}
-              min="0"
-              max="2"
-              step="0.1"
-              onChange={(e) => {
-                updateConfig(
-                  (config) =>
-                    (config.modelConfig.temperature =
-                      ModalConfigValidator.temperature(
-                        e.currentTarget.valueAsNumber,
-                      )),
-                );
-              }}
-            ></InputRange>
-          </SettingItem>
-          <SettingItem
-            title={Locale.Settings.MaxTokens.Title}
-            subTitle={Locale.Settings.MaxTokens.SubTitle}
-          >
-            <input
-              type="number"
-              min={100}
-              max={32000}
-              value={config.modelConfig.max_tokens}
-              onChange={(e) =>
-                updateConfig(
-                  (config) =>
-                    (config.modelConfig.max_tokens =
-                      ModalConfigValidator.max_tokens(
-                        e.currentTarget.valueAsNumber,
-                      )),
-                )
-              }
-            ></input>
-          </SettingItem>
-          <SettingItem
-            title={Locale.Settings.PresencePenlty.Title}
-            subTitle={Locale.Settings.PresencePenlty.SubTitle}
-          >
-            <InputRange
-              value={config.modelConfig.presence_penalty?.toFixed(1)}
-              min="-2"
-              max="2"
-              step="0.5"
-              onChange={(e) => {
-                updateConfig(
-                  (config) =>
-                    (config.modelConfig.presence_penalty =
-                      ModalConfigValidator.presence_penalty(
-                        e.currentTarget.valueAsNumber,
-                      )),
-                );
-              }}
-            ></InputRange>
-          </SettingItem>
+          <ModelConfigList
+            modelConfig={config.modelConfig}
+            updateConfig={(upater) => {
+              const modelConfig = { ...config.modelConfig };
+              upater(modelConfig);
+              config.update((config) => (config.modelConfig = modelConfig));
+            }}
+          />
         </List>
 
         {shouldShowPromptModal && (

+ 28 - 1
app/components/ui-lib.tsx

@@ -1,8 +1,12 @@
 import styles from "./ui-lib.module.scss";
 import LoadingIcon from "../icons/three-dots.svg";
 import CloseIcon from "../icons/close.svg";
+import EyeIcon from "../icons/eye.svg";
+import EyeOffIcon from "../icons/eye-off.svg";
+
 import { createRoot } from "react-dom/client";
-import React, { useEffect } from "react";
+import React, { HTMLProps, useEffect, useState } from "react";
+import { IconButton } from "./button";
 
 export function Popover(props: {
   children: JSX.Element;
@@ -190,3 +194,26 @@ export function Input(props: InputProps) {
     ></textarea>
   );
 }
+
+export function PasswordInput(props: HTMLProps<HTMLInputElement>) {
+  const [visible, setVisible] = useState(false);
+
+  function changeVisibility() {
+    setVisible(!visible);
+  }
+
+  return (
+    <div className={"password-input-container"}>
+      <IconButton
+        icon={visible ? <EyeIcon /> : <EyeOffIcon />}
+        onClick={changeVisibility}
+        className={"password-eye"}
+      />
+      <input
+        {...props}
+        type={visible ? "text" : "password"}
+        className={"password-input"}
+      />
+    </div>
+  );
+}

+ 6 - 5
app/store/app.ts

@@ -334,14 +334,14 @@ export const useChatStore = create<ChatStore>()(
         // get short term and unmemoried long term memory
         const shortTermMemoryMessageIndex = Math.max(
           0,
-          n - config.historyMessageCount,
+          n - config.modelConfig.historyMessageCount,
         );
         const longTermMemoryMessageIndex = session.lastSummarizeIndex;
         const oldestIndex = Math.max(
           shortTermMemoryMessageIndex,
           longTermMemoryMessageIndex,
         );
-        const threshold = config.compressMessageLengthThreshold;
+        const threshold = config.modelConfig.compressMessageLengthThreshold;
 
         // get recent messages as many as possible
         const reversedRecentMessages = [];
@@ -410,7 +410,7 @@ export const useChatStore = create<ChatStore>()(
         if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) {
           const n = toBeSummarizedMsgs.length;
           toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
-            Math.max(0, n - config.historyMessageCount),
+            Math.max(0, n - config.modelConfig.historyMessageCount),
           );
         }
 
@@ -423,11 +423,12 @@ export const useChatStore = create<ChatStore>()(
           "[Chat History] ",
           toBeSummarizedMsgs,
           historyMsgLength,
-          config.compressMessageLengthThreshold,
+          config.modelConfig.compressMessageLengthThreshold,
         );
 
         if (
-          historyMsgLength > config.compressMessageLengthThreshold &&
+          historyMsgLength >
+            config.modelConfig.compressMessageLengthThreshold &&
           session.sendMemory
         ) {
           requestChatStream(

+ 2 - 2
app/store/config.ts

@@ -16,8 +16,6 @@ export enum Theme {
 }
 
 const DEFAULT_CONFIG = {
-  historyMessageCount: 4,
-  compressMessageLengthThreshold: 1000,
   sendBotMessages: true as boolean,
   submitKey: SubmitKey.CtrlEnter as SubmitKey,
   avatar: "1f603",
@@ -34,6 +32,8 @@ const DEFAULT_CONFIG = {
     temperature: 1,
     max_tokens: 2000,
     presence_penalty: 0,
+    historyMessageCount: 4,
+    compressMessageLengthThreshold: 1000,
   },
 };
 

+ 14 - 0
app/styles/globals.scss

@@ -311,3 +311,17 @@ pre {
     overflow: auto;
   }
 }
+
+.password-input-container {
+  max-width: 50%;
+  display: flex;
+  justify-content: flex-end;
+
+  .password-eye {
+    margin-right: 4px;
+  }
+
+  .password-input {
+    min-width: 80%;
+  }
+}