From 0fc9718c35a68cc99959f19f3d9c98d1630d7d8c Mon Sep 17 00:00:00 2001 From: Siddhant Rai Date: Sat, 8 Feb 2025 14:52:32 +0530 Subject: [PATCH] feat: loading state and spinner + delete chunk option --- application/vectorstore/faiss.py | 23 +-- frontend/src/api/endpoints.ts | 2 + frontend/src/api/services/userService.ts | 2 + frontend/src/components/Spinner.tsx | 43 ++++++ frontend/src/settings/Documents.tsx | 175 +++++++++++++++-------- 5 files changed, 172 insertions(+), 73 deletions(-) create mode 100644 frontend/src/components/Spinner.tsx diff --git a/application/vectorstore/faiss.py b/application/vectorstore/faiss.py index f8aaa1ea..87ffcccb 100644 --- a/application/vectorstore/faiss.py +++ b/application/vectorstore/faiss.py @@ -20,19 +20,19 @@ class FaissStore(BaseVectorStore): super().__init__() self.source_id = source_id self.path = get_vectorstore(source_id) - embeddings = self._get_embeddings(settings.EMBEDDINGS_NAME, embeddings_key) + self.embeddings = self._get_embeddings(settings.EMBEDDINGS_NAME, embeddings_key) try: if docs_init: - self.docsearch = FAISS.from_documents(docs_init, embeddings) + self.docsearch = FAISS.from_documents(docs_init, self.embeddings) else: self.docsearch = FAISS.load_local( - self.path, embeddings, allow_dangerous_deserialization=True + self.path, self.embeddings, allow_dangerous_deserialization=True ) except Exception: raise - self.assert_embedding_dimensions(embeddings) + self.assert_embedding_dimensions(self.embeddings) def search(self, *args, **kwargs): return self.docsearch.similarity_search(*args, **kwargs) @@ -84,17 +84,6 @@ class FaissStore(BaseVectorStore): return doc_id def delete_chunk(self, chunk_id): - docstore = self.docsearch.docstore._dict - if chunk_id not in docstore: - return False - - del docstore[chunk_id] - - documents = list(docstore.values()) - if documents: - self.docsearch = FAISS.from_documents(documents, self.embeddings) - else: - self.docsearch = FAISS.from_texts([" "], self.embeddings) - - self.save_local() + self.delete_index([chunk_id]) + self.save_local(self.path) return True diff --git a/frontend/src/api/endpoints.ts b/frontend/src/api/endpoints.ts index 6346a88f..b117e18b 100644 --- a/frontend/src/api/endpoints.ts +++ b/frontend/src/api/endpoints.ts @@ -27,6 +27,8 @@ const endpoints = { GET_CHUNKS: (docId: string, page: number, per_page: number) => `/api/get_chunks?id=${docId}&page=${page}&per_page=${per_page}`, ADD_CHUNK: '/api/add_chunk', + DELETE_CHUNK: (docId: string, chunkId: string) => + `/api/delete_chunk?id=${docId}&chunk_id=${chunkId}`, }, CONVERSATION: { ANSWER: '/api/answer', diff --git a/frontend/src/api/services/userService.ts b/frontend/src/api/services/userService.ts index 1a7befbd..76c704d9 100644 --- a/frontend/src/api/services/userService.ts +++ b/frontend/src/api/services/userService.ts @@ -55,6 +55,8 @@ const userService = { apiClient.get(endpoints.USER.GET_CHUNKS(docId, page, perPage)), addChunk: (data: any): Promise => apiClient.post(endpoints.USER.ADD_CHUNK, data), + deleteChunk: (docId: string, chunkId: string): Promise => + apiClient.delete(endpoints.USER.DELETE_CHUNK(docId, chunkId)), }; export default userService; diff --git a/frontend/src/components/Spinner.tsx b/frontend/src/components/Spinner.tsx new file mode 100644 index 00000000..d34a5665 --- /dev/null +++ b/frontend/src/components/Spinner.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +type SpinnerProps = { + size?: 'small' | 'medium' | 'large'; + color?: string; +}; + +export default function Spinner({ + size = 'medium', + color = 'grey', +}: SpinnerProps) { + const sizeMap = { + small: '20px', + medium: '30px', + large: '40px', + }; + const spinnerSize = sizeMap[size]; + + const spinnerStyle = { + width: spinnerSize, + height: spinnerSize, + aspectRatio: '1', + borderRadius: '50%', + background: ` + radial-gradient(farthest-side, ${color} 94%, #0000) top/8px 8px no-repeat, + conic-gradient(#0000 30%, ${color}) + `, + WebkitMask: + 'radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0)', + animation: 'l13 1s infinite linear', + } as React.CSSProperties; + + const keyframesStyle = `@keyframes l13 { + 100% { transform: rotate(1turn) } + }`; + + return ( + <> + +
+ + ); +} diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index b5e874d5..62cf21e3 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -13,6 +13,7 @@ import Pagination from '../components/DocumentPagination'; import DropdownMenu from '../components/DropdownMenu'; import Input from '../components/Input'; import SkeletonLoader from '../components/SkeletonLoader'; +import Spinner from '../components/Spinner'; import { useDarkTheme } from '../hooks'; import AddChunkModal from '../modals/AddChunkModal'; import ConfirmationModal from '../modals/ConfirmationModal'; @@ -393,9 +394,13 @@ function DocumentChunks({ const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(5); const [totalChunks, setTotalChunks] = useState(0); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); - const [modalState, setModalState] = useState('INACTIVE'); + const [deleteModalState, setDeleteModalState] = useState<{ + state: ActiveState; + chunkId: string | null; + }>({ state: 'INACTIVE', chunkId: null }); + const [addModalState, setAddModalState] = useState('INACTIVE'); const fetchChunks = () => { setLoading(true); @@ -404,6 +409,8 @@ function DocumentChunks({ .getDocumentChunks(document.id ?? '', page, perPage) .then((response) => { if (!response.ok) { + setLoading(false); + setPaginatedChunks([]); throw new Error('Failed to fetch chunks data'); } return response.json(); @@ -413,11 +420,10 @@ function DocumentChunks({ setPerPage(data.per_page); setTotalChunks(data.total); setPaginatedChunks(data.chunks); + setLoading(false); }); } catch (e) { console.log(e); - } finally { - setLoading(false); } }; @@ -442,6 +448,20 @@ function DocumentChunks({ } }; + const handleDeleteChunk = (chunkId: string) => { + try { + userService.deleteChunk(document.id ?? '', chunkId).then((response) => { + if (!response.ok) { + throw new Error('Failed to delete chunk'); + } + setDeleteModalState({ state: 'INACTIVE', chunkId: null }); + fetchChunks(); + }); + } catch (e) { + console.log(e); + } + }; + React.useEffect(() => { fetchChunks(); }, [page, perPage]); @@ -457,7 +477,8 @@ function DocumentChunks({

Back to all documents

-
+
+

{`${totalChunks} Chunks`}

@@ -477,68 +498,110 @@ function DocumentChunks({
-
- {paginatedChunks.filter((chunk) => + {loading ? ( +
+
+ +
+
+ ) : ( +
+ {paginatedChunks.filter((chunk) => + chunk.metadata?.title + .toLowerCase() + .includes(searchTerm.toLowerCase()), + ).length === 0 ? ( +
+ No tools found + No chunks found +
+ ) : ( + paginatedChunks + .filter((chunk) => + chunk.metadata?.title + .toLowerCase() + .includes(searchTerm.toLowerCase()), + ) + .map((chunk, index) => ( +
+
+
+ +
+
+

+ {chunk.metadata?.title} +

+

+ {chunk.text} +

+
+
+
+ )) + )} +
+ )} + {!loading && + paginatedChunks.filter((chunk) => chunk.metadata?.title .toLowerCase() .includes(searchTerm.toLowerCase()), - ).length === 0 ? ( -
- No tools found + { + setPage(page); + }} + onRowsPerPageChange={(rows) => { + setPerPage(rows); + setPage(1); + }} /> - No chunks found
- ) : ( - paginatedChunks - .filter((chunk) => - chunk.metadata?.title - .toLowerCase() - .includes(searchTerm.toLowerCase()), - ) - .map((chunk, index) => ( -
-
-
-

- {chunk.metadata?.title} -

-

- {chunk.text} -

-
-
-
- )) )} -
-
- { - setPage(page); - }} - onRowsPerPageChange={(rows) => { - setPerPage(rows); - setPage(1); - }} - /> -
+ + setDeleteModalState((prev) => ({ ...prev, state })) + } + handleSubmit={() => handleDeleteChunk(deleteModalState.chunkId ?? '')} + submitLabel="Delete" + />