Skip to content

fixed: implement text search by AI-detected tags#1207

Open
ProthamD wants to merge 2 commits intoAOSSIE-Org:mainfrom
ProthamD:feat/text-search-by-tags
Open

fixed: implement text search by AI-detected tags#1207
ProthamD wants to merge 2 commits intoAOSSIE-Org:mainfrom
ProthamD:feat/text-search-by-tags

Conversation

@ProthamD
Copy link

@ProthamD ProthamD commented Mar 2, 2026

feat: implement text search by AI-detected tags

fixes #1048

Screenshot 2026-03-02 233640

🔍 What's the Problem?

The Navbar search bar in PictoPy was purely cosmetic — it accepted text input but did absolutely nothing with it. Clicking the search button or pressing Enter had zero effect. There was also no backend endpoint to support querying images by tag name.

This PR fully implements end-to-end text-based image search by AI-detected tags, making the search bar functional for the first time.


🐛 Root Cause

Frontend (Navbar.tsx):

  • <Input> had no value, onChange, or onKeyDown — making it an uncontrolled, disconnected element
  • Search button had no onClick handler
  • No state management or API integration existed for text search

Backend:

  • GET /images/ only supported filtering by tagged (boolean)
  • No endpoint existed to filter images by specific tag names

✅ Changes Made

Backend

backend/app/database/images.py

  • Added db_search_images_by_tags(tags: List[str]) — performs a case-insensitive SQL query using LOWER() to find all images whose AI-detected tags match any of the provided tag names. Returns the same data shape as the existing db_get_all_images() for full compatibility.

backend/app/routes/images.py

  • Added new endpoint: GET /images/search?tags=dog,beach,person
  • Accepts a comma-separated tags query parameter
  • Validates input (400 if empty), delegates to DB layer, returns GetAllImagesResponse
  • Fully documented with OpenAPI schema

Frontend

frontend/src/api/apiEndpoints.ts

  • Added searchByTags: '/images/search' to imagesEndpoints

frontend/src/api/api-functions/images.ts

  • Added searchImagesByTags(tags: string[]) — joins the array as a comma-separated query param and calls the new endpoint

frontend/src/features/searchSlice.ts

  • Extended SearchState interface with queryText?: string and textSearchActive: boolean
  • Added two new Redux actions: startTextSearch(query) and clearTextSearch()
  • Fully backwards-compatible — existing face search (startSearch / clearSearch) unchanged

frontend/src/components/Navigation/Navbar/Navbar.tsx

  • Wired <Input> with value, onChange, and onKeyDown:
    • Enter → triggers search
    • Escape → clears search and restores full gallery
    • Typing and clearing resets state automatically
  • Added onClick to the Search (🔍) button
  • Added a ✕ clear button that appears only while a text search is active
  • Updated placeholder text from "Add to your search" to "Search by tags (e.g. dog, beach)"

🧪 How to Test

  1. Add a folder in Settings and enable AI Tagging — wait for tagging to complete
  2. Navigate to the Home / Gallery view
  3. In the Navbar, type a YOLO class name (e.g. person, dog, car, bottle)
  4. Press Enter or click the 🔍 button
  5. ✅ Only images containing that AI-detected tag are displayed
  6. Type multiple tags separated by commas or spaces (e.g. dog beach) to search across multiple tags
  7. Press Escape or click to clear the filter and restore the full gallery

🏗️ Architecture Notes

  • The search is additive / OR-based: images matching any of the supplied tags are returned
  • Case-insensitive matching via LOWER() in SQL — searching Dog or DOG works the same as dog
  • No existing endpoints or pipelines were modified — this is a purely additive change
  • The AI tagging pipeline (YOLO, face detection, folder scanning) is completely untouched

📁 Files Changed

File Change
backend/app/database/images.py Added db_search_images_by_tags()
backend/app/routes/images.py Added GET /images/search endpoint
frontend/src/api/apiEndpoints.ts Added searchByTags endpoint constant
frontend/src/api/api-functions/images.ts Added searchImagesByTags() API function
frontend/src/features/searchSlice.ts Extended with text search state & actions
frontend/src/components/Navigation/Navbar/Navbar.tsx Fully wired search bar

