Skip to content

Conversation

@Hemil36
Copy link
Contributor

@Hemil36 Hemil36 commented Oct 3, 2025

Fixes : #550

This pull request introduces a new feature that allows users to manually trigger a global face reclustering operation from the application's settings page. This operation rebuilds all face clusters from scratch, bypassing the usual 24-hour check, and updates both the backend API and frontend UI to support this functionality. Additionally, several minor code cleanups and improvements are included.

Global Face Reclustering Feature:

  • Added a new backend API endpoint /face-clusters/global-recluster (POST) that performs a full reclustering of all faces, updates clusters and assignments, generates cluster images, and updates metadata. Returns a response with the number of clusters created. (backend/app/routes/face_clusters.py, docs/backend/backend_python/openapi.json)
  • Created a new response model GlobalReclusterResponse for the API, and documented the endpoint in OpenAPI. (docs/backend/backend_python/openapi.json)
  • Added utility imports and minor refactoring to support the new endpoint. (backend/app/routes/face_clusters.py, backend/app/database/face_clusters.py)

Frontend Integration:

  • Added a new API function triggerGlobalReclustering and endpoint mapping for the global recluster operation. (frontend/src/api/api-functions/face_clusters.ts, frontend/src/api/apiEndpoints.ts)
  • Updated the settings page (ApplicationControlsCard) to include a new "Recluster Faces" button, which calls the new API, shows loader/info dialogs, and handles errors. Updated the UI layout and description accordingly. (frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx)
image

Summary by CodeRabbit

  • New Features

    • Added a "Recluster Faces" button in Settings to manually trigger global face reclustering with loading, success, and error feedback.
    • Frontend action and API call to invoke global reclustering; response includes success, message, and number of clusters created.
  • Documentation

    • OpenAPI/schema updated to document the new POST /face-clusters/global-recluster endpoint and its response model.

@github-actions github-actions bot added backend enhancement New feature or request frontend labels Oct 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds a manual global face reclustering flow: backend POST /face-clusters/global-recluster endpoint and response schemas, a force-capable reclustering util path, frontend API/endpoint, and a "Recluster Faces" button in Settings wired to a mutation and feedback.

Changes

Cohort / File(s) Summary
Backend Route
backend/app/routes/face_clusters.py
Adds POST /face-clusters/global-recluster endpoint trigger_global_reclustering() that calls cluster_util_face_clusters_sync(force_full_reclustering=True), logs progress, returns GlobalReclusterResponse with data.clusters_created (0 allowed), and maps exceptions to HTTP 500 with ErrorResponse.
Backend Utility
backend/app/utils/face_clusters.py
Updates cluster_util_face_clusters_sync(force_full_reclustering: bool = False) to support forced full reclustering (delete/insert clusters, batch update faces, generate representative images, update metadata) and an incremental path; returns number processed.
Backend Schemas
backend/app/schemas/face_clusters.py
Adds GlobalReclusterData (clusters_created: Optional[int]) and GlobalReclusterResponse (success: bool, message?: str, error?: str, data?: GlobalReclusterData) models.
API Docs
docs/backend/backend_python/openapi.json
Declares POST /face-clusters/global-recluster under Face Clusters tag; 200 response references GlobalReclusterResponse; 500 references ErrorResponse.
Frontend API Layer
frontend/src/api/apiEndpoints.ts, frontend/src/api/api-functions/face_clusters.ts
Adds faceClustersEndpoints.globalRecluster = '/face-clusters/global-recluster' and triggerGlobalReclustering(): Promise<APIResponse> which POSTs to the endpoint and returns the response data.
Frontend UI
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx
Adds reclusterMutation via usePictoMutation(triggerGlobalReclustering, { autoInvalidateTags: ['clusters'] }), integrates useMutationFeedback, updates layout to a grid, and adds a "Recluster Faces" button (Users icon) wired to trigger the mutation and show feedback.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Settings UI
    participant API as Frontend API
    participant Backend as Backend Route
    participant Util as Clustering Util
    participant DB as Database

    User->>UI: Click "Recluster Faces"
    UI->>API: triggerGlobalReclustering()
    API->>Backend: POST /face-clusters/global-recluster
    Backend->>Util: cluster_util_face_clusters_sync(force_full_reclustering=True)
    Util->>DB: Query faces & compute clusters
    alt Faces Found
        Util->>DB: Delete existing clusters
        Util->>DB: Batch insert new clusters
        Util->>DB: Batch update face cluster IDs
        Util->>DB: Generate/update representative images
        Util->>Backend: return clusters_created
    else No Faces
        Util->>Backend: return 0
    end
    Backend->>API: GlobalReclusterResponse { success, message, data: { clusters_created } }
    API->>UI: Response
    UI->>UI: Show feedback, invalidate 'clusters' tag
    UI->>User: Display result message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to transactional safety and bulk DB operations in cluster_util_face_clusters_sync.
  • Verify error handling and response shape in trigger_global_reclustering.
  • Confirm frontend mutation config (autoInvalidateTags: ['clusters']) and feedback UI wiring.
  • Check OpenAPI schema matches backend models.

