|
@@ -14,6 +14,8 @@ import MaskIcon from "../icons/mask.svg";
|
|
|
import MaxIcon from "../icons/max.svg";
|
|
|
import MinIcon from "../icons/min.svg";
|
|
|
import ResetIcon from "../icons/reload.svg";
|
|
|
+import BreakIcon from "../icons/break.svg";
|
|
|
+import SettingsIcon from "../icons/chat-settings.svg";
|
|
|
|
|
|
import LightIcon from "../icons/light.svg";
|
|
|
import DarkIcon from "../icons/dark.svg";
|
|
@@ -51,7 +53,7 @@ import { IconButton } from "./button";
|
|
|
import styles from "./home.module.scss";
|
|
|
import chatStyle from "./chat.module.scss";
|
|
|
|
|
|
-import { ListItem, Modal, showModal } from "./ui-lib";
|
|
|
+import { ListItem, Modal, showModal, showToast } from "./ui-lib";
|
|
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
|
import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant";
|
|
|
import { Avatar } from "./emoji";
|
|
@@ -289,6 +291,24 @@ export function PromptHints(props: {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+function ClearContextDivider() {
|
|
|
+ const chatStore = useChatStore();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ className={chatStyle["clear-context"]}
|
|
|
+ onClick={() =>
|
|
|
+ chatStore.updateCurrentSession(
|
|
|
+ (session) => (session.clearContextIndex = -1),
|
|
|
+ )
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <div className={chatStyle["clear-context-tips"]}>上下文已清除</div>
|
|
|
+ <div className={chatStyle["clear-context-revert-btn"]}>取消清除</div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
function useScrollToBottom() {
|
|
|
// for auto-scroll
|
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -321,6 +341,7 @@ export function ChatActions(props: {
|
|
|
}) {
|
|
|
const config = useAppConfig();
|
|
|
const navigate = useNavigate();
|
|
|
+ const chatStore = useChatStore();
|
|
|
|
|
|
// switch themes
|
|
|
const theme = config.theme;
|
|
@@ -359,7 +380,7 @@ export function ChatActions(props: {
|
|
|
className={`${chatStyle["chat-input-action"]} clickable`}
|
|
|
onClick={props.showPromptModal}
|
|
|
>
|
|
|
- <BrainIcon />
|
|
|
+ <SettingsIcon />
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
@@ -391,6 +412,22 @@ export function ChatActions(props: {
|
|
|
>
|
|
|
<MaskIcon />
|
|
|
</div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ className={`${chatStyle["chat-input-action"]} clickable`}
|
|
|
+ onClick={() => {
|
|
|
+ chatStore.updateCurrentSession((session) => {
|
|
|
+ if ((session.clearContextIndex ?? -1) > 0) {
|
|
|
+ session.clearContextIndex = -1;
|
|
|
+ } else {
|
|
|
+ session.clearContextIndex = session.messages.length;
|
|
|
+ session.memoryPrompt = ""; // will clear memory
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <BreakIcon />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
@@ -602,6 +639,12 @@ export function Chat() {
|
|
|
context.push(copiedHello);
|
|
|
}
|
|
|
|
|
|
+ // clear context index = context length + index in messages
|
|
|
+ const clearContextIndex =
|
|
|
+ (session.clearContextIndex ?? -1) >= 0
|
|
|
+ ? session.clearContextIndex! + context.length
|
|
|
+ : -1;
|
|
|
+
|
|
|
// preview messages
|
|
|
const messages = context
|
|
|
.concat(session.messages as RenderMessage[])
|
|
@@ -736,86 +779,91 @@ export function Chat() {
|
|
|
!(message.preview || message.content.length === 0);
|
|
|
const showTyping = message.preview || message.streaming;
|
|
|
|
|
|
+ const shouldShowClearContextDivider = i === clearContextIndex - 1;
|
|
|
+
|
|
|
return (
|
|
|
- <div
|
|
|
- key={i}
|
|
|
- className={
|
|
|
- isUser ? styles["chat-message-user"] : styles["chat-message"]
|
|
|
- }
|
|
|
- >
|
|
|
- <div className={styles["chat-message-container"]}>
|
|
|
- <div className={styles["chat-message-avatar"]}>
|
|
|
- {message.role === "user" ? (
|
|
|
- <Avatar avatar={config.avatar} />
|
|
|
- ) : (
|
|
|
- <MaskAvatar mask={session.mask} />
|
|
|
- )}
|
|
|
- </div>
|
|
|
- {showTyping && (
|
|
|
- <div className={styles["chat-message-status"]}>
|
|
|
- {Locale.Chat.Typing}
|
|
|
+ <>
|
|
|
+ <div
|
|
|
+ key={i}
|
|
|
+ className={
|
|
|
+ isUser ? styles["chat-message-user"] : styles["chat-message"]
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <div className={styles["chat-message-container"]}>
|
|
|
+ <div className={styles["chat-message-avatar"]}>
|
|
|
+ {message.role === "user" ? (
|
|
|
+ <Avatar avatar={config.avatar} />
|
|
|
+ ) : (
|
|
|
+ <MaskAvatar mask={session.mask} />
|
|
|
+ )}
|
|
|
</div>
|
|
|
- )}
|
|
|
- <div className={styles["chat-message-item"]}>
|
|
|
- {showActions && (
|
|
|
- <div className={styles["chat-message-top-actions"]}>
|
|
|
- {message.streaming ? (
|
|
|
- <div
|
|
|
- className={styles["chat-message-top-action"]}
|
|
|
- onClick={() => onUserStop(message.id ?? i)}
|
|
|
- >
|
|
|
- {Locale.Chat.Actions.Stop}
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <div
|
|
|
- className={styles["chat-message-top-action"]}
|
|
|
- onClick={() => onDelete(message.id ?? i)}
|
|
|
- >
|
|
|
- {Locale.Chat.Actions.Delete}
|
|
|
- </div>
|
|
|
+ {showTyping && (
|
|
|
+ <div className={styles["chat-message-status"]}>
|
|
|
+ {Locale.Chat.Typing}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div className={styles["chat-message-item"]}>
|
|
|
+ {showActions && (
|
|
|
+ <div className={styles["chat-message-top-actions"]}>
|
|
|
+ {message.streaming ? (
|
|
|
<div
|
|
|
className={styles["chat-message-top-action"]}
|
|
|
- onClick={() => onResend(message.id ?? i)}
|
|
|
+ onClick={() => onUserStop(message.id ?? i)}
|
|
|
>
|
|
|
- {Locale.Chat.Actions.Retry}
|
|
|
+ {Locale.Chat.Actions.Stop}
|
|
|
</div>
|
|
|
- </>
|
|
|
- )}
|
|
|
-
|
|
|
- <div
|
|
|
- className={styles["chat-message-top-action"]}
|
|
|
- onClick={() => copyToClipboard(message.content)}
|
|
|
- >
|
|
|
- {Locale.Chat.Actions.Copy}
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <div
|
|
|
+ className={styles["chat-message-top-action"]}
|
|
|
+ onClick={() => onDelete(message.id ?? i)}
|
|
|
+ >
|
|
|
+ {Locale.Chat.Actions.Delete}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ className={styles["chat-message-top-action"]}
|
|
|
+ onClick={() => onResend(message.id ?? i)}
|
|
|
+ >
|
|
|
+ {Locale.Chat.Actions.Retry}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div
|
|
|
+ className={styles["chat-message-top-action"]}
|
|
|
+ onClick={() => copyToClipboard(message.content)}
|
|
|
+ >
|
|
|
+ {Locale.Chat.Actions.Copy}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <Markdown
|
|
|
+ content={message.content}
|
|
|
+ loading={
|
|
|
+ (message.preview || message.content.length === 0) &&
|
|
|
+ !isUser
|
|
|
+ }
|
|
|
+ onContextMenu={(e) => onRightClick(e, message)}
|
|
|
+ onDoubleClickCapture={() => {
|
|
|
+ if (!isMobileScreen) return;
|
|
|
+ setUserInput(message.content);
|
|
|
+ }}
|
|
|
+ fontSize={fontSize}
|
|
|
+ parentRef={scrollRef}
|
|
|
+ defaultShow={i >= messages.length - 10}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ {!isUser && !message.preview && (
|
|
|
+ <div className={styles["chat-message-actions"]}>
|
|
|
+ <div className={styles["chat-message-action-date"]}>
|
|
|
+ {message.date.toLocaleString()}
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
- <Markdown
|
|
|
- content={message.content}
|
|
|
- loading={
|
|
|
- (message.preview || message.content.length === 0) &&
|
|
|
- !isUser
|
|
|
- }
|
|
|
- onContextMenu={(e) => onRightClick(e, message)}
|
|
|
- onDoubleClickCapture={() => {
|
|
|
- if (!isMobileScreen) return;
|
|
|
- setUserInput(message.content);
|
|
|
- }}
|
|
|
- fontSize={fontSize}
|
|
|
- parentRef={scrollRef}
|
|
|
- defaultShow={i >= messages.length - 10}
|
|
|
- />
|
|
|
</div>
|
|
|
- {!isUser && !message.preview && (
|
|
|
- <div className={styles["chat-message-actions"]}>
|
|
|
- <div className={styles["chat-message-action-date"]}>
|
|
|
- {message.date.toLocaleString()}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ {shouldShowClearContextDivider && <ClearContextDivider />}
|
|
|
+ </>
|
|
|
);
|
|
|
})}
|
|
|
</div>
|