Skip to content

Feat: Global Search Support for Face Collections and AI Tags#1338

Open
rohan-pandeyy wants to merge 6 commits into
AOSSIE-Org:mainfrom
rohan-pandeyy:feat/global-search-support
Open

Feat: Global Search Support for Face Collections and AI Tags#1338
rohan-pandeyy wants to merge 6 commits into
AOSSIE-Org:mainfrom
rohan-pandeyy:feat/global-search-support

Conversation

@rohan-pandeyy

@rohan-pandeyy rohan-pandeyy commented Jun 22, 2026

Copy link
Copy Markdown
Member

Fixes #1337

Summary

This PR introduces global search support for face collections and AI-generated tags through the Navbar search workflow.

Users can now submit a search query from the navigation bar and either:

  • Navigate directly to a matching face collection when the query matches a face cluster name.
  • View matching AI-tagged images in a dedicated Search Results page when the query matches an object tag.

The implementation adds a backend-powered image tag search endpoint and a new Search Results page, providing a unified search experience across the photo library.


Changes

Backend

  • Added a database-backed image tag search query.
  • Added a new image search API endpoint.
  • Updated OpenAPI documentation.

Frontend API Layer

  • Added API endpoint definitions for image tag search.
  • Added frontend API functions for querying search results.

Search Results Page

  • Added a dedicated SearchResults page for displaying search matches.
  • Added routing support for search results.
  • Reused the existing image viewing experience and gallery components for displaying matched images.

Navbar Integration

  • Added search submission support from the Navbar.
  • Added query handling via both keyboard submission and search button interaction.
  • Added face-cluster resolution so searches matching a face collection navigate directly to the existing collection view.
  • Added navigation to the dedicated Search Results page for tag-based searches.

User Experience

Face Collection Search

Searching for a face collection name:

John

navigates directly to the existing face collection view for that cluster.

AI Tag Search

Searching for an AI-generated object tag:

chair
dog
car

opens a dedicated Search Results page displaying all matching images.


Why a Dedicated Search Results Page?

Rather than filtering the currently visible gallery, search results are displayed in a dedicated view similar to the search experience found in modern photo management applications.

This provides:

  • A consistent search workflow across the application.
  • A clear separation between browsing and searching.
  • Library-wide search behavior independent of the current page.

Future Scope

This PR focuses exclusively on search result retrieval and display.

The following are intentionally out of scope:

  • Search recommendations
  • Autocomplete
  • Fuzzy matching
  • Search-as-you-type suggestions
  • Ranking/relevance systems
  • Advanced search filters

Screenshots/Recordings:

image image

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: Claude, Gemini

Checklist

  • [x ] My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added image search by tag functionality. Use the navigation bar search to find images by tag (case-insensitive matching).
    • Introduced a new Search Results page that displays matching images in a responsive grid and opens the media viewer when selecting an image.
    • Added a backend search endpoint to power tag-based image retrieval, including associated tag information in the results.
  • Documentation

    • Updated the published OpenAPI spec formatting (no endpoint/behavior changes).

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds end-to-end tag-based image search from backend to frontend. A new db_search_images_by_tag SQL helper and GET /images/search FastAPI route are introduced. The frontend gains an API client function, a controlled Navbar search input with routing logic (cluster name vs. tag search), a SearchResults page, and route registration. The OpenAPI spec is reformatted cosmetically with multi-line array formatting.

Changes

Tag-based global image search

Layer / File(s) Summary
Backend DB query and GET /search route
backend/app/database/images.py, backend/app/routes/images.py
db_search_images_by_tag joins images/image_classes/mappings with case-insensitive tag matching and deduplicates tags per image; GET /images/search?tag= maps results to ImageData and returns GetAllImagesResponse, converting exceptions to 500.
Frontend API endpoint and client function
frontend/src/api/apiEndpoints.ts, frontend/src/api/api-functions/images.ts
imagesEndpoints.searchByTag builds /images/search?tag= with encodeURIComponent; SearchImagesByTagRequest interface and searchImagesByTag async function wrap the apiClient.get call.
Navbar controlled search input and submit handler
frontend/src/constants/routes.ts, frontend/src/components/Navigation/Navbar/Navbar.tsx
ROUTES.SEARCH constant added; Navbar gains searchQuery state, a handleSearchSubmit that matches against cluster names case-insensitively to navigate to /person/:id or falls back to /search?value=..., and wires Enter key and button click to submission.
SearchResults page and route registration
frontend/src/pages/SearchResults/SearchResults.tsx, frontend/src/routes/AppRoutes.tsx
SearchResults reads value from URL params, fetches via usePictoQuery/searchImagesByTag, dispatches loader and setImages to Redux, and renders a back button, results heading, ImageCard grid, and conditional MediaView modal; route registered under Layout.

