From 8f58c95452de465995e9a3a44fb7f7eec7fb0b13 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 22 Mar 2023 16:06:11 -0400 Subject: [PATCH] feat: main styling/ui/ux final changes --- api/lib/db/indexSync.js | 2 +- api/models/Conversation.js | 5 +- api/server/routes/search.js | 23 +++++++-- client/src/components/Main/TextChat.jsx | 2 +- .../components/Messages/Content/Content.jsx | 2 +- .../components/Messages/Content/SubRow.jsx | 9 ++++ .../components/Messages/Content/Wrapper.jsx | 4 +- client/src/components/Messages/Message.jsx | 45 ++++++++-------- client/src/components/Messages/MessageBar.jsx | 38 ++++++++++++++ client/src/components/Messages/index.jsx | 2 +- client/src/components/Nav/NewChat.jsx | 9 +++- client/src/components/Nav/SearchBar.jsx | 12 ++--- client/src/components/Nav/index.jsx | 6 ++- client/src/mobile.css | 8 ++- client/src/store/convoSlice.js | 12 ++++- client/src/store/searchSlice.js | 6 ++- client/src/utils/buildTree.js | 51 ++++++++++--------- 17 files changed, 166 insertions(+), 70 deletions(-) create mode 100644 client/src/components/Messages/Content/SubRow.jsx create mode 100644 client/src/components/Messages/MessageBar.jsx diff --git a/api/lib/db/indexSync.js b/api/lib/db/indexSync.js index 257f645d23..300a7c7503 100644 --- a/api/lib/db/indexSync.js +++ b/api/lib/db/indexSync.js @@ -16,7 +16,7 @@ async function indexSync(req, res, next) { }); const { status } = await client.health(); - console.log(`Meilisearch: ${status}`); + // console.log(`Meilisearch: ${status}`); const result = status === 'available' && !!process.env.SEARCH; if (!result) { diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 84299b2aa1..eec4efceda 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -81,6 +81,7 @@ module.exports = { } const cache = {}; + const convoMap = {}; const promises = []; // will handle a syncing solution soon const deletedConvoIds = []; @@ -104,6 +105,7 @@ module.exports = { cache[page] = []; } cache[page].push(convo); + convoMap[convo.conversationId] = convo; return true; } }); @@ -120,7 +122,8 @@ module.exports = { pageNumber, pageSize, // will handle a syncing solution soon - filter: new Set(deletedConvoIds) + filter: new Set(deletedConvoIds), + convoMap }; } catch (error) { console.log(error); diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 3d2d8a0ce6..68b4dbd681 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -4,6 +4,7 @@ const { MeiliSearch } = require('meilisearch'); const { Message } = require('../../models/Message'); const { Conversation, getConvosQueried } = require('../../models/Conversation'); const { reduceHits } = require('../../lib/utils/reduceHits'); +const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc'); const cache = new Map(); router.get('/sync', async function (req, res) { @@ -52,9 +53,25 @@ router.get('/', async function (req, res) { console.log('messages', messages.length, 'titles', titles.length); const sortedHits = reduceHits(messages, titles); const result = await getConvosQueried(user, sortedHits, pageNumber); - cache.set(q, result.cache); + cache.set(key, result.cache); delete result.cache; - result.messages = messages.filter(message => !result.filter.has(message.conversationId)); + // result.messages = messages.filter(message => { }); + // !result.filter.has(message.conversationId) + + const activeMessages = []; + for (let i = 0; i < messages.length; i++) { + let message = messages[i]; + if (message.conversationId.includes('--')) { + message.conversationId = cleanUpPrimaryKeyValue(message.conversationId); + } + if (result.convoMap[message.conversationId] && !message.error) { + message = { ...message, title: result.convoMap[message.conversationId].title }; + activeMessages.push(message); + } + } + result.messages = activeMessages; + delete result.cache; + delete result.convoMap; // for debugging // console.log(result, messages.length); res.status(200).send(result); @@ -89,7 +106,7 @@ router.get('/enable', async function (req, res) { }); const { status } = await client.health(); - console.log(`Meilisearch: ${status}`); + // console.log(`Meilisearch: ${status}`); result = status === 'available' && !!process.env.SEARCH; return res.send(result); } catch (error) { diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index bd6fb3458a..4cf6e8799c 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -350,7 +350,7 @@ export default function TextChat({ messages }) { const isSearchView = messages?.[0]?.searchResult === true; const getPlaceholderText = () => { if (isSearchView) { - return 'Click a message to open its conversation.' + return 'Click a message title to open its conversation.' } if (disabled) { diff --git a/client/src/components/Messages/Content/Content.jsx b/client/src/components/Messages/Content/Content.jsx index 7dc016808a..0943b9c71b 100644 --- a/client/src/components/Messages/Content/Content.jsx +++ b/client/src/components/Messages/Content/Content.jsx @@ -53,7 +53,7 @@ const code = React.memo((props) => { }); const p = React.memo((props) => { - return

{props?.children}

; + return {props?.children}; }); const blinker = ({ node }) => { diff --git a/client/src/components/Messages/Content/SubRow.jsx b/client/src/components/Messages/Content/SubRow.jsx new file mode 100644 index 0000000000..55e5221258 --- /dev/null +++ b/client/src/components/Messages/Content/SubRow.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export default function SubRow({ children, classes = '', subclasses = '', onClick }) { + return ( +
+
{children}
+
+ ); +} diff --git a/client/src/components/Messages/Content/Wrapper.jsx b/client/src/components/Messages/Content/Wrapper.jsx index 6caaf0d7fb..005e0c6237 100644 --- a/client/src/components/Messages/Content/Wrapper.jsx +++ b/client/src/components/Messages/Content/Wrapper.jsx @@ -3,14 +3,14 @@ import TextWrapper from './TextWrapper'; import Content from './Content'; const Wrapper = React.memo(({ text, generateCursor, isCreatedByUser, searchResult }) => { - if (!isCreatedByUser && searchResult) { + if (searchResult) { return ( ); - } else if (!isCreatedByUser && !searchResult) { + } else if (!isCreatedByUser) { return ( state.submit - ); + const { isSubmitting, model, chatGptLabel, cursor, promptPrefix } = useSelector(state => state.submit); const [abortScroll, setAbort] = useState(false); const { sender, text, searchResult, isCreatedByUser, error, submitting } = message; const textEditor = useRef(null); - // const { convos } = useSelector((state) => state.convo); const last = !message?.children?.length; const edit = message.messageId == currentEditId; const { ask } = useMessageHandler(); const dispatch = useDispatch(); + // const currentConvo = convoMap[message.conversationId]; // const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user'; // const blinker = submitting && isSubmitting && last && !isCreatedByUser; @@ -62,7 +61,7 @@ export default function Message({ } }, [last, message]); - const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId); + const enterEdit = cancel => setCurrentEditId(cancel ? -1 : message.messageId); const handleWheel = () => { if (blinker) { @@ -92,11 +91,10 @@ export default function Message({ '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]'; if (message.bg && searchResult) { - props.className = message.bg + ' cursor-pointer'; + props.className = message.bg.split('hover')[0]; + props.titleClass = message.bg.split(props.className)[1] + ' cursor-pointer'; } - // const wrapText = (text) => ; - const resubmitMessage = () => { const text = textEditor.current.innerText; @@ -135,11 +133,11 @@ export default function Message({
- {typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( + {typeof icon === 'string' && icon.match(/[^\\x00-\\x7F]+/) ? ( {icon} ) : ( icon @@ -153,6 +151,13 @@ export default function Message({
+ {searchResult && ( + {`${message.title} | ${message.sender}`} + )}
{error ? (
@@ -207,15 +212,13 @@ export default function Message({ visible={!error && isCreatedByUser && !edit && !searchResult} onClick={() => enterEdit()} /> -
-
- -
-
+ + +
diff --git a/client/src/components/Messages/MessageBar.jsx b/client/src/components/Messages/MessageBar.jsx new file mode 100644 index 0000000000..76611e52a3 --- /dev/null +++ b/client/src/components/Messages/MessageBar.jsx @@ -0,0 +1,38 @@ +import React, { useRef, useEffect, useState } from 'react'; + +const MessageBar = ({ children, dynamicProps, handleWheel, clickSearchResult }) => { + const ref = useRef(null); + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setIsVisible(true); + observer.unobserve(ref.current); + } + }, + { threshold: 0.1 } + ); + + observer.observe(ref.current); + + return () => { + observer.unobserve(ref.current); + }; + }, []); + + return ( +
+ {isVisible ? children : null} +
+ ); +}; + +export default MessageBar; diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index e38daa679a..030e607941 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -75,7 +75,7 @@ export default function Messages({ messages, messageTree }) {
Model: {modelName} {customModel ? `(${customModel})` : null}
- {(messageTree.length === 0) ? ( + {(messageTree.length === 0 || messages.length === 0 || !messages) ? ( ) : ( <> diff --git a/client/src/components/Nav/NewChat.jsx b/client/src/components/Nav/NewChat.jsx index 90fb88b869..fae7c35ec2 100644 --- a/client/src/components/Nav/NewChat.jsx +++ b/client/src/components/Nav/NewChat.jsx @@ -1,9 +1,10 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import { setNewConvo } from '~/store/convoSlice'; +import { setNewConvo, refreshConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; -import { setSubmission } from '~/store/submitSlice'; +import { setSubmission, setDisabled } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; +import { setInputValue, setQuery } from '~/store/searchSlice'; export default function NewChat() { const dispatch = useDispatch(); @@ -12,7 +13,11 @@ export default function NewChat() { dispatch(setText('')); dispatch(setMessages([])); dispatch(setNewConvo()); + dispatch(refreshConversation()); dispatch(setSubmission({})); + dispatch(setDisabled(false)); + dispatch(setInputValue('')); + dispatch(setQuery('')); }; return ( diff --git a/client/src/components/Nav/SearchBar.jsx b/client/src/components/Nav/SearchBar.jsx index ddbe74ee2c..7aac29b5d1 100644 --- a/client/src/components/Nav/SearchBar.jsx +++ b/client/src/components/Nav/SearchBar.jsx @@ -1,12 +1,13 @@ -import React, { useState, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { debounce } from 'lodash'; -import { useDispatch } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { Search } from 'lucide-react'; -import { setQuery } from '~/store/searchSlice'; +import { setInputValue, setQuery } from '~/store/searchSlice'; export default function SearchBar({ fetch, clearSearch }) { const dispatch = useDispatch(); - const [inputValue, setInputValue] = useState(''); + const { inputValue } = useSelector((state) => state.search); + // const [inputValue, setInputValue] = useState(''); const debouncedChangeHandler = useCallback( debounce((q) => { @@ -28,10 +29,9 @@ export default function SearchBar({ fetch, clearSearch }) { } }; - const changeHandler = (e) => { let q = e.target.value; - setInputValue(q); + dispatch(setInputValue(q)); q = q.trim(); if (q === '') { diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 4e59cc8661..97ee1b1b32 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -54,8 +54,10 @@ export default function Nav({ navVisible, setNavVisible }) { const clearSearch = () => { setPage(1); dispatch(refreshConversation()); - dispatch(setNewConvo()); - dispatch(setMessages([])); + if (!conversationId) { + dispatch(setNewConvo()); + dispatch(setMessages([])); + } dispatch(setDisabled(false)); }; diff --git a/client/src/mobile.css b/client/src/mobile.css index fff8d5a91d..65a1db35ed 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -22,11 +22,17 @@ } @media (min-width: 1024px) { - .sibling-switch-container { + .switch-container { display: none; } } + + .switch-result { + display: block !important; + visibility: visible; + } + @media (max-width: 1024px) { /* .sibling-switch { left: 114px; diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index f0c5de2042..e8da18bb3b 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -17,14 +17,15 @@ const initialState = { refreshConvoHint: 0, search: false, latestMessage: null, - convos: [] + convos: [], + convoMap: {}, }; const currentSlice = createSlice({ name: 'convo', initialState, reducers: { - refreshConversation: (state, action) => { + refreshConversation: (state) => { state.refreshConvoHint = state.refreshConvoHint + 1; }, setConversation: (state, action) => { @@ -69,6 +70,13 @@ const currentSlice = createSlice({ } else { state.convos = convos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); } + + // state.convoMap = convos.reduce((acc, curr) => { + // acc[curr.conversationId] = { ...curr }; + // delete acc[curr.conversationId].conversationId; + // return acc; + // }, {}); + }, setPages: (state, action) => { state.pages = action.payload; diff --git a/client/src/store/searchSlice.js b/client/src/store/searchSlice.js index ef95a926a7..d53c0eea62 100644 --- a/client/src/store/searchSlice.js +++ b/client/src/store/searchSlice.js @@ -4,12 +4,16 @@ const initialState = { searchEnabled: false, search: false, query: '', + inputValue: '', }; const currentSlice = createSlice({ name: 'search', initialState, reducers: { + setInputValue: (state, action) => { + state.inputValue = action.payload; + }, setSearchState: (state, action) => { state.searchEnabled = action.payload; }, @@ -26,6 +30,6 @@ const currentSlice = createSlice({ } }); -export const { setSearchState, setQuery } = currentSlice.actions; +export const { setInputValue, setSearchState, setQuery } = currentSlice.actions; export default currentSlice.reducer; diff --git a/client/src/utils/buildTree.js b/client/src/utils/buildTree.js index 00fe2b134d..fd85c9d078 100644 --- a/client/src/utils/buildTree.js +++ b/client/src/utils/buildTree.js @@ -21,31 +21,32 @@ export default function buildTree(messages, groupAll = false) { } // Group all messages into one tree - // let parentId = null; - // messages.forEach((message, i) => { - // messageMap[message.messageId] = { ...message, bg: i % 2 === 0 ? even : odd, children: [] }; - // const currentMessage = messageMap[message.messageId]; - // const parentMessage = messageMap[parentId]; - // if (parentMessage) parentMessage.children.push(currentMessage); - // else rootMessages.push(currentMessage); - // parentId = message.messageId; - // }); - - // return rootMessages; - - // Group all messages by conversation - // Traverse the messages array and store each element in messageMap. - rootMessages = {}; - let parents = 0; - messages.forEach(message => { - if (message.conversationId in messageMap) { - messageMap[message.conversationId].children.push(message); - } else { - messageMap[message.conversationId] = { ...message, bg: parents % 2 === 0 ? even : odd, children: [] }; - rootMessages[message.conversationId] = messageMap[message.conversationId]; - parents++; - } + let parentId = null; + messages.forEach((message, i) => { + messageMap[message.messageId] = { ...message, bg: i % 2 === 0 ? even : odd, children: [] }; + const currentMessage = messageMap[message.messageId]; + const parentMessage = messageMap[parentId]; + if (parentMessage) parentMessage.children.push(currentMessage); + else rootMessages.push(currentMessage); + parentId = message.messageId; }); - return Object.values(rootMessages); + return rootMessages; + + // Group all messages by conversation, doesn't look great + // Traverse the messages array and store each element in messageMap. + // rootMessages = {}; + // let parents = 0; + // messages.forEach(message => { + // if (message.conversationId in messageMap) { + // messageMap[message.conversationId].children.push(message); + // } else { + // messageMap[message.conversationId] = { ...message, bg: parents % 2 === 0 ? even : odd, children: [] }; + // rootMessages.push(messageMap[message.conversationId]); + // parents++; + // } + // }); + + // // return Object.values(rootMessages); + // return rootMessages; }