Possibly related PRs

Poem

🐰 I twitch my whiskers, press the shiny key,
Faces shuffle, clusters hum — a tidy jamboree.
From scattered sprites to ordered, cozy packs,
I hop, I nudge, and watch the sorting stacks.
Tiny paws applaud — reclustering's free!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: add global face reclustering option in settings with backend API and UI support" accurately reflects the primary changes across the entire changeset. The PR introduces a new global face reclustering feature with backend endpoint support (POST /face-clusters/global-recluster), response models, utility function updates, and frontend integration including a new "Recluster Faces" button in the settings page. The title uses clear, specific language with appropriate conventional commit prefix and captures all major components without unnecessary verbosity or generic phrasing. It provides sufficient clarity for developers scanning commit history to understand the purpose of this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20d16e5 and 8d98416.

📒 Files selected for processing (8)
  • backend/app/database/face_clusters.py (1 hunks)
  • backend/app/routes/albums.py (1 hunks)
  • backend/app/routes/face_clusters.py (3 hunks)
  • backend/app/utils/images.py (2 hunks)
  • docs/backend/backend_python/openapi.json (2 hunks)
  • frontend/src/api/api-functions/face_clusters.ts (1 hunks)
  • frontend/src/api/apiEndpoints.ts (1 hunks)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/api/api-functions/face_clusters.ts (3)
frontend/src/types/API.ts (1)
  • APIResponse (1-8)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/api/apiEndpoints.ts (1)
  • faceClustersEndpoints (5-11)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/features/loaderSlice.ts (2)
  • showLoader (17-20)
  • hideLoader (21-24)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (54-59)
frontend/src/features/infoDialogSlice.ts (1)
  • showInfoDialog (16-30)
backend/app/routes/face_clusters.py (6)
backend/app/utils/face_clusters.py (4)
  • cluster_util_cluster_all_face_embeddings (134-212)
  • to_dict (41-48)
  • _generate_cluster_face_image (513-549)
  • _update_cluster_face_image (311-343)
backend/app/database/face_clusters.py (2)
  • db_delete_all_clusters (179-195)
  • db_insert_clusters_batch (38-77)
backend/app/database/faces.py (1)
  • db_update_face_cluster_ids_batch (271-311)
backend/app/database/metadata.py (2)
  • db_get_metadata (29-49)
  • db_update_metadata (52-75)
backend/app/utils/FaceNet.py (1)
  • FaceNet_util_cosine_similarity (20-23)
