Merge pull request #3 from wtlyu/final-adj

Final adjustments
This commit is contained in:
Danny Avila 2023-03-15 17:08:18 -04:00 committed by GitHub
commit ff45511011
16 changed files with 303 additions and 293 deletions

1
.gitignore vendored
View file

@ -47,7 +47,6 @@ bower_components/
.env
cache.json
api/data/
.eslintrc.js
owner.yml
archive
.vscode/settings.json

View file

@ -1,4 +1,5 @@
const { Configuration, OpenAIApi } = require('openai');
const _ = require('lodash');
const proxyEnvToAxiosProxy = (proxyString) => {
if (!proxyString) return null;
@ -12,15 +13,18 @@ const proxyEnvToAxiosProxy = (proxyString) => {
auth: username && password ? { username, password } : undefined
};
return proxyConfig
}
return proxyConfig;
};
const titleConvo = async ({ message, response, model }) => {
const titleConvo = async ({ model, text, response }) => {
let title = 'New Chat';
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY
});
const openai = new OpenAIApi(configuration);
const completion = await openai.createChatCompletion({
const completion = await openai.createChatCompletion(
{
model: 'gpt-3.5-turbo',
messages: [
{
@ -28,12 +32,28 @@ const titleConvo = async ({ message, response, model }) => {
content:
'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.'
},
{ role: 'user', content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` },
{
role: 'user',
content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${text}"\n\n${model}: "${JSON.stringify(
response?.text
)}"\n\nTitle: `
}
]
}, { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) });
},
{ proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) }
);
//eslint-disable-next-line
return completion.data.choices[0].message.content.replace(/["\.]/g, '');
title = completion.data.choices[0].message.content.replace(/["\.]/g, '');
} catch (e) {
console.error(e);
console.log('There was an issue generating title, see error above');
}
console.log('CONVERSATION TITLE', title);
return title;
};
module.exports = titleConvo;
const throttledTitleConvo = _.throttle(titleConvo, 1000);
module.exports = throttledTitleConvo;

View file

@ -3,13 +3,7 @@ const crypto = require('crypto');
const router = express.Router();
const askBing = require('./askBing');
const askSydney = require('./askSydney');
const {
titleConvo,
askClient,
browserClient,
customClient
// detectCode
} = require('../../app/');
const { titleConvo, askClient, browserClient, customClient } = require('../../app/');
const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const { getMessages } = require('../../models/Message');
@ -42,15 +36,6 @@ router.post('/', async (req, res) => {
...convo
});
// if (model === 'chatgptCustom' && !chatGptLabel && conversationId) {
// const convo = await getConvo({ conversationId });
// if (convo) {
// console.log('found convo for custom gpt', { convo })
// chatGptLabel = convo.chatGptLabel;
// promptPrefix = convo.promptPrefix;
// }
// }
await saveMessage(userMessage);
await saveConvo({ ...userMessage, model, ...convo });
@ -94,17 +79,6 @@ router.post('/regenerate', async (req, res) => {
res
});
} else return handleError(res, { text: 'Parent message not found' });
// if (model === 'chatgptCustom' && !chatGptLabel && conversationId) {
// const convo = await getConvo({ conversationId });
// if (convo) {
// console.log('found convo for custom gpt', { convo })
// chatGptLabel = convo.chatGptLabel;
// promptPrefix = convo.promptPrefix;
// }
// }
// await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix });
});
const ask = async ({
@ -188,7 +162,7 @@ const ask = async ({
gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model;
gptResponse.model = model;
// gptResponse.final = true;
gptResponse.text = await handleText(gptResponse.text);
gptResponse.text = await handleText(gptResponse);
if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') {
gptResponse.chatGptLabel = convo.chatGptLabel;
@ -212,13 +186,7 @@ const ask = async ({
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({
model,
message: text,
response: JSON.stringify(gptResponse?.text)
});
console.log('CONVERSATION TITLE', title);
const title = await titleConvo({ model, text, response: gptResponse });
await saveConvo({
conversationId,

View file

@ -1,10 +1,9 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, getCitations, citeText, askBing } = require('../../app/');
const { titleConvo, askBing } = require('../../app/');
const { saveMessage, getConvoTitle, saveConvo } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => {
const {
@ -129,12 +128,7 @@ const ask = async ({
response.parentMessageId =
overrideParentMessageId || response.parentMessageId || userMessageId;
const links = getCitations(response);
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
response.text = await handleText(response.text);
response.text = await handleText(response, true);
await saveMessage(response);
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
sendMessage(res, {
@ -146,13 +140,7 @@ const ask = async ({
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({
model,
message: text,
response: JSON.stringify(response?.text)
});
console.log('CONVERSATION TITLE', title);
const title = await titleConvo({ model, text, response });
await saveConvo({
conversationId,

View file

@ -1,10 +1,9 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, getCitations, citeText, askSydney } = require('../../app/');
const { titleConvo, askSydney } = require('../../app/');
const { saveMessage, saveConvo, getConvoTitle } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => {
const {
@ -97,7 +96,6 @@ const ask = async ({
console.log('SYDNEY RESPONSE', response);
// console.dir(response, { depth: null });
const hasCitations = response.response.match(citationRegex)?.length > 0;
userMessage.conversationSignature =
convo.conversationSignature || response.conversationSignature;
@ -125,12 +123,6 @@ const ask = async ({
response.parentMessageId =
overrideParentMessageId || response.parentMessageId || userMessageId;
const links = getCitations(response);
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
response.text = await handleText(response.text);
// Save user message
userMessage.conversationId = response.conversationId || conversationId;
await saveMessage(userMessage);
@ -146,6 +138,7 @@ const ask = async ({
});
conversationId = userMessage.conversationId;
response.text = await handleText(response, true);
// Save sydney response & convo, then send
await saveMessage(response);
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
@ -158,13 +151,7 @@ const ask = async ({
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({
model,
message: text,
response: JSON.stringify(response?.text)
});
console.log('CONVERSATION TITLE', title);
const title = await titleConvo({ model, text, response });
await saveConvo({
conversationId,

View file

@ -1,41 +1,12 @@
const express = require('express');
const router = express.Router();
const { titleConvo } = require('../../app/');
const { getConvo, saveConvo, getConvoTitle } = require('../../models');
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
const { getMessages } = require('../../models/Message');
router.get('/', async (req, res) => {
const pageNumber = req.query.pageNumber || 1;
res.status(200).send(await getConvosByPage(pageNumber));
});
router.post('/gen_title', async (req, res) => {
const { conversationId } = req.body.arg;
const convo = await getConvo(conversationId)
const firstMessage = (await getMessages({ conversationId }))[0]
const secondMessage = (await getMessages({ conversationId }))[1]
// if (convo.title == 'New Chat') {
// const title = await titleConvo({
// model: convo?.model,
// message: firstMessage?.text,
// response: JSON.stringify(secondMessage?.text || '')
// });
// console.log('CONVERSATION TITLE', title);
// await saveConvo({
// conversationId,
// title
// })
// res.status(200).send(title);
// } else
return res.status(200).send(convo.title);
});
router.post('/clear', async (req, res) => {
let filter = {};
const { conversationId } = req.body.arg;

View file

@ -1,6 +1,8 @@
const { citeText, detectCode } = require('../../app/');
const _ = require('lodash');
const sanitizeHtml = require('sanitize-html');
const citationRegex = /\[\^\d+?\^]/g;
const { getCitations, citeText, detectCode } = require('../../app/');
// const htmlTagRegex = /(<\/?\s*[a-zA-Z]*\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?)>|<\s*[a-zA-Z]+\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?>|<\/?>))/g;
const handleError = (res, message) => {
res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`);
@ -25,8 +27,13 @@ const createOnProgress = () => {
if (tokens.match(/^\n/)) {
tokens = tokens.replace(/^\n/, '');
}
// if (tokens.includes('```')) {
// tokens = sanitizeHtml(tokens);
// const htmlTags = tokens.match(htmlTagRegex);
// if (tokens.includes('```') && htmlTags && htmlTags.length > 0) {
// htmlTags.forEach((tag) => {
// const sanitizedTag = sanitizeHtml(tag);
// tokens = tokens.replaceAll(tag, sanitizedTag);
// });
// }
if (bing) {
@ -45,12 +52,26 @@ const createOnProgress = () => {
return onProgress;
};
const handleText = async (input) => {
let text = input;
const handleText = async (response, bing = false) => {
let { text } = response;
text = await detectCode(text);
// if (text.includes('```')) {
// text = sanitizeHtml(text);
// text = text.replaceAll(') =&gt;', ') =>');
response.text = text;
if (bing) {
// const hasCitations = response.response.match(citationRegex)?.length > 0;
const links = getCitations(response);
if (response.text.match(citationRegex)?.length > 0) {
text = citeText(response);
}
text += links?.length > 0 ? `\n<small>${links}</small>` : '';
}
// const htmlTags = text.match(htmlTagRegex);
// if (text.includes('```') && htmlTags && htmlTags.length > 0) {
// htmlTags.forEach((tag) => {
// const sanitizedTag = sanitizeHtml(tag);
// text = text.replaceAll(tag, sanitizedTag);
// });
// }
return text;

View file

@ -8,7 +8,7 @@ import useDocumentTitle from '~/hooks/useDocumentTitle';
import { useSelector } from 'react-redux';
const App = () => {
const { messages } = useSelector((state) => state.messages);
const { messages, messageTree } = useSelector((state) => state.messages);
const { title } = useSelector((state) => state.convo);
const { conversationId } = useSelector((state) => state.convo);
const [ navVisible, setNavVisible ]= useState(false)
@ -25,6 +25,7 @@ const App = () => {
) : (
<Messages
messages={messages}
messageTree={messageTree}
/>
)}
<TextChat messages={messages} />

View file

@ -157,15 +157,17 @@ export default function TextChat({ messages }) {
const message = text.trim();
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000';
if (resetConvo(messages, sender)) {
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([...messages, currentMsg, initialResponse]));
dispatch(setMessages([...currentMessages, currentMsg, initialResponse]));
dispatch(setText(''));
const submission = {
@ -177,7 +179,7 @@ export default function TextChat({ messages }) {
chatGptLabel,
promptPrefix,
},
messages,
messages: currentMessages,
currentMsg,
initialResponse,
sender,

View file

@ -1,9 +1,9 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import TextWrapper from './TextWrapper';
import MultiMessage from './MultiMessage';
import { useSelector, useDispatch } from 'react-redux';
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';
@ -11,42 +11,6 @@ import { setText } from '~/store/textSlice';
import { setConversation } from '../../store/convoSlice';
import { getIconOfModel } from '../../utils';
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 <Message
key={messageList[messageList.length - siblingIdx - 1].messageId}
message={messageList[messageList.length - siblingIdx - 1]}
messages={messages}
scrollToBottom={scrollToBottom}
currentEditId={currentEditId}
setCurrentEditId={setCurrentEditId}
siblingIdx={messageList.length - siblingIdx - 1}
siblingCount={messageList.length}
setSiblingIdx={setSiblingIdxRev}
/>
}
export { MultiMessage };
export default function Message({
message,
messages,
@ -57,21 +21,28 @@ export default function Message({
siblingCount,
setSiblingIdx
}) {
const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit);
const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector(
(state) => state.submit
);
const [abortScroll, setAbort] = useState(false);
const { sender, text, isCreatedByUser, error, submitting } = message
const textEditor = useRef(null)
const { sender, text, isCreatedByUser, error, submitting } = message;
const textEditor = useRef(null);
const convo = useSelector((state) => state.convo);
const { initial } = useSelector((state) => state.models);
const { error: convoError } = convo;
const last = !message?.children?.length
const last = !message?.children?.length;
const edit = message.messageId == currentEditId;
const dispatch = useDispatch();
// const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user';
const blinker = submitting && isSubmitting && last && !isCreatedByUser;
const generateCursor = useCallback(() => {
if (!blinker) {
return '';
}
return <span className="result-streaming"></span>;
}, [blinker]);
useEffect(() => {
if (blinker && !abortScroll) {
@ -80,15 +51,10 @@ export default function Message({
}, [isSubmitting, text, blinker, scrollToBottom, abortScroll]);
useEffect(() => {
if (last)
dispatch(setConversation({parentMessageId: message?.messageId}))
}, [last, ])
if (last) dispatch(setConversation({ parentMessageId: message?.messageId }));
}, [last]);
if (sender === '') {
return <Spinner />;
}
const enterEdit = (cancel) => setCurrentEditId(cancel?-1:message.messageId)
const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId);
const handleWheel = () => {
if (blinker) {
@ -103,16 +69,23 @@ export default function Message({
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
};
const icon = getIconOfModel({ sender, isCreatedByUser, model, chatGptLabel, promptPrefix, error });
const icon = getIconOfModel({
sender,
isCreatedByUser,
model,
chatGptLabel,
promptPrefix,
error
});
if (!isCreatedByUser)
props.className =
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
const wrapText = (text) => <TextWrapper text={text} />;
// const wrapText = (text) => <TextWrapper text={text} generateCursor={generateCursor}/>;
const resubmitMessage = () => {
const text = textEditor.current.innerText
const text = textEditor.current.innerText;
if (convoError) {
dispatch(setError(false));
@ -126,13 +99,22 @@ export default function Message({
const fakeMessageId = crypto.randomUUID();
const isCustomModel = model === 'chatgptCustom' || !initial[model];
const currentMsg = {
sender: 'User', text: text.trim(), current: true, isCreatedByUser: true,
sender: 'User',
text: text.trim(),
current: true,
isCreatedByUser: true,
parentMessageId: message?.parentMessageId,
conversationId: message?.conversationId,
messageId: fakeMessageId };
messageId: fakeMessageId
};
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true };
const initialResponse = {
sender,
text: '',
parentMessageId: fakeMessageId,
submitting: true
};
dispatch(setSubmitState(true));
dispatch(setMessages([...messages, currentMsg, initialResponse]));
@ -144,18 +126,18 @@ export default function Message({
...currentMsg,
model,
chatGptLabel,
promptPrefix,
promptPrefix
},
messages: messages,
currentMsg,
initialResponse,
sender,
sender
};
console.log('User Input:', currentMsg?.text);
// handleSubmit(submission);
dispatch(setSubmission(submission));
setSiblingIdx(siblingCount - 1)
setSiblingIdx(siblingCount - 1);
enterEdit(true);
};
@ -166,42 +148,48 @@ export default function Message({
onWheel={handleWheel}
>
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<div className="relative flex w-[30px] flex-col items-end text-right text-xs md:text-sm">
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
{typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? (
<span className=" direction-rtl w-40 overflow-x-scroll">{icon}</span>
) : (
icon
)}
<SiblingSwitch siblingIdx={siblingIdx} siblingCount={siblingCount} setSiblingIdx={setSiblingIdx} />
<SiblingSwitch
siblingIdx={siblingIdx}
siblingCount={siblingCount}
setSiblingIdx={setSiblingIdx}
/>
</div>
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 whitespace-pre-wrap md:gap-3 lg:w-[calc(100%-115px)]">
<div className="flex flex-grow flex-col gap-3">
{error ? (
<div className="flex flex min-h-[20px] flex-col flex-grow items-start gap-4 gap-2 whitespace-pre-wrap text-red-500">
<div className="flex flex min-h-[20px] flex-grow flex-col items-start gap-4 gap-2 whitespace-pre-wrap text-red-500">
<div className="rounded-md border border-red-500 bg-red-500/10 py-2 px-3 text-sm text-gray-600 dark:text-gray-100">
{`An error occurred. Please try again in a few moments.\n\nError message: ${text}`}
</div>
</div>
) :
edit ? (
<div className="flex min-h-[20px] flex-col flex-grow items-start gap-4 whitespace-pre-wrap">
) : edit ? (
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
<div className="markdown prose dark:prose-invert light w-full break-words border-none focus:outline-none"
contentEditable={true} ref={textEditor} suppressContentEditableWarning={true}>
<div
className="markdown prose dark:prose-invert light w-full break-words border-none focus:outline-none"
contentEditable={true}
ref={textEditor}
suppressContentEditableWarning={true}
>
{text}
</div>
<div className="text-center mt-2 flex w-full justify-center">
<div className="mt-2 flex w-full justify-center text-center">
<button
className="btn relative btn-primary mr-2"
className="btn btn-primary relative mr-2"
disabled={isSubmitting}
onClick={resubmitMessage}
>
Save & Submit
</button>
<button
className="btn relative btn-neutral"
className="btn btn-neutral relative"
onClick={() => enterEdit(true)}
>
Cancel
@ -209,16 +197,26 @@ export default function Message({
</div>
</div>
) : (
<div className="flex min-h-[20px] flex-col flex-grow items-start gap-4 whitespace-pre-wrap">
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
<div className="markdown prose dark:prose-invert light w-full break-words">
{!isCreatedByUser ? wrapText(text) : text}
{blinker && <span className="result-streaming"></span>}
{!isCreatedByUser ? (
<TextWrapper
text={text}
generateCursor={generateCursor}
/>
) : (
text
)}
</div>
</div>
)}
</div>
<HoverButtons model={model} visible={!error && isCreatedByUser && !edit} onClick={() => enterEdit()}/>
<HoverButtons
model={model}
visible={!error && isCreatedByUser && !edit}
onClick={() => enterEdit()}
/>
</div>
</div>
</div>

View file

@ -0,0 +1,40 @@
import React, { useState } from 'react';
import Message from './Message';
export default function 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 (!(messageList && messageList.length)) {
return null;
}
if (siblingIdx >= messageList?.length) {
setSiblingIdx(0);
return null;
}
return (
<Message
key={messageList[messageList.length - siblingIdx - 1].messageId}
message={messageList[messageList.length - siblingIdx - 1]}
messages={messages}
scrollToBottom={scrollToBottom}
currentEditId={currentEditId}
setCurrentEditId={setCurrentEditId}
siblingIdx={messageList.length - siblingIdx - 1}
siblingCount={messageList.length}
setSiblingIdx={setSiblingIdxRev}
/>
);
}

View file

@ -46,8 +46,9 @@ const inLineWrap = (parts) => {
});
};
export default function TextWrapper({ text }) {
export default function TextWrapper({ text, generateCursor }) {
let embedTest = false;
let result = null;
// to match unenclosed code blocks
if (text.match(/```/g)?.length === 1) {
@ -137,13 +138,23 @@ export default function TextWrapper({ text }) {
}
});
return <>{codeParts}</>; // return the wrapped text
// return <>{codeParts}</>; // return the wrapped text
result = <>{codeParts}</>;
} else if (text.match(markupRegex)) {
// map over the parts and wrap any text between tildes with <code> tags
const parts = text.split(markupRegex);
const codeParts = inLineWrap(parts);
return <>{codeParts}</>; // return the wrapped text
// return <>{codeParts}</>; // return the wrapped text
result = <>{codeParts}</>;
} else {
return <Markdown options={mdOptions}>{text}</Markdown>;
// return <Markdown options={mdOptions}>{text}</Markdown>;
result = <Markdown options={mdOptions}>{text}</Markdown>;
}
return (
<>
{result}
{(<>{generateCursor()}</>)}
</>
);
}

View file

@ -1,12 +1,12 @@
import React, { useEffect, useState, useRef, useMemo } from 'react';
import Spinner from '../svg/Spinner';
import { CSSTransition } from 'react-transition-group';
import ScrollToBottom from './ScrollToBottom';
import { MultiMessage } from './Message';
import Conversation from '../Conversations/Conversation';
import MultiMessage from './MultiMessage';
import { useSelector } from 'react-redux';
const Messages = ({ messages }) => {
const [currentEditId, setCurrentEditId] = useState(-1)
const Messages = ({ messages, messageTree }) => {
const [currentEditId, setCurrentEditId] = useState(-1);
const { conversationId } = useSelector((state) => state.convo);
const [showScrollButton, setShowScrollButton] = useState(false);
const scrollableRef = useRef(null);
@ -24,26 +24,6 @@ const Messages = ({ messages }) => {
};
}, [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: []};
const parentMessage = messageMap[message.parentMessageId];
if (parentMessage)
parentMessage.children.push(messageMap[message.messageId]);
else
rootMessages.push(messageMap[message.messageId]);
});
return rootMessages;
}
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
setShowScrollButton(false);
@ -79,8 +59,12 @@ const Messages = ({ messages }) => {
onScroll={debouncedHandleScroll}
>
{/* <div className="flex-1 overflow-hidden"> */}
<div className="h-full dark:gpt-dark-gray">
<div className="flex h-full flex-col items-center text-sm dark:gpt-dark-gray">
<div className="dark:gpt-dark-gray h-full">
<div className="dark:gpt-dark-gray flex h-full flex-col items-center text-sm">
{messageTree.length === 0 ? (
<Spinner />
) : (
<>
<MultiMessage
key={conversationId} // avoid internal state mixture
messageList={messageTree}
@ -98,9 +82,10 @@ const Messages = ({ messages }) => {
>
{() => showScrollButton && <ScrollToBottom scrollHandler={scrollHandler} />}
</CSSTransition>
</>
)}
<div
className="group h-32 w-full flex-shrink-0 dark:border-gray-900/50 dark:gpt-dark-gray md:h-48"
className="dark:gpt-dark-gray group h-32 w-full flex-shrink-0 dark:border-gray-900/50 md:h-48"
ref={messagesEndRef}
/>
</div>
@ -110,4 +95,4 @@ const Messages = ({ messages }) => {
);
};
export default Messages;
export default React.memo(Messages);

View file

@ -1,7 +1,9 @@
import { createSlice } from '@reduxjs/toolkit';
import buildTree from '~/utils/buildTree';
const initialState = {
messages: [],
messageTree: []
};
const currentSlice = createSlice({
@ -10,6 +12,7 @@ const currentSlice = createSlice({
reducers: {
setMessages: (state, action) => {
state.messages = action.payload;
state.messageTree = buildTree(action.payload);
},
setEmptyMessage: (state) => {
state.messages = [

View file

@ -1251,7 +1251,6 @@ html {
vertical-align: baseline;
}
/* .result-streaming>:not(ol):not(ul):not(pre):last-child:after,
.result-streaming>ol:last-child li:last-child:after,
.result-streaming>pre:last-child code:after,

View file

@ -0,0 +1,17 @@
export default 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: []};
const parentMessage = messageMap[message.parentMessageId];
if (parentMessage)
parentMessage.children.push(messageMap[message.messageId]);
else
rootMessages.push(messageMap[message.messageId]);
});
return rootMessages;
}