diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 02a0876..fdff2e9 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -53,6 +53,15 @@ def get_single_conversation(): conversation = conversations_collection.find_one({"_id": ObjectId(conversation_id)}) return jsonify(conversation['queries']) +@user.route("/api/update_conversation_name", methods=["POST"]) +def update_conversation_name(): + # update data for a conversation + data = request.get_json() + id = data["id"] + name = data["name"] + conversations_collection.update_one({"_id": ObjectId(id)},{"$set":{"name":name}}) + return {"status": "ok"} + @user.route("/api/feedback", methods=["POST"]) def api_feedback(): diff --git a/frontend/src/About.tsx b/frontend/src/About.tsx index ca318a6..fe26835 100644 --- a/frontend/src/About.tsx +++ b/frontend/src/About.tsx @@ -4,7 +4,7 @@ export default function About() { return (
-
+

About DocsGPT

🦖

diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ad9d72a..4454094 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,9 +18,7 @@ export default function App() {
diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index e065d65..7a55cd0 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -32,6 +32,7 @@ import { useMediaQuery, useOutsideAlerter } from './hooks'; import Upload from './upload/Upload'; import { Doc, getConversations } from './preferences/preferenceApi'; import SelectDocsModal from './preferences/SelectDocsModal'; +import ConversationTile from './conversation/ConversationTile'; interface NavigationProps { navOpen: boolean; @@ -68,16 +69,20 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { useEffect(() => { if (!conversations) { - getConversations() - .then((fetchedConversations) => { - dispatch(setConversations(fetchedConversations)); - }) - .catch((error) => { - console.error('Failed to fetch conversations: ', error); - }); + fetchConversations(); } }, [conversations, dispatch]); + async function fetchConversations() { + return await getConversations() + .then((fetchedConversations) => { + dispatch(setConversations(fetchedConversations)); + }) + .catch((error) => { + console.error('Failed to fetch conversations: ', error); + }); + } + const handleDeleteConversation = (id: string) => { fetch(`${apiHost}/api/delete_conversation?id=${id}`, { method: 'POST', @@ -89,6 +94,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { ) as HTMLElement; const parentElement = imageElement.parentNode as HTMLElement; parentElement.parentNode?.removeChild(parentElement); + fetchConversations(); }) .catch((error) => console.error(error)); }; @@ -126,6 +132,29 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { ); }); }; + + async function updateConversationName(updatedConversation: { + name: string; + id: string; + }) { + await fetch(`${apiHost}/api/update_conversation_name`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updatedConversation), + }) + .then((response) => response.json()) + .then((data) => { + if (data) { + navigate('/'); + fetchConversations(); + } + }) + .catch((err) => { + console.error(err); + }); + } useOutsideAlerter( navRef, () => { @@ -210,41 +239,17 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
{conversations - ? conversations.map((conversation) => { - return ( -
{ - handleConversationClick(conversation.id); - }} - className={`my-auto mx-4 mt-4 flex h-12 cursor-pointer items-center justify-between gap-4 rounded-3xl hover:bg-gray-100 ${ - conversationId === conversation.id ? 'bg-gray-100' : '' - }`} - > -
- -

- {conversation.name.length > 45 - ? conversation.name.substring(0, 45) + '...' - : conversation.name} -

-
- - {conversationId === conversation.id ? ( - Exit { - event.stopPropagation(); - handleDeleteConversation(conversation.id); - }} - /> - ) : null} -
- ); - }) + ? conversations.map((conversation, index) => ( + handleConversationClick(id)} + onDeleteConversation={(id) => handleDeleteConversation(id)} + onSave={(conversation) => + updateConversationName(conversation) + } + /> + )) : null}
diff --git a/frontend/src/assets/checkMark.svg b/frontend/src/assets/checkMark.svg new file mode 100644 index 0000000..9ed02cb --- /dev/null +++ b/frontend/src/assets/checkMark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/checkmark.svg b/frontend/src/assets/checkmark.svg index 682c29d..9ed02cb 100644 --- a/frontend/src/assets/checkmark.svg +++ b/frontend/src/assets/checkmark.svg @@ -1,3 +1,3 @@ - - - + + + \ No newline at end of file diff --git a/frontend/src/assets/edit.svg b/frontend/src/assets/edit.svg new file mode 100644 index 0000000..2565377 --- /dev/null +++ b/frontend/src/assets/edit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/trash.svg b/frontend/src/assets/trash.svg new file mode 100644 index 0000000..d0e4546 --- /dev/null +++ b/frontend/src/assets/trash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/conversation/ConversationTile.tsx b/frontend/src/conversation/ConversationTile.tsx new file mode 100644 index 0000000..592c52b --- /dev/null +++ b/frontend/src/conversation/ConversationTile.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import Edit from '../assets/edit.svg'; +import Exit from '../assets/exit.svg'; +import Message from '../assets/message.svg'; +import CheckMark from '../assets/checkmark.svg'; +import Trash from '../assets/trash.svg'; + +import { selectConversationId } from '../preferences/preferenceSlice'; +import { useOutsideAlerter } from '../hooks'; + +interface ConversationProps { + name: string; + id: string; +} +interface ConversationTileProps { + conversation: ConversationProps; + selectConversation: (arg1: string) => void; + onDeleteConversation: (arg1: string) => void; + onSave: ({ name, id }: ConversationProps) => void; +} + +export default function ConversationTile({ + conversation, + selectConversation, + onDeleteConversation, + onSave, +}: ConversationTileProps) { + const conversationId = useSelector(selectConversationId); + const tileRef = useRef(null); + + const [isEdit, setIsEdit] = useState(false); + const [conversationName, setConversationsName] = useState(''); + useOutsideAlerter( + tileRef, + () => + handleSaveConversation({ + id: conversationId || conversation.id, + name: conversationName, + }), + [conversationName], + ); + + useEffect(() => { + setConversationsName(conversation.name); + }, [conversation.name]); + + function handleEditConversation() { + setIsEdit(true); + } + + function handleSaveConversation(changedConversation: ConversationProps) { + if (changedConversation.name.trim().length) { + onSave(changedConversation); + setIsEdit(false); + } else { + onClear(); + } + } + + function onClear() { + setConversationsName(conversation.name); + setIsEdit(false); + } + return ( +
{ + selectConversation(conversation.id); + }} + className={`my-auto mx-4 mt-4 flex h-12 cursor-pointer items-center justify-between gap-4 rounded-3xl hover:bg-gray-100 ${ + conversationId === conversation.id ? 'bg-gray-100' : '' + }`} + > +
+ + {isEdit ? ( + setConversationsName(e.target.value)} + /> + ) : ( +

+ {conversationName} +

+ )} +
+ {conversationId === conversation.id ? ( +
+ Edit { + event.stopPropagation(); + isEdit + ? handleSaveConversation({ + id: conversationId, + name: conversationName, + }) + : handleEditConversation(); + }} + /> + Exit { + event.stopPropagation(); + isEdit ? onClear() : onDeleteConversation(conversation.id); + }} + /> +
+ ) : null} +
+ ); +}