Skip to content

[Feat] - Webcam Feature#593

Closed
tushar1977 wants to merge 52 commits intoAOSSIE-Org:mainfrom
tushar1977:main
Closed

[Feat] - Webcam Feature#593
tushar1977 wants to merge 52 commits intoAOSSIE-Org:mainfrom
tushar1977:main

Conversation

@tushar1977
Copy link
Copy Markdown
Contributor

@tushar1977 tushar1977 commented Oct 28, 2025

Issue - #502

Added a backend API route to handle face searches using Base64-encoded image data.
Integrated frontend support for capturing images directly from the webcam and sending them to the backend for processing.
Video Ref :- https://drive.google.com/file/d/1hjGRv23Jhul-N_f9bcG4MfUmCyuWt3kT/view?usp=sharing

Summary by CodeRabbit
New Features

Face search accepts either file path or base64 image input; base64 searches (including webcam captures) supported.
Added a webcam capture component and a base64-based face-search API call.
User Experience

Face search dialog redesigned with "Upload Photo" and "Use Webcam", including capture review and retake.
Image rendering now correctly handles data URL images.
Security & Configuration

macOS camera entitlement and tightened content-security policy for camera/media access.

Summary by CodeRabbit

  • New Features

    • Face search now accepts images by file path or base64 (including webcam captures).
    • Added webcam capture component to take, review, retake, and search photos.
  • User Experience

    • Redesigned face search dialog with distinct "Upload Photo" and "Use Webcam" actions.
    • Preserves data URLs when showing query images.
  • Documentation / Permissions

    • macOS camera permission string and app sandboxing enabled.
    • Frontend includes webcam runtime support dependency.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

Replaces the legacy image-tagging flow with a unified face-search pipeline supporting path and base64 inputs, centralizes detection/embedding/matching in a new utility, updates API schemas/endpoints, adds webcam capture in the frontend, and adds macOS camera entitlements and CSP updates.

Changes

Cohort / File(s) Summary
Backend Face Search Refactor
backend/app/routes/face_clusters.py, backend/app/schemas/images.py, backend/app/utils/faceSearch.py
Replaced legacy tagging with a face-search pipeline. Added FaceSearchRequest and InputType. Centralized detection, embedding, similarity matching into perform_face_search(). Added base64 decoding, temp-file handling, cleanup and standardized error responses; removed previous route-local models and embedding logic.
API Documentation Update
docs/backend/backend_python/openapi.json
Replaced AddSingleImageRequest with FaceSearchRequest, added input_type query param referencing InputType, marked requestBody required, and adjusted 400/500 response descriptions.
Frontend API Bindings
frontend/src/api/api-functions/face_clusters.ts, frontend/src/api/apiEndpoints.ts
Added FetchSearchedFacesBase64Request type and fetchSearchedFacesBase64(); updated endpoints to include ?input_type=path and new ?input_type=base64 entry.
Frontend UI Components
frontend/src/components/Dialog/FaceSearchDialog.tsx, frontend/src/components/WebCam/WebCamComponent.tsx, frontend/src/components/Navigation/Navbar/Navbar.tsx
Refactored dialog to expose Upload and Use Webcam actions, added WebcamComponent capturing base64 images and triggering face-search, and updated Navbar to preserve data URLs.
Frontend Dependencies & Config
frontend/package.json, frontend/src-tauri/Entitlements.plist, frontend/src-tauri/Info.plist, frontend/src-tauri/tauri.conf.json
Added react-webcam dependency; added macOS entitlements plist and NSCameraUsageDescription; updated Tauri macOS entitlements reference and CSP.
Frontend Hooks
frontend/src/hooks/useQueryExtension.ts
Extended mutation callbacks to forward an additional mutationContext parameter to user-provided onSuccess/onError/onSettled handlers.
Tauri Version Bump
frontend/src-tauri/Cargo.toml
Updated tauri dependency from 2.3.1 to 2.9.0.
Misc / Root
package.json
Trailing newline only.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as FaceSearchDialog / WebcamComponent
    participant API as Backend API (/face-clusters/face-search)
    participant FS as perform_face_search()

    User->>UI: Open dialog
    alt Upload file (path)
        User->>UI: Select file
        UI->>API: POST /face-clusters/face-search?input_type=path (body: path)
        API->>FS: validate path -> detect -> embed -> compare
    else Capture photo (webcam)
        User->>UI: Open webcam & capture
        UI->>API: POST /face-clusters/face-search?input_type=base64 (body: base64_data)
        API->>API: decode base64 -> write temp file -> validate
        API->>FS: detect -> embed -> compare
    end
    FS->>FS: compute embeddings, cosine similarity, filter by threshold
    FS-->>API: matches / message
    API-->>UI: return results or error
    UI-->>User: display matches or "no matches"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas to focus on:
    • base64 decoding, max payload enforcement, and error handling in the route
    • temp-file creation and guaranteed cleanup path
    • correctness of similarity computation and CONFIDENCE_PERCENT usage in perform_face_search
    • updated OpenAPI schema and query param integration
    • frontend webcam capture → base64 upload flow and mutation callback arity change