OpenAPI spec reformatting

Layer / File(s) Summary
Paths and schemas formatting
docs/backend/backend_python/openapi.json
All tags arrays under paths and required arrays (plus YOLO_model_size enum) under components/schemas reformatted from single-line to multi-line JSON; values unchanged.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Navbar
  participant AppRouter as React Router
  participant SearchResults
  participant searchImagesByTag as searchImagesByTag API
  participant FastAPI as GET /images/search
  participant db_search as db_search_images_by_tag

  User->>Navbar: Enter query, press Enter or click search
  Navbar->>Navbar: Trim input, check clusters for name match
  alt Cluster name match found
    Navbar->>AppRouter: navigate(/person/:cluster_id)
  else No cluster match
    Navbar->>AppRouter: navigate(/search?value=encoded_query)
    AppRouter->>SearchResults: Render with value param
    SearchResults->>searchImagesByTag: GET /images/search?tag=query
    searchImagesByTag->>FastAPI: HTTP GET request
    FastAPI->>db_search: tag lookup (case-insensitive JOIN)
    db_search-->>FastAPI: List[dict] with deduplicated tags per image
    FastAPI-->>SearchResults: GetAllImagesResponse
    SearchResults->>SearchResults: dispatch setImages, render ImageCard grid
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AOSSIE-Org/PictoPy#1314: Modifies the same Navbar.tsx to add expandable search dropdown and person/people navigation logic that this PR now extends with tag-based search routing and results display.

Suggested labels

Python, TypeScript/JavaScript, Documentation

Suggested reviewers

  • rahulharpal1603

🐇 A tag named "dog" or "chair" or "sky",
I hop through the index and give it a try.
The navbar awaits with a query to send,
New routes and new pages from start to the end.
The images appear in a grid, neat and bright —
One search bar to find them all, pure delight! 🌟

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Feat: Global Search Support for Face Collections and AI Tags' accurately and specifically describes the main feature being implemented across the backend and frontend changes.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from issue #1337: users can submit search queries from the navbar, matching face collections navigate to the face collection view, matching AI tags display in a dedicated SearchResults page, search operates against the complete library, and integration with existing workflows is clean.
Out of Scope Changes check ✅ Passed The OpenAPI documentation updates are formatting-only changes with no functional impact, representing maintenance of generated specs. All code changes directly support the #1337 objectives; no unrelated modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/database/images.py`:
- Around line 577-579: The sqlite3.Error exception handler in the image search
function is catching database errors but returning an empty list, which makes
the failure appear as a successful query with zero results. Instead of returning
an empty list when a sqlite3.Error is caught in the except block, you should
re-raise the exception or raise an appropriate exception type to properly
propagate the database error up to the caller, ensuring the API can distinguish
between a legitimate empty search result and an actual backend failure.
- Around line 551-555: In the images_dict dictionary construction at the line
containing "folder_id": str(folder_id) if folder_id is not None else None, the
ternary expression is allowing None to be set when folder_id is None, which
violates the API contract requiring folder_id to be a non-null string. Replace
the None fallback in this ternary expression with an appropriate non-null
default value (such as an empty string) to ensure folder_id is never null in the
returned response dictionary.

In `@backend/app/routes/images.py`:
- Around line 125-132: The exception handler for the image search endpoint is
exposing raw exception details to the client by including str(e) in the
ErrorResponse message field, which can leak sensitive backend information.
Replace the detailed message containing str(e) with a generic client-safe error
message, and add a server-side logger call (using the appropriate logging
mechanism for your application) to capture and log the actual exception details
before raising the HTTPException. This ensures debugging information remains on
the server while clients only receive generic error messages.

In `@frontend/src/components/Navigation/Navbar/Navbar.tsx`:
- Around line 76-88: The handleSearchSubmit function only checks the in-memory
clusters list but does not verify that clusters have been fully loaded before
processing a search query. If a user submits a search before the clusters list
is hydrated, exact face-cluster matches will incorrectly route to the search
page instead of the person page. Add a guard check at the start of
handleSearchSubmit to ensure the clusters array exists and is not empty before
attempting to find a match; if clusters are not yet loaded, return early or
provide appropriate user feedback to prevent misrouting.

