From a4d5f6a3f2dcc78478129f1f9368b1e4ce982e1c Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 21:44:30 +0800 Subject: [PATCH] feat: fully multipath and resubmit --- api/server/routes/convos.js | 2 +- client/src/components/Main/TextChat.jsx | 14 +- client/src/components/Messages/Message.jsx | 191 ++++++++++++------ .../src/components/Messages/SiblingSwitch.jsx | 26 +++ client/src/components/Messages/index.jsx | 53 +++-- 5 files changed, 197 insertions(+), 89 deletions(-) create mode 100644 client/src/components/Messages/SiblingSwitch.jsx diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 5862919f17..a82242db4a 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -22,7 +22,7 @@ router.post('/gen_title', async (req, res) => { : await titleConvo({ model: convo?.model, message: firstMessage?.text, - // response: JSON.stringify(secondMessage?.text || '') + response: JSON.stringify(secondMessage?.text || '') }); await saveConvo({ diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index b541c9a51f..d1a4ed511b 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -34,7 +34,7 @@ export default function TextChat({ messages }) { const messageHandler = (data, currentState, currentMsg) => { const { messages, _currentMsg, message, sender } = currentState; - dispatch(setMessages([...messages, currentMsg, { sender, text: data }])); + dispatch(setMessages([...messages, currentMsg, { sender, text: data, parentMessageId: currentMsg?.messageId, messageId: currentMsg?.messageId + '_' }])); }; const createdHandler = (data, currentState, currentMsg) => { @@ -152,11 +152,13 @@ export default function TextChat({ messages }) { return; } + // this is not a real messageId, it is used as placeholder before real messageId returned + const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; const message = text.trim(); - const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true }; + const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000', messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; - const initialResponse = { sender, text: '' }; + const initialResponse = { sender, text: '', parentMessageId: fakeMessageId }; dispatch(setSubmitState(true)); dispatch(setMessages([...messages, currentMsg, initialResponse])); @@ -166,9 +168,7 @@ export default function TextChat({ messages }) { convo, isCustomModel, message: { - sender: 'User', - text: message, - isCreatedByUser: true, + ...currentMsg, model, chatGptLabel, promptPrefix, @@ -193,7 +193,7 @@ export default function TextChat({ messages }) { payload = { ...payload, conversationId: convo.conversationId, - parentMessageId: convo.parentMessageId + parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' }; } diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 2d6df0360e..1eff8c7d98 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -4,20 +4,59 @@ import { useSelector, useDispatch } from 'react-redux'; import GPTIcon from '../svg/GPTIcon'; import BingIcon from '../svg/BingIcon'; import HoverButtons from './HoverButtons'; +import SiblingSwitch from './SiblingSwitch'; import Spinner from '../svg/Spinner'; import { setError } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; +import { setConversation } from '../../store/convoSlice'; + +const MultiMessage = ({ + messageList, + messages, + scrollToBottom, + currentEditId, + setCurrentEditId +}) => { + const [siblingIdx, setSiblingIdx] = useState(0) + + const setSiblingIdxRev = (value) => { + setSiblingIdx(messageList?.length - value - 1) + } + + if (!messageList?.length) return null; + + if (siblingIdx >= messageList?.length) { + setSiblingIdx(0) + return null + } + + return +} + +export { MultiMessage }; export default function Message({ message, messages, - last = false, scrollToBottom, - edit, - currentEditIdx, - enterEdit + currentEditId, + setCurrentEditId, + siblingIdx, + siblingCount, + setSiblingIdx }) { const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit); const [abortScroll, setAbort] = useState(false); @@ -26,6 +65,9 @@ export default function Message({ const convo = useSelector((state) => state.convo); const { initial } = useSelector((state) => state.models); const { error: convoError } = convo; + const last = !message?.children?.length + + const edit = message.messageId == currentEditId; const dispatch = useDispatch(); @@ -37,11 +79,18 @@ export default function Message({ scrollToBottom(); } }, [isSubmitting, text, blinker, scrollToBottom, abortScroll]); - + + useEffect(() => { + if (last) + dispatch(setConversation({parentMessageId: message?.messageId})) + }, [last, ]) + if (sender === '') { return ; } + const enterEdit = (cancel) => setCurrentEditId(cancel?-1:message.messageId) + const handleWheel = () => { if (blinker) { setAbort(true); @@ -105,97 +154,109 @@ export default function Message({ return; } + // this is not a real messageId, it is used as placeholder before real messageId returned + const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; - const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true }; - console.log(model) + const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true, messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; - const initialResponse = { sender, text: '' }; + const initialResponse = { sender, text: '', parentMessageId: fakeMessageId }; dispatch(setSubmitState(true)); - dispatch(setMessages([...messages.slice(0, currentEditIdx), currentMsg, initialResponse])); + dispatch(setMessages([...messages, currentMsg, initialResponse])); dispatch(setText('')); const submission = { isCustomModel, message: { - ...message, - text: text.trim(), + ...currentMsg, model, chatGptLabel, promptPrefix, }, - messages: messages.slice(0, currentEditIdx), + messages: messages, currentMsg, initialResponse, sender, }; - console.log('User Input:', message); + console.log('User Input:', currentMsg?.text); // handleSubmit(submission); dispatch(setSubmission(submission)); + setSiblingIdx(siblingCount - 1) enterEdit(true); }; return ( -
-
- - {typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( - {icon} - ) : ( - icon - )} - -
-
- {error ? ( -
-
- {text} -
-
- ) : - edit ? ( -
- {/*
*/} - -
+ <> +
+
+ +
+ {typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( + {icon} + ) : ( + icon + )} + +
+
+
+ {error ? ( +
+
{text}
-
- - -
- ) : ( -
- {/*
*/} -
- {!isCreatedByUser ? wrapText(text) : text} - {blinker && } + ) : + edit ? ( +
+ {/*
*/} + +
+ {text} +
+
+ + +
-
- )} + ) : ( +
+ {/*
*/} +
+ {!isCreatedByUser ? wrapText(text) : text} + {blinker && } +
+
+ )} +
+ enterEdit()}/>
- enterEdit()}/>
-
+ + ); } diff --git a/client/src/components/Messages/SiblingSwitch.jsx b/client/src/components/Messages/SiblingSwitch.jsx new file mode 100644 index 0000000000..b5586d6a8c --- /dev/null +++ b/client/src/components/Messages/SiblingSwitch.jsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export default function SiblingSwitch({ + siblingIdx, + siblingCount, + setSiblingIdx +}) { + const previous = () => { + setSiblingIdx(siblingIdx - 1); + } + + const next = () => { + setSiblingIdx(siblingIdx + 1); + } + return siblingCount > 1 ? ( +
+ + {siblingIdx + 1}/{siblingCount} + +
+ ):null; +} diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 7d9bd69d44..5d191e61cf 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,15 +1,17 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef, useMemo } from 'react'; import { CSSTransition } from 'react-transition-group'; import ScrollToBottom from './ScrollToBottom'; -import Message from './Message'; +import { MultiMessage } from './Message'; +import Conversation from '../Conversations/Conversation'; +import { useSelector } from 'react-redux'; const Messages = ({ messages }) => { - const [currentEditIdx, setCurrentEditIdx] = useState(-1) + const [currentEditId, setCurrentEditId] = useState(-1) + const { conversationId } = useSelector((state) => state.convo); const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); - useEffect(() => { const timeoutId = setTimeout(() => { const scrollable = scrollableRef.current; @@ -21,6 +23,29 @@ const Messages = ({ messages }) => { clearTimeout(timeoutId); }; }, [messages]); + + const messageTree = useMemo(() => buildTree(messages), [messages, ]); + + function buildTree(messages) { + let messageMap = {}; + let rootMessages = []; + + // Traverse the messages array and store each element in messageMap. + messages.forEach(message => { + messageMap[message.messageId] = {...message, children: []}; + + if (message.parentMessageId === "00000000-0000-0000-0000-000000000000") { + rootMessages.push(messageMap[message.messageId]); + } else { + const parentMessage = messageMap[message.parentMessageId]; + if (parentMessage) { + parentMessage.children.push(messageMap[message.messageId]); + } + } + }); + + return rootMessages; + } const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); @@ -59,18 +84,14 @@ const Messages = ({ messages }) => { {/*
*/}
- {messages.map((message, i) => ( - setCurrentEditIdx(cancel?-1:i)} - /> - ))} +