Possibly related PRs

Suggested labels

frontend

Suggested reviewers

  • rahulharpal1603

Poem

🐰 I hopped in code with camera bright,

Paths or base64, I took the sight.
I matched the faces, cleaned the trail,
A tiny hop, a whiskered tale.
🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "[Feat] - Webcam Feature" is partially related to the changeset. It clearly refers to a real and significant part of the changes—the addition of a webcam capture component on the frontend that enables users to record photos for face search. However, the title does not fully capture the scope of the changes, which include substantial backend work to support base64-encoded image input alongside path-based input to the face search API. The title focuses narrowly on the UI/frontend experience (webcam) while omitting mention of the backend architectural changes (base64 support, refactored face search pipeline) that enable the feature to work. Despite this narrower focus, the title is specific enough (not vague or generic) and clearly relates to a substantial part of the changeset.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 501552b and 0d56ea4.

📒 Files selected for processing (1)
  • frontend/src-tauri/Cargo.toml (1 hunks)
⏰ 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). (2)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
🔇 Additional comments (1)
frontend/src-tauri/Cargo.toml (1)

14-14: Continuing verification with more specific queries:

Continuing with more targeted searches for patch-level stability:

Verify Tauri 2.9.0 compatibility through testing; version bump appears stable.

Tauri 2 is stabilized and no longer expects breaking changes after the release candidate phase. The jump from 2.3.1 to 2.9.0 is a routine patch/minor update within the stable 2.x release cycle (approximately 6 months apart) and should not introduce breaking changes. Plugin versions can be pinned to patch updates for stable interfaces; security updates are backported and announced on GitHub.

However, verify that unpinned tauri-plugin-* dependencies (specified as "2" without patch pinning) are compatible with tauri 2.9.0 by running a test build. Also confirm tauri-build = "2.0.6" works with the newer core version.


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.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

Copy link
Copy Markdown
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: 7

Caution

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

⚠️ Outside diff range comments (2)
docs/backend/backend_python/openapi.json (1)

1069-1076: Specify the 200 response schema.
Expose GetAllImagesResponse for clients; today it’s {}.

Prefer to generate via FastAPI by adding response_model on the route (see route diff in backend/app/routes/face_clusters.py).

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

208-211: Add response_model for typed 200s (improves OpenAPI).
Aligns docs with actual return.

-@router.post(
-    "/face-search",
-    responses={code: {"model": ErrorResponse} for code in [400, 500]},
-)
+from app.schemas.images import GetAllImagesResponse
+@router.post(
+    "/face-search",
+    response_model=GetAllImagesResponse,
+    responses={code: {"model": ErrorResponse} for code in [400, 500]},
+)
🧹 Nitpick comments (12)
frontend/src-tauri/Info.plist (1)

5-6: Clarify camera usage description for users.

The description mentions "WebRTC" which may be unclear to end users who see this in the permission dialog. Consider a more user-friendly explanation that describes the feature benefit.

Apply this diff to improve the user-facing message:

   <key>NSCameraUsageDescription</key>
-  <string>Request camera access for WebRTC</string>
+  <string>PictoPy needs camera access to capture photos for face search</string>
frontend/src/api/apiEndpoints.ts (1)

7-8: Unify endpoints with a param builder to avoid duplication.
A single factory keeps callers consistent and simplifies future query params.

-  searchForFaces: '/face-clusters/face-search?input_type=path',
-  searchForFacesBase64: '/face-clusters/face-search?input_type=base64',
+  searchForFaces: (type: 'path' | 'base64' = 'path') =>
+    `/face-clusters/face-search?input_type=${type}`,
frontend/src/components/Dialog/FaceSearchDialog.tsx (3)

