diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss
index f57e6c100..bbb083f72 100644
--- a/app/components/chat.module.scss
+++ b/app/components/chat.module.scss
@@ -1,5 +1,29 @@
@import "../styles/animation.scss";
+.chat-input-actions {
+ display: flex;
+ flex-wrap: wrap;
+
+ .chat-input-action {
+ display: inline-flex;
+ border-radius: 20px;
+ font-size: 12px;
+ background-color: var(--white);
+ color: var(--black);
+ border: var(--border-in-light);
+ padding: 4px 10px;
+ animation: slide-in ease 0.3s;
+ box-shadow: var(--card-shadow);
+ transition: all ease 0.3s;
+ margin-bottom: 10px;
+ align-items: center;
+
+ &:not(:last-child) {
+ margin-right: 5px;
+ }
+ }
+}
+
.prompt-toast {
position: absolute;
bottom: -50px;
@@ -81,3 +105,35 @@
user-select: text;
}
}
+.dialog{
+ position: relative;
+ margin: 0 auto;
+ background-color: rgba(0,0,0,0.2);
+ color: var(--black);
+ .dialog-modal{
+ position: absolute;
+ left: 10%;
+ right: 10%;
+ width: 30%;
+ height: 300px;
+ top: 20%;
+ background-color: var(--white);
+ font-size: 12px;
+ color: var(--black);
+
+ border: var(--border-in-light);
+ box-shadow: var(--card-shadow);
+ padding: 10px 20px;
+ border-radius: 8px;
+ animation: slide-in-from-top ease 0.3s;
+ .title{
+ border-bottom: 1px solid #e6e5e5;
+ position: relative;
+ color: var(--black);
+ font-size: 14px;
+ }
+ .content-input{
+ padding: 20px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 33ac3ac57..698458b4c 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -3,6 +3,7 @@ import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
import SendWhiteIcon from "../icons/send-white.svg";
import BrainIcon from "../icons/brain.svg";
+import RenameIcon from "../icons/rename.svg";
import ExportIcon from "../icons/share.svg";
import ReturnIcon from "../icons/return.svg";
import CopyIcon from "../icons/copy.svg";
@@ -14,6 +15,12 @@ import DeleteIcon from "../icons/delete.svg";
import MaxIcon from "../icons/max.svg";
import MinIcon from "../icons/min.svg";
+import LightIcon from "../icons/light.svg";
+import DarkIcon from "../icons/dark.svg";
+import AutoIcon from "../icons/auto.svg";
+import BottomIcon from "../icons/bottom.svg";
+import StopIcon from "../icons/pause.svg";
+
import {
Message,
SubmitKey,
@@ -22,6 +29,7 @@ import {
ROLES,
createMessage,
useAccessStore,
+ Theme,
} from "../store";
import {
@@ -60,7 +68,11 @@ export function Avatar(props: { role: Message["role"] }) {
const config = useChatStore((state) => state.config);
if (props.role !== "user") {
- return ;
+ return (
+
+
+
+ );
}
return (
@@ -312,26 +324,95 @@ export function PromptHints(props: {
);
}
+
function useScrollToBottom() {
// for auto-scroll
const scrollRef = useRef(null);
const [autoScroll, setAutoScroll] = useState(true);
+ const scrollToBottom = () => {
+ const dom = scrollRef.current;
+ if (dom) {
+ setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
+ }
+ };
// auto scroll
useLayoutEffect(() => {
- const dom = scrollRef.current;
- if (dom && autoScroll) {
- setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1);
- }
+ autoScroll && scrollToBottom();
});
return {
scrollRef,
autoScroll,
setAutoScroll,
+ scrollToBottom,
};
}
+export function ChatActions(props: {
+ showPromptModal: () => void;
+ scrollToBottom: () => void;
+ hitBottom: boolean;
+}) {
+ const chatStore = useChatStore();
+
+ // switch themes
+ const theme = chatStore.config.theme;
+ function nextTheme() {
+ const themes = [Theme.Auto, Theme.Light, Theme.Dark];
+ const themeIndex = themes.indexOf(theme);
+ const nextIndex = (themeIndex + 1) % themes.length;
+ const nextTheme = themes[nextIndex];
+ chatStore.updateConfig((config) => (config.theme = nextTheme));
+ }
+
+ // stop all responses
+ const couldStop = ControllerPool.hasPending();
+ const stopAll = () => ControllerPool.stopAll();
+
+ return (
+
+ {couldStop && (
+
+
+
+ )}
+ {!props.hitBottom && (
+
+
+
+ )}
+ {props.hitBottom && (
+
+
+
+ )}
+
+
+ {theme === Theme.Auto ? (
+
+ ) : theme === Theme.Light ? (
+
+ ) : theme === Theme.Dark ? (
+
+ ) : null}
+
+
+ );
+}
+
export function Chat(props: {
showSideBar?: () => void;
sideBarShowing?: boolean;
@@ -350,7 +431,7 @@ export function Chat(props: {
const [beforeInput, setBeforeInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { submitKey, shouldSubmit } = useSubmitHandler();
- const { scrollRef, setAutoScroll } = useScrollToBottom();
+ const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
const [hitBottom, setHitBottom] = useState(false);
const onChatBodyScroll = (e: HTMLElement) => {
@@ -375,16 +456,6 @@ export function Chat(props: {
inputRef.current?.focus();
};
- const scrollInput = () => {
- const dom = inputRef.current;
- if (!dom) return;
- const paddingBottomNum: number = parseInt(
- window.getComputedStyle(dom).paddingBottom,
- 10,
- );
- dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum;
- };
-
// auto grow input
const [inputRows, setInputRows] = useState(2);
const measure = useDebouncedCallback(
@@ -409,7 +480,6 @@ export function Chat(props: {
// only search prompts when user input is short
const SEARCH_TEXT_LIMIT = 30;
const onInput = (text: string) => {
- scrollInput();
setUserInput(text);
const n = text.trim().length;
@@ -467,21 +537,45 @@ export function Chat(props: {
}
};
- const onResend = (botIndex: number) => {
+ const findLastUesrIndex = (messageId: number) => {
// find last user input message and resend
- for (let i = botIndex; i >= 0; i -= 1) {
- if (messages[i].role === "user") {
- setIsLoading(true);
- chatStore
- .onUserInput(messages[i].content)
- .then(() => setIsLoading(false));
- chatStore.updateCurrentSession((session) =>
- session.messages.splice(i, 2),
- );
- inputRef.current?.focus();
- return;
+ let lastUserMessageIndex: number | null = null;
+ for (let i = 0; i < session.messages.length; i += 1) {
+ const message = session.messages[i];
+ if (message.id === messageId) {
+ break;
+ }
+ if (message.role === "user") {
+ lastUserMessageIndex = i;
}
}
+
+ return lastUserMessageIndex;
+ };
+
+ const deleteMessage = (userIndex: number) => {
+ chatStore.updateCurrentSession((session) =>
+ session.messages.splice(userIndex, 2),
+ );
+ };
+
+ const onDelete = (botMessageId: number) => {
+ const userIndex = findLastUesrIndex(botMessageId);
+ if (userIndex === null) return;
+ deleteMessage(userIndex);
+ };
+
+ const onResend = (botMessageId: number) => {
+ // find last user input message and resend
+ const userIndex = findLastUesrIndex(botMessageId);
+ if (userIndex === null) return;
+
+ setIsLoading(true);
+ chatStore
+ .onUserInput(session.messages[userIndex].content)
+ .then(() => setIsLoading(false));
+ deleteMessage(userIndex);
+ inputRef.current?.focus();
};
const config = useChatStore((state) => state.config);
@@ -489,6 +583,8 @@ export function Chat(props: {
const context: RenderMessage[] = session.context.slice();
const accessStore = useAccessStore();
+ const [dialog, setDialog] = useState(false)
+ const [dialogValue, setDialogValue] = useState('')
if (
context.length === 0 &&
@@ -496,10 +592,17 @@ export function Chat(props: {
) {
const copiedHello = Object.assign({}, BOT_HELLO);
if (!accessStore.isAuthorized()) {
- copiedHello.content = Locale.Error.Unauthorized;
+ setDialog(true)
+ // copiedHello.content = Locale.Error.Unauthorized;
}
context.push(copiedHello);
}
+ const handleClick = () =>{
+ accessStore.updateToken(dialogValue);
+ setDialog(false)
+ // const copiedHello = Object.assign({}, BOT_HELLO);
+ // context.push(copiedHello);
+ }
// preview messages
const messages = context
@@ -533,6 +636,13 @@ export function Chat(props: {
const [showPromptModal, setShowPromptModal] = useState(false);
+ const renameSession = () => {
+ const newTopic = prompt(Locale.Chat.Rename, session.topic);
+ if (newTopic && newTopic !== session.topic) {
+ chatStore.updateCurrentSession((session) => (session.topic = newTopic!));
+ }
+ };
+
// Auto focus
useEffect(() => {
if (props.sideBarShowing && isMobileScreen()) return;
@@ -546,14 +656,7 @@ export function Chat(props: {
{
- const newTopic = prompt(Locale.Chat.Rename, session.topic);
- if (newTopic && newTopic !== session.topic) {
- chatStore.updateCurrentSession(
- (session) => (session.topic = newTopic!),
- );
- }
- }}
+ onClickCapture={renameSession}
>
{session.topic}
@@ -572,12 +675,9 @@ export function Chat(props: {
}
+ icon={}
bordered
- title={Locale.Chat.Actions.CompressedHistory}
- onClick={() => {
- setShowPromptModal(true);
- }}
+ onClick={renameSession}
/>
@@ -656,12 +756,20 @@ export function Chat(props: {
{Locale.Chat.Actions.Stop}
) : (
- onResend(i)}
- >
- {Locale.Chat.Actions.Retry}
-
+ <>
+ onDelete(message.id ?? i)}
+ >
+ {Locale.Chat.Actions.Delete}
+
+ onResend(message.id ?? i)}
+ >
+ {Locale.Chat.Actions.Retry}
+
+ >
)}
)}
- {(message.preview || message.content.length === 0) &&
- !isUser ? (
-
- ) : (
- onRightClick(e, message)}
- onDoubleClickCapture={() => {
- if (!isMobileScreen()) return;
- setUserInput(message.content);
- }}
- >
-
-
- )}
+ onRightClick(e, message)}
+ onDoubleClickCapture={() => {
+ if (!isMobileScreen()) return;
+ setUserInput(message.content);
+ }}
+ fontSize={fontSize}
+ parentRef={scrollRef}
+ />
{!isUser && !message.preview && (
@@ -704,6 +810,12 @@ export function Chat(props: {
+
+
setShowPromptModal(true)}
+ scrollToBottom={scrollToBottom}
+ hitBottom={hitBottom}
+ />
+ {dialog ?
+
+
+
认证过期,请重新输入ACCESS-CODE
+
+ {
+ accessStore.updateToken(e.currentTarget.value);
+ setDialogValue(e.currentTarget.value);
+ }} />
+
+
+
+
: ''}
+
);
}