In `@frontend/src/pages/SearchResults/SearchResults.tsx`:
- Around line 27-37: Add a return statement with a cleanup function to the
useEffect hook that contains the showLoader and hideLoader logic. The cleanup
function should dispatch hideLoader() to ensure the loader is properly hidden
when the SearchResults component unmounts, preventing the loader from remaining
in an active state if the component is unmounted while a search request is still
pending.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ec852948-8abf-4048-964f-00b00911e7c3

📥 Commits

Reviewing files that changed from the base of the PR and between b3eee2c and 983f67c.

📒 Files selected for processing (9)
  • backend/app/database/images.py
  • backend/app/routes/images.py
  • docs/backend/backend_python/openapi.json
  • frontend/src/api/api-functions/images.ts
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/components/Navigation/Navbar/Navbar.tsx
  • frontend/src/constants/routes.ts
  • frontend/src/pages/SearchResults/SearchResults.tsx
  • frontend/src/routes/AppRoutes.tsx

Comment thread backend/app/database/images.py
Comment thread backend/app/database/images.py Outdated
Comment thread backend/app/routes/images.py
Comment thread frontend/src/components/Navigation/Navbar/Navbar.tsx
Comment thread frontend/src/pages/SearchResults/SearchResults.tsx Outdated
@rohan-pandeyy rohan-pandeyy changed the title Feat/global search support Feat: Global Search Support for Face Collections and AI Tags Jun 22, 2026
@rohan-pandeyy

Copy link
Copy Markdown
Member Author

@coderabbitai, in addition to the existing review, please check for:

  • Recreated or duplicated concepts
  • Areas where existing patterns or framework capabilities are being reimplemented unnecessarily.
  • Code that is not modular enough and could be split into smaller, more focused, reusable units.
  • Violations of DRY principles or opportunities to improve code reuse.
  • Inconsistent patterns, architectures, or implementations across the codebase where a shared approach would be more appropriate.
  • Missing abstractions where similar logic exists in multiple places.
  • Opportunities to improve separation of concerns, encapsulation, and extensibility.
  • Potential over-engineering or abstractions that add complexity without clear value.
  • Areas that may become difficult to scale, maintain, or extend as the codebase grows.

@rohan-pandeyy

Copy link
Copy Markdown
Member Author

@coderabbitai, your suggestions aren't within the scope of the PR and it's touched files. I was hoping for something within that boundary

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@rohan-pandeyy You're right, I apologize for the scope creep. Here are only the findings strictly within the files this PR touches:


1. 🔴 Double-parse of metadatabackend/app/database/images.py + backend/app/routes/images.py

db_search_images_by_tag calls image_util_parse_metadata inside the DB function, but the new route handler search_images_by_tag then calls it again on the already-parsed dict:

# db_search_images_by_tag (database/images.py) — parses here
metadata_dict = image_util_parse_metadata(metadata)
images_dict[image_id] = { ..., "metadata": metadata_dict, ... }

# search_images_by_tag (routes/images.py) — parses again on already-parsed dict
ImageData(..., metadata=image_util_parse_metadata(image["metadata"]), ...)

Fix: either remove the image_util_parse_metadata call from db_search_images_by_tag (consistent with all other DB functions which return raw strings), or remove it from the route handler.


2. 🟡 displayImages reads directly from query data, bypassing the Redux store — SearchResults.tsx

dispatch(setImages(fetchedImages));           // writes to Redux
const displayImages = (data?.data as Image[]) || [];  // reads from raw query, not the store

The grid renders from displayImages while MediaView consumes the Redux store, so they can diverge on re-renders. Reading from useSelector(selectImages) after the setImages dispatch would be consistent.


3. 🟡 Hardcoded path /person/ not using ROUTES constant — Navbar.tsx

navigate(`/person/${match.cluster_id}`);   // ← hardcoded string
navigate(`/search?value=${encodeURIComponent(trimmed)}`);  // ← also doesn't use ROUTES.SEARCH

ROUTES is already imported in the file. Both navigations should use it:

navigate(`/${ROUTES.PERSON.replace(':clusterId', String(match.cluster_id))}`);
navigate(`/${ROUTES.SEARCH}?value=${encodeURIComponent(trimmed)}`);

4. 🟢 searchImagesByTag not re-exported from the API barrel — frontend/src/api/api-functions/images.ts

Every other API function is re-exported via the @/api/api-functions barrel index; searchImagesByTag is imported directly by path in SearchResults.tsx. It should be added to the barrel's index.ts for consistency.


Issue Severity Touched file
Double-parse of metadata 🔴 Bug database/images.py + routes/images.py
displayImages bypasses Redux 🟡 Medium SearchResults.tsx
Hardcoded /person/ path, not using ROUTES 🟡 Medium Navbar.tsx
searchImagesByTag missing from API barrel 🟢 Low api-functions/images.ts

