diff --git a/frontend/src/api/api-functions/folders.ts b/frontend/src/api/api-functions/folders.ts index edc7efba1..d4fc619bc 100644 --- a/frontend/src/api/api-functions/folders.ts +++ b/frontend/src/api/api-functions/folders.ts @@ -1,6 +1,7 @@ import { foldersEndpoints } from '../apiEndpoints'; -import { apiClient } from '../axiosConfig'; +import { apiClient, syncApiClient } from '../axiosConfig'; import { APIResponse } from '@/types/API'; +import { FolderTaggingStatusResponse } from '@/types/FolderStatus'; // Request Types export interface AddFolderRequest { @@ -69,3 +70,15 @@ export const deleteFolders = async ( ); return response.data; }; + +export const getFoldersTaggingStatus = async (): Promise => { + const response = await syncApiClient.get( + foldersEndpoints.getTaggingStatus, + ); + const res = response.data; + return { + data: res.data as any, + success: res.status === 'success', + message: res.message, + }; +}; diff --git a/frontend/src/api/apiEndpoints.ts b/frontend/src/api/apiEndpoints.ts index 805fc2010..3ff86e5bb 100644 --- a/frontend/src/api/apiEndpoints.ts +++ b/frontend/src/api/apiEndpoints.ts @@ -16,6 +16,7 @@ export const foldersEndpoints = { disableAITagging: '/folders/disable-ai-tagging', deleteFolders: '/folders/delete-folders', syncFolder: '/folders/sync-folder', + getTaggingStatus: '/folders/status', }; export const userPreferencesEndpoints = { diff --git a/frontend/src/components/ui/progress.tsx b/frontend/src/components/ui/progress.tsx index 9ab2080c9..7016ae53f 100644 --- a/frontend/src/components/ui/progress.tsx +++ b/frontend/src/components/ui/progress.tsx @@ -3,23 +3,31 @@ import * as ProgressPrimitive from '@radix-ui/react-progress'; import { cn } from '@/lib/utils'; +type ProgressProps = React.ComponentProps & { + indicatorClassName?: string; +}; + function Progress({ className, value, + indicatorClassName, ...props -}: React.ComponentProps) { +}: ProgressProps) { return ( diff --git a/frontend/src/features/folderSlice.ts b/frontend/src/features/folderSlice.ts index 8f4777d15..2efbe42f6 100644 --- a/frontend/src/features/folderSlice.ts +++ b/frontend/src/features/folderSlice.ts @@ -1,12 +1,16 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { FolderDetails } from '@/types/Folder'; +import { FolderTaggingInfo } from '@/types/FolderStatus'; interface FolderState { folders: FolderDetails[]; + taggingStatus: Record; + lastUpdatedAt?: number; } const initialState: FolderState = { folders: [], + taggingStatus: {}, }; const folderSlice = createSlice({ @@ -66,6 +70,22 @@ const folderSlice = createSlice({ clearFolders(state) { state.folders = []; }, + + // Set tagging status for folders + setTaggingStatus(state, action: PayloadAction) { + const map: Record = {}; + for (const info of action.payload) { + map[info.folder_id] = info; + } + state.taggingStatus = map; + state.lastUpdatedAt = Date.now(); + }, + + // Clear tagging status + clearTaggingStatus(state) { + state.taggingStatus = {}; + state.lastUpdatedAt = undefined; + }, }, }); @@ -75,6 +95,8 @@ export const { updateFolder, removeFolders, clearFolders, + setTaggingStatus, + clearTaggingStatus, } = folderSlice.actions; export default folderSlice.reducer; diff --git a/frontend/src/hooks/useFolderOperations.tsx b/frontend/src/hooks/useFolderOperations.tsx index 7f6b8ced4..0c0fcc559 100644 --- a/frontend/src/hooks/useFolderOperations.tsx +++ b/frontend/src/hooks/useFolderOperations.tsx @@ -8,9 +8,10 @@ import { deleteFolders, } from '@/api/api-functions'; import { selectAllFolders } from '@/features/folderSelectors'; -import { setFolders } from '@/features/folderSlice'; +import { setFolders, setTaggingStatus } from '@/features/folderSlice'; import { FolderDetails } from '@/types/Folder'; import { useMutationFeedback } from './useMutationFeedback'; +import { getFoldersTaggingStatus } from '@/api/api-functions/folders'; /** * Custom hook for folder operations @@ -26,6 +27,18 @@ export const useFolderOperations = () => { queryFn: getAllFolders, }); + const taggingStatusQuery = usePictoQuery({ + queryKey: ['folders', 'tagging-status'], + queryFn: getFoldersTaggingStatus, + staleTime: 1000, + refetchInterval: 1000, + refetchIntervalInBackground: true, + enabled: folders.some((f) => f.AI_Tagging), + retry: 2, // Retry failed requests up to 2 times before giving up + retryOnMount: false, // Don't retry on component mount + refetchOnWindowFocus: false, // Don't refetch when window gains focus + }); + // Apply feedback to the folders query useMutationFeedback( { @@ -51,6 +64,32 @@ export const useFolderOperations = () => { } }, [foldersQuery.data, dispatch]); + // Update Redux store with tagging status on each poll + useEffect(() => { + if (taggingStatusQuery.data?.success) { + const raw = taggingStatusQuery.data.data as any; + if (Array.isArray(raw)) { + dispatch(setTaggingStatus(raw)); + } + } + }, [taggingStatusQuery.data, dispatch]); + + useEffect(() => { + if (taggingStatusQuery.isError) { + console.error( + 'Failed to fetch tagging status:', + taggingStatusQuery.error, + ); + + const errorMessage = taggingStatusQuery.errorMessage || 'Unknown error'; + console.warn(`Tagging status query failed: ${errorMessage}`); + } + }, [ + taggingStatusQuery.isError, + taggingStatusQuery.error, + taggingStatusQuery.errorMessage, + ]); + // Enable AI tagging mutation const enableAITaggingMutation = usePictoMutation({ mutationFn: async (folder_id: string) => diff --git a/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx b/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx index 17407c982..889761f62 100644 --- a/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx +++ b/frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import { Folder, Trash2 } from 'lucide-react'; +import { Folder, Trash2, Check } from 'lucide-react'; import { Switch } from '@/components/ui/switch'; import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { useSelector } from 'react-redux'; +import { RootState } from '@/app/store'; import FolderPicker from '@/components/FolderPicker/FolderPicker'; import { useFolderOperations } from '@/hooks/useFolderOperations'; @@ -22,6 +25,10 @@ const FolderManagementCard: React.FC = () => { deleteFolderPending, } = useFolderOperations(); + const taggingStatus = useSelector( + (state: RootState) => state.folders.taggingStatus, + ); + return ( { + + {folder.AI_Tagging && ( +
+
+ AI Tagging Progress + = 100 + ? 'flex items-center gap-1 text-green-500' + : 'text-muted-foreground' + } + > + {(taggingStatus[folder.folder_id]?.tagging_percentage ?? + 0) >= 100 && } + {Math.round( + taggingStatus[folder.folder_id]?.tagging_percentage ?? + 0, + )} + % + +
+ = 100 + ? 'bg-green-500' + : 'bg-blue-500' + } + /> +
+ )} ))} diff --git a/frontend/src/types/FolderStatus.ts b/frontend/src/types/FolderStatus.ts new file mode 100644 index 000000000..19b80f0eb --- /dev/null +++ b/frontend/src/types/FolderStatus.ts @@ -0,0 +1,12 @@ +export interface FolderTaggingInfo { + folder_id: string; + folder_path: string; + tagging_percentage: number; // 0 - 100 +} + +export interface FolderTaggingStatusResponse { + status: 'success' | 'error'; + data: FolderTaggingInfo[]; + total_folders: number; + message?: string; +}