diff --git a/api/app/clients/chatgpt-browser.js b/api/app/clients/chatgpt-browser.js index 0e8cc25fda..2caf0b2917 100644 --- a/api/app/clients/chatgpt-browser.js +++ b/api/app/clients/chatgpt-browser.js @@ -8,6 +8,7 @@ const browserClient = async ({ model, token, onProgress, + onEventMessage, abortController, userId }) => { @@ -30,7 +31,7 @@ const browserClient = async ({ }; const client = new ChatGPTBrowserClient(clientOptions, store); - let options = { onProgress, abortController }; + let options = { onProgress, onEventMessage, abortController }; if (!!parentMessageId && !!conversationId) { options = { ...options, parentMessageId, conversationId }; diff --git a/api/models/schema/convoSchema.js b/api/models/schema/convoSchema.js index 02f5c93a16..2ece03e508 100644 --- a/api/models/schema/convoSchema.js +++ b/api/models/schema/convoSchema.js @@ -24,6 +24,10 @@ const convoSchema = mongoose.Schema( examples: [{ type: mongoose.Schema.Types.Mixed }], ...conversationPreset, // for bingAI only + bingConversationId: { + type: String, + default: null + }, jailbreakConversationId: { type: String, default: null diff --git a/api/server/routes/ask/askBingAI.js b/api/server/routes/ask/askBingAI.js index a4a962be7b..a9da03ad02 100644 --- a/api/server/routes/ask/askBingAI.js +++ b/api/server/routes/ask/askBingAI.js @@ -129,10 +129,15 @@ const ask = async ({ } }); const abortController = new AbortController(); + let bingConversationId = null; + if (!isNewConversation) { + const convo = await getConvo(req.user.id, conversationId); + bingConversationId = convo.bingConversationId; + } let response = await askBing({ text, parentMessageId: userParentMessageId, - conversationId, + conversationId: bingConversationId ?? conversationId, ...endpointOption, onProgress: progressCallback.call(null, { res, @@ -147,7 +152,7 @@ const ask = async ({ const newConversationId = endpointOption?.jailbreak ? response.jailbreakConversationId : response.conversationId || conversationId; - const newUserMassageId = + const newUserMessageId = response.parentMessageId || response.details.requestId || userMessageId; const newResponseMessageId = response.messageId || response.details.messageId; @@ -156,10 +161,11 @@ const ask = async ({ response.response || response.details.spokenText || '**Bing refused to answer.**'; let responseMessage = { - conversationId: newConversationId, + conversationId, + bingConversationId: newConversationId, messageId: responseMessageId, newMessageId: newResponseMessageId, - parentMessageId: overrideParentMessageId || newUserMassageId, + parentMessageId: overrideParentMessageId || newUserMessageId, sender: endpointOption?.jailbreak ? 'Sydney' : 'BingAI', text: await handleText(response, true), suggestions: @@ -173,31 +179,7 @@ const ask = async ({ await saveMessage(responseMessage); responseMessage.messageId = newResponseMessageId; - // STEP2 update the convosation. - - // First update conversationId if needed - // Note! - // Bing API will not use our conversationId at the first time, - // so change the placeholder conversationId to the real one. - // Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId, - // but in this situation, don't change the conversationId, but create new convo. - - let conversationUpdate = { conversationId: newConversationId, endpoint: 'bingAI' }; - if (conversationId != newConversationId) - if (isNewConversation) { - // change the conversationId to new one - conversationUpdate = { - ...conversationUpdate, - conversationId: conversationId, - newConversationId: newConversationId - }; - } else { - // create new conversation - conversationUpdate = { - ...conversationUpdate, - ...endpointOption - }; - } + let conversationUpdate = { conversationId, bingConversationId: newConversationId, endpoint: 'bingAI' }; if (endpointOption?.jailbreak) { conversationUpdate.jailbreak = true; @@ -210,20 +192,16 @@ const ask = async ({ } await saveConvo(req.user.id, conversationUpdate); - conversationId = newConversationId; - - // STEP3 update the user message - userMessage.conversationId = newConversationId; - userMessage.messageId = newUserMassageId; + userMessage.messageId = newUserMessageId; // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. if (!overrideParentMessageId) await saveMessage({ ...userMessage, messageId: userMessageId, - newMessageId: newUserMassageId + newMessageId: newUserMessageId }); - userMessageId = newUserMassageId; + userMessageId = newUserMessageId; sendMessage(res, { title: await getConvoTitle(req.user.id, conversationId), diff --git a/api/server/routes/ask/askChatGPTBrowser.js b/api/server/routes/ask/askChatGPTBrowser.js index 97fad58d84..61e68cd629 100644 --- a/api/server/routes/ask/askChatGPTBrowser.js +++ b/api/server/routes/ask/askChatGPTBrowser.js @@ -76,7 +76,6 @@ const ask = async ({ userMessage, endpointOption, conversationId, - preSendRequest = true, overrideParentMessageId = null, req, res @@ -92,10 +91,8 @@ const ask = async ({ 'X-Accel-Buffering': 'no' }); - if (preSendRequest) sendMessage(res, { message: userMessage, created: true }); - let responseMessageId = crypto.randomUUID(); - + let getPartialMessage = null; try { let lastSavedTimestamp = 0; const { onProgress: progressCallback, getPartialText } = createOnProgress({ @@ -116,15 +113,30 @@ const ask = async ({ } } }); + + getPartialMessage = getPartialText; const abortController = new AbortController(); let response = await browserClient({ text, parentMessageId: userParentMessageId, conversationId, ...endpointOption, - onProgress: progressCallback.call(null, { res, text }), abortController, - userId + userId, + onProgress: progressCallback.call(null, { res, text }), + onEventMessage: (eventMessage) => { + let data = null; + try { + data = JSON.parse(eventMessage.data); + } catch (e) { + return; + } + + sendMessage(res, { + message: { ...userMessage, conversationId: data.conversation_id }, + created: true + }); + } }); console.log('CLIENT RESPONSE', response); @@ -212,8 +224,8 @@ const ask = async ({ parentMessageId: overrideParentMessageId || userMessageId, unfinished: false, cancelled: false, - error: true, - text: error.message + // error: true, + text: `${getPartialMessage() ?? ''}\n\nError message: "${error.message}"` }; await saveMessage(errorMessage); handleError(res, errorMessage); diff --git a/api/server/routes/ask/askGoogle.js b/api/server/routes/ask/askGoogle.js index f49263392c..9a4698c813 100644 --- a/api/server/routes/ask/askGoogle.js +++ b/api/server/routes/ask/askGoogle.js @@ -1,5 +1,6 @@ const express = require('express'); const router = express.Router(); +const crypto = require('crypto'); const { titleConvo } = require('../../../app/'); const GoogleClient = require('../../../app/google/GoogleClient'); const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models'); @@ -7,7 +8,7 @@ const { handleError, sendMessage, createOnProgress } = require('./handlers'); const requireJwtAuth = require('../../../middleware/requireJwtAuth'); router.post('/', requireJwtAuth, async (req, res) => { - const { endpoint, text, parentMessageId, conversationId } = req.body; + const { endpoint, text, parentMessageId, conversationId: oldConversationId } = req.body; if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' }); if (endpoint !== 'google') return handleError(res, { text: 'Illegal request' }); @@ -31,6 +32,8 @@ router.post('/', requireJwtAuth, async (req, res) => { return handleError(res, { text: `Illegal request: model` }); } + const conversationId = oldConversationId || crypto.randomUUID(); + // eslint-disable-next-line no-use-before-define return await ask({ text, @@ -64,6 +67,8 @@ const ask = async ({ text, endpointOption, parentMessageId = null, conversationI if (!conversationId) { conversationId = data.conversationId; } + + sendMessage(res, { message: userMessage, created: true }); }; const { onProgress: progressCallback } = createOnProgress({ diff --git a/api/server/routes/ask/askOpenAI.js b/api/server/routes/ask/askOpenAI.js index 8fe728792a..13de44db95 100644 --- a/api/server/routes/ask/askOpenAI.js +++ b/api/server/routes/ask/askOpenAI.js @@ -190,7 +190,7 @@ const ask = async ({ console.log('CLIENT RESPONSE', response); const newConversationId = response.conversationId || conversationId; - const newUserMassageId = response.parentMessageId || userMessageId; + const newUserMessageId = response.parentMessageId || userMessageId; const newResponseMessageId = response.messageId; // STEP1 generate response message @@ -200,7 +200,7 @@ const ask = async ({ conversationId: newConversationId, messageId: responseMessageId, newMessageId: newResponseMessageId, - parentMessageId: overrideParentMessageId || newUserMassageId, + parentMessageId: overrideParentMessageId || newUserMessageId, text: await handleText(response), sender: endpointOption?.chatGptLabel || 'ChatGPT', unfinished: false, @@ -234,16 +234,16 @@ const ask = async ({ // STEP3 update the user message userMessage.conversationId = newConversationId; - userMessage.messageId = newUserMassageId; + userMessage.messageId = newUserMessageId; // If response has parentMessageId, the fake userMessage.messageId should be updated to the real one. if (!overrideParentMessageId) await saveMessage({ ...userMessage, messageId: userMessageId, - newMessageId: newUserMassageId + newMessageId: newUserMessageId }); - userMessageId = newUserMassageId; + userMessageId = newUserMessageId; sendMessage(res, { title: await getConvoTitle(req.user.id, conversationId), diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx index bd9b6eb4df..e42742f199 100644 --- a/client/src/components/Input/index.jsx +++ b/client/src/components/Input/index.jsx @@ -5,7 +5,6 @@ import OpenAIOptions from './OpenAIOptions'; import ChatGPTOptions from './ChatGPTOptions'; import BingAIOptions from './BingAIOptions'; import GoogleOptions from './GoogleOptions'; -// import BingStyles from './BingStyles'; import NewConversationMenu from './NewConversationMenu'; import AdjustToneButton from './AdjustToneButton'; import Footer from './Footer'; @@ -21,7 +20,6 @@ export default function TextChat({ isSearchView = false }) { const conversation = useRecoilValue(store.conversation); const latestMessage = useRecoilValue(store.latestMessage); const [text, setText] = useRecoilState(store.text); - // const [text, setText] = useState(''); const endpointsConfig = useRecoilValue(store.endpointsConfig); const isSubmitting = useRecoilValue(store.isSubmitting); @@ -30,8 +28,6 @@ export default function TextChat({ isSearchView = false }) { const disabled = false; const { ask, stopGenerating } = useMessageHandler(); - - // const bingStylesRef = useRef(null); const [showBingToneSetting, setShowBingToneSetting] = useState(false); const isNotAppendable = latestMessage?.unfinished & !isSubmitting || latestMessage?.error; @@ -39,25 +35,15 @@ export default function TextChat({ isSearchView = false }) { // auto focus to input, when enter a conversation. useEffect(() => { if (conversation?.conversationId !== 'search') inputRef.current?.focus(); - // setText(''); }, [conversation?.conversationId]); - // // controls the height of Bing tone style tabs - // useEffect(() => { - // if (!inputRef.current) { - // return; // wait for the ref to be available - // } - - // const resizeObserver = new ResizeObserver(() => { - // const newHeight = inputRef.current.clientHeight; - // if (newHeight >= 24) { - // // 24 is the default height of the input - // bingStylesRef.current.style.bottom = 15 + newHeight + 'px'; - // } - // }); - // resizeObserver.observe(inputRef.current); - // return () => resizeObserver.disconnect(); - // }, [inputRef]); + useEffect(() => { + const timeoutId = setTimeout(() => { + inputRef.current?.focus(); + }, 100); + + return () => clearTimeout(timeoutId); + }, [isSubmitting]); const submitMessage = () => { ask({ text }); diff --git a/package.json b/package.json index d965c1e9b9..5b5fcdf1f7 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "e2e:debug": "cross-env PWDEBUG=1 playwright test --config=e2e/playwright.config.js", "e2e:report": "npx playwright show-report e2e/playwright-report", "e2e:auth": "npx playwright codegen --save-storage=e2e/auth.json http://localhost:3080/", + "e2e:test-auth": "npx playwright codegen --load-storage=e2e/auth.json http://localhost:3080/", "prepare": "husky install", "format": "prettier-eslint --write \"{,!(node_modules)/**/}*.{js,jsx,ts,tsx}\"" },