From e3b0cb7db78a4b393e7a0792c15110ae30fc4db3 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Thu, 16 Mar 2023 21:59:20 +0800 Subject: [PATCH 1/8] feat: show model at the top of conversation messages. --- client/src/components/Messages/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 611a0231d3..27fd406425 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -8,10 +8,14 @@ import { useSelector } from 'react-redux'; const Messages = ({ messages, messageTree }) => { const [currentEditId, setCurrentEditId] = useState(-1); const { conversationId } = useSelector((state) => state.convo); + const { model, customModel, chatGptLabel } = useSelector((state) => state.submit); + const { models } = useSelector((state) => state.models); const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); + const modelName = models.find(element => element.model==model)?.name + useEffect(() => { const timeoutId = setTimeout(() => { const scrollable = scrollableRef.current; @@ -60,6 +64,9 @@ const Messages = ({ messages, messageTree }) => { > {/*
*/}
+
+ Model: {modelName} {customModel?`(${customModel})`:null} +
{messageTree.length === 0 ? ( From 0891566d1ebf273b4eaa7a859a229a9a71d47707 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Fri, 17 Mar 2023 01:49:09 +0800 Subject: [PATCH 2/8] feat: add regenerate to all response message as official --- api/server/routes/ask.js | 49 ++---- .../components/Conversations/Conversation.jsx | 6 +- client/src/components/Main/TextChat.jsx | 165 +++++++++--------- client/src/components/Messages/Message.jsx | 60 +------ .../src/components/Messages/MultiMessage.jsx | 7 +- .../src/components/svg/StopGeneratingIcon.jsx | 7 + client/src/store/convoSlice.js | 9 +- client/src/utils/handleSubmit.js | 85 ++++++++- 8 files changed, 211 insertions(+), 177 deletions(-) create mode 100644 client/src/components/svg/StopGeneratingIcon.jsx diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index f016a49b30..c8313efd68 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -12,7 +12,7 @@ router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; + let { model, text, overrideParentMessageId=null, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } @@ -36,51 +36,22 @@ router.post('/', async (req, res) => { ...convo }); - await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + if (!overrideParentMessageId) { + await saveMessage(userMessage); + await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + } return await ask({ userMessage, model, convo, preSendRequest: true, + overrideParentMessageId, req, res }); }); -router.post('/regenerate', async (req, res) => { - const { model } = req.body; - - const oldUserMessage = await getMessages({ messageId: req.body }); - - if (oldUserMessage) { - const convo = await getConvo(userMessage?.conversationId); - - const userMessageId = crypto.randomUUID(); - - let userMessage = { - ...userMessage, - messageId: userMessageId - }; - - console.log('ask log for regeneration', { - model, - ...userMessage, - ...convo - }); - - return await ask({ - userMessage, - model, - convo, - preSendRequest: false, - req, - res - }); - } else return handleError(res, { text: 'Parent message not found' }); -}); - const ask = async ({ userMessage, overrideParentMessageId = null, @@ -136,10 +107,10 @@ const ask = async ({ gptResponse.text = gptResponse.response; // gptResponse.id = gptResponse.messageId; gptResponse.parentMessageId = overrideParentMessageId || userMessageId; - userMessage.conversationId = conversationId - ? conversationId - : gptResponse.conversationId; - await saveMessage(userMessage); + // userMessage.conversationId = conversationId + // ? conversationId + // : gptResponse.conversationId; + // await saveMessage(userMessage); delete gptResponse.response; } diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 9e5c3dce6d..5d503372fd 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -58,7 +58,8 @@ export default function Conversation({ jailbreakConversationId, conversationSignature, clientId, - invocationId + invocationId, + latestMessage: null }) ); } else { @@ -69,7 +70,8 @@ export default function Conversation({ jailbreakConversationId: null, conversationSignature: null, clientId: null, - invocationId: null + invocationId: null, + latestMessage: null }) ); } diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 9221e67090..e6bd79c8f0 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -7,11 +7,14 @@ import Footer from './Footer'; import TextareaAutosize from 'react-textarea-autosize'; import createPayload from '~/utils/createPayload'; import resetConvo from '~/utils/resetConvo'; +import RegenerateIcon from '../svg/RegenerateIcon'; +import StopGeneratingIcon from '../svg/StopGeneratingIcon'; import { useSelector, useDispatch } from 'react-redux'; import { setConversation, setNewConvo, setError, refreshConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; +import { useMessageHandler } from '../../utils/handleSubmit' export default function TextChat({ messages }) { const [errorMessage, setErrorMessage] = useState(''); @@ -24,7 +27,8 @@ export default function TextChat({ messages }) { const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit); const { text } = useSelector((state) => state.text); - const { error } = convo; + const { error, latestMessage } = convo; + const { ask, regenerate } = useMessageHandler(); // auto focus to input, when enter a conversation. useEffect(() => { @@ -32,9 +36,12 @@ export default function TextChat({ messages }) { }, [convo?.conversationId,]) const messageHandler = (data, currentState, currentMsg) => { - const { messages, _currentMsg, message, sender } = currentState; + const { messages, _currentMsg, message, sender, isRegenerate } = currentState; - dispatch(setMessages([...messages, currentMsg, { sender, text: data, parentMessageId: currentMsg?.messageId, messageId: currentMsg?.messageId + '_', submitting: true }])); + if (isRegenerate) + dispatch(setMessages([...messages, { sender, text: data, parentMessageId: message?.overrideParentMessageId, messageId: message?.overrideParentMessageId + '_', submitting: true }])); + else + dispatch(setMessages([...messages, currentMsg, { sender, text: data, parentMessageId: currentMsg?.messageId, messageId: currentMsg?.messageId + '_', submitting: true }])); }; const createdHandler = (data, currentState, currentMsg) => { @@ -42,6 +49,7 @@ export default function TextChat({ messages }) { dispatch( setConversation({ conversationId, + latestMessage: null }) ); }; @@ -49,12 +57,17 @@ export default function TextChat({ messages }) { const convoHandler = (data, currentState, currentMsg) => { const { requestMessage, responseMessage } = data; const { conversationId } = requestMessage; - const { messages, _currentMsg, message, isCustomModel, sender } = + const { messages, _currentMsg, message, isCustomModel, sender, isRegenerate } = currentState; const { model, chatGptLabel, promptPrefix } = message; - dispatch( - setMessages([...messages, requestMessage, responseMessage,]) - ); + if (isRegenerate) + dispatch( + setMessages([...messages, responseMessage,]) + ); + else + dispatch( + setMessages([...messages, requestMessage, responseMessage,]) + ); const isBing = model === 'bingai' || model === 'sydney'; @@ -82,7 +95,8 @@ export default function TextChat({ messages }) { clientId: null, invocationId: null, chatGptLabel: model === isCustomModel ? chatGptLabel : null, - promptPrefix: model === isCustomModel ? promptPrefix : null + promptPrefix: model === isCustomModel ? promptPrefix : null, + latestMessage: null }) ); } else if ( @@ -98,7 +112,8 @@ export default function TextChat({ messages }) { conversationSignature, clientId, conversationId, - invocationId + invocationId, + latestMessage: null }) ); } else if (model === 'sydney') { @@ -119,7 +134,8 @@ export default function TextChat({ messages }) { conversationSignature, clientId, conversationId, - invocationId + invocationId, + latestMessage: null }) ); } @@ -142,51 +158,8 @@ export default function TextChat({ messages }) { dispatch(setError(true)); return; }; - const submitMessage = () => { - if (error) { - dispatch(setError(false)); - } - - if (!!isSubmitting || text.trim() === '') { - 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 sender = model === 'chatgptCustom' ? chatGptLabel : model; - let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000'; - let currentMessages = messages; - if (resetConvo(currentMessages, sender)) { - parentMessageId = '00000000-0000-0000-0000-000000000000'; - dispatch(setNewConvo()); - currentMessages = []; - } - const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId , messageId: fakeMessageId }; - const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; - - dispatch(setSubmitState(true)); - dispatch(setMessages([...currentMessages, currentMsg, initialResponse])); - dispatch(setText('')); - - const submission = { - convo, - isCustomModel, - message: { - ...currentMsg, - model, - chatGptLabel, - promptPrefix, - }, - messages: currentMessages, - currentMsg, - initialResponse, - sender, - }; - console.log('User Input:', message); - dispatch(setSubmission(submission)); + ask({ text }) }; useEffect(() => { @@ -196,7 +169,8 @@ export default function TextChat({ messages }) { } const currentState = submission; - let currentMsg = currentState.currentMsg; + let currentMsg = {...currentState.message}; + const { server, payload } = createPayload(submission); const onMessage = (e) => { if (stopStream) { @@ -252,6 +226,11 @@ export default function TextChat({ messages }) { }; }, [submission]); + const handleRegenerate = () => { + if (latestMessage&&!latestMessage?.isCreatedByUser) + regenerate(latestMessage) + } + const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -307,32 +286,56 @@ export default function TextChat({ messages }) { errorMessage={errorMessage} /> ) : ( -
- - - -
+ <> + + {(latestMessage&&!latestMessage?.isCreatedByUser)? + isSubmitting? + : + + :null + } + +
+ + + +
+ )}
diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index f7e446463e..d53c46d412 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -8,8 +8,9 @@ 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'; +import { setConversation, setLatestMessage } from '../../store/convoSlice'; import { getIconOfModel } from '../../utils'; +import { useMessageHandler } from '../../utils/handleSubmit' export default function Message({ message, @@ -32,6 +33,7 @@ export default function Message({ const { error: convoError } = convo; const last = !message?.children?.length; const edit = message.messageId == currentEditId; + const { ask } = useMessageHandler(); const dispatch = useDispatch(); // const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user'; @@ -51,7 +53,11 @@ export default function Message({ }, [isSubmitting, text, blinker, scrollToBottom, abortScroll]); useEffect(() => { - if (last) dispatch(setConversation({ parentMessageId: message?.messageId })); + if (last) { + // TODO: stop using conversation.parentMessageId and remove it. + dispatch(setConversation({ parentMessageId: message?.messageId })); + dispatch(setLatestMessage({ ...message })); + } }, [last]); const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId); @@ -87,55 +93,7 @@ export default function Message({ const resubmitMessage = () => { const text = textEditor.current.innerText; - if (convoError) { - dispatch(setError(false)); - } - - if (!!isSubmitting || text.trim() === '') { - 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 = { - sender: 'User', - text: text.trim(), - current: true, - isCreatedByUser: true, - parentMessageId: message?.parentMessageId, - conversationId: message?.conversationId, - messageId: fakeMessageId - }; - const sender = model === 'chatgptCustom' ? chatGptLabel : model; - - const initialResponse = { - sender, - text: '', - parentMessageId: fakeMessageId, - submitting: true - }; - - dispatch(setSubmitState(true)); - dispatch(setMessages([...messages, currentMsg, initialResponse])); - dispatch(setText('')); - - const submission = { - isCustomModel, - message: { - ...currentMsg, - model, - chatGptLabel, - promptPrefix - }, - messages: messages, - currentMsg, - initialResponse, - sender - }; - console.log('User Input:', currentMsg?.text); - // handleSubmit(submission); - dispatch(setSubmission(submission)); + ask({ text, parentMessageId: message?.parentMessageId, conversationId: message?.conversationId,}); setSiblingIdx(siblingCount - 1); enterEdit(true); diff --git a/client/src/components/Messages/MultiMessage.jsx b/client/src/components/Messages/MultiMessage.jsx index 24ab761eb6..1525fc495a 100644 --- a/client/src/components/Messages/MultiMessage.jsx +++ b/client/src/components/Messages/MultiMessage.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Message from './Message'; export default function MultiMessage({ @@ -14,6 +14,11 @@ export default function MultiMessage({ setSiblingIdx(messageList?.length - value - 1); }; + useEffect(() => { + // reset siblingIdx when changes, mostly a new message is submitting. + setSiblingIdx(0); + }, [messageList?.length]) + // if (!messageList?.length) return null; if (!(messageList && messageList.length)) { return null; diff --git a/client/src/components/svg/StopGeneratingIcon.jsx b/client/src/components/svg/StopGeneratingIcon.jsx new file mode 100644 index 0000000000..4c134cafe9 --- /dev/null +++ b/client/src/components/svg/StopGeneratingIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function StopGeneratingIcon() { + return ( + + ); +} diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index c862a8e1e7..e234af87d0 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -15,6 +15,7 @@ const initialState = { pageNumber: 1, pages: 1, refreshConvoHint: 0, + latestMessage: null, convos: [], }; @@ -52,6 +53,7 @@ const currentSlice = createSlice({ state.chatGptLabel = null; state.promptPrefix = null; state.convosLoading = false; + state.latestMessage = null; }, setConvos: (state, action) => { state.convos = action.payload.sort( @@ -66,11 +68,14 @@ const currentSlice = createSlice({ }, removeAll: (state) => { state.convos = []; - } + }, + setLatestMessage: (state, action) => { + state.latestMessage = action.payload; + }, } }); -export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll } = +export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll, setLatestMessage } = currentSlice.actions; export default currentSlice.reducer; diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index 5c4184aac5..5e4969f394 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -1,5 +1,88 @@ import { SSE } from './sse'; -// const newLineRegex = /^\n+/; +import resetConvo from './resetConvo'; +import { useSelector, useDispatch } from 'react-redux'; +import { setNewConvo } from '~/store/convoSlice'; +import { setMessages } from '~/store/messageSlice'; +import { setSubmitState, setSubmission } from '~/store/submitSlice'; +import { setText } from '~/store/textSlice'; + +const useMessageHandler = () => { + const dispatch = useDispatch(); + const convo = useSelector((state) => state.convo); + const { initial } = useSelector((state) => state.models); + const { messages } = useSelector((state) => state.messages); + const { model, chatGptLabel, promptPrefix, isSubmitting } = useSelector((state) => state.submit); + const { text } = useSelector((state) => state.text); + const { latestMessage, error } = convo; + + const ask = ({ text, parentMessageId=null, conversationId=null, messageId=null}, { isRegenerate=false }={}) => { + if (error) { + dispatch(setError(false)); + } + + if (!!isSubmitting || text === '') { + return; + } + + // this is not a real messageId, it is used as placeholder before real messageId returned + text = text.trim(); + const fakeMessageId = crypto.randomUUID(); + const isCustomModel = model === 'chatgptCustom' || !initial[model]; + const sender = model === 'chatgptCustom' ? chatGptLabel : model; + parentMessageId = parentMessageId || latestMessage?.messageId || '00000000-0000-0000-0000-000000000000'; + let currentMessages = messages; + if (resetConvo(currentMessages, sender)) { + parentMessageId = '00000000-0000-0000-0000-000000000000'; + conversationId = null; + dispatch(setNewConvo()); + currentMessages = []; + } + const currentMsg = { sender: 'User', text, current: true, isCreatedByUser: true, parentMessageId, conversationId, messageId: fakeMessageId }; + const initialResponse = { sender, text: '', parentMessageId: isRegenerate?messageId:fakeMessageId, messageId: (isRegenerate?messageId:fakeMessageId) + '_', submitting: true }; + + dispatch(setSubmitState(true)); + if (isRegenerate) { + console.log([...currentMessages, initialResponse]) + dispatch(setMessages([...currentMessages, initialResponse])); + } else { + console.log([...currentMessages, currentMsg, initialResponse]) + dispatch(setMessages([...currentMessages, currentMsg, initialResponse])); + } + dispatch(setText('')); + + const submission = { + convo, + isCustomModel, + message: { + ...currentMsg, + model, + chatGptLabel, + promptPrefix, + overrideParentMessageId: isRegenerate?messageId:null + }, + messages: currentMessages, + isRegenerate, + initialResponse, + sender, + }; + console.log('User Input:', text); + dispatch(setSubmission(submission)); + } + + const regenerate = ({ parentMessageId }) => { + const parentMessage = messages?.find(element => element.messageId == parentMessageId); + + if (parentMessage && parentMessage.isCreatedByUser) + ask({ ...parentMessage }, { isRegenerate: true }) + else + console.error('Failed to regenerate the message: parentMessage not found or not created by user.', message); + } + + + return { ask, regenerate } +} + +export { useMessageHandler }; export default function handleSubmit({ model, From 66ad54168a948f291257972d47b15d45cde9bf4e Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Fri, 17 Mar 2023 02:08:03 +0800 Subject: [PATCH 3/8] feat: regenerate for bingai --- api/server/routes/askBing.js | 11 ++++++++--- api/server/routes/askSydney.js | 14 ++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 0614faf86a..007aba779b 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -9,6 +9,7 @@ router.post('/', async (req, res) => { const { model, text, + overrideParentMessageId=null, parentMessageId, conversationId: oldConversationId, ...convo @@ -37,8 +38,10 @@ router.post('/', async (req, res) => { ...convo }); - await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + if (!overrideParentMessageId) { + await saveMessage(userMessage); + await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + } return await ask({ isNewConversation, @@ -46,6 +49,7 @@ router.post('/', async (req, res) => { model, convo, preSendRequest: true, + overrideParentMessageId, req, res }); @@ -101,7 +105,8 @@ const ask = async ({ convo.conversationSignature || response.conversationSignature; userMessage.conversationId = response.conversationId || conversationId; userMessage.invocationId = response.invocationId; - await saveMessage(userMessage); + if (!overrideParentMessageId) + await saveMessage(userMessage); // Bing API will not use our conversationId at the first time, // so change the placeholder conversationId to the real one. diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a2017cf7d6..77f95371a0 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -9,6 +9,7 @@ router.post('/', async (req, res) => { const { model, text, + overrideParentMessageId=null, parentMessageId, conversationId: oldConversationId, ...convo @@ -37,8 +38,10 @@ router.post('/', async (req, res) => { ...convo }); - await saveMessage(userMessage); - await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + if (!overrideParentMessageId) { + await saveMessage(userMessage); + await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); + } return await ask({ isNewConversation, @@ -46,6 +49,7 @@ router.post('/', async (req, res) => { model, convo, preSendRequest: true, + overrideParentMessageId, req, res }); @@ -102,7 +106,8 @@ const ask = async ({ userMessage.conversationId = response.conversationId || conversationId; userMessage.invocationId = response.invocationId; // Unlike gpt and bing, Sydney will never accept our given userMessage.messageId, it will generate its own one. - await saveMessage(userMessage); + if (!overrideParentMessageId) + await saveMessage(userMessage); // Save sydney response // response.id = response.messageId; @@ -125,7 +130,8 @@ const ask = async ({ // Save user message userMessage.conversationId = response.conversationId || conversationId; - await saveMessage(userMessage); + if (!overrideParentMessageId) + await saveMessage(userMessage); // Bing API will not use our conversationId at the first time, // so change the placeholder conversationId to the real one. From ef9f1ee1cfb180aaec4e6c4c316330885ceec68d Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Fri, 17 Mar 2023 03:13:42 +0800 Subject: [PATCH 4/8] feat: cancellable api request --- api/app/chatgpt-browser.js | 4 +- api/app/chatgpt-client.js | 4 +- api/app/chatgpt-custom.js | 4 +- api/server/routes/ask.js | 11 ++- api/server/routes/askBing.js | 11 ++- api/server/routes/askSydney.js | 11 ++- client/src/components/Main/SubmitButton.jsx | 6 +- client/src/components/Main/TextChat.jsx | 98 ++++++++++----------- client/src/components/Messages/Message.jsx | 2 +- client/src/components/Messages/index.jsx | 2 +- client/src/utils/handleSubmit.js | 7 +- 11 files changed, 93 insertions(+), 67 deletions(-) diff --git a/api/app/chatgpt-browser.js b/api/app/chatgpt-browser.js index a5bea22729..792f108e7d 100644 --- a/api/app/chatgpt-browser.js +++ b/api/app/chatgpt-browser.js @@ -10,7 +10,7 @@ const clientOptions = { proxy: process.env.PROXY || null, }; -const browserClient = async ({ text, onProgress, convo }) => { +const browserClient = async ({ text, onProgress, convo, abortController }) => { const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api'); const store = { @@ -18,7 +18,7 @@ const browserClient = async ({ text, onProgress, convo }) => { }; const client = new ChatGPTBrowserClient(clientOptions, store); - let options = { onProgress }; + let options = { onProgress, abortController }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; diff --git a/api/app/chatgpt-client.js b/api/app/chatgpt-client.js index 350d1210ce..04368e85bd 100644 --- a/api/app/chatgpt-client.js +++ b/api/app/chatgpt-client.js @@ -9,14 +9,14 @@ const clientOptions = { debug: false }; -const askClient = async ({ text, onProgress, convo }) => { +const askClient = async ({ text, onProgress, convo, abortController }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = { store: new KeyvFile({ filename: './data/cache.json' }) }; const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store); - let options = { onProgress }; + let options = { onProgress, abortController }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; diff --git a/api/app/chatgpt-custom.js b/api/app/chatgpt-custom.js index e7a0ee0503..a1c797e31d 100644 --- a/api/app/chatgpt-custom.js +++ b/api/app/chatgpt-custom.js @@ -9,7 +9,7 @@ const clientOptions = { debug: false }; -const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabel }) => { +const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabel, abortController }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = { store: new KeyvFile({ filename: './data/cache.json' }) @@ -23,7 +23,7 @@ const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabe const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store); - let options = { onProgress }; + let options = { onProgress, abortController }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; } diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index c8313efd68..3ce95ea1d6 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -90,6 +90,14 @@ const ask = async ({ try { const progressCallback = createOnProgress(); + + const abortController = new AbortController(); + res.on('close', () => { + console.log('The client has disconnected.'); + // 执行其他操作 + abortController.abort(); + }) + let gptResponse = await client({ text, onProgress: progressCallback.call(null, model, { res, text }), @@ -98,7 +106,8 @@ const ask = async ({ conversationId, ...convo }, - ...convo + ...convo, + abortController }); console.log('CLIENT RESPONSE', gptResponse); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 007aba779b..bff342ca3e 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -84,6 +84,14 @@ const ask = async ({ try { const progressCallback = createOnProgress(); + + const abortController = new AbortController(); + res.on('close', () => { + console.log('The client has disconnected.'); + // 执行其他操作 + abortController.abort(); + }) + let response = await askBing({ text, onProgress: progressCallback.call(null, model, { @@ -95,7 +103,8 @@ const ask = async ({ ...convo, parentMessageId: userParentMessageId, conversationId - } + }, + abortController }); console.log('BING RESPONSE', response); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 77f95371a0..81a3b2fa20 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -84,6 +84,14 @@ const ask = async ({ try { const progressCallback = createOnProgress(); + + const abortController = new AbortController(); + res.on('close', () => { + console.log('The client has disconnected.'); + // 执行其他操作 + abortController.abort(); + }) + let response = await askSydney({ text, onProgress: progressCallback.call(null, model, { @@ -95,7 +103,8 @@ const ask = async ({ parentMessageId: userParentMessageId, conversationId, ...convo - } + }, + abortController }); console.log('SYDNEY RESPONSE', response); diff --git a/client/src/components/Main/SubmitButton.jsx b/client/src/components/Main/SubmitButton.jsx index 1aa437e26e..79b1634c54 100644 --- a/client/src/components/Main/SubmitButton.jsx +++ b/client/src/components/Main/SubmitButton.jsx @@ -1,8 +1,10 @@ import React from 'react'; import { useSelector } from 'react-redux'; -export default function SubmitButton({ submitMessage }) { - const { isSubmitting, disabled } = useSelector((state) => state.submit); +export default function SubmitButton({ submitMessage, disabled }) { + const { isSubmitting } = useSelector((state) => state.submit); + const { error, latestMessage } = useSelector((state) => state.convo); + const clickHandler = (e) => { e.preventDefault(); submitMessage(); diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index e6bd79c8f0..59b81e7182 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -28,7 +28,9 @@ export default function TextChat({ messages }) { useSelector((state) => state.submit); const { text } = useSelector((state) => state.text); const { error, latestMessage } = convo; - const { ask, regenerate } = useMessageHandler(); + const { ask, regenerate, stopGenerating } = useMessageHandler(); + + const isNotAppendable = (!isSubmitting && latestMessage?.submitting) || latestMessage?.error; // auto focus to input, when enter a conversation. useEffect(() => { @@ -231,6 +233,10 @@ export default function TextChat({ messages }) { regenerate(latestMessage) } + const handleStopGenerating = () => { + stopGenerating() + } + const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -273,31 +279,23 @@ export default function TextChat({ messages }) { e.preventDefault(); dispatch(setError(false)); }; - + isNotAppendable return (
- {error ? ( - - ) : ( - <> - - {(latestMessage&&!latestMessage?.isCreatedByUser)? - isSubmitting? - : + + {isSubmitting? + + :(latestMessage&&!latestMessage?.isCreatedByUser)?