backend/app/schemas/images.py (1)
  • ErrorResponse (53-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
🔇 Additional comments (1)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (1)

33-75: Great end-to-end UX wiring for reclustering

Loader + dialog sequencing mirrors our existing update flow, so the new control feels native and keeps users informed through the long-running operation. Nicely done.

@Hemil36 Hemil36 marked this pull request as draft October 3, 2025 16:02
…sistency

feat: enhance global face reclustering functionality with force option
docs: update API description for global reclustering endpoint
@Hemil36 Hemil36 marked this pull request as ready for review October 3, 2025 17:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/utils/face_clusters.py (1)

103-123: Critical: Orphaned face cluster references after reclustering.

After db_delete_all_clusters() at line 103, any face marked as noise by DBSCAN (not present in results) retains its old cluster_id, which now points to a deleted cluster. Only faces present in results are updated by db_update_face_cluster_ids_batch(results) at line 123, leaving orphaned faces with invalid foreign key references.

Before inserting new clusters, clear cluster_id for all faces or only for faces not in results:

+from app.database.faces import db_get_all_faces_with_cluster_names
+
         # Clear old clusters first
         db_delete_all_clusters()
+
+        # Clear orphaned face cluster references
+        all_face_ids = {
+            face["face_id"] for face in db_get_all_faces_with_cluster_names()
+        }
+        reassigned_face_ids = {mapping["face_id"] for mapping in results}
+        faces_to_clear = [
+            {"face_id": face_id, "cluster_id": None}
+            for face_id in all_face_ids - reassigned_face_ids
+        ]
+        if faces_to_clear:
+            db_update_face_cluster_ids_batch(faces_to_clear)

         # Extract unique clusters with their names (without face images yet)
🧹 Nitpick comments (4)
backend/app/database/folders.py (1)

385-387: Remove unnecessary parentheses around return type annotation.

The extra parentheses wrapping the return type annotation serve no functional purpose in Python and are inconsistent with standard Python typing conventions.

Apply this diff:

-def db_get_all_folder_details() -> (
-    List[Tuple[str, str, Optional[str], int, bool, Optional[bool]]]
-):
+def db_get_all_folder_details() -> List[Tuple[str, str, Optional[str], int, bool, Optional[bool]]]:
backend/app/utils/face_clusters.py (1)

137-141: Consider clarifying return value semantics.

The function returns different semantic values depending on the path taken:

  • Full reclustering (line 137): cluster count
  • Incremental (line 141): face assignment count

While this works for the current global reclustering endpoint (which always forces full reclustering), the inconsistency could confuse future callers or lead to incorrect result interpretation.

Consider one of these approaches:

Option 1: Return a structured result:

from typing import NamedTuple

class ClusteringResult(NamedTuple):
    clusters_created: int
    faces_assigned: int
    was_full_recluster: bool

# Update return statements accordingly

Option 2: Add type hints and document the behavior:

def cluster_util_face_clusters_sync(force_full_reclustering: bool = False) -> int:
    """
    ...
    Returns:
        int: Number of clusters created (full reclustering) or 
             number of faces assigned (incremental)
    """
backend/app/routes/face_clusters.py (2)

325-325: Consider moving import to module level.

Importing inside the function can impact performance on repeated calls and typically indicates a code organization issue. Unless this is necessary to avoid circular imports, move the import to the top of the file with other imports.

 from app.schemas.images import AddSingleImageRequest
 from app.utils.FaceNet import FaceNet_util_cosine_similarity
+from app.utils.face_clusters import cluster_util_face_clusters_sync

Then remove line 325 from the function body.


336-340: Redundant ternary expression.

Line 339 uses result if result else 0, but since line 329 already handles the result == 0 case with an early return, result is guaranteed to be non-zero at this point. The ternary is unnecessary.

         return GlobalReclusterResponse(
             success=True,
             message="Global reclustering completed successfully.",
-            clusters_created=result if result else 0,
+            clusters_created=result,
         )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d98416 and 4a60b36.

📒 Files selected for processing (6)
  • backend/app/database/faces.py (2 hunks)
  • backend/app/database/folders.py (1 hunks)
  • backend/app/routes/face_clusters.py (1 hunks)
  • backend/app/utils/face_clusters.py (2 hunks)
  • docs/backend/backend_python/openapi.json (2 hunks)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/app/database/faces.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/backend/backend_python/openapi.json
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
backend/app/routes/face_clusters.py (2)
backend/app/schemas/face_clusters.py (1)
  • ErrorResponse (23-26)
backend/app/utils/face_clusters.py (1)
  • cluster_util_face_clusters_sync (84-141)
backend/app/utils/face_clusters.py (2)
backend/app/database/metadata.py (1)
  • db_get_metadata (29-49)
backend/app/database/faces.py (1)
  • db_update_face_cluster_ids_batch (271-311)
🔇 Additional comments (4)
backend/app/utils/face_clusters.py (1)

84-91: LGTM! Clear parameter addition.

The force_full_reclustering parameter is well-named, properly typed, and documented. The False default preserves backward compatibility.

backend/app/routes/face_clusters.py (3)

305-309: LGTM! Response model is well-defined.

The GlobalReclusterResponse model has clear, required fields that accurately represent the reclustering operation result.


311-320: LGTM! Endpoint definition is appropriate.

The POST method, path, response model, and error handling configuration are all suitable for a manual reclustering trigger.


342-351: LGTM! Error handling is comprehensive.

The exception handling properly logs errors and returns structured error responses. The exposure of exception details in the response is consistent with other endpoints in this file and acceptable for an internal admin operation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff0db6f and 1e6746c.

📒 Files selected for processing (1)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (54-59)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
🔇 Additional comments (5)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5)

