From 7794129929f2e8d9547702330dbd2638fc6ccf97 Mon Sep 17 00:00:00 2001 From: "devendra.parihar" Date: Tue, 1 Oct 2024 22:03:10 +0530 Subject: [PATCH 01/16] new: added ExcelParser(tested) to read .xlsx files --- application/parser/file/bulk.py | 3 +- application/parser/file/tabular_parser.py | 65 +++++++++++++++++++++++ application/requirements.txt | 1 + application/vectorstore/faiss.py | 43 +++++++-------- frontend/src/locale/en.json | 2 +- frontend/src/upload/Upload.tsx | 1 + mlb_teams_2012.csv:Zone.Identifier | 3 ++ test.py | 25 +++++++++ 8 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 mlb_teams_2012.csv:Zone.Identifier create mode 100644 test.py diff --git a/application/parser/file/bulk.py b/application/parser/file/bulk.py index aec6c8c1..79fc2c45 100644 --- a/application/parser/file/bulk.py +++ b/application/parser/file/bulk.py @@ -10,13 +10,14 @@ from application.parser.file.epub_parser import EpubParser from application.parser.file.html_parser import HTMLParser from application.parser.file.markdown_parser import MarkdownParser from application.parser.file.rst_parser import RstParser -from application.parser.file.tabular_parser import PandasCSVParser +from application.parser.file.tabular_parser import PandasCSVParser,ExcelParser from application.parser.schema.base import Document DEFAULT_FILE_EXTRACTOR: Dict[str, BaseParser] = { ".pdf": PDFParser(), ".docx": DocxParser(), ".csv": PandasCSVParser(), + ".xlsx":ExcelParser(), ".epub": EpubParser(), ".md": MarkdownParser(), ".rst": RstParser(), diff --git a/application/parser/file/tabular_parser.py b/application/parser/file/tabular_parser.py index 81355ae0..b2dbd193 100644 --- a/application/parser/file/tabular_parser.py +++ b/application/parser/file/tabular_parser.py @@ -113,3 +113,68 @@ class PandasCSVParser(BaseParser): return (self._row_joiner).join(text_list) else: return text_list + + +class ExcelParser(BaseParser): + r"""Excel (.xlsx) parser. + + Parses Excel files using Pandas `read_excel` function. + If special parameters are required, use the `pandas_config` dict. + + Args: + concat_rows (bool): whether to concatenate all rows into one document. + If set to False, a Document will be created for each row. + True by default. + + col_joiner (str): Separator to use for joining cols per row. + Set to ", " by default. + + row_joiner (str): Separator to use for joining each row. + Only used when `concat_rows=True`. + Set to "\n" by default. + + pandas_config (dict): Options for the `pandas.read_excel` function call. + Refer to https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html + for more information. + Set to empty dict by default, this means pandas will try to figure + out the table structure on its own. + + """ + + def __init__( + self, + *args: Any, + concat_rows: bool = True, + col_joiner: str = ", ", + row_joiner: str = "\n", + pandas_config: dict = {}, + **kwargs: Any + ) -> None: + """Init params.""" + super().__init__(*args, **kwargs) + self._concat_rows = concat_rows + self._col_joiner = col_joiner + self._row_joiner = row_joiner + self._pandas_config = pandas_config + + def _init_parser(self) -> Dict: + """Init parser.""" + return {} + + def parse_file(self, file: Path, errors: str = "ignore") -> Union[str, List[str]]: + """Parse file.""" + try: + import pandas as pd + except ImportError: + raise ValueError("pandas module is required to read Excel files.") + + df = pd.read_excel(file, **self._pandas_config) + + text_list = df.apply( + lambda row: (self._col_joiner).join(row.astype(str).tolist()), axis=1 + ).tolist() + + if self._concat_rows: + return (self._row_joiner).join(text_list) + else: + return text_list \ No newline at end of file diff --git a/application/requirements.txt b/application/requirements.txt index d7621cfd..6a57dd12 100644 --- a/application/requirements.txt +++ b/application/requirements.txt @@ -49,6 +49,7 @@ openapi3-parser==1.1.18 orjson==3.10.7 packaging==24.1 pandas==2.2.3 +openpyxl==3.1.5 pathable==0.4.3 pillow==10.4.0 portalocker==2.10.1 diff --git a/application/vectorstore/faiss.py b/application/vectorstore/faiss.py index a8839cd2..afa55db9 100644 --- a/application/vectorstore/faiss.py +++ b/application/vectorstore/faiss.py @@ -3,30 +3,27 @@ from application.vectorstore.base import BaseVectorStore from application.core.settings import settings import os -def get_vectorstore(path): +def get_vectorstore(path: str) -> str: if path: - vectorstore = "indexes/"+path - vectorstore = os.path.join("application", vectorstore) + vectorstore = os.path.join("application", "indexes", path) else: vectorstore = os.path.join("application") - return vectorstore class FaissStore(BaseVectorStore): - - def __init__(self, source_id, embeddings_key, docs_init=None): + def __init__(self, source_id: str, embeddings_key: str, docs_init=None): super().__init__() self.path = get_vectorstore(source_id) embeddings = self._get_embeddings(settings.EMBEDDINGS_NAME, embeddings_key) - if docs_init: - self.docsearch = FAISS.from_documents( - docs_init, embeddings - ) - else: - self.docsearch = FAISS.load_local( - self.path, embeddings, - allow_dangerous_deserialization=True - ) + + try: + if docs_init: + self.docsearch = FAISS.from_documents(docs_init, embeddings) + else: + self.docsearch = FAISS.load_local(self.path, embeddings, allow_dangerous_deserialization=True) + except Exception: + raise + self.assert_embedding_dimensions(embeddings) def search(self, *args, **kwargs): @@ -42,16 +39,12 @@ class FaissStore(BaseVectorStore): return self.docsearch.delete(*args, **kwargs) def assert_embedding_dimensions(self, embeddings): - """ - Check that the word embedding dimension of the docsearch index matches - the dimension of the word embeddings used - """ + """Check that the word embedding dimension of the docsearch index matches the dimension of the word embeddings used.""" if settings.EMBEDDINGS_NAME == "huggingface_sentence-transformers/all-mpnet-base-v2": - try: - word_embedding_dimension = embeddings.dimension - except AttributeError as e: - raise AttributeError("'dimension' attribute not found in embeddings instance. Make sure the embeddings object is properly initialized.") from e + word_embedding_dimension = getattr(embeddings, 'dimension', None) + if word_embedding_dimension is None: + raise AttributeError("'dimension' attribute not found in embeddings instance.") + docsearch_index_dimension = self.docsearch.index.d if word_embedding_dimension != docsearch_index_dimension: - raise ValueError(f"Embedding dimension mismatch: embeddings.dimension ({word_embedding_dimension}) " + - f"!= docsearch index dimension ({docsearch_index_dimension})") \ No newline at end of file + raise ValueError(f"Embedding dimension mismatch: embeddings.dimension ({word_embedding_dimension}) != docsearch index dimension ({docsearch_index_dimension})") diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index 645703a2..7ba8add1 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -77,7 +77,7 @@ "remote": "Remote", "name": "Name", "choose": "Choose Files", - "info": "Please upload .pdf, .txt, .rst, .csv, .docx, .md, .zip limited to 25mb", + "info": "Please upload .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .zip limited to 25mb", "uploadedFiles": "Uploaded Files", "cancel": "Cancel", "train": "Train", diff --git a/frontend/src/upload/Upload.tsx b/frontend/src/upload/Upload.tsx index b898e4b6..cf56a21b 100644 --- a/frontend/src/upload/Upload.tsx +++ b/frontend/src/upload/Upload.tsx @@ -270,6 +270,7 @@ function Upload({ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'], 'text/csv': ['.csv'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'], }, }); diff --git a/mlb_teams_2012.csv:Zone.Identifier b/mlb_teams_2012.csv:Zone.Identifier new file mode 100644 index 00000000..1bf0b28e --- /dev/null +++ b/mlb_teams_2012.csv:Zone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +HostUrl=https://github.com/ diff --git a/test.py b/test.py new file mode 100644 index 00000000..59d75188 --- /dev/null +++ b/test.py @@ -0,0 +1,25 @@ +from pathlib import Path +from application.parser.file.tabular_parser import ExcelParser,PandasCSVParser + +# Define the path to the .xlsx file +file_path = Path("/home/dev523/DocsGPT/Ledgers in Default Template.xlsx") +parser = ExcelParser(concat_rows=True, pandas_config={}) + +# Initialize the ExcelParser +# file_path = Path("/home/dev523/DocsGPT/mlb_teams_2012.csv") +# parser = PandasCSVParser(concat_rows=True, pandas_config={}) + + + +# Initialize the parser configuration (this can be customized if needed) +parser.init_parser() + +# Check if the parser config is set (this is optional) +if parser.parser_config_set: + print("Parser config has been set.") + +# Parse the Excel file +parsed_data = parser.parse_file(file_path) +print(parsed_data) + + From e9a7722915aefafd58d9077a8b6536c6150ce495 Mon Sep 17 00:00:00 2001 From: "devendra.parihar" Date: Tue, 1 Oct 2024 22:11:17 +0530 Subject: [PATCH 02/16] new: added ExcelParser(tested) to read .xlsx files --- test.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index 59d75188..00000000 --- a/test.py +++ /dev/null @@ -1,25 +0,0 @@ -from pathlib import Path -from application.parser.file.tabular_parser import ExcelParser,PandasCSVParser - -# Define the path to the .xlsx file -file_path = Path("/home/dev523/DocsGPT/Ledgers in Default Template.xlsx") -parser = ExcelParser(concat_rows=True, pandas_config={}) - -# Initialize the ExcelParser -# file_path = Path("/home/dev523/DocsGPT/mlb_teams_2012.csv") -# parser = PandasCSVParser(concat_rows=True, pandas_config={}) - - - -# Initialize the parser configuration (this can be customized if needed) -parser.init_parser() - -# Check if the parser config is set (this is optional) -if parser.parser_config_set: - print("Parser config has been set.") - -# Parse the Excel file -parsed_data = parser.parse_file(file_path) -print(parsed_data) - - From 09a15e2e59b119055ca31883b9da9b402c333c25 Mon Sep 17 00:00:00 2001 From: Devendra Parihar <54232149+Devparihar5@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:59:40 +0530 Subject: [PATCH 03/16] Delete mlb_teams_2012.csv:Zone.Identifier --- mlb_teams_2012.csv:Zone.Identifier | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 mlb_teams_2012.csv:Zone.Identifier diff --git a/mlb_teams_2012.csv:Zone.Identifier b/mlb_teams_2012.csv:Zone.Identifier deleted file mode 100644 index 1bf0b28e..00000000 --- a/mlb_teams_2012.csv:Zone.Identifier +++ /dev/null @@ -1,3 +0,0 @@ -[ZoneTransfer] -ZoneId=3 -HostUrl=https://github.com/ From d897315355d0e7ec5333d2e22f0f22e7efc5f29a Mon Sep 17 00:00:00 2001 From: Piyush Tilokani <2021.piyush.tilokani@ves.ac.in> Date: Sat, 5 Oct 2024 17:26:58 +0530 Subject: [PATCH 04/16] Make logo clickable --- frontend/src/Navigation.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index 7d3333ee..c9540e23 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -225,8 +225,10 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'} >
- -