ASSIST:

i took assistance of githu copilot to code review, to solve some installation problems and fix my updated coding logics

Summary by CodeRabbit

  • New Features
    • Tag-based image search accessible from the navigation bar (enter comma-separated tags, press Enter or click Search).
    • Frontend now updates gallery with tag search results and offers a clear-text-search button and Escape-to-clear behavior.
    • New backend search endpoint and client API support returning images that match any of the provided tags.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 971b050 and 88f9318.

📒 Files selected for processing (1)
  • frontend/src/components/Navigation/Navbar/Navbar.tsx

Walkthrough

Added a text-based tag search feature: backend exposes a GET /images/search endpoint and db_search_images_by_tags; frontend adds API client, Navbar input handlers (Enter/Escape/Search), and Redux state to start/clear text searches. Responses map to existing ImageData shape.

Changes

Cohort / File(s) Summary
Backend Search Implementation
backend/app/database/images.py, backend/app/routes/images.py
Added db_search_images_by_tags(tags: List[str]) to query images by tag names (case-insensitive, joins and aggregation) and new GET /images/search route that validates input, invokes the DB function, maps results to ImageData, and returns structured responses with 400/500 handling.
Frontend API Layer
frontend/src/api/apiEndpoints.ts, frontend/src/api/api-functions/images.ts
Added imagesEndpoints.searchByTags = '/images/search' and searchImagesByTags(tags: string[]): Promise<APIResponse> which calls the new backend endpoint using comma-separated tags.
Frontend UI & State
frontend/src/components/Navigation/Navbar/Navbar.tsx, frontend/src/features/searchSlice.ts
Navbar: added textQuery, handlers for onChange/onKeyDown (Enter triggers search, Escape clears), Search button click, and integration with searchImagesByTags. Redux: added queryText and textSearchActive fields plus startTextSearch and clearTextSearch reducers.

Sequence Diagram

sequenceDiagram
    participant User
    participant Navbar as Navbar Component
    participant API as Frontend API
    participant Backend as Backend API
    participant DB as Database

    User->>Navbar: Type tags & press Enter / click Search
    activate Navbar
    Navbar->>Navbar: Validate non-empty tags
    Navbar->>Navbar: Dispatch startTextSearch
    Navbar->>API: searchImagesByTags(tags)
    deactivate Navbar

    activate API
    API->>Backend: GET /images/search?tags=tag1,tag2
    deactivate API

    activate Backend
    Backend->>Backend: Validate tags
    Backend->>DB: db_search_images_by_tags(tags)
    deactivate Backend

    activate DB
    DB->>DB: JOIN images, mappings, tags (case-insensitive) and aggregate
    DB-->>Backend: image list
    deactivate DB

    Backend-->>API: GetAllImagesResponse (images)
    API-->>Navbar: APIResponse with images
    Navbar->>Navbar: Map results to ImageData & update display
    Navbar-->>User: Show filtered images
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

Python, TypeScript/JavaScript

Poem

🐰 Hopping through tags with a twitch of my nose,

I press Enter and watch the results as they rose,
From Navbar to DB, a quick little sprint,
Images pop up — oh what a glint!
Celebrate the search with a carrot-shaped hint 🥕

🚥 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 title 'fixed: implement text search by AI-detected tags' accurately summarizes the main objective of adding text-based image search functionality using AI-detected tags across frontend and backend.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #1048 by implementing frontend state management (queryText, textSearchActive, actions), Navbar controls (input handlers, search button, clear button), backend database search function, new search endpoint with validation, and API integration.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing text search by AI-detected tags: backend search function and endpoint, frontend API layer, state management, and Navbar UI integration. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 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: 3

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

297-339: Extract shared row-to-image mapping to avoid duplication.

This block duplicates the aggregation/parsing flow already present in db_get_all_images, which increases drift risk when response shape evolves.

♻️ Suggested refactor direction
+def _rows_to_image_dicts(results: list[tuple]) -> list[dict]:
+    from app.utils.images import image_util_parse_metadata
+    images_dict = {}
+    for (...) in results:
+        ...
+    images = []
+    for image_data in images_dict.values():
+        if not image_data["tags"]:
+            image_data["tags"] = None
+        images.append(image_data)
+    images.sort(key=lambda x: x["path"])
+    return images