2-7: LGTM!

The Users icon import is properly added and used for the new "Recluster Faces" button.


18-20: LGTM!

The required imports for the global reclustering feature are properly added. The use of usePictoMutation and useMutationFeedback addresses the previous review feedback. Based on learnings


40-42: LGTM!

The mutation setup correctly uses usePictoMutation with the reclustering API function.


56-58: LGTM!

The click handler correctly invokes the mutation with undefined since the reclustering API requires no parameters.


117-117: LGTM!

The UI updates are well-implemented:

  • The description now appropriately mentions face clustering.
  • The layout change to a responsive grid (grid-cols-1 md:grid-cols-3) provides better responsive behavior.
  • The new "Recluster Faces" button follows the existing styling patterns, includes a helpful tooltip, and is properly wired to the handler.

Also applies to: 119-119, 142-152

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (1)

63-65: Consider disabling the button during reclustering.

The handler correctly triggers the mutation. However, adding disabled={reclusterMutation.isPending} to the button would prevent users from triggering multiple simultaneous reclustering operations.

Apply this diff to disable the button while reclustering is in progress:

 <Button
   onClick={onGlobalReclusterClick}
+  disabled={reclusterMutation.isPending}
   variant="outline"
   className="flex h-12 w-full gap-3"
   title="Rebuild all face clusters from scratch using latest clustering algorithms"
 >

Also applies to: 149-159

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e6746c and 33faa7e.

📒 Files selected for processing (1)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (54-59)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
🔇 Additional comments (6)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (6)

2-7: LGTM! Icon import added correctly.

The Users icon import from lucide-react is appropriate for the face reclustering button.


18-20: Previous review feedback addressed correctly.

The imports for triggerGlobalReclustering, usePictoMutation, and useMutationFeedback correctly implement the pattern suggested in prior review comments.


40-43: Mutation setup is correct.

The usePictoMutation configuration properly wires the reclustering API and ensures cluster queries are invalidated after completion.


45-60: Correctly implements reactive feedback options.

This implementation matches Option 2 from the previous review comment. The useMemo dependencies on reclusterMutation.successMessage and reclusterMutation.errorMessage ensure that useMutationFeedback receives the updated backend messages when the mutation completes, resolving the timing issue flagged earlier.


124-124: Description accurately reflects the expanded functionality.


126-126: Responsive grid layout improves button presentation.

The switch to a responsive grid (grid-cols-1 md:grid-cols-3) ensures consistent sizing and spacing for the three control buttons across different screen sizes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (2)

60-64: Consider adding user confirmation before reclustering.

While the mutation and feedback hooks are correctly implemented, global face reclustering can be a resource-intensive operation. Consider adding a confirmation dialog before starting the operation to prevent accidental triggers.

