From 59caf381f70b5470d68cf7158f2a5d25ab087f9b Mon Sep 17 00:00:00 2001 From: Siddhant Rai Date: Mon, 5 Aug 2024 14:17:21 +0530 Subject: [PATCH 1/2] fix: changed query box from div to textarea for handling paste, autoscroll --- application/api/user/routes.py | 292 +++++++++++++-------- frontend/src/conversation/Conversation.tsx | 45 ++-- frontend/src/index.css | 3 +- 3 files changed, 209 insertions(+), 131 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index a26ddc51c..91b90d6ad 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -44,7 +44,7 @@ def delete_conversation(): return {"status": "ok"} -@user.route("/api/delete_all_conversations", methods=["POST"]) +@user.route("/api/delete_all_conversations", methods=["GET"]) def delete_all_conversations(): user_id = "local" conversations_collection.delete_many({"user": user_id}) @@ -256,7 +256,7 @@ def combined_json(): "docLink": "default", "model": settings.EMBEDDINGS_NAME, "location": "remote", - "tokens":"" + "tokens": "", } ] # structure: name, language, version, description, fullName, date, docLink @@ -273,7 +273,7 @@ def combined_json(): "docLink": index["location"], "model": settings.EMBEDDINGS_NAME, "location": "local", - "tokens" : index["tokens"] if ("tokens" in index.keys()) else "" + "tokens": index["tokens"] if ("tokens" in index.keys()) else "", } ) if settings.VECTOR_STORE == "faiss": @@ -295,7 +295,7 @@ def combined_json(): "docLink": "duckduck_search", "model": settings.EMBEDDINGS_NAME, "location": "custom", - "tokens":"" + "tokens": "", } ) if "brave_search" in settings.RETRIEVERS_ENABLED: @@ -310,7 +310,7 @@ def combined_json(): "docLink": "brave_search", "model": settings.EMBEDDINGS_NAME, "location": "custom", - "tokens":"" + "tokens": "", } ) @@ -496,138 +496,204 @@ def delete_api_key(): return {"status": "ok"} -#route to share conversation +# route to share conversation ##isPromptable should be passed through queries -@user.route("/api/share",methods=["POST"]) +@user.route("/api/share", methods=["POST"]) def share_conversation(): try: data = request.get_json() user = "local" if "user" not in data else data["user"] conversation_id = data["conversation_id"] isPromptable = request.args.get("isPromptable").lower() == "true" - - conversation = conversations_collection.find_one({"_id": ObjectId(conversation_id)}) + + conversation = conversations_collection.find_one( + {"_id": ObjectId(conversation_id)} + ) current_n_queries = len(conversation["queries"]) - - ##generate binary representation of uuid + + ##generate binary representation of uuid explicit_binary = Binary.from_uuid(uuid.uuid4(), UuidRepresentation.STANDARD) - - if(isPromptable): + + if isPromptable: source = "default" if "source" not in data else data["source"] prompt_id = "default" if "prompt_id" not in data else data["prompt_id"] chunks = "2" if "chunks" not in data else data["chunks"] - - name = conversation["name"]+"(shared)" - pre_existing_api_document = api_key_collection.find_one({ - "prompt_id":prompt_id, - "chunks":chunks, - "source":source, - "user":user - }) + + name = conversation["name"] + "(shared)" + pre_existing_api_document = api_key_collection.find_one( + { + "prompt_id": prompt_id, + "chunks": chunks, + "source": source, + "user": user, + } + ) api_uuid = str(uuid.uuid4()) - if(pre_existing_api_document): - api_uuid = pre_existing_api_document["key"] - pre_existing = shared_conversations_collections.find_one({ - "conversation_id":DBRef("conversations",ObjectId(conversation_id)), - "isPromptable":isPromptable, - "first_n_queries":current_n_queries, - "user":user, - "api_key":api_uuid - }) - if(pre_existing is not None): - return jsonify({"success":True, "identifier":str(pre_existing["uuid"].as_uuid())}),200 - else: - shared_conversations_collections.insert_one({ - "uuid":explicit_binary, - "conversation_id": { - "$ref":"conversations", - "$id":ObjectId(conversation_id) - } , - "isPromptable":isPromptable, - "first_n_queries":current_n_queries, - "user":user, - "api_key":api_uuid - }) - return jsonify({"success":True,"identifier":str(explicit_binary.as_uuid())}) + if pre_existing_api_document: + api_uuid = pre_existing_api_document["key"] + pre_existing = shared_conversations_collections.find_one( + { + "conversation_id": DBRef( + "conversations", ObjectId(conversation_id) + ), + "isPromptable": isPromptable, + "first_n_queries": current_n_queries, + "user": user, + "api_key": api_uuid, + } + ) + if pre_existing is not None: + return ( + jsonify( + { + "success": True, + "identifier": str(pre_existing["uuid"].as_uuid()), + } + ), + 200, + ) + else: + shared_conversations_collections.insert_one( + { + "uuid": explicit_binary, + "conversation_id": { + "$ref": "conversations", + "$id": ObjectId(conversation_id), + }, + "isPromptable": isPromptable, + "first_n_queries": current_n_queries, + "user": user, + "api_key": api_uuid, + } + ) + return jsonify( + {"success": True, "identifier": str(explicit_binary.as_uuid())} + ) else: api_key_collection.insert_one( - { - "name": name, - "key": api_uuid, - "source": source, - "user": user, - "prompt_id": prompt_id, - "chunks": chunks, - } - ) - shared_conversations_collections.insert_one({ - "uuid":explicit_binary, - "conversation_id": { - "$ref":"conversations", - "$id":ObjectId(conversation_id) - } , - "isPromptable":isPromptable, - "first_n_queries":current_n_queries, - "user":user, - "api_key":api_uuid - }) + { + "name": name, + "key": api_uuid, + "source": source, + "user": user, + "prompt_id": prompt_id, + "chunks": chunks, + } + ) + shared_conversations_collections.insert_one( + { + "uuid": explicit_binary, + "conversation_id": { + "$ref": "conversations", + "$id": ObjectId(conversation_id), + }, + "isPromptable": isPromptable, + "first_n_queries": current_n_queries, + "user": user, + "api_key": api_uuid, + } + ) ## Identifier as route parameter in frontend - return jsonify({"success":True, "identifier":str(explicit_binary.as_uuid())}),201 - + return ( + jsonify( + {"success": True, "identifier": str(explicit_binary.as_uuid())} + ), + 201, + ) + ##isPromptable = False - pre_existing = shared_conversations_collections.find_one({ - "conversation_id":DBRef("conversations",ObjectId(conversation_id)), - "isPromptable":isPromptable, - "first_n_queries":current_n_queries, - "user":user - }) - if(pre_existing is not None): - return jsonify({"success":True, "identifier":str(pre_existing["uuid"].as_uuid())}),200 - else: - shared_conversations_collections.insert_one({ - "uuid":explicit_binary, - "conversation_id": { - "$ref":"conversations", - "$id":ObjectId(conversation_id) - } , - "isPromptable":isPromptable, - "first_n_queries":current_n_queries, - "user":user - }) + pre_existing = shared_conversations_collections.find_one( + { + "conversation_id": DBRef("conversations", ObjectId(conversation_id)), + "isPromptable": isPromptable, + "first_n_queries": current_n_queries, + "user": user, + } + ) + if pre_existing is not None: + return ( + jsonify( + {"success": True, "identifier": str(pre_existing["uuid"].as_uuid())} + ), + 200, + ) + else: + shared_conversations_collections.insert_one( + { + "uuid": explicit_binary, + "conversation_id": { + "$ref": "conversations", + "$id": ObjectId(conversation_id), + }, + "isPromptable": isPromptable, + "first_n_queries": current_n_queries, + "user": user, + } + ) ## Identifier as route parameter in frontend - return jsonify({"success":True, "identifier":str(explicit_binary.as_uuid())}),201 - except Exception as err: - print (err) - return jsonify({"success":False,"error":str(err)}),400 - -#route to get publicly shared conversations -@user.route("/api/shared_conversation/",methods=["GET"]) -def get_publicly_shared_conversations(identifier : str): + return ( + jsonify( + {"success": True, "identifier": str(explicit_binary.as_uuid())} + ), + 201, + ) + except Exception as err: + print(err) + return jsonify({"success": False, "error": str(err)}), 400 + + +# route to get publicly shared conversations +@user.route("/api/shared_conversation/", methods=["GET"]) +def get_publicly_shared_conversations(identifier: str): try: - query_uuid = Binary.from_uuid(uuid.UUID(identifier), UuidRepresentation.STANDARD) - shared = shared_conversations_collections.find_one({"uuid":query_uuid}) - conversation_queries=[] - if shared and 'conversation_id' in shared and isinstance(shared['conversation_id'], DBRef): - # Resolve the DBRef - conversation_ref = shared['conversation_id'] + query_uuid = Binary.from_uuid( + uuid.UUID(identifier), UuidRepresentation.STANDARD + ) + shared = shared_conversations_collections.find_one({"uuid": query_uuid}) + conversation_queries = [] + if ( + shared + and "conversation_id" in shared + and isinstance(shared["conversation_id"], DBRef) + ): + # Resolve the DBRef + conversation_ref = shared["conversation_id"] conversation = db.dereference(conversation_ref) - if(conversation is None): - return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404 - conversation_queries = conversation['queries'][:(shared["first_n_queries"])] + if conversation is None: + return ( + jsonify( + { + "sucess": False, + "error": "might have broken url or the conversation does not exist", + } + ), + 404, + ) + conversation_queries = conversation["queries"][ + : (shared["first_n_queries"]) + ] for query in conversation_queries: - query.pop("sources") ## avoid exposing sources + query.pop("sources") ## avoid exposing sources else: - return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404 + return ( + jsonify( + { + "sucess": False, + "error": "might have broken url or the conversation does not exist", + } + ), + 404, + ) date = conversation["_id"].generation_time.isoformat() res = { - "success":True, - "queries":conversation_queries, - "title":conversation["name"], - "timestamp":date - } - if(shared["isPromptable"] and "api_key" in shared): + "success": True, + "queries": conversation_queries, + "title": conversation["name"], + "timestamp": date, + } + if shared["isPromptable"] and "api_key" in shared: res["api_key"] = shared["api_key"] return jsonify(res), 200 except Exception as err: - print (err) - return jsonify({"success":False,"error":str(err)}),400 \ No newline at end of file + print(err) + return jsonify({"success": False, "error": str(err)}), 400 diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index 4588f73c3..3a9f99382 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useRef, useState } from 'react'; +import React, { Fragment, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -31,7 +31,7 @@ export default function Conversation() { const conversationId = useSelector(selectConversationId); const dispatch = useDispatch(); const endMessageRef = useRef(null); - const inputRef = useRef(null); + const inputRef = useRef(null); const [isDarkTheme] = useDarkTheme(); const [hasScrolledToLast, setHasScrolledToLast] = useState(true); const fetchStream = useRef(null); @@ -48,7 +48,7 @@ export default function Conversation() { }, [queries.length, queries[queries.length - 1]]); useEffect(() => { - const element = document.getElementById('inputbox') as HTMLInputElement; + const element = document.getElementById('inputbox') as HTMLTextAreaElement; if (element) { element.focus(); } @@ -119,14 +119,14 @@ export default function Conversation() { }; const handleQuestionSubmission = () => { - if (inputRef.current?.textContent && status !== 'loading') { + if (inputRef.current?.value && status !== 'loading') { if (lastQueryReturnedErr) { // update last failed query with new prompt dispatch( updateQuery({ index: queries.length - 1, query: { - prompt: inputRef.current.textContent, + prompt: inputRef.current.value, }, }), ); @@ -135,9 +135,9 @@ export default function Conversation() { isRetry: true, }); } else { - handleQuestion({ question: inputRef.current.textContent }); + handleQuestion({ question: inputRef.current.value }); } - inputRef.current.textContent = ''; + inputRef.current.value = ''; } }; @@ -191,12 +191,24 @@ export default function Conversation() { return responseView; }; - const handlePaste = (e: React.ClipboardEvent) => { - e.preventDefault(); - const text = e.clipboardData.getData('text/plain'); - inputRef.current && (inputRef.current.innerText = text); + const handleInput = () => { + if (inputRef.current) { + if (window.innerWidth < 350) inputRef.current.style.height = 'auto'; + else inputRef.current.style.height = '64px'; + inputRef.current.style.height = `${Math.min( + inputRef.current.scrollHeight, + 96, + )}px`; + } }; + useEffect(() => { + handleInput(); + window.addEventListener('resize', handleInput); + return () => { + window.removeEventListener('resize', handleInput); + }; + }, []); return (
{conversationId && ( @@ -267,22 +279,21 @@ export default function Conversation() {
-
-
+ {status === 'loading' ? ( Date: Mon, 5 Aug 2024 14:25:21 +0530 Subject: [PATCH 2/2] fix: handleInput after submission of query --- frontend/src/conversation/Conversation.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index 3a9f99382..b457dab54 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -138,6 +138,7 @@ export default function Conversation() { handleQuestion({ question: inputRef.current.value }); } inputRef.current.value = ''; + handleInput(); } };