Then call the helper from both db_get_all_images and db_search_images_by_tags.

As per coding guidelines, "Look for code duplication".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/database/images.py` around lines 297 - 339, Extract the
duplicated row-to-image aggregation into a single helper (e.g., parse_image_rows
or _aggregate_image_rows) that accepts the raw query results iterable and
returns the list of image dicts sorted by "path"; implement the same logic
currently in db_search_images_by_tags (use image_util_parse_metadata, build
images_dict keyed by image_id, append unique tags, set tags to None if empty,
cast folder_id to str, booleans for isTagged/isFavourite, and captured_at
handling), then replace the aggregation block in both db_search_images_by_tags
and db_get_all_images to call this new helper and return its result.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/routes/images.py`:
- Around line 133-183: Add a new test_images.py following the existing test
patterns to cover the search_images_by_tags endpoint and its DB function: write
route tests that call search_images_by_tags (via test client) to assert 400 on
empty/invalid tags, assert OR semantics by seeding images with overlapping tags
and verifying GetAllImagesResponse contains any matching ImageData (check id,
path, tags, thumbnailPath, metadata via image_util_parse_metadata expectations),
and simulate an internal error by patching/db_search_images_by_tags to raise an
exception and assert a 500 with the ErrorResponse shape; also add unit tests for
db_search_images_by_tags to verify correct results for single/multiple tags and
edge cases (no matches), and ensure tests clean up/seed fixtures consistent with
test_albums.py pattern.

In `@frontend/src/components/Navigation/Navbar/Navbar.tsx`:
- Around line 26-29: The current split using /[,\s]+/ in Navbar.tsx breaks
multi-word tags (e.g., "traffic light"); change the logic in the search handling
around textQuery/trimmed/tags so you split only on commas (e.g., use
trimmed.split(',') ), then .map(tag => tag.trim()) and .filter(Boolean) to
preserve multi-word tags while removing empty entries; keep the existing early
return for empty trimmed.
- Around line 40-43: The text search clear handler (handleClearTextSearch) only
resets state but doesn’t reload the full gallery; add a useEffect in Home.tsx
that watches textSearchActive and when it becomes false dispatches the same
fetch/loader used by startTextSearch (or the existing "load all images" action
used by face search clearing) to restore the full gallery; locate the effect
near the face-search clearing effect and ensure it triggers on textSearchActive
changing to false and calls the same image-fetching logic (or action creator)
that startTextSearch uses so clearing via the button or Escape shows all images
again.

---

Nitpick comments:
In `@backend/app/database/images.py`:
- Around line 297-339: Extract the duplicated row-to-image aggregation into a
single helper (e.g., parse_image_rows or _aggregate_image_rows) that accepts the
raw query results iterable and returns the list of image dicts sorted by "path";
implement the same logic currently in db_search_images_by_tags (use
image_util_parse_metadata, build images_dict keyed by image_id, append unique
tags, set tags to None if empty, cast folder_id to str, booleans for
isTagged/isFavourite, and captured_at handling), then replace the aggregation
block in both db_search_images_by_tags and db_get_all_images to call this new
helper and return its result.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8274ebd and 971b050.

📒 Files selected for processing (6)
  • backend/app/database/images.py
  • backend/app/routes/images.py
  • frontend/src/api/api-functions/images.ts
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/components/Navigation/Navbar/Navbar.tsx
  • frontend/src/features/searchSlice.ts

