fix: changed query box from div to textarea for handling paste, autoscroll

This commit is contained in:
Siddhant Rai
2024-08-05 14:17:21 +05:30
parent 181cb1b1bd
commit 59caf381f7
3 changed files with 213 additions and 135 deletions

View File

@@ -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
##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
})
## 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
return (
jsonify(
{"success": True, "identifier": str(explicit_binary.as_uuid())}
),
201,
)
#route to get publicly shared conversations
@user.route("/api/shared_conversation/<string:identifier>",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']
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"])]
for query in conversation_queries:
query.pop("sources") ## avoid exposing sources
##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:
return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404
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/<string:identifier>", 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"]
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"])
]
for query in conversation_queries:
query.pop("sources") ## avoid exposing sources
else:
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
print(err)
return jsonify({"success": False, "error": str(err)}), 400

View File

@@ -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<AppDispatch>();
const endMessageRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const [isDarkTheme] = useDarkTheme();
const [hasScrolledToLast, setHasScrolledToLast] = useState(true);
const fetchStream = useRef<any>(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 (
<div className="flex h-screen flex-col gap-7 pb-2">
{conversationId && (
@@ -267,22 +279,21 @@ export default function Conversation() {
</div>
<div className="flex w-11/12 flex-col items-end self-center rounded-2xl bg-opacity-0 pb-1 sm:w-8/12">
<div className="flex h-full w-full items-center rounded-[40px] border border-silver bg-white py-1 dark:bg-raisin-black">
<div
<div className="flex w-full items-center rounded-[40px] border border-silver bg-white py-1 dark:bg-raisin-black">
<textarea
id="inputbox"
ref={inputRef}
tabIndex={1}
placeholder={t('inputPlaceholder')}
contentEditable
onPaste={handlePaste}
className={`inputbox-style max-h-24 w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-white pt-5 pb-[22px] text-base leading-tight opacity-100 focus:outline-none dark:bg-raisin-black dark:text-bright-gray`}
className={`inputbox-style h-16 w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-white pt-5 pb-[22px] text-base leading-tight opacity-100 focus:outline-none dark:bg-raisin-black dark:text-bright-gray`}
onInput={handleInput}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleQuestionSubmission();
}
}}
></div>
></textarea>
{status === 'loading' ? (
<img
src={isDarkTheme ? SpinnerDark : Spinner}

View File

@@ -424,7 +424,8 @@ template {
width: 0;
}
.inputbox-style[contenteditable] {
.inputbox-style {
resize: none;
padding-left: 36px;
padding-right: 36px;
}