Example implementation:

  const onGlobalReclusterClick = () => {
+   dispatch(
+     showInfoDialog({
+       title: 'Confirm Global Reclustering',
+       message: 'This will rebuild all face clusters from scratch. This operation may take several minutes. Do you want to continue?',
+       variant: 'warning',
+       onConfirm: () => {
          reclusterMutation.mutate(undefined);
+       },
+     }),
+   );
-   reclusterMutation.mutate(undefined);
  };

Note: Verify that showInfoDialog supports onConfirm callback, or use an alternative confirmation dialog component.


148-158: LGTM! Button implementation is consistent and well-structured.

The new button follows the established patterns and includes a helpful tooltip description.

Optional: Disable button during operation.

While the loading feedback provides visual indication, explicitly disabling the button prevents accidental multiple clicks:

  <Button
    onClick={onGlobalReclusterClick}
    variant="outline"
    className="flex h-12 w-full gap-3"
    title="Rebuild all face clusters from scratch using latest clustering algorithms"
+   disabled={reclusterMutation.isPending}
  >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33faa7e and d4f67e4.

📒 Files selected for processing (1)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (54-59)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tauri Build Check (windows-latest)
🔇 Additional comments (5)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5)

2-7: LGTM! Imports are correct and properly used.

The new imports for the Users icon, API function, and mutation hooks are all properly utilized in the component.

Also applies to: 18-20


40-43: LGTM! Mutation setup follows best practices.

The mutation is correctly configured with automatic cache invalidation for cluster-related queries.


45-58: LGTM! Dynamic message handling is correctly implemented.

The memoized feedback options with proper dependencies ensure that backend messages are displayed when available while providing sensible fallbacks. This correctly addresses the previous review concern about non-functional dynamic message references.


123-123: LGTM! Description accurately reflects new functionality.

The updated description clearly communicates that face clustering management is now available in this section.


125-125: LGTM! Responsive grid layout accommodates the new button well.

The grid layout provides good structure for the three control buttons with appropriate responsive behavior.

@rahulharpal1603
Copy link
Contributor

@Hemil36
Please resolve conflicts.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/utils/face_clusters.py (1)

106-139: Transaction safety not implemented despite docstring claim.

The docstring promises "transaction safety" but the code performs multiple separate database operations without wrapping them in a transaction. If any operation fails (delete, insert, update faces, update metadata), the database will be left in an inconsistent state.

For a user-triggered global recluster operation, this is a critical data integrity risk.

Wrap the full reclustering block in a transaction:

import sqlite3
from app.config.settings import DATABASE_PATH

conn = sqlite3.connect(DATABASE_PATH)
try:
    conn.execute("BEGIN TRANSACTION")
    
    # Perform clustering operation
    results = cluster_util_cluster_all_face_embeddings()
    
    if not results:
        conn.rollback()
        return 0
    
    results = [result.to_dict() for result in results]
    
    # All database operations within the transaction
    # ... (delete, insert, update operations)
    
    conn.commit()
    return len(cluster_list)
except Exception as e:
    conn.rollback()
    logger.error(f"Error during face clustering: {e}")
    raise
finally:
    conn.close()

Alternatively, refactor the database functions to accept a connection object and manage the transaction at this level.

🧹 Nitpick comments (3)
backend/app/utils/face_clusters.py (2)

101-102: Verify: Silent success on empty clustering results.

Returning 0 when results is empty may be ambiguous—it could mean either "no faces to cluster" or "clustering produced no valid clusters." Consider logging an info message here to distinguish this case from the normal success path at line 141.

Add logging to clarify the early return:

 if not results:
+    logger.info("No clustering results to process (no faces or all noise)")
     return 0

141-145: Note: Return values have different semantics.

Line 141 returns the number of clusters created (full reclustering), while line 145 returns the number of face assignments made (incremental). Both are valid success metrics, but callers should be aware that the meaning differs based on which path is taken.

