From ea0a6e413d42c32f61f8ca7acb09a30b42ef1dd8 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Tue, 11 Feb 2025 02:07:54 +0530 Subject: [PATCH 01/10] (feat:bubble) formattedtable/inline code --- .../src/conversation/ConversationBubble.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index 668b0935..bfa225cc 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -351,7 +351,7 @@ const ConversationBubble = forwardRef<
) : ( - {children} + + {children} + ); }, ul({ children }) { @@ -382,8 +384,8 @@ const ConversationBubble = forwardRef< }, table({ children }) { return ( -
- +
+
{children}
@@ -391,24 +393,24 @@ const ConversationBubble = forwardRef< }, thead({ children }) { return ( - + {children} ); }, tr({ children }) { return ( - + {children} ); }, - td({ children }) { - return {children}; - }, th({ children }) { return {children}; }, + td({ children }) { + return {children}; + }, }} > {preprocessLaTeX(message)} From ac447dd05591ebf58b97331c1e287a4b915a003a Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Tue, 11 Feb 2025 23:55:30 +0530 Subject: [PATCH 02/10] (feat:settings) smoother transitions --- frontend/src/settings/APIKeys.tsx | 153 +++++++++++++++++----------- frontend/src/settings/Analytics.tsx | 83 ++++++++------- frontend/src/settings/Documents.tsx | 58 ++++++----- frontend/src/settings/Logs.tsx | 14 ++- 4 files changed, 184 insertions(+), 124 deletions(-) diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index 44242cda..748cadb8 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import userService from '../api/services/userService'; @@ -11,18 +11,28 @@ import SkeletonLoader from '../components/SkeletonLoader'; export default function APIKeys() { const { t } = useTranslation(); - const [isCreateModalOpen, setCreateModal] = React.useState(false); - const [isSaveKeyModalOpen, setSaveKeyModal] = React.useState(false); - const [newKey, setNewKey] = React.useState(''); - const [apiKeys, setApiKeys] = React.useState([]); - const [loading, setLoading] = useState(true); + const [isCreateModalOpen, setCreateModal] = useState(false); + const [isSaveKeyModalOpen, setSaveKeyModal] = useState(false); + const [newKey, setNewKey] = useState(''); + const [apiKeys, setApiKeys] = useState([]); + const [loading, setLoading] = useState(false); const [keyToDelete, setKeyToDelete] = useState<{ id: string; name: string; } | null>(null); - const handleFetchKeys = async () => { - setLoading(true); + const setLoadingWithMinDuration = useCallback((isLoading: boolean) => { + if (isLoading) { + setLoading(true); + } else { + setTimeout(() => { + setLoading(false); + }, 2000); + } + }, []); + + const handleFetchKeys = useCallback(async () => { + setLoadingWithMinDuration(true); try { const response = await userService.getAPIKeys(); if (!response.ok) { @@ -33,60 +43,81 @@ export default function APIKeys() { } catch (error) { console.log(error); } finally { - setLoading(false); + setLoadingWithMinDuration(false); + } + }, [setLoadingWithMinDuration]); + + const handleDeleteKey = useCallback( + (id: string) => { + setLoadingWithMinDuration(true); + userService + .deleteAPIKey({ id }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to delete API Key'); + } + return response.json(); + }) + .then((data) => { + if (data.success === true) { + setApiKeys((previous) => previous.filter((elem) => elem.id !== id)); + } + setKeyToDelete(null); + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + setLoadingWithMinDuration(false); + }); + }, + [setLoadingWithMinDuration], + ); + + const handleCreateKey = useCallback( + (payload: { + name: string; + source?: string; + retriever?: string; + prompt_id: string; + chunks: string; + }) => { + setLoadingWithMinDuration(true); + userService + .createAPIKey(payload) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to create API Key'); + } + return response.json(); + }) + .then((data) => { + setApiKeys((prevKeys) => [...prevKeys, data]); + setCreateModal(false); + setNewKey(data.key); + setSaveKeyModal(true); + handleFetchKeys(); + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + setLoadingWithMinDuration(false); + }); + }, + [handleFetchKeys, setLoadingWithMinDuration], + ); + + useEffect(() => { + handleFetchKeys(); + }, [handleFetchKeys]); + + const confirmDelete = () => { + if (keyToDelete) { + handleDeleteKey(keyToDelete.id); } }; - const handleDeleteKey = (id: string) => { - userService - .deleteAPIKey({ id }) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to delete API Key'); - } - return response.json(); - }) - .then((data) => { - data.success === true && - setApiKeys((previous) => previous.filter((elem) => elem.id !== id)); - setKeyToDelete(null); - }) - .catch((error) => { - console.error(error); - }); - }; - - const handleCreateKey = (payload: { - name: string; - source?: string; - retriever?: string; - prompt_id: string; - chunks: string; - }) => { - userService - .createAPIKey(payload) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to create API Key'); - } - return response.json(); - }) - .then((data) => { - setApiKeys([...apiKeys, data]); - setCreateModal(false); - setNewKey(data.key); - setSaveKeyModal(true); - handleFetchKeys(); - }) - .catch((error) => { - console.error(error); - }); - }; - - React.useEffect(() => { - handleFetchKeys(); - }, []); - return (
@@ -118,7 +149,7 @@ export default function APIKeys() { modalState="ACTIVE" setModalState={() => setKeyToDelete(null)} submitLabel={t('modals.deleteConv.delete')} - handleSubmit={() => handleDeleteKey(keyToDelete.id)} + handleSubmit={confirmDelete} handleCancel={() => setKeyToDelete(null)} /> )} @@ -161,7 +192,7 @@ export default function APIKeys() { )} {Array.isArray(apiKeys) && - apiKeys?.map((element, index) => ( + apiKeys.map((element, index) => ( (false); + const [loadingTokens, setLoadingTokens] = useState(false); + const [loadingFeedback, setLoadingFeedback] = useState(false); + + const setLoadingWithMinDuration = useCallback( + ( + setter: React.Dispatch>, + isLoading: boolean, + ) => { + if (isLoading) { + setter(true); + } else { + setTimeout(() => { + setter(false); + }, 2000); + } + }, + [], + ); const fetchChatbots = async () => { try { @@ -105,84 +121,75 @@ export default function Analytics() { } }; - const fetchMessagesData = async (chatbot_id?: string, filter?: string) => { - setLoadingMessages(true); + const fetchMessagesData = useCallback(async () => { + setLoadingWithMinDuration(setLoadingMessages, true); try { const response = await userService.getMessageAnalytics({ - api_key_id: chatbot_id, - filter_option: filter, + api_key_id: selectedChatbot?.id, + filter_option: messagesFilter.value, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); } const data = await response.json(); setMessagesData(data.messages); + setLoadingWithMinDuration(setLoadingMessages, false); } catch (error) { console.error(error); } finally { - setLoadingMessages(false); + setLoadingWithMinDuration(setLoadingMessages, false); } - }; + }, [selectedChatbot, messagesFilter, setLoadingWithMinDuration]); - const fetchTokenData = async (chatbot_id?: string, filter?: string) => { - setLoadingTokens(true); + const fetchTokenData = useCallback(async () => { + setLoadingWithMinDuration(setLoadingTokens, true); try { const response = await userService.getTokenAnalytics({ - api_key_id: chatbot_id, - filter_option: filter, + api_key_id: selectedChatbot?.id, + filter_option: tokenUsageFilter.value, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); } const data = await response.json(); setTokenUsageData(data.token_usage); + setLoadingWithMinDuration(setLoadingTokens, false); } catch (error) { console.error(error); } finally { - setLoadingTokens(false); + setLoadingWithMinDuration(setLoadingTokens, false); } - }; + }, [selectedChatbot, tokenUsageFilter, setLoadingWithMinDuration]); - const fetchFeedbackData = async (chatbot_id?: string, filter?: string) => { - setLoadingFeedback(true); + const fetchFeedbackData = useCallback(async () => { + setLoadingWithMinDuration(setLoadingFeedback, true); try { const response = await userService.getFeedbackAnalytics({ - api_key_id: chatbot_id, - filter_option: filter, + api_key_id: selectedChatbot?.id, + filter_option: feedbackFilter.value, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); } const data = await response.json(); setFeedbackData(data.feedback); + setLoadingWithMinDuration(setLoadingFeedback, false); } catch (error) { console.error(error); } finally { - setLoadingFeedback(false); + setLoadingWithMinDuration(setLoadingFeedback, false); } - }; + }, [selectedChatbot, feedbackFilter, setLoadingWithMinDuration]); useEffect(() => { fetchChatbots(); }, []); useEffect(() => { - const id = selectedChatbot?.id; - const filter = messagesFilter; - fetchMessagesData(id, filter?.value); - }, [selectedChatbot, messagesFilter]); - - useEffect(() => { - const id = selectedChatbot?.id; - const filter = tokenUsageFilter; - fetchTokenData(id, filter?.value); - }, [selectedChatbot, tokenUsageFilter]); - - useEffect(() => { - const id = selectedChatbot?.id; - const filter = feedbackFilter; - fetchFeedbackData(id, filter?.value); - }, [selectedChatbot, feedbackFilter]); + fetchMessagesData(); + fetchTokenData(); + fetchFeedbackData(); + }, [fetchMessagesData, fetchTokenData, fetchFeedbackData]); return (
diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 88590165..44a12104 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -7,11 +7,11 @@ import caretSort from '../assets/caret-sort.svg'; import DropdownMenu from '../components/DropdownMenu'; import SkeletonLoader from '../components/SkeletonLoader'; import Input from '../components/Input'; -import Upload from '../upload/Upload'; // Import the Upload component +import Upload from '../upload/Upload'; import Pagination from '../components/DocumentPagination'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; -import { Doc, DocumentsProps, ActiveState } from '../models/misc'; // Ensure ActiveState type is imported +import { Doc, DocumentsProps, ActiveState } from '../models/misc'; import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi'; import { setSourceDocs } from '../preferences/preferenceSlice'; import { setPaginatedDocuments } from '../preferences/preferenceSlice'; @@ -41,15 +41,13 @@ const Documents: React.FC = ({ }) => { const { t } = useTranslation(); const dispatch = useDispatch(); - // State for search input const [searchTerm, setSearchTerm] = useState(''); - // State for modal: active/inactive - const [modalState, setModalState] = useState('INACTIVE'); // Initialize with inactive state - const [isOnboarding, setIsOnboarding] = useState(false); // State for onboarding flag + const [modalState, setModalState] = useState('INACTIVE'); + const [isOnboarding, setIsOnboarding] = useState(false); const [loading, setLoading] = useState(false); const [sortField, setSortField] = useState<'date' | 'tokens'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); - // Pagination + const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(10); const [totalPages, setTotalPages] = useState(1); @@ -61,6 +59,16 @@ const Documents: React.FC = ({ { label: t('settings.documents.syncFrequency.monthly'), value: 'monthly' }, ]; + const setLoadingWithMinDuration = useCallback((isLoading: boolean) => { + if (isLoading) { + setLoading(true); + } else { + setTimeout(() => { + setLoading(false); + }, 2000); + } + }, []); + const refreshDocs = useCallback( ( field: 'date' | 'tokens' | undefined, @@ -69,11 +77,7 @@ const Documents: React.FC = ({ ) => { const page = pageNumber ?? currentPage; const rowsPerPg = rows ?? rowsPerPage; - - // If field is undefined, (Pagination or Search) use the current sortField const newSortField = field ?? sortField; - - // If field is undefined, (Pagination or Search) use the current sortOrder const newSortOrder = field === sortField ? sortOrder === 'asc' @@ -81,13 +85,12 @@ const Documents: React.FC = ({ : 'asc' : sortOrder; - // If field is defined, update the sortField and sortOrder if (field) { setSortField(newSortField); setSortOrder(newSortOrder); } - setLoading(true); + setLoadingWithMinDuration(true); getDocsWithPagination( newSortField, newSortOrder, @@ -98,17 +101,26 @@ const Documents: React.FC = ({ .then((data) => { dispatch(setPaginatedDocuments(data ? data.docs : [])); setTotalPages(data ? data.totalPages : 0); + setLoadingWithMinDuration(false); }) - .catch((error) => console.error(error)) - .finally(() => { - setLoading(false); + .catch((error) => { + console.error(error); + setLoadingWithMinDuration(false); }); }, - [currentPage, rowsPerPage, sortField, sortOrder, searchTerm], + [ + currentPage, + rowsPerPage, + sortField, + sortOrder, + searchTerm, + dispatch, + setLoadingWithMinDuration, + ], ); const handleManageSync = (doc: Doc, sync_frequency: string) => { - setLoading(true); + setLoadingWithMinDuration(true); userService .manageSync({ source_id: doc.id, sync_frequency }) .then(() => { @@ -128,10 +140,11 @@ const Documents: React.FC = ({ setPaginatedDocuments(paginatedData ? paginatedData.docs : []), ); setTotalPages(paginatedData ? paginatedData.totalPages : 0); + setLoadingWithMinDuration(false); }) - .catch((error) => console.error('Error in handleManageSync:', error)) - .finally(() => { - setLoading(false); + .catch((error) => { + console.error('Error in handleManageSync:', error); + setLoadingWithMinDuration(false); }); }; @@ -202,7 +215,6 @@ const Documents: React.FC = ({ ) : (
{' '} - {/* Removed overflow-auto */}
@@ -249,7 +261,7 @@ const Documents: React.FC = ({ diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index 9ff577b3..a3e7b3fe 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -18,6 +18,16 @@ export default function Logs() { const [loadingChatbots, setLoadingChatbots] = useState(true); const [loadingLogs, setLoadingLogs] = useState(true); + const setLoadingLogsWithMinDuration = useCallback((isLoading: boolean) => { + if (isLoading) { + setLoadingLogs(true); + } else { + setTimeout(() => { + setLoadingLogs(false); + }, 2000); + } + }, []); + const fetchChatbots = async () => { setLoadingChatbots(true); try { @@ -35,7 +45,7 @@ export default function Logs() { }; const fetchLogs = async () => { - setLoadingLogs(true); + setLoadingLogsWithMinDuration(true); try { const response = await userService.getLogs({ page: page, @@ -51,7 +61,7 @@ export default function Logs() { } catch (error) { console.error(error); } finally { - setLoadingLogs(false); + setLoadingLogsWithMinDuration(false); } }; From 9424f6303a8611e6b4c65eafc3ef80d3b908a4fd Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Wed, 12 Feb 2025 20:10:21 +0530 Subject: [PATCH 03/10] (feat:upload) smooth transitions on advanced fields --- frontend/src/upload/Upload.tsx | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/src/upload/Upload.tsx b/frontend/src/upload/Upload.tsx index 0e030beb..248d9f11 100644 --- a/frontend/src/upload/Upload.tsx +++ b/frontend/src/upload/Upload.tsx @@ -54,16 +54,28 @@ function Upload({ const advancedFields = schema.filter((field) => field.advanced); return ( - <> - {generalFields.map((field: FormField) => renderField(field))} +
+
+ {generalFields.map((field: FormField) => renderField(field))} +
- {advancedFields.length > 0 && showAdvancedOptions && ( - <> -
- {advancedFields.map((field: FormField) => renderField(field))} - + {advancedFields.length > 0 && ( +
+
+
+
+ {advancedFields.map((field: FormField) => renderField(field))} +
+
+
)} - +
); }; From d08861fb30bf34b05366ab182cbc3b8ae86e698e Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Thu, 13 Feb 2025 01:16:11 +0530 Subject: [PATCH 04/10] (fix/docs) revert effected portions --- frontend/src/settings/Analytics.tsx | 50 ++-- frontend/src/settings/Documents.tsx | 358 ++++++++++++++++++++++++---- 2 files changed, 344 insertions(+), 64 deletions(-) diff --git a/frontend/src/settings/Analytics.tsx b/frontend/src/settings/Analytics.tsx index d163066f..e748e943 100644 --- a/frontend/src/settings/Analytics.tsx +++ b/frontend/src/settings/Analytics.tsx @@ -121,12 +121,12 @@ export default function Analytics() { } }; - const fetchMessagesData = useCallback(async () => { - setLoadingWithMinDuration(setLoadingMessages, true); + const fetchMessagesData = async (chatbot_id?: string, filter?: string) => { + setLoadingMessages(true); try { const response = await userService.getMessageAnalytics({ - api_key_id: selectedChatbot?.id, - filter_option: messagesFilter.value, + api_key_id: chatbot_id, + filter_option: filter, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); @@ -139,14 +139,14 @@ export default function Analytics() { } finally { setLoadingWithMinDuration(setLoadingMessages, false); } - }, [selectedChatbot, messagesFilter, setLoadingWithMinDuration]); + }; - const fetchTokenData = useCallback(async () => { - setLoadingWithMinDuration(setLoadingTokens, true); + const fetchTokenData = async (chatbot_id?: string, filter?: string) => { + setLoadingTokens(true); try { const response = await userService.getTokenAnalytics({ - api_key_id: selectedChatbot?.id, - filter_option: tokenUsageFilter.value, + api_key_id: chatbot_id, + filter_option: filter, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); @@ -159,14 +159,14 @@ export default function Analytics() { } finally { setLoadingWithMinDuration(setLoadingTokens, false); } - }, [selectedChatbot, tokenUsageFilter, setLoadingWithMinDuration]); + }; - const fetchFeedbackData = useCallback(async () => { - setLoadingWithMinDuration(setLoadingFeedback, true); + const fetchFeedbackData = async (chatbot_id?: string, filter?: string) => { + setLoadingFeedback(true); try { const response = await userService.getFeedbackAnalytics({ - api_key_id: selectedChatbot?.id, - filter_option: feedbackFilter.value, + api_key_id: chatbot_id, + filter_option: filter, }); if (!response.ok) { throw new Error('Failed to fetch analytics data'); @@ -179,17 +179,29 @@ export default function Analytics() { } finally { setLoadingWithMinDuration(setLoadingFeedback, false); } - }, [selectedChatbot, feedbackFilter, setLoadingWithMinDuration]); + }; useEffect(() => { fetchChatbots(); }, []); useEffect(() => { - fetchMessagesData(); - fetchTokenData(); - fetchFeedbackData(); - }, [fetchMessagesData, fetchTokenData, fetchFeedbackData]); + const id = selectedChatbot?.id; + const filter = messagesFilter; + fetchMessagesData(id, filter?.value); + }, [selectedChatbot, messagesFilter]); + + useEffect(() => { + const id = selectedChatbot?.id; + const filter = tokenUsageFilter; + fetchTokenData(id, filter?.value); + }, [selectedChatbot, tokenUsageFilter]); + + useEffect(() => { + const id = selectedChatbot?.id; + const filter = feedbackFilter; + fetchFeedbackData(id, filter?.value); + }, [selectedChatbot, feedbackFilter]); return (
diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 44a12104..99b40dc9 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -1,24 +1,33 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import PropTypes from 'prop-types'; -import userService from '../api/services/userService'; -import SyncIcon from '../assets/sync.svg'; -import Trash from '../assets/trash.svg'; -import caretSort from '../assets/caret-sort.svg'; -import DropdownMenu from '../components/DropdownMenu'; -import SkeletonLoader from '../components/SkeletonLoader'; -import Input from '../components/Input'; -import Upload from '../upload/Upload'; -import Pagination from '../components/DocumentPagination'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; -import { Doc, DocumentsProps, ActiveState } from '../models/misc'; -import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi'; -import { setSourceDocs } from '../preferences/preferenceSlice'; -import { setPaginatedDocuments } from '../preferences/preferenceSlice'; -import { formatDate } from '../utils/dateTimeUtils'; -import ConfirmationModal from '../modals/ConfirmationModal'; -// Utility function to format numbers +import userService from '../api/services/userService'; +import ArrowLeft from '../assets/arrow-left.svg'; +import caretSort from '../assets/caret-sort.svg'; +import Edit from '../assets/edit.svg'; +import NoFilesDarkIcon from '../assets/no-files-dark.svg'; +import NoFilesIcon from '../assets/no-files.svg'; +import SyncIcon from '../assets/sync.svg'; +import Trash from '../assets/trash.svg'; +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 ChunkModal from '../modals/ChunkModal'; +import ConfirmationModal from '../modals/ConfirmationModal'; +import { ActiveState, Doc, DocumentsProps } from '../models/misc'; +import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi'; +import { + setPaginatedDocuments, + setSourceDocs, +} from '../preferences/preferenceSlice'; +import Upload from '../upload/Upload'; +import { formatDate } from '../utils/dateTimeUtils'; +import { ChunkType } from './types'; + const formatTokens = (tokens: number): string => { const roundToTwoDecimals = (num: number): string => { return (Math.round((num + Number.EPSILON) * 100) / 100).toString(); @@ -35,19 +44,20 @@ const formatTokens = (tokens: number): string => { } }; -const Documents: React.FC = ({ +export default function Documents({ paginatedDocuments, handleDeleteDocument, -}) => { +}: DocumentsProps) { const { t } = useTranslation(); const dispatch = useDispatch(); + const [searchTerm, setSearchTerm] = useState(''); const [modalState, setModalState] = useState('INACTIVE'); const [isOnboarding, setIsOnboarding] = useState(false); const [loading, setLoading] = useState(false); const [sortField, setSortField] = useState<'date' | 'tokens'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); - + // Pagination const [currentPage, setCurrentPage] = useState(1); const [rowsPerPage, setRowsPerPage] = useState(10); const [totalPages, setTotalPages] = useState(1); @@ -58,6 +68,7 @@ const Documents: React.FC = ({ { label: t('settings.documents.syncFrequency.weekly'), value: 'weekly' }, { label: t('settings.documents.syncFrequency.monthly'), value: 'monthly' }, ]; + const [showDocumentChunks, setShowDocumentChunks] = useState(); const setLoadingWithMinDuration = useCallback((isLoading: boolean) => { if (isLoading) { @@ -77,7 +88,11 @@ const Documents: React.FC = ({ ) => { const page = pageNumber ?? currentPage; const rowsPerPg = rows ?? rowsPerPage; + + // If field is undefined, (Pagination or Search) use the current sortField const newSortField = field ?? sortField; + + // If field is undefined, (Pagination or Search) use the current sortOrder const newSortOrder = field === sortField ? sortOrder === 'asc' @@ -85,6 +100,7 @@ const Documents: React.FC = ({ : 'asc' : sortOrder; + // If field is defined, update the sortField and sortOrder if (field) { setSortField(newSortField); setSortOrder(newSortOrder); @@ -101,22 +117,13 @@ const Documents: React.FC = ({ .then((data) => { dispatch(setPaginatedDocuments(data ? data.docs : [])); setTotalPages(data ? data.totalPages : 0); - setLoadingWithMinDuration(false); }) - .catch((error) => { - console.error(error); + .catch((error) => console.error(error)) + .finally(() => { setLoadingWithMinDuration(false); }); }, - [ - currentPage, - rowsPerPage, - sortField, - sortOrder, - searchTerm, - dispatch, - setLoadingWithMinDuration, - ], + [currentPage, rowsPerPage, sortField, sortOrder, searchTerm], ); const handleManageSync = (doc: Doc, sync_frequency: string) => { @@ -140,10 +147,9 @@ const Documents: React.FC = ({ setPaginatedDocuments(paginatedData ? paginatedData.docs : []), ); setTotalPages(paginatedData ? paginatedData.totalPages : 0); - setLoadingWithMinDuration(false); }) - .catch((error) => { - console.error('Error in handleManageSync:', error); + .catch((error) => console.error('Error in handleManageSync:', error)) + .finally(() => { setLoadingWithMinDuration(false); }); }; @@ -172,7 +178,14 @@ const Documents: React.FC = ({ refreshDocs(undefined, 1, rowsPerPage); }, [searchTerm]); - return ( + return showDocumentChunks ? ( + { + setShowDocumentChunks(undefined); + }} + /> + ) : (
@@ -196,6 +209,7 @@ const Documents: React.FC = ({ setSearchTerm(e.target.value); setCurrentPage(1); }} + borderVariant="thin" />
{t('settings.documents.noData')}
@@ -261,14 +276,18 @@ const Documents: React.FC = ({ ) : ( currentDocuments.map((document, index) => ( - + setShowDocumentChunks(document)} + > )} {Array.isArray(apiKeys) && - apiKeys.map((element, index) => ( + apiKeys?.map((element, index) => ( (false); - const [loadingTokens, setLoadingTokens] = useState(false); - const [loadingFeedback, setLoadingFeedback] = useState(false); - - const setLoadingWithMinDuration = useCallback( - ( - setter: React.Dispatch>, - isLoading: boolean, - ) => { - if (isLoading) { - setter(true); - } else { - setTimeout(() => { - setter(false); - }, 2000); - } - }, - [], - ); + const [loadingMessages, setLoadingMessages] = useLoaderState(true); + const [loadingTokens, setLoadingTokens] = useLoaderState(true); + const [loadingFeedback, setLoadingFeedback] = useLoaderState(true); const fetchChatbots = async () => { try { @@ -133,11 +118,10 @@ export default function Analytics() { } const data = await response.json(); setMessagesData(data.messages); - setLoadingWithMinDuration(setLoadingMessages, false); } catch (error) { console.error(error); } finally { - setLoadingWithMinDuration(setLoadingMessages, false); + setLoadingMessages(false); } }; @@ -153,11 +137,10 @@ export default function Analytics() { } const data = await response.json(); setTokenUsageData(data.token_usage); - setLoadingWithMinDuration(setLoadingTokens, false); } catch (error) { console.error(error); } finally { - setLoadingWithMinDuration(setLoadingTokens, false); + setLoadingTokens(false); } }; @@ -173,11 +156,10 @@ export default function Analytics() { } const data = await response.json(); setFeedbackData(data.feedback); - setLoadingWithMinDuration(setLoadingFeedback, false); } catch (error) { console.error(error); } finally { - setLoadingWithMinDuration(setLoadingFeedback, false); + setLoadingFeedback(false); } }; diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 99b40dc9..32254a8f 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -15,7 +15,7 @@ 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 { useDarkTheme, useLoaderState } from '../hooks'; import ChunkModal from '../modals/ChunkModal'; import ConfirmationModal from '../modals/ConfirmationModal'; import { ActiveState, Doc, DocumentsProps } from '../models/misc'; @@ -54,7 +54,7 @@ export default function Documents({ const [searchTerm, setSearchTerm] = useState(''); const [modalState, setModalState] = useState('INACTIVE'); const [isOnboarding, setIsOnboarding] = useState(false); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useLoaderState(false); const [sortField, setSortField] = useState<'date' | 'tokens'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // Pagination @@ -70,16 +70,6 @@ export default function Documents({ ]; const [showDocumentChunks, setShowDocumentChunks] = useState(); - const setLoadingWithMinDuration = useCallback((isLoading: boolean) => { - if (isLoading) { - setLoading(true); - } else { - setTimeout(() => { - setLoading(false); - }, 2000); - } - }, []); - const refreshDocs = useCallback( ( field: 'date' | 'tokens' | undefined, @@ -106,7 +96,7 @@ export default function Documents({ setSortOrder(newSortOrder); } - setLoadingWithMinDuration(true); + setLoading(true); getDocsWithPagination( newSortField, newSortOrder, @@ -120,14 +110,14 @@ export default function Documents({ }) .catch((error) => console.error(error)) .finally(() => { - setLoadingWithMinDuration(false); + setLoading(false); }); }, [currentPage, rowsPerPage, sortField, sortOrder, searchTerm], ); const handleManageSync = (doc: Doc, sync_frequency: string) => { - setLoadingWithMinDuration(true); + setLoading(true); userService .manageSync({ source_id: doc.id, sync_frequency }) .then(() => { @@ -150,7 +140,7 @@ export default function Documents({ }) .catch((error) => console.error('Error in handleManageSync:', error)) .finally(() => { - setLoadingWithMinDuration(false); + setLoading(false); }); }; @@ -229,7 +219,6 @@ export default function Documents({ ) : (
{' '} - {/* Removed overflow-auto */}
{t('settings.documents.noData')}
= ({ )} ); -}; +} -Documents.propTypes = { - //documents: PropTypes.array.isRequired, - handleDeleteDocument: PropTypes.func.isRequired, -}; +function DocumentChunks({ + document, + handleGoBack, +}: { + document: Doc; + handleGoBack: () => void; +}) { + const { t } = useTranslation(); + const [isDarkTheme] = useDarkTheme(); + const [paginatedChunks, setPaginatedChunks] = useState([]); + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(5); + const [totalChunks, setTotalChunks] = useState(0); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [addModal, setAddModal] = useState('INACTIVE'); + const [editModal, setEditModal] = useState<{ + state: ActiveState; + chunk: ChunkType | null; + }>({ state: 'INACTIVE', chunk: null }); -export default Documents; + const fetchChunks = () => { + setLoading(true); + try { + userService + .getDocumentChunks(document.id ?? '', page, perPage) + .then((response) => { + if (!response.ok) { + setLoading(false); + setPaginatedChunks([]); + throw new Error('Failed to fetch chunks data'); + } + return response.json(); + }) + .then((data) => { + setPage(data.page); + setPerPage(data.per_page); + setTotalChunks(data.total); + setPaginatedChunks(data.chunks); + setLoading(false); + }); + } catch (e) { + console.log(e); + } + }; + + const handleAddChunk = (title: string, text: string) => { + try { + userService + .addChunk({ + id: document.id ?? '', + text: text, + metadata: { + title: title, + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to add chunk'); + } + fetchChunks(); + }); + } catch (e) { + console.log(e); + } + }; + + const handleUpdateChunk = (title: string, text: string, chunk: ChunkType) => { + try { + userService + .updateChunk({ + id: document.id ?? '', + chunk_id: chunk.doc_id, + text: text, + metadata: { + title: title, + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to update chunk'); + } + fetchChunks(); + }); + } catch (e) { + console.log(e); + } + }; + + const handleDeleteChunk = (chunk: ChunkType) => { + try { + userService + .deleteChunk(document.id ?? '', chunk.doc_id) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to delete chunk'); + } + setEditModal({ state: 'INACTIVE', chunk: null }); + fetchChunks(); + }); + } catch (e) { + console.log(e); + } + }; + + React.useEffect(() => { + fetchChunks(); + }, [page, perPage]); + return ( +
+
+ +

Back to all documents

+
+
+
+

{`${totalChunks} Chunks`}

+ + { + setSearchTerm(e.target.value); + }} + borderVariant="thin" + /> +
+ +
+ {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 && ( +
+ { + setPage(page); + }} + onRowsPerPageChange={(rows) => { + setPerPage(rows); + setPage(1); + }} + /> +
+ )} + + setEditModal((prev) => ({ ...prev, state }))} + handleSubmit={(title, text) => { + handleUpdateChunk(title, text, editModal.chunk as ChunkType); + }} + originalText={editModal.chunk?.text} + originalTitle={editModal.chunk?.metadata?.title} + handleDelete={() => { + handleDeleteChunk(editModal.chunk as ChunkType); + }} + /> +
+ ); +} From 181bf69994990a15b8c9949b80d6973eee31ef9b Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Thu, 13 Feb 2025 17:16:17 +0530 Subject: [PATCH 05/10] (feat:transitions) custom hook to loading state --- frontend/src/hooks/index.ts | 20 ++++ frontend/src/settings/APIKeys.tsx | 154 ++++++++++++---------------- frontend/src/settings/Analytics.tsx | 34 ++---- frontend/src/settings/Documents.tsx | 28 ++--- frontend/src/settings/Logs.tsx | 19 +--- 5 files changed, 108 insertions(+), 147 deletions(-) diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index b993f3da..56f50406 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -112,3 +112,23 @@ export function useDarkTheme() { return [isDarkTheme, toggleTheme, componentMounted] as const; } + +export function useLoaderState( + initialState = false, + delay = 500, +): [boolean, (value: boolean) => void] { + const [state, setState] = useState(initialState); + + const setLoaderState = (value: boolean) => { + if (value) { + setState(true); + } else { + // Only add delay when changing from true to false + setTimeout(() => { + setState(false); + }, delay); + } + }; + + return [state, setLoaderState]; +} diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index 748cadb8..2226544c 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import userService from '../api/services/userService'; @@ -8,6 +8,7 @@ import SaveAPIKeyModal from '../modals/SaveAPIKeyModal'; import ConfirmationModal from '../modals/ConfirmationModal'; import { APIKeyData } from './types'; import SkeletonLoader from '../components/SkeletonLoader'; +import { useLoaderState } from '../hooks'; export default function APIKeys() { const { t } = useTranslation(); @@ -15,24 +16,14 @@ export default function APIKeys() { const [isSaveKeyModalOpen, setSaveKeyModal] = useState(false); const [newKey, setNewKey] = useState(''); const [apiKeys, setApiKeys] = useState([]); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useLoaderState(true); const [keyToDelete, setKeyToDelete] = useState<{ id: string; name: string; } | null>(null); - const setLoadingWithMinDuration = useCallback((isLoading: boolean) => { - if (isLoading) { - setLoading(true); - } else { - setTimeout(() => { - setLoading(false); - }, 2000); - } - }, []); - - const handleFetchKeys = useCallback(async () => { - setLoadingWithMinDuration(true); + const handleFetchKeys = async () => { + setLoading(true); try { const response = await userService.getAPIKeys(); if (!response.ok) { @@ -43,81 +34,68 @@ export default function APIKeys() { } catch (error) { console.log(error); } finally { - setLoadingWithMinDuration(false); - } - }, [setLoadingWithMinDuration]); - - const handleDeleteKey = useCallback( - (id: string) => { - setLoadingWithMinDuration(true); - userService - .deleteAPIKey({ id }) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to delete API Key'); - } - return response.json(); - }) - .then((data) => { - if (data.success === true) { - setApiKeys((previous) => previous.filter((elem) => elem.id !== id)); - } - setKeyToDelete(null); - }) - .catch((error) => { - console.error(error); - }) - .finally(() => { - setLoadingWithMinDuration(false); - }); - }, - [setLoadingWithMinDuration], - ); - - const handleCreateKey = useCallback( - (payload: { - name: string; - source?: string; - retriever?: string; - prompt_id: string; - chunks: string; - }) => { - setLoadingWithMinDuration(true); - userService - .createAPIKey(payload) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to create API Key'); - } - return response.json(); - }) - .then((data) => { - setApiKeys((prevKeys) => [...prevKeys, data]); - setCreateModal(false); - setNewKey(data.key); - setSaveKeyModal(true); - handleFetchKeys(); - }) - .catch((error) => { - console.error(error); - }) - .finally(() => { - setLoadingWithMinDuration(false); - }); - }, - [handleFetchKeys, setLoadingWithMinDuration], - ); - - useEffect(() => { - handleFetchKeys(); - }, [handleFetchKeys]); - - const confirmDelete = () => { - if (keyToDelete) { - handleDeleteKey(keyToDelete.id); + setLoading(false); } }; + const handleDeleteKey = (id: string) => { + setLoading(true); + userService + .deleteAPIKey({ id }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to delete API Key'); + } + return response.json(); + }) + .then((data) => { + data.success === true && + setApiKeys((previous) => previous.filter((elem) => elem.id !== id)); + setKeyToDelete(null); + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleCreateKey = (payload: { + name: string; + source?: string; + retriever?: string; + prompt_id: string; + chunks: string; + }) => { + setLoading(true); + userService + .createAPIKey(payload) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to create API Key'); + } + return response.json(); + }) + .then((data) => { + setApiKeys([...apiKeys, data]); + setCreateModal(false); + setNewKey(data.key); + setSaveKeyModal(true); + handleFetchKeys(); + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + setLoading(false); + }); + }; + + React.useEffect(() => { + handleFetchKeys(); + }, []); + return (
@@ -149,7 +127,7 @@ export default function APIKeys() { modalState="ACTIVE" setModalState={() => setKeyToDelete(null)} submitLabel={t('modals.deleteConv.delete')} - handleSubmit={confirmDelete} + handleSubmit={() => handleDeleteKey(keyToDelete.id)} handleCancel={() => setKeyToDelete(null)} /> )} @@ -192,7 +170,7 @@ export default function APIKeys() {
@@ -276,7 +265,7 @@ export default function Documents({ @@ -405,7 +394,7 @@ function DocumentChunks({ const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(5); const [totalChunks, setTotalChunks] = useState(0); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useLoaderState(true); const [searchTerm, setSearchTerm] = useState(''); const [addModal, setAddModal] = useState('INACTIVE'); const [editModal, setEditModal] = useState<{ @@ -435,6 +424,7 @@ function DocumentChunks({ }); } catch (e) { console.log(e); + setLoading(false); } }; diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index a3e7b3fe..aa4e32a4 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -7,6 +7,7 @@ import Dropdown from '../components/Dropdown'; import SkeletonLoader from '../components/SkeletonLoader'; import { APIKeyData, LogData } from './types'; import CoppyButton from '../components/CopyButton'; +import { useLoaderState } from '../hooks'; export default function Logs() { const { t } = useTranslation(); @@ -15,18 +16,8 @@ export default function Logs() { const [logs, setLogs] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); - const [loadingChatbots, setLoadingChatbots] = useState(true); - const [loadingLogs, setLoadingLogs] = useState(true); - - const setLoadingLogsWithMinDuration = useCallback((isLoading: boolean) => { - if (isLoading) { - setLoadingLogs(true); - } else { - setTimeout(() => { - setLoadingLogs(false); - }, 2000); - } - }, []); + const [loadingChatbots, setLoadingChatbots] = useLoaderState(true); + const [loadingLogs, setLoadingLogs] = useLoaderState(true); const fetchChatbots = async () => { setLoadingChatbots(true); @@ -45,7 +36,7 @@ export default function Logs() { }; const fetchLogs = async () => { - setLoadingLogsWithMinDuration(true); + setLoadingLogs(true); try { const response = await userService.getLogs({ page: page, @@ -61,7 +52,7 @@ export default function Logs() { } catch (error) { console.error(error); } finally { - setLoadingLogsWithMinDuration(false); + setLoadingLogs(false); } }; From adb2947b52d37860670b03ecfad07d9448fbc88d Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 14 Feb 2025 01:01:07 +0530 Subject: [PATCH 06/10] (feat:transitions): reshaped the tablular loaders --- frontend/src/components/SkeletonLoader.tsx | 225 ++++++++++++--------- frontend/src/settings/APIKeys.tsx | 126 ++++++------ frontend/src/settings/Documents.tsx | 10 +- 3 files changed, 200 insertions(+), 161 deletions(-) diff --git a/frontend/src/components/SkeletonLoader.tsx b/frontend/src/components/SkeletonLoader.tsx index e9a136e4..1b59d610 100644 --- a/frontend/src/components/SkeletonLoader.tsx +++ b/frontend/src/components/SkeletonLoader.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; interface SkeletonLoaderProps { count?: number; - component?: 'default' | 'analysis' | 'chatbot' | 'logs'; + component?: 'default' | 'analysis' | 'logs' | 'table' | 'chatbot'; } const SkeletonLoader: React.FC = ({ @@ -32,105 +32,144 @@ const SkeletonLoader: React.FC = ({ }; }, [count]); + if (component === 'table') { + return ( + <> + {[...Array(4)].map((_, idx) => ( + + + + + + + ))} + + ); + } + + if (component === 'chatbot') { + return ( + <> + {[...Array(4)].map((_, idx) => ( + + + + + + + ))} + + ); + } + + return (
{component === 'default' ? ( - [...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )) - ) : component === 'analysis' ? ( - [...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
-
-
+ <> + {[...Array(skeletonCount)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- )) - ) : component === 'chatbot' ? ( -
-
-
-
-
-
-
-
- - {[...Array(skeletonCount * 6)].map((_, idx) => ( -
-
-
-
-
))} -
- ) : ( - [...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
+ + ) : component === 'analysis' ? ( + <> + {[...Array(skeletonCount)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- )) - )} + ))} + + ) : component === 'logs' ? ( + <> + {[...Array(skeletonCount)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+ ))} + + ) : null}
); }; diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index 2226544c..642566f1 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -49,8 +49,9 @@ export default function APIKeys() { return response.json(); }) .then((data) => { - data.success === true && + if (data.success === true) { setApiKeys((previous) => previous.filter((elem) => elem.id !== id)); + } setKeyToDelete(null); }) .catch((error) => { @@ -133,73 +134,72 @@ export default function APIKeys() { )}
- {loading ? ( - - ) : ( -
-
-
-
{t('settings.documents.noData')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - - - - - +
+
+
+
- {t('settings.apiKeys.name')} - - {t('settings.apiKeys.sourceDoc')} - - {t('settings.apiKeys.key')} -
+ + + + + + + + + + {loading ? ( + + ) : !apiKeys?.length ? ( + + - - - {!apiKeys?.length && ( - - + + + + - )} - {Array.isArray(apiKeys) && - apiKeys?.map((element, index) => ( - - - - - - - ))} - -
+ {t('settings.apiKeys.name')} + + {t('settings.apiKeys.sourceDoc')} + + {t('settings.apiKeys.key')} +
+ {t('settings.apiKeys.noData')} +
- {t('settings.apiKeys.noData')} + ) : ( + Array.isArray(apiKeys) && + apiKeys.map((element, index) => ( +
{element.name}{element.source}{element.key} + {`Delete + setKeyToDelete({ + id: element.id, + name: element.name, + }) + } + />
{element.name}{element.source}{element.key} - {`Delete - setKeyToDelete({ - id: element.id, - name: element.name, - }) - } - /> -
-
+ )) + )} + +
- )} +
diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 32254a8f..98ff7656 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -214,9 +214,7 @@ export default function Documents({ - {loading ? ( - - ) : ( +
{' '}
@@ -261,7 +259,9 @@ export default function Documents({ - {!currentDocuments?.length ? ( + {loading ? ( + + ) : !currentDocuments?.length ? (
- )} +
From 200a3b81e51261b76d3798aafd8bd8fd92356a33 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 14 Feb 2025 02:20:29 +0530 Subject: [PATCH 07/10] (feat:loaders) loader for logs, dropdown --- frontend/src/components/SkeletonLoader.tsx | 44 ++++- frontend/src/settings/Documents.tsx | 214 ++++++++++----------- frontend/src/settings/Logs.tsx | 55 +++--- 3 files changed, 171 insertions(+), 142 deletions(-) diff --git a/frontend/src/components/SkeletonLoader.tsx b/frontend/src/components/SkeletonLoader.tsx index 1b59d610..a73fdd17 100644 --- a/frontend/src/components/SkeletonLoader.tsx +++ b/frontend/src/components/SkeletonLoader.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; interface SkeletonLoaderProps { count?: number; - component?: 'default' | 'analysis' | 'logs' | 'table' | 'chatbot'; + component?: 'default' | 'analysis' | 'logs' | 'table' | 'chatbot' | 'logsTable' | 'dropdown'; } const SkeletonLoader: React.FC = ({ @@ -59,10 +59,7 @@ const SkeletonLoader: React.FC = ({ return ( <> {[...Array(4)].map((_, idx) => ( - +
@@ -81,6 +78,39 @@ const SkeletonLoader: React.FC = ({ ); } + if (component === 'dropdown') { + return ( +
+
+
+
+
+
+
+ ); + } + + if (component === 'logsTable') { + return ( +
+ {[...Array(8)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+ ); + } return (
@@ -152,7 +182,7 @@ const SkeletonLoader: React.FC = ({
))} - ) : component === 'logs' ? ( + ) : component === 'logs' ? ( <> {[...Array(skeletonCount)].map((_, idx) => (
= ({ }; export default SkeletonLoader; + + diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 98ff7656..e72a6e16 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -214,122 +214,118 @@ export default function Documents({
- -
- {' '} -
- - - - - - -
- {t('settings.documents.name')} - -
- {t('settings.documents.date')} - refreshDocs('date')} - src={caretSort} - alt="sort" - /> -
-
-
- - {t('settings.documents.tokenUsage')} - - - {t('settings.documents.tokenUsage')} - - refreshDocs('tokens')} - src={caretSort} - alt="sort" - /> -
-
- - {t('settings.documents.actions')} +
+ {' '} +
+ + + + + + + + {t('settings.documents.tokenUsage')} + + refreshDocs('tokens')} + src={caretSort} + alt="sort" + /> + + + + + + + {loading ? ( + + ) : !currentDocuments?.length ? ( + + - - - {loading ? ( - - ) : !currentDocuments?.length ? ( - + ) : ( + currentDocuments.map((document, index) => ( + setShowDocumentChunks(document)} + > + + + - ) : ( - currentDocuments.map((document, index) => ( - setShowDocumentChunks(document)} - > - - - - - - )) - )} - -
+ {t('settings.documents.name')} + +
+ {t('settings.documents.date')} + refreshDocs('date')} + src={caretSort} + alt="sort" + /> +
+
+
+ + {t('settings.documents.tokenUsage')} -
+ + {t('settings.documents.actions')} + +
+ {t('settings.documents.noData')} +
- {t('settings.documents.noData')} + {document.name} + + {document.date ? formatDate(document.date) : ''} + + {document.tokens ? formatTokens(+document.tokens) : ''} + +
+ {!document.syncFrequency && ( +
+ )} + {document.syncFrequency && ( + { + handleManageSync(document, value); + }} + defaultValue={document.syncFrequency} + icon={SyncIcon} + /> + )} + +
- {document.name} - - {document.date ? formatDate(document.date) : ''} - - {document.tokens - ? formatTokens(+document.tokens) - : ''} - -
- {!document.syncFrequency && ( -
- )} - {document.syncFrequency && ( - { - handleManageSync(document, value); - }} - defaultValue={document.syncFrequency} - icon={SyncIcon} - /> - )} - -
-
-
+ )) + )} + +
- +
diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index aa4e32a4..d1255b1d 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -67,16 +67,16 @@ export default function Logs() { return (
-
- - {loadingChatbots ? ( - - ) : ( + {loadingChatbots ? ( + + ) : ( +
+ - )} -
+
+ )}
- {loadingLogs ? ( - - ) : ( - - )} +
); @@ -123,9 +119,10 @@ export default function Logs() { type LogsTableProps = { logs: LogData[]; setPage: React.Dispatch>; + loading: boolean; }; -function LogsTable({ logs, setPage }: LogsTableProps) { +function LogsTable({ logs, setPage, loading }: LogsTableProps) { const { t } = useTranslation(); const observerRef = useRef(); const firstObserver = useCallback((node: HTMLDivElement) => { @@ -148,15 +145,19 @@ function LogsTable({ logs, setPage }: LogsTableProps) { ref={observerRef} className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-px" > - {logs.map((log, index) => { - if (index === logs.length - 1) { - return ( -
- -
- ); - } else return ; - })} + {loading ? ( + + ) : ( + logs.map((log, index) => { + if (index === logs.length - 1) { + return ( +
+ +
+ ); + } else return ; + }) + )}
); From cd06334049febfc252d4711aaf67485d270ba939 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 14 Feb 2025 17:38:58 +0530 Subject: [PATCH 08/10] (feat:transitions) uniform color and animation --- frontend/.eslintrc.cjs | 1 + frontend/src/components/SkeletonLoader.tsx | 326 ++++++++++----------- frontend/src/settings/Logs.tsx | 2 +- 3 files changed, 160 insertions(+), 169 deletions(-) diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 323a0d3c..88fb220b 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -18,6 +18,7 @@ module.exports = { }, plugins: ['react', 'unused-imports'], rules: { + 'react/prop-types': 'off', 'unused-imports/no-unused-imports': 'error', 'react/react-in-jsx-scope': 'off', 'prettier/prettier': [ diff --git a/frontend/src/components/SkeletonLoader.tsx b/frontend/src/components/SkeletonLoader.tsx index a73fdd17..b73c5835 100644 --- a/frontend/src/components/SkeletonLoader.tsx +++ b/frontend/src/components/SkeletonLoader.tsx @@ -2,7 +2,13 @@ import { useState, useEffect } from 'react'; interface SkeletonLoaderProps { count?: number; - component?: 'default' | 'analysis' | 'logs' | 'table' | 'chatbot' | 'logsTable' | 'dropdown'; + component?: + | 'default' + | 'analysis' + | 'logs' + | 'table' + | 'chatbot' + | 'dropdown'; } const SkeletonLoader: React.FC = ({ @@ -32,178 +38,162 @@ const SkeletonLoader: React.FC = ({ }; }, [count]); - if (component === 'table') { - return ( - <> - {[...Array(4)].map((_, idx) => ( - - -
- - -
- - -
- - -
- - - ))} - - ); - } + const renderTable = () => ( + <> + {[...Array(4)].map((_, idx) => ( + + +
+ + +
+ + +
+ + +
+ + + ))} + + ); - if (component === 'chatbot') { - return ( - <> - {[...Array(4)].map((_, idx) => ( - - -
- - -
- - -
- - -
- - - ))} - - ); - } + const renderChatbot = () => ( + <> + {[...Array(4)].map((_, idx) => ( + + +
+ + +
+ + +
+ + +
+ + + ))} + + ); - if (component === 'dropdown') { - return ( -
-
-
-
-
-
+ const renderDropdown = () => ( +
+
+
+
+
- ); - } - - if (component === 'logsTable') { - return ( -
- {[...Array(8)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
-
- ))} -
- ); - } - - return ( -
- {component === 'default' ? ( - <> - {[...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))} - - ) : component === 'analysis' ? ( - <> - {[...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))} - - ) : component === 'logs' ? ( - <> - {[...Array(skeletonCount)].map((_, idx) => ( -
-
-
-
-
-
-
-
-
- ))} - - ) : null}
); + + const renderLogs = () => ( +
+ {[...Array(8)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+ ))} +
+ ); + + const renderDefault = () => ( + <> + {[...Array(skeletonCount)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} + + ); + + const renderAnalysis = () => ( + <> + {[...Array(skeletonCount)].map((_, idx) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} + + ); + + const componentMap = { + table: renderTable, + chatbot: renderChatbot, + dropdown: renderDropdown, + logs: renderLogs, + default: renderDefault, + analysis: renderAnalysis, + }; + + const render = componentMap[component] || componentMap.default; + + return <>{render()}; }; export default SkeletonLoader; - - diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index d1255b1d..3847e775 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -146,7 +146,7 @@ function LogsTable({ logs, setPage, loading }: LogsTableProps) { className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-px" > {loading ? ( - + ) : ( logs.map((log, index) => { if (index === logs.length - 1) { From 7661273cfd070ccde2b0da610373c29838924327 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 14 Feb 2025 19:50:19 +0530 Subject: [PATCH 09/10] (fix/logs) append loader --- frontend/src/settings/Logs.tsx | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index 3847e775..3f4d725b 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -145,19 +145,16 @@ function LogsTable({ logs, setPage, loading }: LogsTableProps) { ref={observerRef} className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-px" > - {loading ? ( - - ) : ( - logs.map((log, index) => { - if (index === logs.length - 1) { - return ( -
- -
- ); - } else return ; - }) - )} + {logs?.map((log, index) => { + if (index === logs.length - 1) { + return ( +
+ +
+ ); + } else return ; + })} + {loading && }
); From 71970a0d1d1c1bc7359809d658fb16c4a2a1102e Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Fri, 14 Feb 2025 20:06:50 +0530 Subject: [PATCH 10/10] (feat:transit): reduce delay to 250 --- frontend/src/hooks/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index 56f50406..2247cb07 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -115,7 +115,7 @@ export function useDarkTheme() { export function useLoaderState( initialState = false, - delay = 500, + delay = 250, ): [boolean, (value: boolean) => void] { const [state, setState] = useState(initialState);