✏️ Learnings added
Learnt from: rohan-pandeyy
URL: https://github.com/AOSSIE-Org/PictoPy/pull/1338

Timestamp: 2026-06-22T18:56:41.379Z
Learning: For PictoPy (AOSSIE-Org/PictoPy), when reviewing PRs, restrict suggestions to files that are actually touched/changed in the PR. Do not suggest refactors or abstractions involving untouched files or broader codebase patterns unless the user explicitly asks for a broader review.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@AOSSIE-Org AOSSIE-Org deleted a comment from coderabbitai Bot Jun 22, 2026
- Remove metadata double-parsing in image DB search
- Read search results from Redux store via selector
- Replace hardcoded routes with ROUTES constants
- Import searchImagesByTag from API barrel
- Return empty string when folder_id is missing to prevent schema validation failures
- Re-raise sqlite3 errors instead of masking them as empty search results
- Log detailed exceptions server-side while returning safe client-facing errors
- Prevent search navigation while clusters are still loading in Navbar
- Add loader cleanup on SearchResults unmount to avoid stuck loading state
- Verify frontend type safety with successful TypeScript check

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
backend/app/database/images.py (1)

496-571: ⚠️ Potential issue | 🟠 Major

Extract shared image grouping and tag deduplication logic to reduce duplication.

The Python-level grouping and tag deduplication pattern in db_search_images_by_tag (lines 531–571) is nearly identical to the same pattern in db_get_all_images (lines 195–240). Both functions:

  • Query images with LEFT JOIN image_classes + LEFT JOIN mappings
  • Group results by image_id using images_dict
  • Deduplicate tags with the same condition check
  • Convert to list with identical logic for handling empty tag lists
  • Sort by path

Consider extracting this shared logic into a helper function (e.g., _process_images_with_tags) to improve maintainability and reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/database/images.py` around lines 496 - 571, Extract the shared
image grouping and tag deduplication logic that appears in both
db_search_images_by_tag and db_get_all_images into a separate helper function
(e.g., _process_images_with_tags). This helper should accept the cursor results
as a parameter and return the processed list of images with deduplicated tags,
sorted by path. Then replace the identical logic blocks in both
db_search_images_by_tag (around lines 531-571) and db_get_all_images (around
lines 195-240) with calls to this new helper function.

Source: Path instructions

🧹 Nitpick comments (1)
backend/app/database/images.py (1)

574-574: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use logger.exception() for richer error context.

When logging before re-raising an exception, logger.exception() automatically includes the full stack trace, making debugging easier.

📊 Suggested improvement
-        logger.error(f"Error searching images by tag: {e}")
+        logger.exception(f"Error searching images by tag: {e}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/database/images.py` at line 574, The error logging statement in
the image search function uses logger.error() which only logs the exception
message without the stack trace. Replace logger.error(f"Error searching images
by tag: {e}") with logger.exception() instead, passing a descriptive message as
the argument. This will automatically capture and include the full stack trace
in the logs, providing richer context for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/app/database/images.py`:
- Around line 496-571: Extract the shared image grouping and tag deduplication
logic that appears in both db_search_images_by_tag and db_get_all_images into a
separate helper function (e.g., _process_images_with_tags). This helper should
accept the cursor results as a parameter and return the processed list of images
with deduplicated tags, sorted by path. Then replace the identical logic blocks
in both db_search_images_by_tag (around lines 531-571) and db_get_all_images
(around lines 195-240) with calls to this new helper function.

---

Nitpick comments:
In `@backend/app/database/images.py`:
- Line 574: The error logging statement in the image search function uses
logger.error() which only logs the exception message without the stack trace.
Replace logger.error(f"Error searching images by tag: {e}") with
logger.exception() instead, passing a descriptive message as the argument. This
will automatically capture and include the full stack trace in the logs,
providing richer context for debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f72e33ef-5e2d-4681-88cc-60b2506ae94f

📥 Commits

Reviewing files that changed from the base of the PR and between 983f67c and 788f526.

📒 Files selected for processing (4)
  • backend/app/database/images.py
  • backend/app/routes/images.py
  • frontend/src/components/Navigation/Navbar/Navbar.tsx
  • frontend/src/pages/SearchResults/SearchResults.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/components/Navigation/Navbar/Navbar.tsx
  • frontend/src/pages/SearchResults/SearchResults.tsx
  • backend/app/routes/images.py

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.

Global Search Support for Face Collections and AI Tags

1 participant