Comment on lines +133 to +183
@router.get(
"/search",
response_model=GetAllImagesResponse,
responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}},
)
def search_images_by_tags(
tags: str = Query(
...,
description="Comma-separated tag names to search for (e.g. dog,beach,person)",
)
):
"""Return images whose AI-detected tags contain any of the requested tags."""
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
if not tag_list:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ErrorResponse(
success=False,
error="Bad request",
message="At least one tag must be provided.",
).model_dump(),
)
try:
images = db_search_images_by_tags(tag_list)
image_data = [
ImageData(
id=image["id"],
path=image["path"],
folder_id=image["folder_id"],
thumbnailPath=image["thumbnailPath"],
metadata=image_util_parse_metadata(image["metadata"]),
isTagged=image["isTagged"],
isFavourite=image.get("isFavourite", False),
tags=image["tags"],
)
for image in images
]
return GetAllImagesResponse(
success=True,
message=f"Found {len(image_data)} image(s) matching tags: {', '.join(tag_list)}",
data=image_data,
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=ErrorResponse(
success=False,
error="Internal server error",
message=f"Unable to search images: {str(e)}",
).model_dump(),
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for tests covering the search_images_by_tags function and related functionality
rg -n -C2 "search_images_by_tags|db_search_images_by_tags|/images/search|tags.*dog.*beach" -g "*test*" -t py

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Also search more broadly for test files related to images
fd -type f -name "*test*.py" | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 232


🏁 Script executed:

# Check the structure of test directories
fd -type d -name "test*" -o -name "*test"

Repository: AOSSIE-Org/PictoPy

Length of output: 232


🏁 Script executed:

# Find all test files
fd -type f -name "*test*.py"

Repository: AOSSIE-Org/PictoPy

Length of output: 232


🏁 Script executed:

# Search for any test directory
find . -type d \( -name test -o -name tests -o -name "*test*" \) | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 108


🏁 Script executed:

# Search for imports of the search function or route handler in any Python files
rg "search_images_by_tags|db_search_images_by_tags" --type py

Repository: AOSSIE-Org/PictoPy

Length of output: 395


🏁 Script executed:

# Check if there's a test file for the images route
find . -type f -name "*.py" -path "*test*" | xargs grep -l "search.*image\|image.*search" 2>/dev/null

Repository: AOSSIE-Org/PictoPy

Length of output: 46


🏁 Script executed:

# List contents of the backend/tests directory
find ./backend/tests -type f -name "*.py" | head -30

Repository: AOSSIE-Org/PictoPy

Length of output: 259


🏁 Script executed:

# Search for any references to images in test files
rg "search.*image\|image.*search\|test.*search" ./backend/tests -A 3

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Look for test files specifically for the routes
find ./backend/tests -type f -name "*image*"

Repository: AOSSIE-Org/PictoPy

Length of output: 44


🏁 Script executed:

# Check the structure of backend/tests
ls -la ./backend/tests/

Repository: AOSSIE-Org/PictoPy

Length of output: 692


🏁 Script executed:

# Search all test files for any reference to /images endpoint
rg "/images|images_route|test.*image" ./backend/tests/

Repository: AOSSIE-Org/PictoPy

Length of output: 1847


🏁 Script executed:

# Check the conftest.py to understand test setup
cat ./backend/tests/conftest.py

Repository: AOSSIE-Org/PictoPy

Length of output: 1602


🏁 Script executed:

# Look at one of the test files to understand the pattern and structure
head -50 ./backend/tests/test_folders.py

Repository: AOSSIE-Org/PictoPy

Length of output: 1411


Add automated tests for /images/search endpoint before merge.

No test coverage exists for the search_images_by_tags endpoint or its database function. This critical user-facing search feature lacks tests for validation (empty/invalid tags), OR semantics (matching any tag), and error scenarios. Given the established test pattern in the codebase (test_albums.py, test_folders.py, test_face_clusters.py), a test_images.py file with comprehensive route and database-level tests is required to meet project standards.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/routes/images.py` around lines 133 - 183, Add a new
test_images.py following the existing test patterns to cover the
search_images_by_tags endpoint and its DB function: write route tests that call
search_images_by_tags (via test client) to assert 400 on empty/invalid tags,
assert OR semantics by seeding images with overlapping tags and verifying
GetAllImagesResponse contains any matching ImageData (check id, path, tags,
thumbnailPath, metadata via image_util_parse_metadata expectations), and
simulate an internal error by patching/db_search_images_by_tags to raise an
exception and assert a 500 with the ErrorResponse shape; also add unit tests for
db_search_images_by_tags to verify correct results for single/multiple tags and
edge cases (no matches), and ensure tests clean up/seed fixtures consistent with
test_albums.py pattern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG:Text Search Bar is Non-Functional

1 participant