Consider documenting the return value semantics in the docstring:

 def cluster_util_face_clusters_sync(force_full_reclustering: bool = False):
     """
     Smart face clustering with transaction safety.
     Decides between full reclustering or incremental assignment based on 24-hour rule and face count.
 
     Args:
         force_full_reclustering: If True, forces full reclustering regardless of 24-hour rule
+
+    Returns:
+        int: Number of clusters created (full reclustering) or number of faces assigned (incremental)
     """
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (1)

148-158: Minor styling inconsistency with other buttons.

The new "Recluster Faces" button is missing the cursor-pointer class that's present on the other two buttons (lines 129 and 140). While buttons have a pointer cursor by default, maintaining consistency with the existing buttons would be cleaner.

Apply this diff for consistency:

 <Button
   onClick={onGlobalReclusterClick}
   variant="outline"
-  className="flex h-12 w-full gap-3"
+  className="flex h-12 w-full cursor-pointer gap-3"
   title="Rebuild all face clusters from scratch using latest clustering algorithms"
 >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4f67e4 and 17459fc.

📒 Files selected for processing (6)
  • backend/app/routes/face_clusters.py (1 hunks)
  • backend/app/utils/face_clusters.py (2 hunks)
  • docs/backend/backend_python/openapi.json (2 hunks)
  • frontend/src/api/api-functions/face_clusters.ts (1 hunks)
  • frontend/src/api/apiEndpoints.ts (1 hunks)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/api/apiEndpoints.ts
  • docs/backend/backend_python/openapi.json
  • backend/app/routes/face_clusters.py
🧰 Additional context used
🧬 Code graph analysis (3)
backend/app/utils/face_clusters.py (2)
backend/app/database/metadata.py (1)
  • db_get_metadata (33-55)
backend/app/database/faces.py (1)
  • db_update_face_cluster_ids_batch (288-330)
frontend/src/api/api-functions/face_clusters.ts (3)
frontend/src/types/API.ts (1)
  • APIResponse (1-8)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/api/apiEndpoints.ts (1)
  • faceClustersEndpoints (5-12)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (68-73)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
🔇 Additional comments (10)
backend/app/utils/face_clusters.py (2)

88-95: LGTM! Backward-compatible API enhancement.

The new force_full_reclustering parameter is well-documented and maintains backward compatibility with existing callers.


97-97: LGTM! Correctly implements forced reclustering.

The conditional logic properly bypasses the 24-hour rule when force_full_reclustering is True, as intended by the PR objectives.

frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (7)

2-7: LGTM!

Icon imports are properly organized and the new Users icon is correctly used for the face reclustering button.


18-20: LGTM!

Required imports for the mutation flow are properly added and used throughout the component.


40-43: LGTM!

The mutation is properly configured with automatic cache invalidation for the 'clusters' tag, ensuring the UI reflects the new clustering results.


45-58: LGTM!

The feedback options correctly use reactive properties from usePictoMutation with proper memoization, addressing the previous review comment. The implementation now properly handles dynamic messages from the backend.


60-64: LGTM!

The mutation feedback hook is correctly applied, and the click handler properly triggers the mutation without parameters.


123-123: LGTM!

The card description is appropriately updated to reflect the new face clustering functionality.


125-125: LGTM!

The responsive grid layout properly accommodates the three action buttons with appropriate breakpoints.

frontend/src/api/api-functions/face_clusters.ts (1)

68-73: Verify API timeout for long-running operation.

The apiClient has a 10-second timeout. The /face-clusters/global-recluster endpoint performs a synchronous operation that returns full results immediately (including clusters_created data in the response). For large photo collections, this global reclustering operation may exceed the 10-second timeout. Verify whether this timeout is adequate for your typical use cases or consider extending it for this endpoint.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17459fc and 48c5ee4.

📒 Files selected for processing (2)
  • backend/app/routes/face_clusters.py (2 hunks)
  • backend/app/schemas/face_clusters.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/routes/face_clusters.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)

@Hemil36 Hemil36 marked this pull request as draft October 30, 2025 08:29
…a models; update related components for improved response handling
@Hemil36 Hemil36 marked this pull request as ready for review October 30, 2025 09:09
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/app/routes/face_clusters.py (1)