62-79: Add feature detection and clearer permission error.
Guard for missing mediaDevices; keep dialog open on failure.

 const handleWebCam = async () => {
-  try {
-    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+  try {
+    if (!('mediaDevices' in navigator) || !navigator.mediaDevices?.getUserMedia) {
+      throw new Error('MediaDevices API not available');
+    }
+    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
     stream.getTracks().forEach((track) => track.stop());
     navigate(`/${ROUTES.HOME}`);
     setIsDialogOpen(false);
     setShowCamera(true);
   } catch (error) {
     dispatch(
       showInfoDialog({
-        title: 'Webcam Not Supported',
-        message:
-          'Webcam is not supported or access was denied on this device.',
+        title: 'Webcam Not Available',
+        message: 'Camera access is unavailable or was denied.',
         variant: 'error',
       }),
     );
   }
 };

81-89: Navigate only after a file is chosen.
Avoid unintended page change when user cancels.

-  navigate(`/${ROUTES.HOME}`);
   const filePath = await pickSingleFile();
   if (filePath) {
+    navigate(`/${ROUTES.HOME}`);
     setIsDialogOpen(false);
     dispatch(startSearch(filePath));
     dispatch(showLoader('Searching faces...'));
     getSearchImages(filePath);
   }

116-142: Remove redundant disabled={false}.
Keeps JSX terse; defaults to enabled.

-  <Button
-    onClick={handlePickFile}
-    disabled={false}
+  <Button
+    onClick={handlePickFile}

(Same for the Webcam button.)

frontend/src/components/WebCam/WebCamComponent.tsx (2)

40-65: Avoid double notifications; let onSuccess decide.
Prevent a “Success” dialog followed by “No Match Found”.

-useMutationFeedback(getSearchImagesBase64, {
-  showLoading: true,
-  loadingMessage: 'Searching for images...',
-  successTitle: 'Search Complete',
-  successMessage: 'Images matching your search have been found.',
-  errorTitle: 'Search Error',
-  errorMessage: 'Failed to search images. Please try again.',
-  onSuccess: () => {
+useMutationFeedback(getSearchImagesBase64, {
+  showLoading: true,
+  loadingMessage: 'Searching for images...',
+  showSuccess: false,
+  errorTitle: 'Search Error',
+  errorMessage: 'Failed to search images. Please try again.',
+  onSuccess: () => {
     const result = getSearchImagesBase64.data?.data as Image[];
     if (result && result.length > 0) {
       dispatch(setResults(result));
+      dispatch(
+        showInfoDialog({
+          title: 'Search Complete',
+          message: `Found ${result.length} matching image(s).`,
+          variant: 'info',
+        }),
+      );
     } else {
       dispatch(
         showInfoDialog({
           title: 'No Match Found',
           message:
             'We couldn’t find any matching faces in your gallery for this photo.',
           variant: 'info',
         }),
       );
       dispatch(setResults([]));
       dispatch(clearSearch());
     }
     getSearchImagesBase64.reset();
   },
 });

125-131: Reduce payload size from the webcam.
Set JPEG quality to control base64 size and speed up upload.

-  <Webcam
+  <Webcam
     audio={false}
     ref={webcamRef}
-    screenshotFormat="image/jpeg"
+    screenshotFormat="image/jpeg"
+    screenshotQuality={0.9}
     videoConstraints={videoConstraints}
     className="w-full max-w-md rounded-lg border"
   />
backend/app/utils/faceSearch.py (2)

45-69: Avoid double embedding computation.
FaceDetector already initializes FaceNet and computes embeddings; consider returning embeddings from detect_faces (when forSearch=True) to eliminate the extra FaceNet session here.


78-83: Rename CONFIDENCE_PERCENT for clarity—currently misleading naming.

The constant is defined as 0.6 in backend/app/config/settings.py (using [0,1] scale), which correctly matches cosine similarity ranges. However, the name "CONFIDENCE_PERCENT" suggests a [0,100] scale. Rename to CONFIDENCE_THRESHOLD or SIMILARITY_THRESHOLD in settings and all usages.

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

1045-1056: Drop duplicate descriptions on parameter.
In some tools, $ref + sibling schema keys cause issues. Keep description at the parameter level.


1516-1543: Capture conditional requirements.
Consider oneOf to express: when input_type=path require path; when input_type=base64 require base64_data.

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

277-284: Consider allowing HEIC/HEIF if needed.
iOS captures may be HEIC. If supported by your pipeline, add to allowed list.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdcc9e1 and df38c90.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • backend/app/routes/face_clusters.py (3 hunks)
  • backend/app/schemas/images.py (1 hunks)
  • backend/app/utils/faceSearch.py (1 hunks)
  • docs/backend/backend_python/openapi.json (4 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src-tauri/Entitlements.plist (1 hunks)
  • frontend/src-tauri/Info.plist (1 hunks)
  • frontend/src-tauri/tauri.conf.json (2 hunks)
  • frontend/src/api/api-functions/face_clusters.ts (2 hunks)
  • frontend/src/api/apiEndpoints.ts (1 hunks)
  • frontend/src/components/Dialog/FaceSearchDialog.tsx (3 hunks)
  • frontend/src/components/Navigation/Navbar/Navbar.tsx (1 hunks)
  • frontend/src/components/WebCam/WebCamComponent.tsx (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
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/components/WebCam/WebCamComponent.tsx (6)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoMutation (26-78)
frontend/src/api/api-functions/face_clusters.ts (1)
  • fetchSearchedFacesBase64 (58-66)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
frontend/src/types/Media.ts (1)
  • Image (13-22)
frontend/src/features/searchSlice.ts (3)
  • setResults (24-26)
  • clearSearch (27-31)
  • startSearch (20-23)
frontend/src/features/infoDialogSlice.ts (1)
  • showInfoDialog (16-30)
backend/app/routes/face_clusters.py (2)
backend/app/schemas/images.py (3)
  • FaceSearchRequest (11-13)
  • InputType (6-8)
  • ErrorResponse (59-62)
backend/app/utils/faceSearch.py (1)
  • perform_face_search (35-106)
backend/app/utils/faceSearch.py (4)
backend/app/database/faces.py (1)
  • get_all_face_embeddings (144-216)
backend/app/models/FaceDetector.py (2)
  • FaceDetector (15-79)
  • detect_faces (26-67)
backend/app/models/FaceNet.py (2)
  • FaceNet (12-29)
  • get_embedding (20-25)
backend/app/utils/FaceNet.py (1)
  • FaceNet_util_cosine_similarity (20-23)
frontend/src/components/Dialog/FaceSearchDialog.tsx (4)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-11)
frontend/src/features/infoDialogSlice.ts (1)
  • showInfoDialog (16-30)
frontend/src/features/searchSlice.ts (1)
  • startSearch (20-23)
frontend/src/features/loaderSlice.ts (1)
  • showLoader (17-20)
⏰ 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: Tauri Build Check (windows-latest)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
🔇 Additional comments (8)
package.json (1)

20-20: LGTM!

Standard formatting improvement - adds trailing newline per convention.

frontend/src/components/Navigation/Navbar/Navbar.tsx (1)

36-40: LGTM!

The conditional logic correctly handles both data URLs (from webcam/base64) and file paths. Optional chaining safely handles undefined values.

frontend/src-tauri/tauri.conf.json (2)

29-30: LGTM!

The macOS configuration correctly references the entitlements file and uses ad-hoc signing (appropriate for development builds).


66-66: LGTM!

The CSP correctly adds media-src with required sources for webcam access and includes ipc: for Tauri communication. The directive syntax is correct.

backend/app/schemas/images.py (1)

6-8: LGTM!

The InputType enum clearly defines the two supported input modalities for face search.

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

18-20: LGTM!

The request type follows the established pattern and correctly defines the base64 input structure.


58-66: LGTM!

The function implementation follows the established pattern, correctly posts to the base64 endpoint, and maintains type safety.

frontend/package.json (1)

64-64: Test react-webcam functionality with React 19 in your application.

react-webcam@7.2.0 declares peerDependencies "react": ">=16.2.0" and "react-dom": ">=16.2.0", so React 19 satisfies the requirement. However, the package author may not have explicitly tested on React 19, so verify that the webcam functionality works as expected in your app before merging.

Comment on lines +255 to +267
MAX_B64_LEN = 14_000_000 # 10MB
if len(base64_data) > MAX_B64_LEN:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ErrorResponse(
success=False,
error="Payload too large",
message="Base64 image exceeds maximum allowed size.",
).model_dump(),
)
try:
image_bytes = base64.b64decode(base64_data.split(",")[-1])
except (Base64Error, ValueError):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Decode first, enforce byte-size, and use a secure temp file.
Current length check is on the base64 string (imprecise), and writes to a predictable path. Decode, validate bytes, respond with 413, and use tempfile.NamedTemporaryFile.

-        MAX_B64_LEN = 14_000_000  # 10MB
-        if len(base64_data) > MAX_B64_LEN:
-            raise HTTPException(
-                status_code=status.HTTP_400_BAD_REQUEST,
-                detail=ErrorResponse(
-                    success=False,
-                    error="Payload too large",
-                    message="Base64 image exceeds maximum allowed size.",
-                ).model_dump(),
-            )
-        try:
-            image_bytes = base64.b64decode(base64_data.split(",")[-1])
+        try:
+            # Decode first; then validate exact byte size.
+            image_bytes = base64.b64decode(base64_data.split(",")[-1])
         except (Base64Error, ValueError):
             raise HTTPException(
                 status_code=status.HTTP_400_BAD_REQUEST,
                 detail=ErrorResponse(
                     success=False,
                     error="Invalid base64 data",
                     message="The provided base64 image data is malformed or invalid.",
                 ).model_dump(),
             )
 
+        MAX_IMAGE_BYTES = 10 * 1024 * 1024  # 10 MiB
+        if len(image_bytes) > MAX_IMAGE_BYTES:
+            raise HTTPException(
+                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
+                detail=ErrorResponse(
+                    success=False,
+                    error="Payload too large",
+                    message="Image exceeds maximum allowed size (10 MiB).",
+                ).model_dump(),
+            )
@@
-        image_id = str(uuid.uuid4())[:8]
-        temp_dir = "temp_uploads"
-        os.makedirs(temp_dir, exist_ok=True)
-        local_image_path = os.path.join(temp_dir, f"{image_id}.{extension}")
-
-        with open(local_image_path, "wb") as f:
-            f.write(image_bytes)
+        import tempfile
+        with tempfile.NamedTemporaryFile(
+            prefix="pictopy_", suffix=f".{extension}", delete=False
+        ) as tmp:
+            tmp.write(image_bytes)
+            local_image_path = tmp.name

Also applies to: 277-294

🤖 Prompt for AI Agents
In backend/app/routes/face_clusters.py around lines 255-267 (also apply same
changes to 277-294): the current logic checks the length of the base64 string
and writes to a predictable path; instead decode the base64 first, then enforce
a byte-size limit (respond with HTTP 413 Payload Too Large), and only after
validating size write to a secure temporary file using
tempfile.NamedTemporaryFile (delete=False if needed) to avoid predictable paths;
also catch decoding errors and return a 400 with a clear error payload as
before.

Comment on lines +11 to +13
class FaceSearchRequest(BaseModel):
path: Optional[str] = None
base64_data: Optional[str] = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation to ensure at least one input field is provided.

Both path and base64_data are optional with no constraint enforcement. The API could receive requests with both fields set to None, leading to errors downstream.

Apply this diff to add validation using Pydantic's model validator:

+from pydantic import model_validator
+
 class FaceSearchRequest(BaseModel):
     path: Optional[str] = None
     base64_data: Optional[str] = None
+
+    @model_validator(mode='after')
+    def check_at_least_one_field(self):
+        if not self.path and not self.base64_data:
+            raise ValueError("Either 'path' or 'base64_data' must be provided")
+        if self.path and self.base64_data:
+            raise ValueError("Only one of 'path' or 'base64_data' should be provided")
+        return self
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class FaceSearchRequest(BaseModel):
path: Optional[str] = None
base64_data: Optional[str] = None
from pydantic import model_validator
class FaceSearchRequest(BaseModel):
path: Optional[str] = None
base64_data: Optional[str] = None
@model_validator(mode='after')
def check_at_least_one_field(self):
if not self.path and not self.base64_data:
raise ValueError("Either 'path' or 'base64_data' must be provided")
if self.path and self.base64_data:
raise ValueError("Only one of 'path' or 'base64_data' should be provided")
return self
🤖 Prompt for AI Agents
In backend/app/schemas/images.py around lines 11 to 13, the FaceSearchRequest
model declares path and base64_data as Optional but does not validate that at
least one is provided; add a Pydantic root validator (or @root_validator) to
check that either path or base64_data is not None/empty and raise a ValueError
with a clear message if both are missing/empty so the API rejects invalid
requests early.

Comment on lines +11 to +33
class BoundingBox(BaseModel):
x: float
y: float
width: float
height: float


class ImageData(BaseModel):
id: str
path: str
folder_id: str
thumbnailPath: str
metadata: Dict[str, Any]
isTagged: bool
tags: Optional[List[str]] = None
bboxes: BoundingBox


class GetAllImagesResponse(BaseModel):
success: bool
message: str
data: List[ImageData]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix schema mismatch and avoid duplicate models.
Local ImageData defines bboxes: BoundingBox (singular) while DB returns a list; also duplicates schemas already in app.schemas.images.

-from pydantic import BaseModel
-from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL
+from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL
+from app.schemas.images import ImageData, GetAllImagesResponse
@@
-class BoundingBox(BaseModel):
-    x: float
-    y: float
-    width: float
-    height: float
-
-
-class ImageData(BaseModel):
-    id: str
-    path: str
-    folder_id: str
-    thumbnailPath: str
-    metadata: Dict[str, Any]
-    isTagged: bool
-    tags: Optional[List[str]] = None
-    bboxes: BoundingBox
-
-
-class GetAllImagesResponse(BaseModel):
-    success: bool
-    message: str
-    data: List[ImageData]
+# Reuse shared schemas to prevent drift with OpenAPI.
@@
                 matches.append(
                     ImageData(
                         id=image["id"],
                         path=image["path"],
                         folder_id=image["folder_id"],
                         thumbnailPath=image["thumbnailPath"],
                         metadata=image["metadata"],
                         isTagged=image["isTagged"],
                         tags=image["tags"],
-                        bboxes=image["bbox"],
+                        # Note: bbox/bboxes omitted here to match ImageData schema.
                     )
                 )

Follow-up: If bbox must be returned, extend the shared ImageData schema and OpenAPI accordingly instead of redefining models locally. Based on learnings

Also applies to: 18-27, 29-33, 84-93

🤖 Prompt for AI Agents
backend/app/utils/faceSearch.py lines ~11-33 (and also affecting ranges 18-27,
29-33, 84-93): the local ImageData and BoundingBox schemas are incorrect and
duplicate definitions that already exist in app.schemas.images; change bboxes
from a single BoundingBox to a List[BoundingBox], remove the duplicated model
definitions, and import the shared BoundingBox and ImageData schemas from
app.schemas.images instead; if you need to include thumbnailPath, metadata or
isTagged fields not present in the shared schema, extend the imported ImageData
(or create a subclass) so the OpenAPI schema reflects the additional fields, and
update GetAllImagesResponse to use the shared/extended ImageData type. Ensure
typing and imports are adjusted and that the OpenAPI exposure uses the
shared/extended model rather than locally redefining it.

Comment on lines +1 to +8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add camera entitlement for sandboxed macOS app.

The App Sandbox is enabled, but the camera-specific entitlement is missing. For webcam functionality to work in a sandboxed macOS app, you need to explicitly grant camera access.

Apply this diff to add the required camera entitlement:

 <dict>
     <key>com.apple.security.app-sandbox</key>
     <true/>
+    <key>com.apple.security.device.camera</key>
+    <true/>
 </dict>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
🤖 Prompt for AI Agents
In frontend/src-tauri/Entitlements.plist around lines 1 to 8, the App Sandbox is
enabled but the camera-specific entitlement is missing; add the
com.apple.security.device.camera key set to true in the entitlements plist to
grant webcam access for the sandboxed macOS app, and ensure you also add a
user-facing NSCameraUsageDescription string in Info.plist (if not already
present) so the app can request camera permission at runtime.

@@ -19,8 +19,11 @@ import { fetchSearchedFaces } from '@/api/api-functions';
import { showInfoDialog } from '@/features/infoDialogSlice';
import { useNavigate } from 'react-router';
import { ROUTES } from '@/constants/routes';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify import source for useNavigate.
Many setups use react-router-dom; ensure this matches your project.


🏁 Script executed:

#!/bin/bash
rg -nC2 -P "import\\s+\\{\\s*useNavigate\\s*\\}\\s+from\\s+'react-router[^']*';" --glob "frontend/**"

Length of output: 1424



---

🌐 Web query:

```web
React Router useNavigate import source react-router-dom correct package

💡 Result:

Use this import:

import { useNavigate } from 'react-router-dom';

React Router v6 adds the useNavigate hook and react-router-dom re-exports it (ensure you have react-router-dom v6+ and that your component is inside a ). [1][2]


Fix incorrect import source for useNavigate.

The import should be from 'react-router-dom', not 'react-router'. React Router v6+ requires useNavigate to be imported from the web-specific package (react-router-dom), which re-exports the hook. Update line 20 to:

import { useNavigate } from 'react-router-dom';

This same issue affects frontend/src/pages/InitialSteps/InitialSteps.tsx (line 2) and frontend/src/components/FaceCollections.tsx (line 3).

🤖 Prompt for AI Agents
In frontend/src/components/Dialog/FaceSearchDialog.tsx around line 21 (and
similarly in frontend/src/pages/InitialSteps/InitialSteps.tsx line 2 and
frontend/src/components/FaceCollections.tsx line 3), the useNavigate hook is
incorrectly imported from 'react-router'; change the import source to
'react-router-dom' so useNavigate is imported from the web-specific package
(replace the module string only, keeping the named import).

@@ -0,0 +1,176 @@
import { useState, useRef, useCallback } from 'react';
import { useMutationFeedback } from '../../hooks/useMutationFeedback.tsx';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix import path (drop extension; use alias).
The .tsx extension and relative path can break builds and inconsistently bypass tsconfig paths.

-import { useMutationFeedback } from '../../hooks/useMutationFeedback.tsx';
+import { useMutationFeedback } from '@/hooks/useMutationFeedback';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useMutationFeedback } from '../../hooks/useMutationFeedback.tsx';
import { useMutationFeedback } from '@/hooks/useMutationFeedback';
🤖 Prompt for AI Agents
In frontend/src/components/WebCam/WebCamComponent.tsx around line 2, the import
currently uses a relative path with a .tsx extension which can break builds and
bypass tsconfig path aliases; update the import to remove the .tsx extension and
use the project alias (e.g. replace "../../hooks/useMutationFeedback.tsx" with
the configured alias path to useMutationFeedback, ensuring the import relies on
tsconfig/webpack aliases and not a relative file-extension import).

Comment on lines +67 to +74
const capture = useCallback(() => {
if (webcamRef.current) {
const imageSrc = webcamRef.current.getScreenshot();
setCapturedImageUrl(imageSrc);
setShowCamera(false);
}
}, [webcamRef]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Handle null from getScreenshot to avoid a blank dialog.
When the camera isn’t ready, getScreenshot() can return null; UI currently becomes empty.

-const capture = useCallback(() => {
-  if (webcamRef.current) {
-    const imageSrc = webcamRef.current.getScreenshot();
-    setCapturedImageUrl(imageSrc);
-    setShowCamera(false);
-  }
-}, [webcamRef]);
+const capture = useCallback(() => {
+  if (!webcamRef.current) return;
+  const imageSrc = webcamRef.current.getScreenshot();
+  if (!imageSrc) {
+    dispatch(
+      showInfoDialog({
+        title: 'Camera Not Ready',
+        message: 'Please wait for the camera to initialize, then try again.',
+        variant: 'error',
+      }),
+    );
+    return;
+  }
+  setCapturedImageUrl(imageSrc);
+  setShowCamera(false);
+}, [dispatch]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/components/WebCam/WebCamComponent.tsx around lines 67 to 74,
getScreenshot() can return null when the camera isn’t ready; update the capture
callback to check the returned imageSrc for null before calling
setCapturedImageUrl and setShowCamera(false). If imageSrc is null, do not close
the camera or set the captured URL; instead handle the failure path (e.g., keep
camera open, optionally show a user-facing error/toast or retry) so the dialog
does not become blank.

@rahulharpal1603 rahulharpal1603 added enhancement New feature or request UI backend labels Oct 28, 2025
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@rahulharpal1603 rahulharpal1603 linked an issue Oct 28, 2025 that may be closed by this pull request
7 tasks
@rahulharpal1603 rahulharpal1603 linked an issue Oct 28, 2025 that may be closed by this pull request
7 tasks
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

Copy link
Copy Markdown
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

♻️ Duplicate comments (1)
frontend/src/components/Dialog/FaceSearchDialog.tsx (1)

20-20: Fix incorrect import source for useNavigate (previously flagged).

As noted in the previous review, the import should be from 'react-router-dom', not 'react-router'. For React Router v6+ web applications, useNavigate must be imported from the web-specific package.

Apply this diff:

-import { useNavigate } from 'react-router';
+import { useNavigate } from 'react-router-dom';
🧹 Nitpick comments (2)
frontend/src/components/Dialog/FaceSearchDialog.tsx (2)

62-79: Verify the navigation and state update timing.

The function navigates to home (line 66) before showing the camera component (line 68). Since state updates are asynchronous, this sequencing could lead to unexpected UI behavior if the navigation interrupts the camera initialization. Consider whether navigation should occur after the camera permission check or if the current flow is intentional for UX reasons.

Optionally, you can provide more specific error feedback by checking the error type:

     } catch (error) {
+      const errorName = error instanceof DOMException ? error.name : 'Unknown';
+      let message = 'Webcam is not supported or access was denied on this device.';
+      
+      if (errorName === 'NotAllowedError') {
+        message = 'Camera access was denied. Please enable camera permissions in your browser settings.';
+      } else if (errorName === 'NotFoundError') {
+        message = 'No camera device was found on this device.';
+      }
+
       dispatch(
         showInfoDialog({
           title: 'Webcam Not Supported',
-          message:
-            'Webcam is not supported or access was denied on this device.',
+          message,
           variant: 'error',
         }),
       );

94-103: Remove redundant props for cleaner code.

Several props can be removed as they're either redundant or handled by the parent component:

  1. Line 95: The onClick handler is unnecessary since DialogTrigger automatically handles opening the dialog.
  2. Lines 118 and 132: disabled={false} is the default value and can be omitted.

Apply this diff:

         <DialogTrigger asChild>
           <Button
-            onClick={() => setIsDialogOpen(true)}
             variant="ghost"
             size="icon"
             className="h-8 w-8 cursor-pointer p-1"
           >
             <ScanFace className="h-4 w-4" />
             <span className="sr-only">Face Detection Search</span>
           </Button>
         </DialogTrigger>
 
         <DialogContent className="sm:max-w-md">
           <DialogHeader>
             <DialogTitle>Face Detection Search</DialogTitle>
             <DialogDescription>
               Search for images containing specific faces by uploading a photo
               or using your webcam.
             </DialogDescription>
           </DialogHeader>
 
           <div className="grid grid-cols-2 gap-4 py-4">
             {/* Upload Button */}
             <Button
               onClick={handlePickFile}
-              disabled={false}
               className="flex h-32 cursor-pointer flex-col items-center justify-center gap-2 p-0"
               variant="outline"
             >
               <ScanFace className="text-muted-foreground mb-1 h-8 w-8" />
               <span className="text-sm font-medium">Upload Photo</span>
               <span className="text-muted-foreground text-center text-xs">
                 Select a file from your device
               </span>
             </Button>
 
             {/* Webcam Button */}
             <Button
               onClick={handleWebCam}
-              disabled={false}
               className="flex h-32 cursor-pointer flex-col items-center justify-center gap-2 p-0"
               variant="outline"
             >
               <Camera className="text-muted-foreground mb-1 h-8 w-8" />
               <span className="text-sm font-medium">Use Webcam</span>
               <span className="text-muted-foreground text-center text-xs">
                 Capture with camera
               </span>
             </Button>
           </div>
         </DialogContent>

Also applies to: 116-127, 130-141

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d45a4b1 and 501552b.

📒 Files selected for processing (2)
  • frontend/src/components/Dialog/FaceSearchDialog.tsx (3 hunks)
  • frontend/src/components/Navigation/Navbar/Navbar.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/Dialog/FaceSearchDialog.tsx (4)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-11)
frontend/src/features/infoDialogSlice.ts (1)
  • showInfoDialog (16-30)
frontend/src/features/searchSlice.ts (1)
  • startSearch (20-23)
frontend/src/features/loaderSlice.ts (1)
  • showLoader (17-20)
⏰ 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). (3)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Backend Tests
🔇 Additional comments (4)
frontend/src/components/Navigation/Navbar/Navbar.tsx (1)

36-40: LGTM! Correctly handles both data URLs and file paths.

The conditional logic appropriately distinguishes between base64-encoded data URLs (from webcam captures) and file system paths, ensuring proper image rendering for both input sources.

frontend/src/components/Dialog/FaceSearchDialog.tsx (3)

2-2: LGTM! Import changes align with new webcam feature.

The updated imports correctly reflect the new UI elements (Camera, ScanFace icons) and webcam functionality (WebcamComponent).

Also applies to: 22-22


26-26: LGTM! State management for webcam component is correct.


80-89: LGTM! File picker flow improvements enhance UX.

The added navigation and dialog closing after file selection create a cleaner user experience during the face search workflow.

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@tushar1977 tushar1977 closed this Oct 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend enhancement New feature or request UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Implement the face search API endpoint and use it in the UI.

2 participants