From ac447dd05591ebf58b97331c1e287a4b915a003a Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Tue, 11 Feb 2025 23:55:30 +0530 Subject: [PATCH] (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); } };
{t('settings.documents.noData')}