From 07689928488079ebe57f5fa56309d25ae8cb3105 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 8 Jul 2024 03:03:46 +0530 Subject: [PATCH 01/13] add route to share and fetch public conversations --- application/api/user/routes.py | 52 +++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 62addc26..b914b61b 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -4,10 +4,12 @@ import shutil from flask import Blueprint, request, jsonify from urllib.parse import urlparse import requests +import json from pymongo import MongoClient from bson.objectid import ObjectId +from bson.binary import Binary, UuidRepresentation from werkzeug.utils import secure_filename - +from bson.dbref import DBRef from application.api.user.tasks import ingest, ingest_remote from application.core.settings import settings @@ -20,6 +22,8 @@ vectors_collection = db["vectors"] prompts_collection = db["prompts"] feedback_collection = db["feedback"] api_key_collection = db["api_keys"] +shared_conversations_collections = db["shared_conversations"] + user = Blueprint("user", __name__) current_dir = os.path.dirname( @@ -491,3 +495,49 @@ def delete_api_key(): } ) return {"status": "ok"} + + +#route to share conversation +@user.route("/api/share",methods=["POST"]) +def share_conversation(): + try: + data = request.get_json() + conversation_id = data["conversation_id"] + isPromptable = request.args.get("isPromptable").lower() == "true" + conversation = conversations_collection.find_one({"_id": ObjectId(conversation_id)}) + current_n_queries = len(conversation["queries"]) + explicit_binary = Binary.from_uuid(uuid.uuid4(), UuidRepresentation.STANDARD) + shared_conversations_collections.insert_one({ + "uuid":explicit_binary, + "conversation_id": { + "$ref":"conversations", + "$id":ObjectId(conversation_id) + } , + "isPromptable":isPromptable, + "first_n_queries":current_n_queries + }) + ## Identifier as route parameter in frontend + return jsonify({"success":True, "identifier":str(explicit_binary.as_uuid())}),201 + except Exception as 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'] + conversation = db.dereference(conversation_ref) + conversation_queries = conversation['queries'] + else: + return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404 + + return jsonify({"success":True,"queries":conversation_queries}),200 + except Exception as err: + print (err) + return jsonify({"success":False,"error":str(err)}),400 + \ No newline at end of file From edfe5e11566edba2c1b1c80282a81a2aa0e8b7e4 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 8 Jul 2024 15:59:19 +0530 Subject: [PATCH 02/13] restrict redundant sharing, add user field --- application/api/user/routes.py | 41 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index b914b61b..8db821fa 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -502,22 +502,36 @@ def delete_api_key(): def share_conversation(): try: data = request.get_json() + user = "local" + if(hasattr(data,"user")): + user = data["user"] conversation_id = data["conversation_id"] isPromptable = request.args.get("isPromptable").lower() == "true" conversation = conversations_collection.find_one({"_id": ObjectId(conversation_id)}) current_n_queries = len(conversation["queries"]) - explicit_binary = Binary.from_uuid(uuid.uuid4(), UuidRepresentation.STANDARD) - shared_conversations_collections.insert_one({ - "uuid":explicit_binary, - "conversation_id": { - "$ref":"conversations", - "$id":ObjectId(conversation_id) - } , - "isPromptable":isPromptable, - "first_n_queries":current_n_queries + pre_existing = shared_conversations_collections.find_one({ + "conversation_id":DBRef("conversations",ObjectId(conversation_id)), + "isPromptable":isPromptable, + "first_n_queries":current_n_queries }) - ## Identifier as route parameter in frontend - return jsonify({"success":True, "identifier":str(explicit_binary.as_uuid())}),201 + print("pre_existing",pre_existing) + if(pre_existing is not None): + explicit_binary = pre_existing["uuid"] + return jsonify({"success":True, "identifier":str(explicit_binary.as_uuid())}),200 + else: + explicit_binary = Binary.from_uuid(uuid.uuid4(), UuidRepresentation.STANDARD) + 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: return jsonify({"success":False,"error":str(err)}),400 @@ -532,11 +546,10 @@ def get_publicly_shared_conversations(identifier : str): # Resolve the DBRef conversation_ref = shared['conversation_id'] conversation = db.dereference(conversation_ref) - conversation_queries = conversation['queries'] + conversation_queries = conversation['queries'][:(shared["first_n_queries"])] else: return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404 - - return jsonify({"success":True,"queries":conversation_queries}),200 + return jsonify(conversation_queries),200 except Exception as err: print (err) return jsonify({"success":False,"error":str(err)}),400 From d6e59a6a0a1754adccb011c4a08960ae815f3917 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Thu, 11 Jul 2024 21:45:47 +0530 Subject: [PATCH 03/13] conversation tile: add menu, add share modal --- application/api/user/routes.py | 3 +- frontend/package-lock.json | 10 ++ frontend/src/assets/red-trash.svg | 3 + frontend/src/assets/share.svg | 3 + frontend/src/assets/three-dots.svg | 3 + .../src/conversation/ConversationTile.tsx | 154 ++++++++++++++---- .../src/modals/ShareConversationModal.tsx | 93 +++++++++++ frontend/tailwind.config.cjs | 1 + 8 files changed, 238 insertions(+), 32 deletions(-) create mode 100644 frontend/src/assets/red-trash.svg create mode 100644 frontend/src/assets/share.svg create mode 100644 frontend/src/assets/three-dots.svg create mode 100644 frontend/src/modals/ShareConversationModal.tsx diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 8db821fa..12744f0f 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -498,6 +498,7 @@ def delete_api_key(): #route to share conversation +##isPromptable should be passed through queries @user.route("/api/share",methods=["POST"]) def share_conversation(): try: @@ -526,7 +527,7 @@ def share_conversation(): "$ref":"conversations", "$id":ObjectId(conversation_id) } , - "isPromptable":isPromptable, + "isPromptable":isPromptable, "first_n_queries":current_n_queries, "user":user }) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 83e0a930..2b868da2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@reduxjs/toolkit": "^1.9.2", "@vercel/analytics": "^0.1.10", "i18next": "^23.11.5", + "i18next-browser-languagedetector": "^8.0.0", "prop-types": "^15.8.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", @@ -4194,6 +4195,15 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", diff --git a/frontend/src/assets/red-trash.svg b/frontend/src/assets/red-trash.svg new file mode 100644 index 00000000..b3331d95 --- /dev/null +++ b/frontend/src/assets/red-trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/share.svg b/frontend/src/assets/share.svg new file mode 100644 index 00000000..4699e16b --- /dev/null +++ b/frontend/src/assets/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/three-dots.svg b/frontend/src/assets/three-dots.svg new file mode 100644 index 00000000..6462b942 --- /dev/null +++ b/frontend/src/assets/three-dots.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/conversation/ConversationTile.tsx b/frontend/src/conversation/ConversationTile.tsx index b9d2301d..94731b90 100644 --- a/frontend/src/conversation/ConversationTile.tsx +++ b/frontend/src/conversation/ConversationTile.tsx @@ -5,11 +5,14 @@ import Exit from '../assets/exit.svg'; import Message from '../assets/message.svg'; import MessageDark from '../assets/message-dark.svg'; import { useDarkTheme } from '../hooks'; +import ConfirmationModal from '../modals/ConfirmationModal'; import CheckMark2 from '../assets/checkMark2.svg'; -import Trash from '../assets/trash.svg'; - +import Trash from '../assets/red-trash.svg'; +import Share from '../assets/share.svg'; +import threeDots from '../assets/three-dots.svg'; import { selectConversationId } from '../preferences/preferenceSlice'; - +import { ActiveState } from '../models/misc'; +import { ShareConversationModal } from '../modals/ShareConversationModal'; interface ConversationProps { name: string; id: string; @@ -32,13 +35,18 @@ export default function ConversationTile({ const [isDarkTheme] = useDarkTheme(); const [isEdit, setIsEdit] = useState(false); const [conversationName, setConversationsName] = useState(''); - + const [isOpen, setOpen] = useState(false); + const [isShareModalOpen, setShareModalState] = useState(false); + const [deleteModalState, setDeleteModalState] = + useState('INACTIVE'); + const menuRef = useRef(null); useEffect(() => { setConversationsName(conversation.name); }, [conversation.name]); function handleEditConversation() { setIsEdit(true); + setOpen(false); } function handleSaveConversation(changedConversation: ConversationProps) { @@ -50,6 +58,18 @@ export default function ConversationTile({ } } + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); function onClear() { setConversationsName(conversation.name); setIsEdit(false); @@ -79,7 +99,7 @@ export default function ConversationTile({ setConversationsName(e.target.value)} /> @@ -90,36 +110,108 @@ export default function ConversationTile({ )} {conversationId === conversation.id && ( -
- Edit { - event.stopPropagation(); - isEdit - ? handleSaveConversation({ +
+ {isEdit ? ( +
+ Edit { + event.stopPropagation(); + handleSaveConversation({ id: conversationId, name: conversationName, - }) - : handleEditConversation(); - }} - /> - Exit { - event.stopPropagation(); - isEdit ? onClear() : onDeleteConversation(conversation.id); - }} - /> + }); + }} + /> + Exit { + event.stopPropagation(); + onClear(); + }} + /> +
+ ) : ( + + )} + {isOpen && ( +
+ + + +
+ )}
)} + onDeleteConversation(conversation.id)} + submitLabel="Delete" + /> + {isShareModalOpen && conversationId && ( + { + setShareModalState(false); + }} + conversationId={conversationId} + /> + )}
); } diff --git a/frontend/src/modals/ShareConversationModal.tsx b/frontend/src/modals/ShareConversationModal.tsx new file mode 100644 index 00000000..886901b5 --- /dev/null +++ b/frontend/src/modals/ShareConversationModal.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Spinner from '../assets/spinner.svg'; +import Exit from '../assets/exit.svg'; +const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; + +export const ShareConversationModal = ({ + close, + conversationId, +}: { + close: () => void; + conversationId: string; +}) => { + const [identifier, setIdentifier] = useState(null); + const [isCopied, setIsCopied] = useState(false); + type StatusType = 'loading' | 'idle' | 'fetched' | 'failed'; + const [status, setStatus] = useState('idle'); + const { t } = useTranslation(); + const domain = window.location.origin; + const handleCopyKey = (url: string) => { + navigator.clipboard.writeText(url); + setIsCopied(true); + }; + const shareCoversationPublicly: (isPromptable: boolean) => void = ( + isPromptable = false, + ) => { + setStatus('loading'); + fetch(`${apiHost}/api/share?isPromptable=${isPromptable}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ conversation_id: conversationId }), + }) + .then((res) => { + console.log(res.status); + return res.json(); + }) + .then((data) => { + if (data.success && data.identifier) { + setIdentifier(data.identifier); + setStatus('fetched'); + } else setStatus('failed'); + }) + .catch((err) => setStatus('failed')); + }; + return ( +
+
+ +
+

Create a public page to share

+

+ Source document, personal information and further conversation will + remain private +

+
+ {`${domain}/shared/${ + identifier ?? '....' + }`} + {status === 'fetched' ? ( + + ) : ( + + )} +
+
+
+
+ ); +}; diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index 63f99513..938eacee 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -48,6 +48,7 @@ module.exports = { 'soap':'#D8CCF1', 'independence':'#54546D', 'philippine-yellow':'#FFC700', + 'bright-gray':'#EBEBEB' }, }, }, From 019bf013ac6990c8a7b2eb9e9ab256c014fbe0e1 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 12 Jul 2024 02:51:59 +0530 Subject: [PATCH 04/13] add css class: no-scrollbar --- frontend/src/index.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index c1d80714..7e0b3bda 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -22,6 +22,18 @@ background: #b1afaf; } +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ /* Document From 02187fed4e6ecc3156c111e31e39a9d916035c7a Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Sun, 14 Jul 2024 03:27:53 +0530 Subject: [PATCH 05/13] add timetamp in iso, remove sources --- application/api/user/routes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 12744f0f..4b6bba8f 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -547,10 +547,15 @@ def get_publicly_shared_conversations(identifier : str): # Resolve the DBRef conversation_ref = shared['conversation_id'] conversation = db.dereference(conversation_ref) - 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 else: return jsonify({"sucess":False,"error":"might have broken url or the conversation does not exist"}),404 - return jsonify(conversation_queries),200 + date = conversation["_id"].generation_time.isoformat() + return jsonify({"success":True,"queries":conversation_queries,"title":conversation["name"],"timestamp":date}),200 except Exception as err: print (err) return jsonify({"success":False,"error":str(err)}),400 From 81d7fe3fdba409806a3620e51f315bc7fb816cf5 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Sun, 14 Jul 2024 03:29:06 +0530 Subject: [PATCH 06/13] refactor App, add /shared/id page --- frontend/src/App.tsx | 87 +++++++++-- .../src/conversation/SharedConversation.tsx | 146 ++++++++++++++++++ frontend/src/hooks/index.ts | 14 +- .../src/modals/ShareConversationModal.tsx | 4 +- 4 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 frontend/src/conversation/SharedConversation.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3083f1f3..2aa8a8fc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,5 @@ import { Routes, Route } from 'react-router-dom'; +import { ReactElement, useEffect } from 'react'; import Navigation from './Navigation'; import Conversation from './conversation/Conversation'; import About from './About'; @@ -8,29 +9,93 @@ import { useMediaQuery } from './hooks'; import { useState } from 'react'; import Setting from './settings'; import './locale/i18n'; - +import SharedConversation from './conversation/SharedConversation'; +import { useDarkTheme } from './hooks'; inject(); -export default function App() { +function MainLayout({ children }: { children: ReactElement }) { const { isMobile } = useMediaQuery(); const [navOpen, setNavOpen] = useState(!isMobile); return ( -
+ <>
- - } /> - } /> - } /> - } /> - + {children}
-
+ + ); +} + +function Layout({ children }: { children: ReactElement }) { + return ( + <> +
{children}
+ + ); +} +export default function App() { + const [isDarkTheme] = useDarkTheme(); + useEffect(() => { + localStorage.setItem('selectedTheme', isDarkTheme ? 'Dark' : 'Light'); + if (isDarkTheme) { + document + .getElementById('root') + ?.classList.add('dark', 'dark:bg-raisin-black'); + } else { + document.getElementById('root')?.classList.remove('dark'); + } + }, [isDarkTheme]); + return ( + <> + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {/* default page */} + + + + } + /> + + ); } diff --git a/frontend/src/conversation/SharedConversation.tsx b/frontend/src/conversation/SharedConversation.tsx new file mode 100644 index 00000000..82e6be62 --- /dev/null +++ b/frontend/src/conversation/SharedConversation.tsx @@ -0,0 +1,146 @@ +import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import { Query } from './conversationModels'; +import ConversationBubble from './ConversationBubble'; +import { Fragment } from 'react'; +const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; +const SharedConversation = () => { + const params = useParams(); + const navigate = useNavigate(); + const { identifier } = params; //identifier is a uuid, not conversationId + const [queries, setQueries] = useState([]); + const [title, setTitle] = useState(''); + const [date, setDate] = useState(''); + + function formatISODate(isoDateStr: string) { + const date = new Date(isoDateStr); + + const monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'June', + 'July', + 'Aug', + 'Sept', + 'Oct', + 'Nov', + 'Dec', + ]; + + const month = monthNames[date.getMonth()]; + const day = date.getDate(); + const year = date.getFullYear(); + + let hours = date.getHours(); + const minutes = date.getMinutes(); + const ampm = hours >= 12 ? 'PM' : 'AM'; + + hours = hours % 12; + hours = hours ? hours : 12; + const minutesStr = minutes < 10 ? '0' + minutes : minutes; + const formattedDate = `Published ${month} ${day}, ${year} at ${hours}:${minutesStr} ${ampm}`; + return formattedDate; + } + const fetchQueris = () => { + fetch(`${apiHost}/api/shared_conversation/${identifier}`) + .then((res) => { + if (res.status === 404 || res.status === 400) navigate('/pagenotfound'); + return res.json(); + }) + .then((data) => { + if (data.success) { + setQueries(data.queries); + setTitle(data.title); + setDate(formatISODate(data.timestamp)); + } + }); + }; + + const prepResponseView = (query: Query, index: number) => { + let responseView; + if (query.response) { + responseView = ( + + ); + } else if (query.error) { + responseView = ( + + ); + } + return responseView; + }; + useEffect(() => { + fetchQueris(); + }, []); + return ( +
+
+ {queries.length > 0 && ( +
+
+
+

+ {title} +

+

+ Created with{' '} + + DocsGPT + +

+

+ {date} +

+
+
+ {queries.map((query, index) => { + return ( + + + + {prepResponseView(query, index)} + + ); + })} +
+
+
+ )} +
+ + + This is a chatbot that uses the GPT-3, Faiss and LangChain to answer + questions. + +
+
+
+ ); +}; + +export default SharedConversation; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 6248b3f8..c8258ad2 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -77,21 +77,23 @@ export function useDarkTheme() { // Set dark mode based on local storage preference if (savedMode === 'Dark') { setIsDarkTheme(true); - document.documentElement.classList.add('dark'); - document.documentElement.classList.add('dark:bg-raisin-black'); + document + .getElementById('root') + ?.classList.add('dark', 'dark:bg-raisin-black'); } else { // If no preference found, set to default (light mode) setIsDarkTheme(false); - document.documentElement.classList.remove('dark'); + document.getElementById('root')?.classList.remove('dark'); } }, []); useEffect(() => { localStorage.setItem('selectedTheme', isDarkTheme ? 'Dark' : 'Light'); if (isDarkTheme) { - document.documentElement.classList.add('dark'); - document.documentElement.classList.add('dark:bg-raisin-black'); + document + .getElementById('root') + ?.classList.add('dark', 'dark:bg-raisin-black'); } else { - document.documentElement.classList.remove('dark'); + document.getElementById('root')?.classList.remove('dark'); } }, [isDarkTheme]); //method to toggle theme diff --git a/frontend/src/modals/ShareConversationModal.tsx b/frontend/src/modals/ShareConversationModal.tsx index 886901b5..d32c5f90 100644 --- a/frontend/src/modals/ShareConversationModal.tsx +++ b/frontend/src/modals/ShareConversationModal.tsx @@ -57,13 +57,13 @@ export const ShareConversationModal = ({ remain private

- {`${domain}/shared/${ + {`${domain}/share/${ identifier ?? '....' }`} {status === 'fetched' ? (
)} )} onDeleteConversation(conversation.id)} - submitLabel="Delete" + submitLabel={t('convTile.delete')} /> {isShareModalOpen && conversationId && ( { const [queries, setQueries] = useState([]); const [title, setTitle] = useState(''); const [date, setDate] = useState(''); - + const { t } = useTranslation(); function formatISODate(isoDateStr: string) { const date = new Date(isoDateStr); @@ -97,7 +98,7 @@ const SharedConversation = () => { {title}

- Created with{' '} + {t('sharedConv.subtitle')}{' '} DocsGPT @@ -131,11 +132,10 @@ const SharedConversation = () => { onClick={() => navigate('/')} className="w-fit rounded-full bg-purple-30 p-4 text-white shadow-xl transition-colors duration-200 hover:bg-purple-taupe" > - Get Started with DocsGPT + {t('sharedConv.button')} - This is a chatbot that uses the GPT-3, Faiss and LangChain to answer - questions. + {t('sharedConv.meta')} diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index e59fbedc..e914693a 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -103,6 +103,22 @@ "deleteConv": { "confirm": "Are you sure you want to delete all the conversations?", "delete": "Delete" + }, + "shareConv": { + "label": "Create a public page to share", + "note": "Source document, personal information and further conversation will remain private", + "create": "Create" } + }, + "sharedConv": { + "subtitle": "Created with", + "button": "Get Started with DocsGPT", + "meta": "This is a chatbot that uses the GPT-3, Faiss and LangChain to answer questions." + }, + "convTile": { + "share": "Share", + "delete": "Delete", + "rename": "Rename", + "deleteWarning": "Are you sure you want to delete this conversation?" } } diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json index 70966e4b..78a4aa29 100644 --- a/frontend/src/locale/es.json +++ b/frontend/src/locale/es.json @@ -103,6 +103,22 @@ "deleteConv": { "confirm": "¿Está seguro de que desea eliminar todas las conversaciones?", "delete": "Eliminar" + }, + "shareConv": { + "label": "Crear una página pública para compartir", + "note": "El documento original, la información personal y las conversaciones posteriores permanecerán privadas", + "create": "Crear" } + }, + "sharedConv": { + "subtitle": "Creado con", + "button": "Comienza con DocsGPT", + "meta": "Este es un chatbot que utiliza GPT-3, Faiss y LangChain para responder preguntas." + }, + "convTile": { + "share": "Compartir", + "delete": "Eliminar", + "rename": "Renombrar", + "deleteWarning": "¿Está seguro de que desea eliminar esta conversación?" } } diff --git a/frontend/src/locale/jp.json b/frontend/src/locale/jp.json index a025a17a..697b137f 100644 --- a/frontend/src/locale/jp.json +++ b/frontend/src/locale/jp.json @@ -103,6 +103,22 @@ "deleteConv": { "confirm": "すべての会話を削除してもよろしいですか?", "delete": "削除" + }, + "shareConv": { + "label": "共有ページを作成して共有する", + "note": "ソースドキュメント、個人情報、および以降の会話は非公開のままになります", + "create": "作成" } + }, + "sharedConv": { + "subtitle": "作成者", + "button": "DocsGPT を始める", + "meta": "GPT-3、Faiss、および LangChain を使用して質問に答えるチャットボットです" + }, + "convTile": { + "share": "共有", + "delete": "削除", + "rename": "名前変更", + "deleteWarning": "この会話を削除してもよろしいですか?" } } diff --git a/frontend/src/locale/zh.json b/frontend/src/locale/zh.json index f686ea20..8161f740 100644 --- a/frontend/src/locale/zh.json +++ b/frontend/src/locale/zh.json @@ -103,6 +103,22 @@ "deleteConv": { "confirm": "您确定要删除所有对话吗?", "delete": "删除" + }, + "shareConv": { + "label": "创建用于分享的公共页面", + "note": "源文档、个人信息和后续对话将保持私密", + "create": "创建" } + }, + "sharedConv": { + "subtitle": "使用创建", + "button": "开始使用 DocsGPT", + "meta": "这是一个使用 GPT-3、Faiss 和 LangChain 来回答问题的聊天机器人。" + }, + "convTile": { + "share": "分享", + "delete": "删除", + "rename": "重命名", + "deleteWarning": "您确定要删除此对话吗?" } } diff --git a/frontend/src/modals/ShareConversationModal.tsx b/frontend/src/modals/ShareConversationModal.tsx index d32c5f90..a6ab1e0f 100644 --- a/frontend/src/modals/ShareConversationModal.tsx +++ b/frontend/src/modals/ShareConversationModal.tsx @@ -51,11 +51,8 @@ export const ShareConversationModal = ({
-

Create a public page to share

-

- Source document, personal information and further conversation will - remain private -

+

{t('modals.shareConv.label')}

+

{t('modals.shareConv.note')}

{`${domain}/share/${ identifier ?? '....' @@ -76,7 +73,7 @@ export const ShareConversationModal = ({ shareCoversationPublicly(false); }} > - Create + {t('modals.shareConv.create')} {status === 'loading' && ( Date: Mon, 15 Jul 2024 02:55:38 +0530 Subject: [PATCH 08/13] feedback visible conditioned, update meta info in shared --- frontend/src/App.tsx | 6 +- .../src/conversation/ConversationBubble.tsx | 122 +++++++++--------- .../src/conversation/SharedConversation.tsx | 94 +++++++------- frontend/src/locale/en.json | 2 +- frontend/src/locale/es.json | 2 +- frontend/src/locale/jp.json | 2 +- frontend/src/locale/zh.json | 2 +- 7 files changed, 114 insertions(+), 116 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2aa8a8fc..38694182 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,11 +33,7 @@ function MainLayout({ children }: { children: ReactElement }) { } function Layout({ children }: { children: ReactElement }) { - return ( - <> -
{children}
- - ); + return
{children}
; } export default function App() { const [isDarkTheme] = useDarkTheme(); diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index fc5d469e..4adfbf94 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -1,6 +1,6 @@ import { forwardRef, useState } from 'react'; import Avatar from '../components/Avatar'; -import CoppyButton from '../components/CopyButton'; +import CopyButton from '../components/CopyButton'; import remarkGfm from 'remark-gfm'; import { FEEDBACK, MESSAGE_TYPE } from './conversationModels'; import classes from './ConversationBubble.module.css'; @@ -103,7 +103,7 @@ const ConversationBubble = forwardRef< className={`absolute right-3 top-3 lg:invisible ${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} `} > -
@@ -215,78 +215,82 @@ const ConversationBubble = forwardRef< ${type !== 'ERROR' ? 'group-hover:lg:visible' : ''}`} >
- +
-
-
+ {handleFeedback && ( + <>
- +
+ { - handleFeedback?.('LIKE'); - setIsLikeClicked(true); - setIsDislikeClicked(false); - }} - onMouseEnter={() => setIsLikeHovered(true)} - onMouseLeave={() => setIsLikeHovered(false)} - > + onClick={() => { + handleFeedback?.('LIKE'); + setIsLikeClicked(true); + setIsDislikeClicked(false); + }} + onMouseEnter={() => setIsLikeHovered(true)} + onMouseLeave={() => setIsLikeHovered(false)} + > +
+
-
- -
-
- { - handleFeedback?.('DISLIKE'); - setIsDislikeClicked(true); - setIsLikeClicked(false); - }} - onMouseEnter={() => setIsDislikeHovered(true)} - onMouseLeave={() => setIsDislikeHovered(false)} - > +
+
+ { + handleFeedback?.('DISLIKE'); + setIsDislikeClicked(true); + setIsLikeClicked(false); + }} + onMouseEnter={() => setIsDislikeHovered(true)} + onMouseLeave={() => setIsDislikeHovered(false)} + > +
+
-
-
+ + )} {sources && openSource !== null && sources[openSource] && ( diff --git a/frontend/src/conversation/SharedConversation.tsx b/frontend/src/conversation/SharedConversation.tsx index ff9ab3fb..1b9fb781 100644 --- a/frontend/src/conversation/SharedConversation.tsx +++ b/frontend/src/conversation/SharedConversation.tsx @@ -87,58 +87,56 @@ const SharedConversation = () => { useEffect(() => { fetchQueris(); }, []); - return ( -
-
- {queries.length > 0 && ( -
-
-
-

- {title} -

-

- {t('sharedConv.subtitle')}{' '} - - DocsGPT - -

-

- {date} -

-
-
- {queries.map((query, index) => { - return ( - - - {prepResponseView(query, index)} - - ); - })} -
-
+ return ( +
+
+
+
+

+ {title} +

+

+ {t('sharedConv.subtitle')}{' '} + + DocsGPT + +

+

+ {date} +

+
+
+ {queries?.map((query, index) => { + return ( + + + + {prepResponseView(query, index)} + + ); + })}
- )} -
- - - {t('sharedConv.meta')} -
+ +
+ + + {t('sharedConv.meta')} + +
); }; diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index e914693a..0f5aa708 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -113,7 +113,7 @@ "sharedConv": { "subtitle": "Created with", "button": "Get Started with DocsGPT", - "meta": "This is a chatbot that uses the GPT-3, Faiss and LangChain to answer questions." + "meta": "DocsGPT uses GenAI, please review critical information using sources." }, "convTile": { "share": "Share", diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json index 78a4aa29..b91b67e9 100644 --- a/frontend/src/locale/es.json +++ b/frontend/src/locale/es.json @@ -113,7 +113,7 @@ "sharedConv": { "subtitle": "Creado con", "button": "Comienza con DocsGPT", - "meta": "Este es un chatbot que utiliza GPT-3, Faiss y LangChain para responder preguntas." + "meta": "DocsGPT utiliza GenAI, por favor revise la información crítica utilizando fuentes." }, "convTile": { "share": "Compartir", diff --git a/frontend/src/locale/jp.json b/frontend/src/locale/jp.json index 697b137f..75e62589 100644 --- a/frontend/src/locale/jp.json +++ b/frontend/src/locale/jp.json @@ -113,7 +113,7 @@ "sharedConv": { "subtitle": "作成者", "button": "DocsGPT を始める", - "meta": "GPT-3、Faiss、および LangChain を使用して質問に答えるチャットボットです" + "meta": "DocsGPT は GenAI を使用しています、情報源を使用して重要情報を確認してください。" }, "convTile": { "share": "共有", diff --git a/frontend/src/locale/zh.json b/frontend/src/locale/zh.json index 8161f740..e46fe254 100644 --- a/frontend/src/locale/zh.json +++ b/frontend/src/locale/zh.json @@ -113,7 +113,7 @@ "sharedConv": { "subtitle": "使用创建", "button": "开始使用 DocsGPT", - "meta": "这是一个使用 GPT-3、Faiss 和 LangChain 来回答问题的聊天机器人。" + "meta": "DocsGPT 使用 GenAI,请使用资源查看关键信息。" }, "convTile": { "share": "分享", From 7b8458b47de2cf708dac742e84972af0c582e39e Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 15 Jul 2024 05:00:13 +0530 Subject: [PATCH 09/13] fix layout --- frontend/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 38694182..dff27112 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -20,7 +20,7 @@ function MainLayout({ children }: { children: ReactElement }) { <>
Date: Mon, 15 Jul 2024 05:13:28 +0530 Subject: [PATCH 10/13] minor fix --- frontend/src/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dff27112..99a63cbc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,10 +17,10 @@ function MainLayout({ children }: { children: ReactElement }) { const { isMobile } = useMediaQuery(); const [navOpen, setNavOpen] = useState(!isMobile); return ( - <> +
{children}
- +
); } From 1107a2f2bcdb2bff164b42d6cc8a42eb703662c6 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Mon, 15 Jul 2024 17:56:23 +0530 Subject: [PATCH 11/13] refactor App.tsx: better convention --- frontend/src/App.tsx | 58 ++++--------------- frontend/src/PageNotFound.tsx | 6 +- .../src/conversation/SharedConversation.tsx | 2 +- 3 files changed, 15 insertions(+), 51 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 99a63cbc..05792187 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ import { Routes, Route } from 'react-router-dom'; -import { ReactElement, useEffect } from 'react'; +import { useEffect } from 'react'; import Navigation from './Navigation'; import Conversation from './conversation/Conversation'; import About from './About'; @@ -9,11 +9,12 @@ import { useMediaQuery } from './hooks'; import { useState } from 'react'; import Setting from './settings'; import './locale/i18n'; +import { Outlet } from 'react-router-dom'; import SharedConversation from './conversation/SharedConversation'; import { useDarkTheme } from './hooks'; inject(); -function MainLayout({ children }: { children: ReactElement }) { +function MainLayout() { const { isMobile } = useMediaQuery(); const [navOpen, setNavOpen] = useState(!isMobile); return ( @@ -26,15 +27,12 @@ function MainLayout({ children }: { children: ReactElement }) { : 'ml-0 md:ml-16' }`} > - {children} +
); } -function Layout({ children }: { children: ReactElement }) { - return
{children}
; -} export default function App() { const [isDarkTheme] = useDarkTheme(); useEffect(() => { @@ -50,47 +48,13 @@ export default function App() { return ( <> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - {/* default page */} - - - - } - /> + }> + } /> + } /> + } /> + + } /> + } /> ); diff --git a/frontend/src/PageNotFound.tsx b/frontend/src/PageNotFound.tsx index eaea5cc9..0b86d7c1 100644 --- a/frontend/src/PageNotFound.tsx +++ b/frontend/src/PageNotFound.tsx @@ -2,11 +2,11 @@ import { Link } from 'react-router-dom'; export default function PageNotFound() { return ( -
-

+

+

404

The page you are looking for does not exist.

-

diff --git a/frontend/src/conversation/SharedConversation.tsx b/frontend/src/conversation/SharedConversation.tsx index 1b9fb781..e365c6f0 100644 --- a/frontend/src/conversation/SharedConversation.tsx +++ b/frontend/src/conversation/SharedConversation.tsx @@ -89,7 +89,7 @@ const SharedConversation = () => { }, []); return ( -
+
From e2b76d9c2963640a9a846f7dfe831c97d859ff1b Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Tue, 16 Jul 2024 02:09:36 +0530 Subject: [PATCH 12/13] feat(share): share btn above conversations --- frontend/src/conversation/Conversation.tsx | 31 +++++++++++++++++++ .../src/modals/ShareConversationModal.tsx | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index 487e8ea7..115abcd8 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -11,6 +11,7 @@ import { selectStatus, updateQuery, } from './conversationSlice'; +import { selectConversationId } from '../preferences/preferenceSlice'; import Send from './../assets/send.svg'; import SendDark from './../assets/send_dark.svg'; import Spinner from './../assets/spinner.svg'; @@ -20,9 +21,13 @@ import { sendFeedback } from './conversationApi'; import { useTranslation } from 'react-i18next'; import ArrowDown from './../assets/arrow-down.svg'; import RetryIcon from '../components/RetryIcon'; +import ShareIcon from '../assets/share.svg'; +import { ShareConversationModal } from '../modals/ShareConversationModal'; + export default function Conversation() { const queries = useSelector(selectQueries); const status = useSelector(selectStatus); + const conversationId = useSelector(selectConversationId); const dispatch = useDispatch(); const endMessageRef = useRef(null); const inputRef = useRef(null); @@ -31,6 +36,7 @@ export default function Conversation() { const fetchStream = useRef(null); const [eventInterrupt, setEventInterrupt] = useState(false); const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false); + const [isShareModalOpen, setShareModalState] = useState(false); const { t } = useTranslation(); const handleUserInterruption = () => { @@ -192,6 +198,31 @@ export default function Conversation() { return (
+ {conversationId && ( + <> + + {isShareModalOpen && ( + { + setShareModalState(false); + }} + conversationId={conversationId} + /> + )} + + )}
) : (