DocsGPT

+ + +

DocsGPT

+
-
+
{tabs.map((tab, index) => ( +
+
+ {tabs.map((tab, index) => ( + + ))} +
+
+ +
+
+ ); +}; + +export default SettingsBar; diff --git a/frontend/src/settings/index.tsx b/frontend/src/settings/index.tsx index 46687684..ea3d4428 100644 --- a/frontend/src/settings/index.tsx +++ b/frontend/src/settings/index.tsx @@ -3,8 +3,7 @@ import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import userService from '../api/services/userService'; -import ArrowLeft from '../assets/arrow-left.svg'; -import ArrowRight from '../assets/arrow-right.svg'; +import SettingsBar from '../components/SettingsBar'; import i18n from '../locale/i18n'; import { Doc } from '../models/misc'; import { @@ -21,13 +20,6 @@ import Widgets from './Widgets'; export default function Settings() { const dispatch = useDispatch(); const { t } = useTranslation(); - const tabs = [ - t('settings.general.label'), - t('settings.documents.label'), - t('settings.apiKeys.label'), - t('settings.analytics.label'), - t('settings.logs.label'), - ]; const [activeTab, setActiveTab] = React.useState(t('settings.general.label')); const [widgetScreenshot, setWidgetScreenshot] = React.useState( null, @@ -61,39 +53,7 @@ export default function Settings() {

{t('settings.label')}

-
-
- -
-
- {tabs.map((tab, index) => ( - - ))} -
-
- -
-
+ {renderActiveTab()} {/* {activeTab === 'Widgets' && ( @@ -105,13 +65,6 @@ export default function Settings() { ); - function scrollTabs(direction: number) { - const container = document.querySelector('.flex-nowrap'); - if (container) { - container.scrollLeft += direction * 100; // Adjust the scroll amount as needed - } - } - function renderActiveTab() { switch (activeTab) { case t('settings.general.label'): From 989d6eee0f53440dfb45bbff32f5e60a044f4c2b Mon Sep 17 00:00:00 2001 From: shatanikmahanty Date: Sun, 6 Oct 2024 11:56:26 +0530 Subject: [PATCH 10/16] Fix: Minimise sidebar on nav item click on Mobile --- frontend/src/Navigation.tsx | 57 +++++++++++++++------- frontend/src/components/SourceDropdown.tsx | 3 ++ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index 87cbbe51..8f732515 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -199,22 +199,24 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { menu toggle )}
-
+
{ + if (isMobile) { + setNavOpen(!navOpen); + } + }}>

DocsGPT

@@ -227,15 +229,17 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { menu toggle
{ + if (isMobile) { + setNavOpen(!navOpen); + } dispatch(setConversation([])); dispatch( updateConversationId({ @@ -244,8 +248,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { ); }} className={({ isActive }) => - `${ - isActive ? 'bg-gray-3000 dark:bg-transparent' : '' + `${isActive ? 'bg-gray-3000 dark:bg-transparent' : '' } group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray hover:bg-gray-3000 dark:border-purple-taupe dark:text-white dark:hover:bg-transparent` } > @@ -293,21 +296,35 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { isDocsListOpen={isDocsListOpen} setIsDocsListOpen={setIsDocsListOpen} handleDeleteClick={handleDeleteClick} + handlePostDocumentSelect={(doc) => { + if (isMobile) { + setNavOpen(!navOpen) + } + }} /> setUploadModalState('ACTIVE')} - > + onClick={() => { + setUploadModalState('ACTIVE') + if (isMobile) { + setNavOpen(!navOpen); + } + } + }>

{t('sourceDocs')}

{ + if (isMobile) { + setNavOpen(!navOpen); + } + }} to="/settings" className={({ isActive }) => - `my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${ - isActive ? 'bg-gray-3000 dark:bg-transparent' : '' + `my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${isActive ? 'bg-gray-3000 dark:bg-transparent' : '' }` } > @@ -323,10 +340,14 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
{ + if (isMobile) { + setNavOpen(!navOpen); + } + }} to="/about" className={({ isActive }) => - `my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${ - isActive ? 'bg-gray-3000 dark:bg-[#28292E]' : '' + `my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${isActive ? 'bg-gray-3000 dark:bg-[#28292E]' : '' }` } > diff --git a/frontend/src/components/SourceDropdown.tsx b/frontend/src/components/SourceDropdown.tsx index d5146da5..efce3467 100644 --- a/frontend/src/components/SourceDropdown.tsx +++ b/frontend/src/components/SourceDropdown.tsx @@ -11,6 +11,7 @@ type Props = { isDocsListOpen: boolean; setIsDocsListOpen: React.Dispatch>; handleDeleteClick: any; + handlePostDocumentSelect: any; }; function SourceDropdown({ @@ -20,6 +21,7 @@ function SourceDropdown({ setIsDocsListOpen, isDocsListOpen, handleDeleteClick, + handlePostDocumentSelect, // Callback function fired after a document is selected }: Props) { const dispatch = useDispatch(); const { t } = useTranslation(); @@ -85,6 +87,7 @@ function SourceDropdown({ onClick={() => { dispatch(setSelectedDocs(option)); setIsDocsListOpen(false); + handlePostDocumentSelect(option); }} > Date: Sun, 6 Oct 2024 18:48:16 +0530 Subject: [PATCH 11/16] Adjust positioning of logo --- frontend/src/Navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index c9540e23..e40729a6 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -225,7 +225,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'} >
- +

DocsGPT

From 27f97bc55d62873374500bf446b31a24d52bac96 Mon Sep 17 00:00:00 2001 From: shatanikmahanty Date: Mon, 7 Oct 2024 10:12:18 +0530 Subject: [PATCH 12/16] Fix: Close nav bar on conversation click --- frontend/src/Navigation.tsx | 1 + frontend/src/conversation/ConversationTile.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index ceae6716..48bb73bd 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -280,6 +280,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { key={conversation.id} conversation={conversation} selectConversation={(id) => handleConversationClick(id)} + onCoversationClick={() => {if (isMobile) { setNavOpen(false) }}} onDeleteConversation={(id) => handleDeleteConversation(id)} onSave={(conversation) => updateConversationName(conversation) diff --git a/frontend/src/conversation/ConversationTile.tsx b/frontend/src/conversation/ConversationTile.tsx index 3a0b85e4..5db4ceab 100644 --- a/frontend/src/conversation/ConversationTile.tsx +++ b/frontend/src/conversation/ConversationTile.tsx @@ -22,6 +22,7 @@ interface ConversationProps { interface ConversationTileProps { conversation: ConversationProps; selectConversation: (arg1: string) => void; + onCoversationClick: () => void; //Callback to handle click on conversation tile regardless of selected or not onDeleteConversation: (arg1: string) => void; onSave: ({ name, id }: ConversationProps) => void; } @@ -29,6 +30,7 @@ interface ConversationTileProps { export default function ConversationTile({ conversation, selectConversation, + onCoversationClick, onDeleteConversation, onSave, }: ConversationTileProps) { @@ -90,6 +92,7 @@ export default function ConversationTile({ setIsHovered(false); }} onClick={() => { + onCoversationClick(); conversationId !== conversation.id && selectConversation(conversation.id); }} From 1f9c167bd23af0b002e1ddefb5ab323a8e1d1787 Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 7 Oct 2024 12:54:11 +0100 Subject: [PATCH 13/16] fix: hard breaking long string words and intelligently breaking or skipping hyphenated/whitespace to next line for good reading XP --- frontend/src/conversation/ConversationBubble.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index 543699ed..2463d122 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -59,7 +59,12 @@ const ConversationBubble = forwardRef< className={`flex flex-row-reverse self-end flex-wrap ${className}`} > -
+
{message}
From c8648101a7e6b8059bd7fc6be476652091deee2c Mon Sep 17 00:00:00 2001 From: utin-francis-peter Date: Mon, 7 Oct 2024 15:57:47 +0100 Subject: [PATCH 14/16] build: style key fix --- frontend/src/conversation/ConversationBubble.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index 2463d122..3d956e2e 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -61,7 +61,7 @@ const ConversationBubble = forwardRef<
From f01c04bb24db3a645817a14055d3fb8ea4a2755a Mon Sep 17 00:00:00 2001 From: shatanikmahanty Date: Mon, 7 Oct 2024 21:14:13 +0530 Subject: [PATCH 15/16] Fix: Add type to parameter in handlePostDocumentSelect callback --- frontend/src/Navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index 48bb73bd..0d99fd75 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -304,7 +304,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { isDocsListOpen={isDocsListOpen} setIsDocsListOpen={setIsDocsListOpen} handleDeleteClick={handleDeleteClick} - handlePostDocumentSelect={(doc) => { + handlePostDocumentSelect={(option?: string) => { if (isMobile) { setNavOpen(!navOpen) } From 976e7b67657ef34a02a0b2d1bc0697f5faf057fb Mon Sep 17 00:00:00 2001 From: Prosenjit Swarnakar Date: Mon, 7 Oct 2024 22:11:26 +0530 Subject: [PATCH 16/16] Fix: Menu position on conversation list scroll --- frontend/src/conversation/ConversationTile.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/conversation/ConversationTile.tsx b/frontend/src/conversation/ConversationTile.tsx index 5db4ceab..d689f09f 100644 --- a/frontend/src/conversation/ConversationTile.tsx +++ b/frontend/src/conversation/ConversationTile.tsx @@ -161,7 +161,12 @@ export default function ConversationTile({ )} {isOpen && ( -
+