Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion frontend/src/api/api-functions/folders.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -69,3 +70,15 @@ export const deleteFolders = async (
);
return response.data;
};

export const getFoldersTaggingStatus = async (): Promise<APIResponse> => {
const response = await syncApiClient.get<FolderTaggingStatusResponse>(
foldersEndpoints.getTaggingStatus,
);
const res = response.data;
return {
data: res.data as any,
success: res.status === 'success',
message: res.message,
};
};
1 change: 1 addition & 0 deletions frontend/src/api/apiEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/components/ui/progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,31 @@ import * as ProgressPrimitive from '@radix-ui/react-progress';

import { cn } from '@/lib/utils';

type ProgressProps = React.ComponentProps<typeof ProgressPrimitive.Root> & {
indicatorClassName?: string;
};

function Progress({
className,
value,
indicatorClassName,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
}: ProgressProps) {
return (
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
'relative h-2 w-full overflow-hidden rounded-full bg-white',
className,
)}
{...props}
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
className={cn(
'bg-primary h-full w-full flex-1 transition-transform duration-700 ease-in-out will-change-transform',
indicatorClassName,
)}
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/features/folderSlice.ts
Original file line number Diff line number Diff line change
@@ -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<string, FolderTaggingInfo>;
lastUpdatedAt?: number;
}

const initialState: FolderState = {
folders: [],
taggingStatus: {},
};

const folderSlice = createSlice({
Expand Down Expand Up @@ -66,6 +70,22 @@ const folderSlice = createSlice({
clearFolders(state) {
state.folders = [];
},

// Set tagging status for folders
setTaggingStatus(state, action: PayloadAction<FolderTaggingInfo[]>) {
const map: Record<string, FolderTaggingInfo> = {};
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;
},
},
});

Expand All @@ -75,6 +95,8 @@ export const {
updateFolder,
removeFolders,
clearFolders,
setTaggingStatus,
clearTaggingStatus,
} = folderSlice.actions;

export default folderSlice.reducer;
41 changes: 40 additions & 1 deletion frontend/src/hooks/useFolderOperations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
{
Expand All @@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -22,6 +25,10 @@ const FolderManagementCard: React.FC = () => {
deleteFolderPending,
} = useFolderOperations();

const taggingStatus = useSelector(
(state: RootState) => state.folders.taggingStatus,
);

return (
<SettingsCard
icon={Folder}
Expand Down Expand Up @@ -70,6 +77,41 @@ const FolderManagementCard: React.FC = () => {
</Button>
</div>
</div>

{folder.AI_Tagging && (
<div className="mt-3">
<div className="text-muted-foreground mb-1 flex items-center justify-between text-xs">
<span>AI Tagging Progress</span>
<span
className={
(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100
? 'flex items-center gap-1 text-green-500'
: 'text-muted-foreground'
}
>
{(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100 && <Check className="h-3 w-3" />}
{Math.round(
taggingStatus[folder.folder_id]?.tagging_percentage ??
0,
)}
%
</span>
</div>
<Progress
value={
taggingStatus[folder.folder_id]?.tagging_percentage ?? 0
}
indicatorClassName={
(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100
? 'bg-green-500'
: 'bg-blue-500'
}
/>
</div>
)}
</div>
))}
</div>
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/types/FolderStatus.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading