From 1974050c4a7db822f48bd759a411d697660845f2 Mon Sep 17 00:00:00 2001 From: lishiyun1227 Date: Mon, 17 Apr 2023 12:03:52 +0800 Subject: [PATCH 01/27] add --- test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 test.txt diff --git a/test.txt b/test.txt new file mode 100644 index 00000000000..2a61ba0195a --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +just for test. \ No newline at end of file From 09dbbdc4299458e8bae728daacfb031493aa3f09 Mon Sep 17 00:00:00 2001 From: lishiyun1227 Date: Mon, 17 Apr 2023 13:29:59 +0800 Subject: [PATCH 02/27] add dialog --- app/components/chat.module.scss | 56 +++++++ app/components/chat.tsx | 256 ++++++++++++++++++++++++-------- 2 files changed, 247 insertions(+), 65 deletions(-) diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index f57e6c10011..bbb083f724d 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 33ac3ac5770..698458b4c94 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); - - // auto scroll - useLayoutEffect(() => { + const scrollToBottom = () => { const dom = scrollRef.current; - if (dom && autoScroll) { + if (dom) { setTimeout(() => (dom.scrollTop = dom.scrollHeight), 1); } + }; + + // auto scroll + useLayoutEffect(() => { + 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} + />