304-346: Consider moving the import to module level.

The import of cluster_util_face_clusters_sync on Line 318 is placed inside the function. While this works, imports are conventionally placed at the module level in Python. Moving it to the top improves IDE support, linting accuracy, and marginally improves performance.

Apply this diff to move the import to the module level:

 from app.database.face_clusters import (
     db_get_cluster_by_id,
     db_update_cluster,
     db_get_all_clusters_with_face_counts,
     db_get_images_by_cluster_id,  # Add this import
 )
+from app.utils.face_clusters import cluster_util_face_clusters_sync
 from app.schemas.face_clusters import (

Then remove the import from inside the function:

     try:
         logger.info("Starting manual global face reclustering...")
 
-        # Use the smart clustering function with force flag set to True
-        from app.utils.face_clusters import cluster_util_face_clusters_sync
-
         result = cluster_util_face_clusters_sync(force_full_reclustering=True)

Note: If this import is placed inside the function to avoid a circular import, you may safely disregard this suggestion and add a comment explaining the reason.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 665e80f and 9db4b49.

📒 Files selected for processing (4)
  • backend/app/routes/face_clusters.py (2 hunks)
  • backend/app/schemas/face_clusters.py (1 hunks)
  • docs/backend/backend_python/openapi.json (2 hunks)
  • frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
backend/app/routes/face_clusters.py (2)
backend/app/schemas/face_clusters.py (3)
  • GlobalReclusterResponse (81-85)
  • GlobalReclusterData (77-78)
  • ErrorResponse (23-26)
backend/app/utils/face_clusters.py (1)
  • cluster_util_face_clusters_sync (88-145)
frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (3)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • triggerGlobalReclustering (68-73)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
🔇 Additional comments (8)
backend/app/schemas/face_clusters.py (1)

77-85: LGTM! Previous feedback addressed.

The response model structure now correctly aligns with the established pattern used by other response models in this file. The use of optional fields for message, error, and nested data provides consistent error handling across the API.

docs/backend/backend_python/openapi.json (2)

1145-1176: LGTM! OpenAPI spec correctly documents the new endpoint.

The endpoint definition is clear and consistent with other Face Clusters endpoints. The description accurately conveys that this operation bypasses the normal 24-hour reclustering rule.


1997-2058: LGTM! Schema definitions align with backend models.

The OpenAPI schema definitions for GlobalReclusterData and GlobalReclusterResponse correctly match the Pydantic models and follow the established response pattern throughout the API.

backend/app/routes/face_clusters.py (1)

21-22: LGTM! Imports follow the established pattern.

The new response model imports are correctly placed with other schema imports from app.schemas.face_clusters.

frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx (4)

2-7: LGTM! Icon import updated correctly.

The Users icon from lucide-react is appropriately chosen for the face reclustering feature.


18-20: LGTM! Required dependencies imported.

The imports for triggerGlobalReclustering, usePictoMutation, and useMutationFeedback are correctly added to support the new reclustering feature.


40-70: LGTM! Mutation and feedback properly configured.

The implementation correctly addresses previous review feedback:

  • Uses usePictoMutation with proper configuration
  • Memoizes feedback options with correct dependencies to ensure dynamic messages update reactively
  • Auto-invalidates the clusters query key to refresh cluster data after reclustering
  • Properly extracts successMessage and errorMessage from the mutation state

129-165: LGTM! UI updated with new reclustering control.

The changes are well-executed:

  • Updated description accurately reflects the new capability
  • Grid layout provides a clean, responsive design for the three controls
  • The "Recluster Faces" button includes a helpful title attribute explaining its purpose
  • Button styling is consistent with the existing controls

Copy link
Contributor

@rahulharpal1603 rahulharpal1603 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Hemil36!

@rahulharpal1603 rahulharpal1603 merged commit 67d2434 into AOSSIE-Org:main Oct 30, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Manual Face Clustering Option During Folder Addition

2 participants