mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: add session config modal
This commit is contained in:
		@@ -53,6 +53,20 @@
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-title {
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					  margin-bottom: 10px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .section-title-action {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.context-prompt {
 | 
					.context-prompt {
 | 
				
			||||||
  .context-prompt-row {
 | 
					  .context-prompt-row {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
@@ -81,25 +95,13 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.memory-prompt {
 | 
					.memory-prompt {
 | 
				
			||||||
  margin-top: 20px;
 | 
					  margin: 20px 0;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  .memory-prompt-title {
 | 
					 | 
				
			||||||
    font-size: 12px;
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    margin-bottom: 10px;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .memory-prompt-action {
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      align-items: center;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .memory-prompt-content {
 | 
					  .memory-prompt-content {
 | 
				
			||||||
    background-color: var(--gray);
 | 
					    background-color: var(--white);
 | 
				
			||||||
    border-radius: 6px;
 | 
					    color: var(--black);
 | 
				
			||||||
 | 
					    border: var(--border-in-light);
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
    padding: 10px;
 | 
					    padding: 10px;
 | 
				
			||||||
    font-size: 12px;
 | 
					    font-size: 12px;
 | 
				
			||||||
    user-select: text;
 | 
					    user-select: text;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { useDebounce, useDebouncedCallback } from "use-debounce";
 | 
					import { useDebouncedCallback } from "use-debounce";
 | 
				
			||||||
import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
 | 
					import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import SendWhiteIcon from "../icons/send-white.svg";
 | 
					import SendWhiteIcon from "../icons/send-white.svg";
 | 
				
			||||||
@@ -9,8 +9,6 @@ import ReturnIcon from "../icons/return.svg";
 | 
				
			|||||||
import CopyIcon from "../icons/copy.svg";
 | 
					import CopyIcon from "../icons/copy.svg";
 | 
				
			||||||
import DownloadIcon from "../icons/download.svg";
 | 
					import DownloadIcon from "../icons/download.svg";
 | 
				
			||||||
import LoadingIcon from "../icons/three-dots.svg";
 | 
					import LoadingIcon from "../icons/three-dots.svg";
 | 
				
			||||||
import BotIcon from "../icons/bot.svg";
 | 
					 | 
				
			||||||
import BlackBotIcon from "../icons/black-bot.svg";
 | 
					 | 
				
			||||||
import AddIcon from "../icons/add.svg";
 | 
					import AddIcon from "../icons/add.svg";
 | 
				
			||||||
import DeleteIcon from "../icons/delete.svg";
 | 
					import DeleteIcon from "../icons/delete.svg";
 | 
				
			||||||
import MaxIcon from "../icons/max.svg";
 | 
					import MaxIcon from "../icons/max.svg";
 | 
				
			||||||
@@ -33,12 +31,13 @@ import {
 | 
				
			|||||||
  Theme,
 | 
					  Theme,
 | 
				
			||||||
  ModelType,
 | 
					  ModelType,
 | 
				
			||||||
  useAppConfig,
 | 
					  useAppConfig,
 | 
				
			||||||
 | 
					  ModelConfig,
 | 
				
			||||||
 | 
					  DEFAULT_TOPIC,
 | 
				
			||||||
} from "../store";
 | 
					} from "../store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  copyToClipboard,
 | 
					  copyToClipboard,
 | 
				
			||||||
  downloadAs,
 | 
					  downloadAs,
 | 
				
			||||||
  getEmojiUrl,
 | 
					 | 
				
			||||||
  selectOrCopy,
 | 
					  selectOrCopy,
 | 
				
			||||||
  autoGrowTextArea,
 | 
					  autoGrowTextArea,
 | 
				
			||||||
  useMobileScreen,
 | 
					  useMobileScreen,
 | 
				
			||||||
@@ -54,10 +53,11 @@ import { IconButton } from "./button";
 | 
				
			|||||||
import styles from "./home.module.scss";
 | 
					import styles from "./home.module.scss";
 | 
				
			||||||
import chatStyle from "./chat.module.scss";
 | 
					import chatStyle from "./chat.module.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Input, Modal, showModal } from "./ui-lib";
 | 
					import { Input, List, ListItem, Modal, Popover, showModal } from "./ui-lib";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
import { Path } from "../constant";
 | 
					import { Path } from "../constant";
 | 
				
			||||||
 | 
					import { ModelConfigList } from "./model-config";
 | 
				
			||||||
 | 
					import { AvatarPicker } from "./emoji";
 | 
				
			||||||
const Markdown = dynamic(
 | 
					const Markdown = dynamic(
 | 
				
			||||||
  async () => memo((await import("./markdown")).Markdown),
 | 
					  async () => memo((await import("./markdown")).Markdown),
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -65,32 +65,10 @@ const Markdown = dynamic(
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
 | 
					const Avatar = dynamic(async () => (await import("./emoji")).Avatar, {
 | 
				
			||||||
  loading: () => <LoadingIcon />,
 | 
					  loading: () => <LoadingIcon />,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Avatar(props: { role: Message["role"]; model?: ModelType }) {
 | 
					 | 
				
			||||||
  const config = useAppConfig();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (props.role !== "user") {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <div className="no-dark">
 | 
					 | 
				
			||||||
        {props.model?.startsWith("gpt-4") ? (
 | 
					 | 
				
			||||||
          <BlackBotIcon className={styles["user-avtar"]} />
 | 
					 | 
				
			||||||
        ) : (
 | 
					 | 
				
			||||||
          <BotIcon className={styles["user-avtar"]} />
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className={styles["user-avtar"]}>
 | 
					 | 
				
			||||||
      <Emoji unified={config.avatar} size={18} getEmojiUrl={getEmojiUrl} />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function exportMessages(messages: Message[], topic: string) {
 | 
					function exportMessages(messages: Message[], topic: string) {
 | 
				
			||||||
  const mdText =
 | 
					  const mdText =
 | 
				
			||||||
    `# ${topic}\n\n` +
 | 
					    `# ${topic}\n\n` +
 | 
				
			||||||
@@ -129,15 +107,13 @@ function exportMessages(messages: Message[], topic: string) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function PromptToast(props: {
 | 
					function ContextPrompts() {
 | 
				
			||||||
  showToast?: boolean;
 | 
					 | 
				
			||||||
  showModal?: boolean;
 | 
					 | 
				
			||||||
  setShowModal: (_: boolean) => void;
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  const chatStore = useChatStore();
 | 
					  const chatStore = useChatStore();
 | 
				
			||||||
  const session = chatStore.currentSession();
 | 
					  const session = chatStore.currentSession();
 | 
				
			||||||
  const context = session.context;
 | 
					  const context = session.context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [showPicker, setShowPicker] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const addContextPrompt = (prompt: Message) => {
 | 
					  const addContextPrompt = (prompt: Message) => {
 | 
				
			||||||
    chatStore.updateCurrentSession((session) => {
 | 
					    chatStore.updateCurrentSession((session) => {
 | 
				
			||||||
      session.context.push(prompt);
 | 
					      session.context.push(prompt);
 | 
				
			||||||
@@ -156,6 +132,165 @@ function PromptToast(props: {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
 | 
				
			||||||
 | 
					        {context.map((c, i) => (
 | 
				
			||||||
 | 
					          <div className={chatStyle["context-prompt-row"]} key={i}>
 | 
				
			||||||
 | 
					            <select
 | 
				
			||||||
 | 
					              value={c.role}
 | 
				
			||||||
 | 
					              className={chatStyle["context-role"]}
 | 
				
			||||||
 | 
					              onChange={(e) =>
 | 
				
			||||||
 | 
					                updateContextPrompt(i, {
 | 
				
			||||||
 | 
					                  ...c,
 | 
				
			||||||
 | 
					                  role: e.target.value as any,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {ROLES.map((r) => (
 | 
				
			||||||
 | 
					                <option key={r} value={r}>
 | 
				
			||||||
 | 
					                  {r}
 | 
				
			||||||
 | 
					                </option>
 | 
				
			||||||
 | 
					              ))}
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					            <Input
 | 
				
			||||||
 | 
					              value={c.content}
 | 
				
			||||||
 | 
					              type="text"
 | 
				
			||||||
 | 
					              className={chatStyle["context-content"]}
 | 
				
			||||||
 | 
					              rows={1}
 | 
				
			||||||
 | 
					              onInput={(e) =>
 | 
				
			||||||
 | 
					                updateContextPrompt(i, {
 | 
				
			||||||
 | 
					                  ...c,
 | 
				
			||||||
 | 
					                  content: e.currentTarget.value as any,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <IconButton
 | 
				
			||||||
 | 
					              icon={<DeleteIcon />}
 | 
				
			||||||
 | 
					              className={chatStyle["context-delete-button"]}
 | 
				
			||||||
 | 
					              onClick={() => removeContextPrompt(i)}
 | 
				
			||||||
 | 
					              bordered
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        ))}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className={chatStyle["context-prompt-row"]}>
 | 
				
			||||||
 | 
					          <IconButton
 | 
				
			||||||
 | 
					            icon={<AddIcon />}
 | 
				
			||||||
 | 
					            text={Locale.Context.Add}
 | 
				
			||||||
 | 
					            bordered
 | 
				
			||||||
 | 
					            className={chatStyle["context-prompt-button"]}
 | 
				
			||||||
 | 
					            onClick={() =>
 | 
				
			||||||
 | 
					              addContextPrompt({
 | 
				
			||||||
 | 
					                role: "system",
 | 
				
			||||||
 | 
					                content: "",
 | 
				
			||||||
 | 
					                date: "",
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <List>
 | 
				
			||||||
 | 
					        <ListItem title={"角色头像"}>
 | 
				
			||||||
 | 
					          <Popover
 | 
				
			||||||
 | 
					            content={
 | 
				
			||||||
 | 
					              <AvatarPicker
 | 
				
			||||||
 | 
					                onEmojiClick={(emoji) =>
 | 
				
			||||||
 | 
					                  chatStore.updateCurrentSession(
 | 
				
			||||||
 | 
					                    (session) => (session.avatar = emoji),
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              ></AvatarPicker>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            open={showPicker}
 | 
				
			||||||
 | 
					            onClose={() => setShowPicker(false)}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <div onClick={() => setShowPicker(true)}>
 | 
				
			||||||
 | 
					              {session.avatar ? (
 | 
				
			||||||
 | 
					                <Avatar avatar={session.avatar} />
 | 
				
			||||||
 | 
					              ) : (
 | 
				
			||||||
 | 
					                <Avatar model={session.modelConfig.model} />
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </Popover>
 | 
				
			||||||
 | 
					        </ListItem>
 | 
				
			||||||
 | 
					        <ListItem title={"对话标题"}>
 | 
				
			||||||
 | 
					          <input
 | 
				
			||||||
 | 
					            type="text"
 | 
				
			||||||
 | 
					            value={session.topic}
 | 
				
			||||||
 | 
					            onInput={(e) =>
 | 
				
			||||||
 | 
					              chatStore.updateCurrentSession(
 | 
				
			||||||
 | 
					                (session) => (session.topic = e.currentTarget.value),
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          ></input>
 | 
				
			||||||
 | 
					        </ListItem>
 | 
				
			||||||
 | 
					        <ListItem
 | 
				
			||||||
 | 
					          title={`${Locale.Memory.Title} (${session.lastSummarizeIndex} of 
 | 
				
			||||||
 | 
					          ${session.messages.length})`}
 | 
				
			||||||
 | 
					          subTitle={session.memoryPrompt || Locale.Memory.EmptyContent}
 | 
				
			||||||
 | 
					        ></ListItem>
 | 
				
			||||||
 | 
					      </List>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SessionConfigModel(props: { onClose: () => void }) {
 | 
				
			||||||
 | 
					  const chatStore = useChatStore();
 | 
				
			||||||
 | 
					  const config = useAppConfig();
 | 
				
			||||||
 | 
					  const session = chatStore.currentSession();
 | 
				
			||||||
 | 
					  const context = session.context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const updateConfig = (updater: (config: ModelConfig) => void) => {
 | 
				
			||||||
 | 
					    const config = { ...session.modelConfig };
 | 
				
			||||||
 | 
					    updater(config);
 | 
				
			||||||
 | 
					    chatStore.updateCurrentSession((session) => (session.modelConfig = config));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="modal-mask">
 | 
				
			||||||
 | 
					      <Modal
 | 
				
			||||||
 | 
					        title={Locale.Context.Edit}
 | 
				
			||||||
 | 
					        onClose={() => props.onClose()}
 | 
				
			||||||
 | 
					        actions={[
 | 
				
			||||||
 | 
					          <IconButton
 | 
				
			||||||
 | 
					            key="reset"
 | 
				
			||||||
 | 
					            icon={<CopyIcon />}
 | 
				
			||||||
 | 
					            bordered
 | 
				
			||||||
 | 
					            text="重置预设"
 | 
				
			||||||
 | 
					            onClick={() =>
 | 
				
			||||||
 | 
					              confirm(Locale.Memory.ResetConfirm) && chatStore.resetSession()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          />,
 | 
				
			||||||
 | 
					          <IconButton
 | 
				
			||||||
 | 
					            key="copy"
 | 
				
			||||||
 | 
					            icon={<CopyIcon />}
 | 
				
			||||||
 | 
					            bordered
 | 
				
			||||||
 | 
					            text="保存预设"
 | 
				
			||||||
 | 
					            onClick={() => copyToClipboard(session.memoryPrompt)}
 | 
				
			||||||
 | 
					          />,
 | 
				
			||||||
 | 
					        ]}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <ContextPrompts />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ModelConfigList
 | 
				
			||||||
 | 
					          modelConfig={session.modelConfig}
 | 
				
			||||||
 | 
					          updateConfig={updateConfig}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Modal>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function PromptToast(props: {
 | 
				
			||||||
 | 
					  showToast?: boolean;
 | 
				
			||||||
 | 
					  showModal?: boolean;
 | 
				
			||||||
 | 
					  setShowModal: (_: boolean) => void;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const chatStore = useChatStore();
 | 
				
			||||||
 | 
					  const session = chatStore.currentSession();
 | 
				
			||||||
 | 
					  const context = session.context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className={chatStyle["prompt-toast"]} key="prompt-toast">
 | 
					    <div className={chatStyle["prompt-toast"]} key="prompt-toast">
 | 
				
			||||||
      {props.showToast && (
 | 
					      {props.showToast && (
 | 
				
			||||||
@@ -171,115 +306,7 @@ function PromptToast(props: {
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      {props.showModal && (
 | 
					      {props.showModal && (
 | 
				
			||||||
        <div className="modal-mask">
 | 
					        <SessionConfigModel onClose={() => props.setShowModal(false)} />
 | 
				
			||||||
          <Modal
 | 
					 | 
				
			||||||
            title={Locale.Context.Edit}
 | 
					 | 
				
			||||||
            onClose={() => props.setShowModal(false)}
 | 
					 | 
				
			||||||
            actions={[
 | 
					 | 
				
			||||||
              <IconButton
 | 
					 | 
				
			||||||
                key="reset"
 | 
					 | 
				
			||||||
                icon={<CopyIcon />}
 | 
					 | 
				
			||||||
                bordered
 | 
					 | 
				
			||||||
                text={Locale.Memory.Reset}
 | 
					 | 
				
			||||||
                onClick={() =>
 | 
					 | 
				
			||||||
                  confirm(Locale.Memory.ResetConfirm) &&
 | 
					 | 
				
			||||||
                  chatStore.resetSession()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              />,
 | 
					 | 
				
			||||||
              <IconButton
 | 
					 | 
				
			||||||
                key="copy"
 | 
					 | 
				
			||||||
                icon={<CopyIcon />}
 | 
					 | 
				
			||||||
                bordered
 | 
					 | 
				
			||||||
                text={Locale.Memory.Copy}
 | 
					 | 
				
			||||||
                onClick={() => copyToClipboard(session.memoryPrompt)}
 | 
					 | 
				
			||||||
              />,
 | 
					 | 
				
			||||||
            ]}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <>
 | 
					 | 
				
			||||||
              <div className={chatStyle["context-prompt"]}>
 | 
					 | 
				
			||||||
                {context.map((c, i) => (
 | 
					 | 
				
			||||||
                  <div className={chatStyle["context-prompt-row"]} key={i}>
 | 
					 | 
				
			||||||
                    <select
 | 
					 | 
				
			||||||
                      value={c.role}
 | 
					 | 
				
			||||||
                      className={chatStyle["context-role"]}
 | 
					 | 
				
			||||||
                      onChange={(e) =>
 | 
					 | 
				
			||||||
                        updateContextPrompt(i, {
 | 
					 | 
				
			||||||
                          ...c,
 | 
					 | 
				
			||||||
                          role: e.target.value as any,
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                      {ROLES.map((r) => (
 | 
					 | 
				
			||||||
                        <option key={r} value={r}>
 | 
					 | 
				
			||||||
                          {r}
 | 
					 | 
				
			||||||
                        </option>
 | 
					 | 
				
			||||||
                      ))}
 | 
					 | 
				
			||||||
                    </select>
 | 
					 | 
				
			||||||
                    <Input
 | 
					 | 
				
			||||||
                      value={c.content}
 | 
					 | 
				
			||||||
                      type="text"
 | 
					 | 
				
			||||||
                      className={chatStyle["context-content"]}
 | 
					 | 
				
			||||||
                      rows={1}
 | 
					 | 
				
			||||||
                      onInput={(e) =>
 | 
					 | 
				
			||||||
                        updateContextPrompt(i, {
 | 
					 | 
				
			||||||
                          ...c,
 | 
					 | 
				
			||||||
                          content: e.currentTarget.value as any,
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                    <IconButton
 | 
					 | 
				
			||||||
                      icon={<DeleteIcon />}
 | 
					 | 
				
			||||||
                      className={chatStyle["context-delete-button"]}
 | 
					 | 
				
			||||||
                      onClick={() => removeContextPrompt(i)}
 | 
					 | 
				
			||||||
                      bordered
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                ))}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <div className={chatStyle["context-prompt-row"]}>
 | 
					 | 
				
			||||||
                  <IconButton
 | 
					 | 
				
			||||||
                    icon={<AddIcon />}
 | 
					 | 
				
			||||||
                    text={Locale.Context.Add}
 | 
					 | 
				
			||||||
                    bordered
 | 
					 | 
				
			||||||
                    className={chatStyle["context-prompt-button"]}
 | 
					 | 
				
			||||||
                    onClick={() =>
 | 
					 | 
				
			||||||
                      addContextPrompt({
 | 
					 | 
				
			||||||
                        role: "system",
 | 
					 | 
				
			||||||
                        content: "",
 | 
					 | 
				
			||||||
                        date: "",
 | 
					 | 
				
			||||||
                      })
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div className={chatStyle["memory-prompt"]}>
 | 
					 | 
				
			||||||
                <div className={chatStyle["memory-prompt-title"]}>
 | 
					 | 
				
			||||||
                  <span>
 | 
					 | 
				
			||||||
                    {Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "}
 | 
					 | 
				
			||||||
                    {session.messages.length})
 | 
					 | 
				
			||||||
                  </span>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  <label className={chatStyle["memory-prompt-action"]}>
 | 
					 | 
				
			||||||
                    {Locale.Memory.Send}
 | 
					 | 
				
			||||||
                    <input
 | 
					 | 
				
			||||||
                      type="checkbox"
 | 
					 | 
				
			||||||
                      checked={session.sendMemory}
 | 
					 | 
				
			||||||
                      onChange={() =>
 | 
					 | 
				
			||||||
                        chatStore.updateCurrentSession(
 | 
					 | 
				
			||||||
                          (session) =>
 | 
					 | 
				
			||||||
                            (session.sendMemory = !session.sendMemory),
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    ></input>
 | 
					 | 
				
			||||||
                  </label>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className={chatStyle["memory-prompt-content"]}>
 | 
					 | 
				
			||||||
                  {session.memoryPrompt || Locale.Memory.EmptyContent}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </>
 | 
					 | 
				
			||||||
          </Modal>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@@ -654,7 +681,7 @@ export function Chat() {
 | 
				
			|||||||
            className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`}
 | 
					            className={`${styles["window-header-main-title"]} ${styles["chat-body-title"]}`}
 | 
				
			||||||
            onClickCapture={renameSession}
 | 
					            onClickCapture={renameSession}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {session.topic}
 | 
					            {!session.topic ? DEFAULT_TOPIC : session.topic}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div className={styles["window-header-sub-title"]}>
 | 
					          <div className={styles["window-header-sub-title"]}>
 | 
				
			||||||
            {Locale.Chat.SubTitle(session.messages.length)}
 | 
					            {Locale.Chat.SubTitle(session.messages.length)}
 | 
				
			||||||
@@ -739,7 +766,13 @@ export function Chat() {
 | 
				
			|||||||
            >
 | 
					            >
 | 
				
			||||||
              <div className={styles["chat-message-container"]}>
 | 
					              <div className={styles["chat-message-container"]}>
 | 
				
			||||||
                <div className={styles["chat-message-avatar"]}>
 | 
					                <div className={styles["chat-message-avatar"]}>
 | 
				
			||||||
                  <Avatar role={message.role} model={message.model} />
 | 
					                  {message.role === "user" ? (
 | 
				
			||||||
 | 
					                    <Avatar avatar={config.avatar} />
 | 
				
			||||||
 | 
					                  ) : session.avatar ? (
 | 
				
			||||||
 | 
					                    <Avatar avatar={session.avatar} />
 | 
				
			||||||
 | 
					                  ) : (
 | 
				
			||||||
 | 
					                    <Avatar model={message.model ?? "gpt-3.5-turbo"} />
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                {showTyping && (
 | 
					                {showTyping && (
 | 
				
			||||||
                  <div className={styles["chat-message-status"]}>
 | 
					                  <div className={styles["chat-message-status"]}>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										59
									
								
								app/components/emoji.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/components/emoji.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					import EmojiPicker, {
 | 
				
			||||||
 | 
					  Emoji,
 | 
				
			||||||
 | 
					  EmojiStyle,
 | 
				
			||||||
 | 
					  Theme as EmojiTheme,
 | 
				
			||||||
 | 
					} from "emoji-picker-react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ModelType } from "../store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import BotIcon from "../icons/bot.svg";
 | 
				
			||||||
 | 
					import BlackBotIcon from "../icons/black-bot.svg";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getEmojiUrl(unified: string, style: EmojiStyle) {
 | 
				
			||||||
 | 
					  return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function AvatarPicker(props: {
 | 
				
			||||||
 | 
					  onEmojiClick: (emojiId: string) => void;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <EmojiPicker
 | 
				
			||||||
 | 
					      lazyLoadEmojis
 | 
				
			||||||
 | 
					      theme={EmojiTheme.AUTO}
 | 
				
			||||||
 | 
					      getEmojiUrl={getEmojiUrl}
 | 
				
			||||||
 | 
					      onEmojiClick={(e) => {
 | 
				
			||||||
 | 
					        props.onEmojiClick(e.unified);
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Avatar(props: { model?: ModelType; avatar?: string }) {
 | 
				
			||||||
 | 
					  if (props.model) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="no-dark">
 | 
				
			||||||
 | 
					        {props.model?.startsWith("gpt-4") ? (
 | 
				
			||||||
 | 
					          <BlackBotIcon className="user-avtar" />
 | 
				
			||||||
 | 
					        ) : (
 | 
				
			||||||
 | 
					          <BotIcon className="user-avtar" />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="user-avtar">
 | 
				
			||||||
 | 
					      {props.avatar && <EmojiAvatar avatar={props.avatar} />}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function EmojiAvatar(props: { avatar: string; size?: number }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Emoji
 | 
				
			||||||
 | 
					      unified={props.avatar}
 | 
				
			||||||
 | 
					      size={props.size ?? 18}
 | 
				
			||||||
 | 
					      getEmojiUrl={getEmojiUrl}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -368,17 +368,6 @@
 | 
				
			|||||||
  margin-top: 5px;
 | 
					  margin-top: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.user-avtar {
 | 
					 | 
				
			||||||
  height: 30px;
 | 
					 | 
				
			||||||
  width: 30px;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
  border: var(--border-in-light);
 | 
					 | 
				
			||||||
  box-shadow: var(--card-shadow);
 | 
					 | 
				
			||||||
  border-radius: 10px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.chat-message-item {
 | 
					.chat-message-item {
 | 
				
			||||||
  box-sizing: border-box;
 | 
					  box-sizing: border-box;
 | 
				
			||||||
  max-width: 100%;
 | 
					  max-width: 100%;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										141
									
								
								app/components/model-config.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								app/components/model-config.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					import styles from "./settings.module.scss";
 | 
				
			||||||
 | 
					import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Locale from "../locales";
 | 
				
			||||||
 | 
					import { InputRange } from "./input-range";
 | 
				
			||||||
 | 
					import { List, ListItem } from "./ui-lib";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ModelConfigList(props: {
 | 
				
			||||||
 | 
					  modelConfig: ModelConfig;
 | 
				
			||||||
 | 
					  updateConfig: (updater: (config: ModelConfig) => void) => void;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <List>
 | 
				
			||||||
 | 
					      <ListItem 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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <ListItem
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					      <ListItem title={Locale.Memory.Title} subTitle={Locale.Memory.Send}>
 | 
				
			||||||
 | 
					        <input
 | 
				
			||||||
 | 
					          type="checkbox"
 | 
				
			||||||
 | 
					          checked={props.modelConfig.sendMemory}
 | 
				
			||||||
 | 
					          onChange={(e) =>
 | 
				
			||||||
 | 
					            props.updateConfig(
 | 
				
			||||||
 | 
					              (config) => (config.sendMemory = e.currentTarget.checked),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ></input>
 | 
				
			||||||
 | 
					      </ListItem>
 | 
				
			||||||
 | 
					    </List>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,16 +5,6 @@
 | 
				
			|||||||
  overflow: auto;
 | 
					  overflow: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.settings-title {
 | 
					 | 
				
			||||||
  font-size: 14px;
 | 
					 | 
				
			||||||
  font-weight: bolder;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.settings-sub-title {
 | 
					 | 
				
			||||||
  font-size: 12px;
 | 
					 | 
				
			||||||
  font-weight: normal;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.avatar {
 | 
					.avatar {
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
 | 
					import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import styles from "./settings.module.scss";
 | 
					import styles from "./settings.module.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ResetIcon from "../icons/reload.svg";
 | 
					import ResetIcon from "../icons/reload.svg";
 | 
				
			||||||
@@ -10,30 +8,27 @@ import CopyIcon from "../icons/copy.svg";
 | 
				
			|||||||
import ClearIcon from "../icons/clear.svg";
 | 
					import ClearIcon from "../icons/clear.svg";
 | 
				
			||||||
import EditIcon from "../icons/edit.svg";
 | 
					import EditIcon from "../icons/edit.svg";
 | 
				
			||||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
 | 
					import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
 | 
				
			||||||
 | 
					import { ModelConfigList } from "./model-config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IconButton } from "./button";
 | 
					import { IconButton } from "./button";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  SubmitKey,
 | 
					  SubmitKey,
 | 
				
			||||||
  useChatStore,
 | 
					  useChatStore,
 | 
				
			||||||
  Theme,
 | 
					  Theme,
 | 
				
			||||||
  ALL_MODELS,
 | 
					 | 
				
			||||||
  useUpdateStore,
 | 
					  useUpdateStore,
 | 
				
			||||||
  useAccessStore,
 | 
					  useAccessStore,
 | 
				
			||||||
  ModalConfigValidator,
 | 
					 | 
				
			||||||
  useAppConfig,
 | 
					  useAppConfig,
 | 
				
			||||||
  ChatConfig,
 | 
					 | 
				
			||||||
  ModelConfig,
 | 
					 | 
				
			||||||
} from "../store";
 | 
					} from "../store";
 | 
				
			||||||
import { Avatar } from "./chat";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
 | 
					import Locale, { AllLangs, changeLang, getLang } from "../locales";
 | 
				
			||||||
import { copyToClipboard, getEmojiUrl } from "../utils";
 | 
					import { copyToClipboard } from "../utils";
 | 
				
			||||||
import Link from "next/link";
 | 
					import Link from "next/link";
 | 
				
			||||||
import { Path, UPDATE_URL } from "../constant";
 | 
					import { Path, UPDATE_URL } from "../constant";
 | 
				
			||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
					import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
				
			||||||
import { ErrorBoundary } from "./error";
 | 
					import { ErrorBoundary } from "./error";
 | 
				
			||||||
import { InputRange } from "./input-range";
 | 
					import { InputRange } from "./input-range";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { Avatar, AvatarPicker } from "./emoji";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function UserPromptModal(props: { onClose?: () => void }) {
 | 
					function UserPromptModal(props: { onClose?: () => void }) {
 | 
				
			||||||
  const promptStore = usePromptStore();
 | 
					  const promptStore = usePromptStore();
 | 
				
			||||||
@@ -136,148 +131,6 @@ function UserPromptModal(props: { onClose?: () => void }) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function SettingItem(props: {
 | 
					 | 
				
			||||||
  title: string;
 | 
					 | 
				
			||||||
  subTitle?: string;
 | 
					 | 
				
			||||||
  children: JSX.Element;
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <ListItem>
 | 
					 | 
				
			||||||
      <div className={styles["settings-title"]}>
 | 
					 | 
				
			||||||
        <div>{props.title}</div>
 | 
					 | 
				
			||||||
        {props.subTitle && (
 | 
					 | 
				
			||||||
          <div className={styles["settings-sub-title"]}>{props.subTitle}</div>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      {props.children}
 | 
					 | 
				
			||||||
    </ListItem>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function ModelConfigList(props: {
 | 
					 | 
				
			||||||
  modelConfig: ModelConfig;
 | 
					 | 
				
			||||||
  updateConfig: (updater: (config: ModelConfig) => void) => void;
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <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>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Settings() {
 | 
					export function Settings() {
 | 
				
			||||||
  const navigate = useNavigate();
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
 | 
					  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
 | 
				
			||||||
@@ -401,16 +254,13 @@ export function Settings() {
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className={styles["settings"]}>
 | 
					      <div className={styles["settings"]}>
 | 
				
			||||||
        <List>
 | 
					        <List>
 | 
				
			||||||
          <SettingItem title={Locale.Settings.Avatar}>
 | 
					          <ListItem title={Locale.Settings.Avatar}>
 | 
				
			||||||
            <Popover
 | 
					            <Popover
 | 
				
			||||||
              onClose={() => setShowEmojiPicker(false)}
 | 
					              onClose={() => setShowEmojiPicker(false)}
 | 
				
			||||||
              content={
 | 
					              content={
 | 
				
			||||||
                <EmojiPicker
 | 
					                <AvatarPicker
 | 
				
			||||||
                  lazyLoadEmojis
 | 
					                  onEmojiClick={(avatar: string) => {
 | 
				
			||||||
                  theme={EmojiTheme.AUTO}
 | 
					                    updateConfig((config) => (config.avatar = avatar));
 | 
				
			||||||
                  getEmojiUrl={getEmojiUrl}
 | 
					 | 
				
			||||||
                  onEmojiClick={(e) => {
 | 
					 | 
				
			||||||
                    updateConfig((config) => (config.avatar = e.unified));
 | 
					 | 
				
			||||||
                    setShowEmojiPicker(false);
 | 
					                    setShowEmojiPicker(false);
 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
@@ -421,12 +271,12 @@ export function Settings() {
 | 
				
			|||||||
                className={styles.avatar}
 | 
					                className={styles.avatar}
 | 
				
			||||||
                onClick={() => setShowEmojiPicker(true)}
 | 
					                onClick={() => setShowEmojiPicker(true)}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <Avatar role="user" />
 | 
					                <Avatar avatar={config.avatar} />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </Popover>
 | 
					            </Popover>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
 | 
					            title={Locale.Settings.Update.Version(currentVersion ?? "unknown")}
 | 
				
			||||||
            subTitle={
 | 
					            subTitle={
 | 
				
			||||||
              checkingUpdate
 | 
					              checkingUpdate
 | 
				
			||||||
@@ -449,9 +299,9 @@ export function Settings() {
 | 
				
			|||||||
                onClick={() => checkUpdate(true)}
 | 
					                onClick={() => checkUpdate(true)}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem title={Locale.Settings.SendKey}>
 | 
					          <ListItem title={Locale.Settings.SendKey}>
 | 
				
			||||||
            <select
 | 
					            <select
 | 
				
			||||||
              value={config.submitKey}
 | 
					              value={config.submitKey}
 | 
				
			||||||
              onChange={(e) => {
 | 
					              onChange={(e) => {
 | 
				
			||||||
@@ -467,12 +317,9 @@ export function Settings() {
 | 
				
			|||||||
                </option>
 | 
					                </option>
 | 
				
			||||||
              ))}
 | 
					              ))}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ListItem>
 | 
					          <ListItem title={Locale.Settings.Theme}>
 | 
				
			||||||
            <div className={styles["settings-title"]}>
 | 
					 | 
				
			||||||
              {Locale.Settings.Theme}
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <select
 | 
					            <select
 | 
				
			||||||
              value={config.theme}
 | 
					              value={config.theme}
 | 
				
			||||||
              onChange={(e) => {
 | 
					              onChange={(e) => {
 | 
				
			||||||
@@ -489,7 +336,7 @@ export function Settings() {
 | 
				
			|||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
          </ListItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem title={Locale.Settings.Lang.Name}>
 | 
					          <ListItem title={Locale.Settings.Lang.Name}>
 | 
				
			||||||
            <select
 | 
					            <select
 | 
				
			||||||
              value={getLang()}
 | 
					              value={getLang()}
 | 
				
			||||||
              onChange={(e) => {
 | 
					              onChange={(e) => {
 | 
				
			||||||
@@ -502,9 +349,9 @@ export function Settings() {
 | 
				
			|||||||
                </option>
 | 
					                </option>
 | 
				
			||||||
              ))}
 | 
					              ))}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.FontSize.Title}
 | 
					            title={Locale.Settings.FontSize.Title}
 | 
				
			||||||
            subTitle={Locale.Settings.FontSize.SubTitle}
 | 
					            subTitle={Locale.Settings.FontSize.SubTitle}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@@ -521,9 +368,9 @@ export function Settings() {
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            ></InputRange>
 | 
					            ></InputRange>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem title={Locale.Settings.TightBorder}>
 | 
					          <ListItem title={Locale.Settings.TightBorder}>
 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
              type="checkbox"
 | 
					              type="checkbox"
 | 
				
			||||||
              checked={config.tightBorder}
 | 
					              checked={config.tightBorder}
 | 
				
			||||||
@@ -533,9 +380,9 @@ export function Settings() {
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            ></input>
 | 
					            ></input>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem title={Locale.Settings.SendPreviewBubble}>
 | 
					          <ListItem title={Locale.Settings.SendPreviewBubble}>
 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
              type="checkbox"
 | 
					              type="checkbox"
 | 
				
			||||||
              checked={config.sendPreviewBubble}
 | 
					              checked={config.sendPreviewBubble}
 | 
				
			||||||
@@ -546,12 +393,12 @@ export function Settings() {
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            ></input>
 | 
					            ></input>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
        </List>
 | 
					        </List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <List>
 | 
					        <List>
 | 
				
			||||||
          {enabledAccessControl ? (
 | 
					          {enabledAccessControl ? (
 | 
				
			||||||
            <SettingItem
 | 
					            <ListItem
 | 
				
			||||||
              title={Locale.Settings.AccessCode.Title}
 | 
					              title={Locale.Settings.AccessCode.Title}
 | 
				
			||||||
              subTitle={Locale.Settings.AccessCode.SubTitle}
 | 
					              subTitle={Locale.Settings.AccessCode.SubTitle}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@@ -563,12 +410,12 @@ export function Settings() {
 | 
				
			|||||||
                  accessStore.updateCode(e.currentTarget.value);
 | 
					                  accessStore.updateCode(e.currentTarget.value);
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </SettingItem>
 | 
					            </ListItem>
 | 
				
			||||||
          ) : (
 | 
					          ) : (
 | 
				
			||||||
            <></>
 | 
					            <></>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.Token.Title}
 | 
					            title={Locale.Settings.Token.Title}
 | 
				
			||||||
            subTitle={Locale.Settings.Token.SubTitle}
 | 
					            subTitle={Locale.Settings.Token.SubTitle}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@@ -580,9 +427,9 @@ export function Settings() {
 | 
				
			|||||||
                accessStore.updateToken(e.currentTarget.value);
 | 
					                accessStore.updateToken(e.currentTarget.value);
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.Usage.Title}
 | 
					            title={Locale.Settings.Usage.Title}
 | 
				
			||||||
            subTitle={
 | 
					            subTitle={
 | 
				
			||||||
              showUsage
 | 
					              showUsage
 | 
				
			||||||
@@ -604,11 +451,11 @@ export function Settings() {
 | 
				
			|||||||
                onClick={checkUsage}
 | 
					                onClick={checkUsage}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
        </List>
 | 
					        </List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <List>
 | 
					        <List>
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.Prompt.Disable.Title}
 | 
					            title={Locale.Settings.Prompt.Disable.Title}
 | 
				
			||||||
            subTitle={Locale.Settings.Prompt.Disable.SubTitle}
 | 
					            subTitle={Locale.Settings.Prompt.Disable.SubTitle}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
@@ -622,9 +469,9 @@ export function Settings() {
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            ></input>
 | 
					            ></input>
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <SettingItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.Prompt.List}
 | 
					            title={Locale.Settings.Prompt.List}
 | 
				
			||||||
            subTitle={Locale.Settings.Prompt.ListCount(
 | 
					            subTitle={Locale.Settings.Prompt.ListCount(
 | 
				
			||||||
              builtinCount,
 | 
					              builtinCount,
 | 
				
			||||||
@@ -636,19 +483,17 @@ export function Settings() {
 | 
				
			|||||||
              text={Locale.Settings.Prompt.Edit}
 | 
					              text={Locale.Settings.Prompt.Edit}
 | 
				
			||||||
              onClick={() => setShowPromptModal(true)}
 | 
					              onClick={() => setShowPromptModal(true)}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </SettingItem>
 | 
					          </ListItem>
 | 
				
			||||||
        </List>
 | 
					        </List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <List>
 | 
					        <ModelConfigList
 | 
				
			||||||
          <ModelConfigList
 | 
					          modelConfig={config.modelConfig}
 | 
				
			||||||
            modelConfig={config.modelConfig}
 | 
					          updateConfig={(upater) => {
 | 
				
			||||||
            updateConfig={(upater) => {
 | 
					            const modelConfig = { ...config.modelConfig };
 | 
				
			||||||
              const modelConfig = { ...config.modelConfig };
 | 
					            upater(modelConfig);
 | 
				
			||||||
              upater(modelConfig);
 | 
					            config.update((config) => (config.modelConfig = modelConfig));
 | 
				
			||||||
              config.update((config) => (config.modelConfig = modelConfig));
 | 
					          }}
 | 
				
			||||||
            }}
 | 
					        />
 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </List>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {shouldShowPromptModal && (
 | 
					        {shouldShowPromptModal && (
 | 
				
			||||||
          <UserPromptModal onClose={() => setShowPromptModal(false)} />
 | 
					          <UserPromptModal onClose={() => setShowPromptModal(false)} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,16 @@
 | 
				
			|||||||
  border-bottom: var(--border-in-light);
 | 
					  border-bottom: var(--border-in-light);
 | 
				
			||||||
  padding: 10px 20px;
 | 
					  padding: 10px 20px;
 | 
				
			||||||
  animation: slide-in ease 0.6s;
 | 
					  animation: slide-in ease 0.6s;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .list-item-title {
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    font-weight: bolder;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .list-item-sub-title {
 | 
				
			||||||
 | 
					    font-size: 12px;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.list {
 | 
					.list {
 | 
				
			||||||
@@ -89,6 +99,8 @@
 | 
				
			|||||||
    padding: var(--modal-padding);
 | 
					    padding: var(--modal-padding);
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    justify-content: flex-end;
 | 
					    justify-content: flex-end;
 | 
				
			||||||
 | 
					    border-top: var(--border-in-light);
 | 
				
			||||||
 | 
					    box-shadow: var(--shadow);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .modal-actions {
 | 
					    .modal-actions {
 | 
				
			||||||
      display: flex;
 | 
					      display: flex;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,12 +33,22 @@ export function Card(props: { children: JSX.Element[]; className?: string }) {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ListItem(props: { children: JSX.Element[] }) {
 | 
					export function ListItem(props: {
 | 
				
			||||||
  if (props.children.length > 2) {
 | 
					  title: string;
 | 
				
			||||||
    throw Error("Only Support Two Children");
 | 
					  subTitle?: string;
 | 
				
			||||||
  }
 | 
					  children?: JSX.Element | JSX.Element[];
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
  return <div className={styles["list-item"]}>{props.children}</div>;
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={styles["list-item"]}>
 | 
				
			||||||
 | 
					      <div className={styles["list-item-title"]}>
 | 
				
			||||||
 | 
					        <div>{props.title}</div>
 | 
				
			||||||
 | 
					        {props.subTitle && (
 | 
				
			||||||
 | 
					          <div className={styles["list-item-sub-title"]}>{props.subTitle}</div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {props.children}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function List(props: { children: JSX.Element[] | JSX.Element }) {
 | 
					export function List(props: { children: JSX.Element[] | JSX.Element }) {
 | 
				
			||||||
@@ -63,7 +73,7 @@ export function Loading() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface ModalProps {
 | 
					interface ModalProps {
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  children?: JSX.Element;
 | 
					  children?: JSX.Element | JSX.Element[];
 | 
				
			||||||
  actions?: JSX.Element[];
 | 
					  actions?: JSX.Element[];
 | 
				
			||||||
  onClose?: () => void;
 | 
					  onClose?: () => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,7 +39,7 @@ const cn = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  Memory: {
 | 
					  Memory: {
 | 
				
			||||||
    Title: "历史摘要",
 | 
					    Title: "历史摘要",
 | 
				
			||||||
    EmptyContent: "尚未总结",
 | 
					    EmptyContent: "对话内容过短,无需总结",
 | 
				
			||||||
    Send: "启用总结并发送摘要",
 | 
					    Send: "启用总结并发送摘要",
 | 
				
			||||||
    Copy: "复制摘要",
 | 
					    Copy: "复制摘要",
 | 
				
			||||||
    Reset: "重置对话",
 | 
					    Reset: "重置对话",
 | 
				
			||||||
@@ -172,8 +172,8 @@ const cn = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  Context: {
 | 
					  Context: {
 | 
				
			||||||
    Toast: (x: any) => `已设置 ${x} 条前置上下文`,
 | 
					    Toast: (x: any) => `已设置 ${x} 条前置上下文`,
 | 
				
			||||||
    Edit: "前置上下文和历史记忆",
 | 
					    Edit: "当前对话设置",
 | 
				
			||||||
    Add: "新增一条",
 | 
					    Add: "新增预设对话",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ import { isMobileScreen, trimTopic } from "../utils";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import Locale from "../locales";
 | 
					import Locale from "../locales";
 | 
				
			||||||
import { showToast } from "../components/ui-lib";
 | 
					import { showToast } from "../components/ui-lib";
 | 
				
			||||||
import { ModelType, useAppConfig } from "./config";
 | 
					import { ModelConfig, ModelType, useAppConfig } from "./config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Message = ChatCompletionResponseMessage & {
 | 
					export type Message = ChatCompletionResponseMessage & {
 | 
				
			||||||
  date: string;
 | 
					  date: string;
 | 
				
			||||||
@@ -42,16 +42,18 @@ export interface ChatStat {
 | 
				
			|||||||
export interface ChatSession {
 | 
					export interface ChatSession {
 | 
				
			||||||
  id: number;
 | 
					  id: number;
 | 
				
			||||||
  topic: string;
 | 
					  topic: string;
 | 
				
			||||||
  sendMemory: boolean;
 | 
					  avatar?: string;
 | 
				
			||||||
  memoryPrompt: string;
 | 
					  memoryPrompt: string;
 | 
				
			||||||
  context: Message[];
 | 
					  context: Message[];
 | 
				
			||||||
  messages: Message[];
 | 
					  messages: Message[];
 | 
				
			||||||
  stat: ChatStat;
 | 
					  stat: ChatStat;
 | 
				
			||||||
  lastUpdate: string;
 | 
					  lastUpdate: string;
 | 
				
			||||||
  lastSummarizeIndex: number;
 | 
					  lastSummarizeIndex: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modelConfig: ModelConfig;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
 | 
					export const DEFAULT_TOPIC = Locale.Store.DefaultTopic;
 | 
				
			||||||
export const BOT_HELLO: Message = createMessage({
 | 
					export const BOT_HELLO: Message = createMessage({
 | 
				
			||||||
  role: "assistant",
 | 
					  role: "assistant",
 | 
				
			||||||
  content: Locale.Store.BotHello,
 | 
					  content: Locale.Store.BotHello,
 | 
				
			||||||
@@ -63,7 +65,6 @@ function createEmptySession(): ChatSession {
 | 
				
			|||||||
  return {
 | 
					  return {
 | 
				
			||||||
    id: Date.now(),
 | 
					    id: Date.now(),
 | 
				
			||||||
    topic: DEFAULT_TOPIC,
 | 
					    topic: DEFAULT_TOPIC,
 | 
				
			||||||
    sendMemory: true,
 | 
					 | 
				
			||||||
    memoryPrompt: "",
 | 
					    memoryPrompt: "",
 | 
				
			||||||
    context: [],
 | 
					    context: [],
 | 
				
			||||||
    messages: [],
 | 
					    messages: [],
 | 
				
			||||||
@@ -74,6 +75,8 @@ function createEmptySession(): ChatSession {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    lastUpdate: createDate,
 | 
					    lastUpdate: createDate,
 | 
				
			||||||
    lastSummarizeIndex: 0,
 | 
					    lastSummarizeIndex: 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modelConfig: useAppConfig.getState().modelConfig,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,6 +32,7 @@ const DEFAULT_CONFIG = {
 | 
				
			|||||||
    temperature: 1,
 | 
					    temperature: 1,
 | 
				
			||||||
    max_tokens: 2000,
 | 
					    max_tokens: 2000,
 | 
				
			||||||
    presence_penalty: 0,
 | 
					    presence_penalty: 0,
 | 
				
			||||||
 | 
					    sendMemory: true,
 | 
				
			||||||
    historyMessageCount: 4,
 | 
					    historyMessageCount: 4,
 | 
				
			||||||
    compressMessageLengthThreshold: 1000,
 | 
					    compressMessageLengthThreshold: 1000,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
export * from "./app";
 | 
					export * from "./chat";
 | 
				
			||||||
export * from "./update";
 | 
					export * from "./update";
 | 
				
			||||||
export * from "./access";
 | 
					export * from "./access";
 | 
				
			||||||
export * from "./config";
 | 
					export * from "./config";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -325,3 +325,14 @@ pre {
 | 
				
			|||||||
    min-width: 80%;
 | 
					    min-width: 80%;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.user-avtar {
 | 
				
			||||||
 | 
					  height: 30px;
 | 
				
			||||||
 | 
					  width: 30px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  border: var(--border-in-light);
 | 
				
			||||||
 | 
					  box-shadow: var(--card-shadow);
 | 
				
			||||||
 | 
					  border-radius: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import { EmojiStyle } from "emoji-picker-react";
 | 
					 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { showToast } from "./components/ui-lib";
 | 
					import { showToast } from "./components/ui-lib";
 | 
				
			||||||
import Locale from "./locales";
 | 
					import Locale from "./locales";
 | 
				
			||||||
@@ -90,10 +89,6 @@ export function selectOrCopy(el: HTMLElement, content: string) {
 | 
				
			|||||||
  return true;
 | 
					  return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getEmojiUrl(unified: string, style: EmojiStyle) {
 | 
					 | 
				
			||||||
  return `https://cdn.staticfile.org/emoji-datasource-apple/14.0.0/img/${style}/64/${unified}.png`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getDomContentWidth(dom: HTMLElement) {
 | 
					function getDomContentWidth(dom: HTMLElement) {
 | 
				
			||||||
  const style = window.getComputedStyle(dom);
 | 
					  const style = window.getComputedStyle(dom);
 | 
				
			||||||
  const paddingWidth =
 | 
					  const paddingWidth =
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user