-
Notifications
You must be signed in to change notification settings - Fork 278
fix: display proper status for empty folders in AI tagging #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||
| import React from 'react'; | ||||||
| import { Folder, Trash2, Check } from 'lucide-react'; | ||||||
| import { Folder, Trash2, Check, Loader2, AlertCircle } from 'lucide-react'; | ||||||
|
|
||||||
| import { Switch } from '@/components/ui/switch'; | ||||||
| import { Button } from '@/components/ui/button'; | ||||||
|
|
@@ -28,6 +28,27 @@ const FolderManagementCard: React.FC = () => { | |||||
| const taggingStatus = useSelector( | ||||||
| (state: RootState) => state.folders.taggingStatus, | ||||||
| ); | ||||||
| const folderStatusTimestamps = useSelector( | ||||||
| (state: RootState) => state.folders.folderStatusTimestamps, | ||||||
| ); | ||||||
|
|
||||||
| const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => { | ||||||
| if (!folderHasAITagging) return false; | ||||||
|
|
||||||
| const status = taggingStatus[folderId]; | ||||||
| if (!status) return true; | ||||||
|
|
||||||
| const timestamp = folderStatusTimestamps[folderId]; | ||||||
| if (!timestamp) return true; | ||||||
|
|
||||||
| const timeSinceUpdate = Date.now() - timestamp; | ||||||
|
|
||||||
| if (status.total_images === 0 && timeSinceUpdate < 3000) { | ||||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| return false; | ||||||
| }; | ||||||
|
|
||||||
| return ( | ||||||
| <SettingsCard | ||||||
|
|
@@ -37,84 +58,98 @@ const FolderManagementCard: React.FC = () => { | |||||
| > | ||||||
| {folders.length > 0 ? ( | ||||||
| <div className="space-y-3"> | ||||||
| {folders.map((folder: FolderDetails, index: number) => ( | ||||||
| <div | ||||||
| key={index} | ||||||
| className="group border-border bg-background/50 relative rounded-lg border p-4 transition-all hover:border-gray-300 hover:shadow-sm dark:hover:border-gray-600" | ||||||
| > | ||||||
| <div className="flex items-center justify-between"> | ||||||
| <div className="min-w-0 flex-1"> | ||||||
| <div className="flex items-center gap-3"> | ||||||
| <Folder className="h-4 w-4 flex-shrink-0 text-gray-500 dark:text-gray-400" /> | ||||||
| <span className="text-foreground truncate"> | ||||||
| {folder.folder_path} | ||||||
| </span> | ||||||
| </div> | ||||||
| </div> | ||||||
| {folders.map((folder: FolderDetails, index: number) => { | ||||||
| const status = taggingStatus[folder.folder_id]; | ||||||
| const loading = isStatusLoading( | ||||||
| folder.folder_id, | ||||||
| folder.AI_Tagging, | ||||||
| ); | ||||||
| const hasImages = status && status.total_images > 0; | ||||||
| const isEmpty = status && status.total_images === 0 && !loading; | ||||||
| const isComplete = status && status.tagging_percentage >= 100; | ||||||
|
|
||||||
| <div className="ml-4 flex items-center gap-4"> | ||||||
| <div className="flex items-center gap-3"> | ||||||
| <span className="text-muted-foreground text-sm"> | ||||||
| AI Tagging | ||||||
| </span> | ||||||
| <Switch | ||||||
| className="cursor-pointer" | ||||||
| checked={folder.AI_Tagging} | ||||||
| onCheckedChange={() => toggleAITagging(folder)} | ||||||
| disabled={ | ||||||
| enableAITaggingPending || disableAITaggingPending | ||||||
| } | ||||||
| /> | ||||||
| return ( | ||||||
| <div | ||||||
| key={index} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use Using array Apply this diff: - key={index}
+ key={folder.folder_id}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| className="group border-border bg-background/50 relative rounded-lg border p-4 transition-all hover:border-gray-300 hover:shadow-sm dark:hover:border-gray-600" | ||||||
| > | ||||||
| <div className="flex items-center justify-between"> | ||||||
| <div className="min-w-0 flex-1"> | ||||||
| <div className="flex items-center gap-3"> | ||||||
| <Folder className="h-4 w-4 flex-shrink-0 text-gray-500 dark:text-gray-400" /> | ||||||
| <span className="text-foreground truncate"> | ||||||
| {folder.folder_path} | ||||||
| </span> | ||||||
| </div> | ||||||
| </div> | ||||||
|
|
||||||
| <Button | ||||||
| onClick={() => deleteFolder(folder.folder_id)} | ||||||
| variant="outline" | ||||||
| size="sm" | ||||||
| className="h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400" | ||||||
| disabled={deleteFolderPending} | ||||||
| > | ||||||
| <Trash2 className="h-4 w-4" /> | ||||||
| </Button> | ||||||
| </div> | ||||||
| </div> | ||||||
| <div className="ml-4 flex items-center gap-4"> | ||||||
| <div className="flex items-center gap-3"> | ||||||
| <span className="text-muted-foreground text-sm"> | ||||||
| AI Tagging | ||||||
| </span> | ||||||
| <Switch | ||||||
| className="cursor-pointer" | ||||||
| checked={folder.AI_Tagging} | ||||||
| onCheckedChange={() => toggleAITagging(folder)} | ||||||
| disabled={ | ||||||
| enableAITaggingPending || disableAITaggingPending | ||||||
| } | ||||||
| /> | ||||||
| </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' | ||||||
| } | ||||||
| <Button | ||||||
| onClick={() => deleteFolder(folder.folder_id)} | ||||||
| variant="outline" | ||||||
| size="sm" | ||||||
| className="h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400" | ||||||
| disabled={deleteFolderPending} | ||||||
| > | ||||||
| {(taggingStatus[folder.folder_id]?.tagging_percentage ?? | ||||||
| 0) >= 100 && <Check className="h-3 w-3" />} | ||||||
| {Math.round( | ||||||
| taggingStatus[folder.folder_id]?.tagging_percentage ?? | ||||||
| 0, | ||||||
| )} | ||||||
| % | ||||||
| </span> | ||||||
| <Trash2 className="h-4 w-4" /> | ||||||
| </Button> | ||||||
| </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> | ||||||
| ))} | ||||||
|
|
||||||
| {folder.AI_Tagging && ( | ||||||
| <div className="mt-3"> | ||||||
| {loading ? ( | ||||||
| <div className="text-muted-foreground flex items-center gap-2 text-xs"> | ||||||
| <Loader2 className="h-3 w-3 animate-spin" /> | ||||||
| <span>Loading status...</span> | ||||||
| </div> | ||||||
| ) : isEmpty ? ( | ||||||
| <div className="text-muted-foreground flex items-center gap-2 text-xs"> | ||||||
| <AlertCircle className="h-3 w-3" /> | ||||||
| <span>No images found in this folder</span> | ||||||
| </div> | ||||||
| ) : hasImages ? ( | ||||||
| <> | ||||||
| <div className="text-muted-foreground mb-1 flex items-center justify-between text-xs"> | ||||||
| <span>AI Tagging Progress</span> | ||||||
| <span | ||||||
| className={ | ||||||
| isComplete | ||||||
| ? 'flex items-center gap-1 text-green-500' | ||||||
| : 'text-muted-foreground' | ||||||
| } | ||||||
| > | ||||||
| {isComplete && <Check className="h-3 w-3" />} | ||||||
| {Math.round(status.tagging_percentage)}% | ||||||
| </span> | ||||||
| </div> | ||||||
| <Progress | ||||||
| value={status.tagging_percentage} | ||||||
| indicatorClassName={ | ||||||
| isComplete ? 'bg-green-500' : 'bg-blue-500' | ||||||
| } | ||||||
| /> | ||||||
| </> | ||||||
| ) : null} | ||||||
| </div> | ||||||
| )} | ||||||
| </div> | ||||||
| ); | ||||||
| })} | ||||||
| </div> | ||||||
| ) : ( | ||||||
| <div className="py-8 text-center"> | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing timestamp more gracefully to avoid indefinite loading.
If
statusexists buttimestampis missing (line 42), the function returnstrue(loading), which would show a loading spinner indefinitely. This edge case could occur if state is restored from persistence without timestamps or in rare race conditions.Consider treating a missing timestamp as "skip grace period" rather than "still loading":
const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => { if (!folderHasAITagging) return false; const status = taggingStatus[folderId]; if (!status) return true; const timestamp = folderStatusTimestamps[folderId]; - if (!timestamp) return true; - - const timeSinceUpdate = Date.now() - timestamp; + // If no timestamp, treat as "no grace period needed" + const timeSinceUpdate = timestamp ? Date.now() - timestamp : Infinity; if (status.total_images === 0 && timeSinceUpdate < 3000) { return true; } return false; };This way, if the timestamp is missing,
timeSinceUpdateisInfinity, so the 3-second grace period check is bypassed and the actual status (empty or progress) is shown.📝 Committable suggestion
🤖 Prompt for AI Agents