diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js
index 45b23d0bd6..0774b92ddf 100644
--- a/api/server/routes/convos.js
+++ b/api/server/routes/convos.js
@@ -13,7 +13,9 @@ router.get('/', async (req, res) => {
router.get('/:conversationId', async (req, res) => {
const { conversationId } = req.params;
const convo = await getConvo(req?.session?.user?.username, conversationId);
- res.status(200).send(convo.toObject());
+
+ if (convo) res.status(200).send(convo.toObject());
+ else res.status(404).end();
});
router.post('/clear', async (req, res) => {
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 404dce05a8..d8673b69ed 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import Root from './routes/Root';
import Chat from './routes/Chat';
+import Search from './routes/Search';
import store from './store';
import userAuth from './utils/userAuth';
import { useRecoilState, useSetRecoilState } from 'recoil';
@@ -23,8 +24,12 @@ const router = createBrowserRouter([
)
},
{
- path: 'chat/:conversationId',
+ path: 'chat/:conversationId?',
element:
+ },
+ {
+ path: 'search/:query?',
+ element:
}
]
}
diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx
index 359767aad0..9eac938c4c 100644
--- a/client/src/components/Input/index.jsx
+++ b/client/src/components/Input/index.jsx
@@ -13,7 +13,7 @@ import { useMessageHandler } from '../../utils/handleSubmit';
import store from '~/store';
-export default function TextChat() {
+export default function TextChat({ isSearchView = false }) {
const inputRef = useRef(null);
const isComposing = useRef(false);
@@ -36,7 +36,7 @@ export default function TextChat() {
// auto focus to input, when enter a conversation.
useEffect(() => {
- inputRef.current?.focus();
+ if (conversation?.conversationId !== 'search') inputRef.current?.focus();
setText('');
}, [conversation?.conversationId]);
@@ -108,7 +108,6 @@ export default function TextChat() {
setText(value);
};
- const isSearchView = messages?.[0]?.searchResult === true;
const getPlaceholderText = () => {
if (isSearchView) {
return 'Click a message title to open its conversation.';
diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx
index 262575116d..45c56f1616 100644
--- a/client/src/components/Messages/index.jsx
+++ b/client/src/components/Messages/index.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef, useCallback } from 'react';
-import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
+import { useRecoilValue } from 'recoil';
import Spinner from '../svg/Spinner';
import { throttle } from 'lodash';
import { CSSTransition } from 'react-transition-group';
@@ -8,18 +8,25 @@ import MultiMessage from './MultiMessage';
import store from '~/store';
-export default function Messages() {
+export default function Messages({ isSearchView = false }) {
const [currentEditId, setCurrentEditId] = useState(-1);
- const messagesTree = useRecoilValue(store.messagesTree);
- const conversation = useRecoilValue(store.conversation) || {};
- const { conversationId, model, chatGptLabel } = conversation;
- const models = useRecoilValue(store.models) || [];
const [showScrollButton, setShowScrollButton] = useState(false);
const scrollableRef = useRef(null);
const messagesEndRef = useRef(null);
+ const messagesTree = useRecoilValue(store.messagesTree);
+ const searchResultMessagesTree = useRecoilValue(store.searchResultMessagesTree);
+
+ const _messagesTree = isSearchView ? searchResultMessagesTree : messagesTree;
+
+ const conversation = useRecoilValue(store.conversation) || {};
+ const { conversationId, model, chatGptLabel } = conversation;
+
+ const models = useRecoilValue(store.models) || [];
const modelName = models.find(element => element.model == model)?.name;
+ const searchQuery = useRecoilValue(store.searchQuery);
+
useEffect(() => {
const timeoutId = setTimeout(() => {
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
@@ -36,7 +43,7 @@ export default function Messages() {
clearTimeout(timeoutId);
window.removeEventListener('scroll', handleScroll);
};
- }, [messagesTree]);
+ }, [_messagesTree]);
const scrollToBottom = useCallback(
throttle(
@@ -81,16 +88,18 @@ export default function Messages() {
- Model: {modelName} {chatGptLabel ? `(${chatGptLabel})` : null}
+ {isSearchView
+ ? `Search: ${searchQuery}`
+ : `Model: ${modelName} ${chatGptLabel ? `(${chatGptLabel})` : ''}`}
- {messagesTree === null ? (
+ {_messagesTree === null ? (
) : (
<>
{
setSearchQuery(q);
- if (q.length > 0) {
- fetch(q, 1);
- }
}, 750),
[setSearchQuery]
);
+ useEffect(() => {
+ if (searchQuery.length > 0) {
+ fetch(searchQuery, 1);
+ setInputValue(searchQuery);
+ }
+ }, [searchQuery]);
+
const handleKeyUp = e => {
const { value } = e.target;
if (e.keyCode === 8 && value === '') {
diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx
index 9d1c69d342..85523033ea 100644
--- a/client/src/components/Nav/index.jsx
+++ b/client/src/components/Nav/index.jsx
@@ -27,12 +27,12 @@ export default function Nav({ navVisible, setNavVisible }) {
const searchQuery = useRecoilValue(store.searchQuery);
const isSearchEnabled = useRecoilValue(store.isSearchEnabled);
const isSearching = useRecoilValue(store.isSearching);
- const { newConversation } = store.useConversation();
+ const { newConversation, searchPlaceholderConversation } = store.useConversation();
// current conversation
const conversation = useRecoilValue(store.conversation);
const { conversationId } = conversation || {};
- const setMessages = useSetRecoilState(store.messages);
+ const setSearchResultMessages = useSetRecoilState(store.searchResultMessages);
// refreshConversationsHint is used for other components to ask refresh of Nav
const refreshConversationsHint = useRecoilValue(store.refreshConversationsHint);
@@ -66,10 +66,8 @@ export default function Nav({ navVisible, setNavVisible }) {
setPageNumber(res.pageNumber);
setPages(res.pages);
setIsFetching(false);
- if (res.messages?.length > 0) {
- setMessages(res.messages);
- // dispatch(setDisabled(true));
- }
+ searchPlaceholderConversation();
+ setSearchResultMessages(res.messages);
};
// TODO: dont need this
diff --git a/client/src/routes/Chat.jsx b/client/src/routes/Chat.jsx
index 58aa0500d3..1468dfa654 100644
--- a/client/src/routes/Chat.jsx
+++ b/client/src/routes/Chat.jsx
@@ -8,11 +8,9 @@ import TextChat from '../components/Input';
import store from '~/store';
import manualSWR from '~/utils/fetchers';
-// import TextChat from './components/Main/TextChat';
-
-// {/* */}
export default function Chat() {
+ const searchQuery = useRecoilValue(store.searchQuery);
const [conversation, setConversation] = useRecoilState(store.conversation);
const setMessages = useSetRecoilState(store.messages);
const messagesTree = useRecoilValue(store.messagesTree);
@@ -28,18 +26,23 @@ export default function Chat() {
useEffect(() => {
if (conversation === null) {
// no current conversation, we need to do something
- if (conversationId == 'new') {
+ if (conversationId === 'new') {
// create new
newConversation();
- } else {
+ } else if (conversationId) {
// fetch it from server
conversationTrigger().then(setConversation);
setMessages(null);
- console.log('NEED TO FETCH DATA');
+ } else {
+ navigate(`/chat/new`);
}
- } else if (conversation?.conversationId !== conversationId)
+ } else if (conversation?.conversationId === 'search') {
+ // jump to search page
+ navigate(`/search/${searchQuery}`);
+ } else if (conversation?.conversationId !== conversationId) {
// conversationId (in url) should always follow conversation?.conversationId, unless conversation is null
navigate(`/chat/${conversation?.conversationId}`);
+ }
}, [conversation, conversationId]);
// when messagesTree is null (<=> messages is null)
@@ -50,7 +53,12 @@ export default function Chat() {
}
}, [conversation?.conversationId]);
+ // if not a conversation
+ if (conversation?.conversationId === 'search') return null;
+ // if conversationId not match
if (conversation?.conversationId !== conversationId) return null;
+ // if conversationId is null
+ if (!conversationId) return null;
return (
<>
diff --git a/client/src/routes/Search.jsx b/client/src/routes/Search.jsx
new file mode 100644
index 0000000000..f91e7e224b
--- /dev/null
+++ b/client/src/routes/Search.jsx
@@ -0,0 +1,50 @@
+import React, { useEffect } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import { useRecoilState, useRecoilValue } from 'recoil';
+
+import Messages from '../components/Messages';
+import TextChat from '../components/Input';
+
+import store from '~/store';
+
+export default function Search() {
+ const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery);
+ const conversation = useRecoilValue(store.conversation);
+ const { searchPlaceholderConversation } = store.useConversation();
+ const { query } = useParams();
+ const navigate = useNavigate();
+
+ // when conversation changed or conversationId (in url) changed
+ useEffect(() => {
+ if (conversation === null) {
+ // no current conversation, we need to do something
+ if (query) {
+ // create new
+ searchPlaceholderConversation();
+ setSearchQuery(query);
+ } else {
+ navigate(`/chat/new`);
+ }
+ } else if (conversation?.conversationId === 'search') {
+ // jump to search page
+ if (searchQuery !== query) navigate(`/search/${searchQuery}`);
+ } else {
+ // conversationId (in url) should always follow conversation?.conversationId, unless conversation is null
+ navigate(`/chat/${conversation?.conversationId}`);
+ }
+ }, [conversation, query, searchQuery]);
+
+ // if not a search
+ if (conversation?.conversationId !== 'search') return null;
+ // if query not match
+ if (searchQuery !== query) return null;
+ // if query is null
+ if (!query) return null;
+
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js
index 9e1c0f080e..7ae123c254 100644
--- a/client/src/store/conversation.js
+++ b/client/src/store/conversation.js
@@ -1,13 +1,5 @@
import models from './models';
-import {
- atom,
- selector,
- useRecoilValue,
- useSetRecoilState,
- useResetRecoilState,
- useRecoilCallback,
- useRecoilState
-} from 'recoil';
+import { atom, selector, useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
import buildTree from '~/utils/buildTree';
// current conversation, can be null (need to be fetched from server)
@@ -42,9 +34,7 @@ const messages = atom({
const messagesTree = selector({
key: 'messagesTree',
get: ({ get }) => {
- const _messages = get(messages);
- const groupAll = _messages?.[0]?.searchResult;
- return buildTree(_messages, groupAll);
+ return buildTree(get(messages), false);
}
});
@@ -78,7 +68,6 @@ const useConversation = () => {
try {
// try to use current model
const { _model = null, _chatGptLabel = null, _promptPrefix = null } = prev_conversation || {};
- console.log(_model, _chatGptLabel, _promptPrefix);
if (prevModelsFilter[_model]) {
model = _model;
chatGptLabel = _chatGptLabel;
@@ -142,7 +131,27 @@ const useConversation = () => {
);
};
- return { newConversation, switchToConversation };
+ const searchPlaceholderConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => {
+ switchToConversation(
+ {
+ conversationId: 'search',
+ title: 'Search',
+ jailbreakConversationId: null,
+ conversationSignature: null,
+ clientId: null,
+ invocationId: null,
+ model: model,
+ chatGptLabel: chatGptLabel,
+ promptPrefix: promptPrefix,
+ user: null,
+ suggestions: [],
+ toneStyle: null
+ },
+ []
+ );
+ };
+
+ return { newConversation, switchToConversation, searchPlaceholderConversation };
};
export default {
diff --git a/client/src/store/search.js b/client/src/store/search.js
index 554a012b72..ebc956def5 100644
--- a/client/src/store/search.js
+++ b/client/src/store/search.js
@@ -1,4 +1,5 @@
import { atom, selector } from 'recoil';
+import buildTree from '~/utils/buildTree';
const isSearchEnabled = atom({
key: 'isSearchEnabled',
@@ -10,6 +11,18 @@ const searchQuery = atom({
default: ''
});
+const searchResultMessages = atom({
+ key: 'searchResultMessages',
+ default: null
+});
+
+const searchResultMessagesTree = selector({
+ key: 'searchResultMessagesTree',
+ get: ({ get }) => {
+ return buildTree(get(searchResultMessages), true);
+ }
+});
+
const isSearching = selector({
key: 'isSearching',
get: ({ get }) => {
@@ -21,5 +34,7 @@ const isSearching = selector({
export default {
isSearchEnabled,
isSearching,
+ searchResultMessages,
+ searchResultMessagesTree,
searchQuery
};