Skip to content

feat(chat): add GIF picker with Giphy integration - #127

Merged
Akash504-ai merged 3 commits into
CodePlaygroundHub:mainfrom
Komal290106:feat/gif-picker
Jul 27, 2026
Merged

feat(chat): add GIF picker with Giphy integration#127
Akash504-ai merged 3 commits into
CodePlaygroundHub:mainfrom
Komal290106:feat/gif-picker

Conversation

@Komal290106

@Komal290106 Komal290106 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What changed

Integrates a GIF picker into the message composer, backed by Giphy, as requested in #118.

  • Backend proxy (/api/gif/trending, /api/gif/search) — keeps the Giphy API key server-side only, never exposed to the client. Protected by protectRoute and a dedicated rate limiter (30 req/min per IP) to gracefully handle abuse/rate-limit scenarios.
  • GifPicker component — trending GIFs load on open, debounced keyword search (400ms), paginated "Load more," and distinct loading/empty/error states.
  • Composer integration — new GIF button sits alongside the existing emoji/image/file buttons in MessageInput, hidden for AI chats just like those.
  • No schema or migration changes. Selecting a GIF reuses the existing imagePreview state and send pipeline — the GIF URL flows through sendMessage/sendGroupMessage exactly like an uploaded photo, gets uploaded to Cloudinary server-side, and is stored on the existing Message.image field. This means DMs, groups, reactions, replies, and message rendering all work with zero changes to message.controller.js or the Message model.

Why this approach

Rather than building a parallel "GIF message" type, this treats a GIF exactly like an image message, since the app already has a complete image-message pipeline (composer → Cloudinary upload → socket emit → render). This kept the diff small and low-risk, per the issue's goal of not disrupting existing messaging functionality.

Testing performed

  • ✅ Trending GIFs load on opening the picker
  • ✅ Search by keyword (e.g. "cat") returns relevant, debounced results
  • ✅ Preview/selection before sending
  • ✅ Sent and rendered correctly (animated) in a 1:1 DM
  • ✅ Sent and rendered correctly in a group chat
  • ✅ GIF button correctly hidden in the AI assistant chat, matching existing image/file/emoji/mic behavior
  • ✅ Pagination via "Load more"
  • ✅ Graceful error state when the provider is unreachable/misconfigured
  • ✅ Full backend test suite passes with no regressions (199 passed, 2 skipped, 0 failed)
  • ✅ Frontend build passes (vite build)
  • npm run lint clean on all new/modified files (no new errors; pre-existing repo-wide warnings unaffected)

Screenshots:
Trending
Screenshot 2026-07-20 170243
Cat Search
Screenshot 2026-07-20 171620
Sent in chat
Screenshot 2026-07-20 170949
Sent in group
Screenshot 2026-07-20 171210

Known limitations

  • Requires a GIPHY_API_KEY env var to be configured; without it, the picker shows a graceful "not available" message rather than crashing (documented in .env.example).
  • Currently uses Giphy only (no Tenor fallback), per the issue's "e.g. GIPHY or Tenor" wording — happy to add Tenor as a follow-up if maintainers prefer.
  • Rate limit (30 req/min/IP) is a starting point and can be tuned.

Closes #118

Summary by CodeRabbit

  • New Features
    • Added a GIF picker to message composition with trending browsing, search, preview selection, and “Load more” pagination.
    • Integrated GIF selection into the message composer with automatic dismissal when clicking outside the picker.
    • Added protected GIF trending/search endpoints backed by GIPHY, including clear loading, empty, and error states.
    • Improved reliability by preventing outdated search results from overwriting newer ones.
  • Configuration
    • Added GIPHY_API_KEY to the environment configuration example.

- Add backend proxy routes for Giphy trending/search (keeps API key server-side)
- Add GifPicker component with debounced search and pagination
- Wire GIF button into MessageInput, reusing existing image send pipeline
- No schema changes; GIFs flow through existing Message.image field

Closes CodePlaygroundHub#118
Copilot AI review requested due to automatic review settings July 20, 2026 11:59
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Akash Santra 's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a GIPHY-backed GIF service and protected API endpoints, plus a frontend picker for trending/search results, pagination, error handling, and GIF selection from the message composer.

Changes

GIF Picker Integration

Layer / File(s) Summary
GIPHY service and configuration
backend/.env.example, backend/src/services/gif.service.js
Adds GIPHY configuration, API key validation, response mapping, pagination, trending retrieval, and search retrieval.
GIF API routes and handlers
backend/src/middleware/rateLimiter.js, backend/src/routes/gif.routes.js, backend/src/controllers/gif.controller.js, backend/src/index.js
Adds protected and rate-limited /api/gif/trending and /api/gif/search endpoints with provider and request error responses.
GIF picker browsing and search
frontend/src/components/GifPicker.jsx
Adds debounced search, trending results, pagination, stale-request protection, loading states, error states, and GIF selection.
Message composer GIF selection
frontend/src/components/MessageInput.jsx
Adds a GIF toolbar button, picker dismissal handling, and selected GIF preview integration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MessageInput
  participant GifPicker
  participant GIFRoutes
  participant GifService
  participant GIPHYAPI
  MessageInput->>GifPicker: Open picker
  GifPicker->>GIFRoutes: Request trending or search GIFs
  GIFRoutes->>GifService: Invoke GIF handler
  GifService->>GIPHYAPI: Fetch GIF data
  GIPHYAPI-->>GifService: Return GIF data and pagination
  GifService-->>GIFRoutes: Return normalized GIF results
  GIFRoutes-->>GifPicker: Return GIF results
  GifPicker-->>MessageInput: Select GIF URL
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a Giphy-backed GIF picker for chat.
Linked Issues check ✅ Passed The PR implements trending/search GIF browsing, preview, pagination, error handling, and composer integration for direct and group chats.
Out of Scope Changes check ✅ Passed The changes all support the GIF picker feature and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@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: 2

🧹 Nitpick comments (2)
backend/src/services/gif.service.js (1)

6-13: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a smaller GIF variant for the sent message, not the original.

url maps to images.original, which for many Giphy GIFs is multiple MB. Since this URL becomes Message.image and goes through the existing upload pipeline, sending a smaller rendition (e.g. fixed_height or downsized) would reduce bandwidth/storage while keeping visual quality adequate for chat.

♻️ Suggested change
 const mapGif = (gif) => ({
   id: gif.id,
   title: gif.title,
   previewUrl: gif.images?.fixed_width_small?.url || gif.images?.fixed_width?.url,
-  url: gif.images?.original?.url,
+  url: gif.images?.downsized?.url || gif.images?.fixed_height?.url || gif.images?.original?.url,
   width: gif.images?.fixed_width?.width,
   height: gif.images?.fixed_width?.height,
 });
🤖 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/src/services/gif.service.js` around lines 6 - 13, Update the url
mapping in mapGif to use an appropriate smaller Giphy rendition, such as
fixed_height or downsized, instead of images.original; preserve a fallback chain
so the message still receives an available GIF URL when the preferred variant is
missing.
frontend/src/components/GifPicker.jsx (1)

74-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Page-size 24 is duplicated from the backend's DEFAULT_LIMIT.

offset + 24 assumes the backend's page size (gif.service.js DEFAULT_LIMIT), but the response only returns { gifs, hasMore }, not the actual count/limit used. If the backend default ever changes, pagination will silently skip or repeat items.

🤖 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 `@frontend/src/components/GifPicker.jsx` around lines 74 - 77, Update
handleLoadMore to advance pagination using the actual number of GIFs returned by
the previous fetch, rather than the hardcoded 24. Track or expose that returned
count alongside gifs/hasMore, and use it when calculating the next offset while
preserving the existing loading and hasMore guards.
🤖 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 `@frontend/src/components/GifPicker.jsx`:
- Around line 59-72: Prevent the debounce effect from scheduling an initial
fetch when query is empty on mount, while preserving the immediate trending
fetch in the mount effect. Update the effects around fetchGifs and debounceRef
so subsequent query changes still trigger the 400ms debounced search without
duplicate requests.

In `@frontend/src/components/MessageInput.jsx`:
- Around line 443-472: Update the GIF toggle handler in MessageInput to close
the emoji picker when opening GIFs, and update the emoji toggle handler
symmetrically to close the GIF picker when opening emojis. Preserve each
picker’s existing toggle behavior so only one popover can be open at a time.

---

Nitpick comments:
In `@backend/src/services/gif.service.js`:
- Around line 6-13: Update the url mapping in mapGif to use an appropriate
smaller Giphy rendition, such as fixed_height or downsized, instead of
images.original; preserve a fallback chain so the message still receives an
available GIF URL when the preferred variant is missing.

In `@frontend/src/components/GifPicker.jsx`:
- Around line 74-77: Update handleLoadMore to advance pagination using the
actual number of GIFs returned by the previous fetch, rather than the hardcoded
24. Track or expose that returned count alongside gifs/hasMore, and use it when
calculating the next offset while preserving the existing loading and hasMore
guards.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 384da454-3011-4649-ab48-e13afa3a05ac

📥 Commits

Reviewing files that changed from the base of the PR and between 64c82d1 and 354d74d.

📒 Files selected for processing (8)
  • backend/.env.example
  • backend/src/controllers/gif.controller.js
  • backend/src/index.js
  • backend/src/middleware/rateLimiter.js
  • backend/src/routes/gif.routes.js
  • backend/src/services/gif.service.js
  • frontend/src/components/GifPicker.jsx
  • frontend/src/components/MessageInput.jsx

Comment thread frontend/src/components/GifPicker.jsx Outdated
Comment thread frontend/src/components/MessageInput.jsx
@Akash504-ai

Copy link
Copy Markdown
Member

Thanks for the update and for sharing the demo! Before I proceed with the final review, could you please make sure all remaining CodeRabbit review comments are addressed (where applicable)? Once those are resolved, I'll complete the manual review and testing.

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/GifPicker.jsx (1)

118-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Label the icon-only close button.

Screen-reader users receive an unnamed button. Add aria-label="Close GIF picker".

🤖 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 `@frontend/src/components/GifPicker.jsx` around lines 118 - 124, Update the
icon-only button invoking onClose in GifPicker to include the accessible label
“Close GIF picker” via aria-label, while preserving its existing behavior and
styling.
🤖 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 `@frontend/src/components/GifPicker.jsx`:
- Around line 54-62: Update the GIF loading error handling in the component’s
pagination flow to track append failures separately from the initial-load error.
Preserve the existing GIF list and “Load more” control when append is true,
while retaining the current behavior of clearing the grid and showing the global
error for non-append failures; ensure the separate pagination error is cleared
on a successful retry.

---

Outside diff comments:
In `@frontend/src/components/GifPicker.jsx`:
- Around line 118-124: Update the icon-only button invoking onClose in GifPicker
to include the accessible label “Close GIF picker” via aria-label, while
preserving its existing behavior and styling.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c20a68d-81d7-4eda-956e-98c435e2735a

📥 Commits

Reviewing files that changed from the base of the PR and between 354d74d and 971a85c.

📒 Files selected for processing (3)
  • backend/.env.example
  • frontend/src/components/GifPicker.jsx
  • frontend/src/components/MessageInput.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/MessageInput.jsx

Comment on lines +54 to +62
const status = err?.response?.status;

if (status === 503) {
setError("GIF search isn't available right now.");
} else {
setError("Couldn't load GIFs. Please try again.");
}

if (!append) setGifs([]);

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve loaded GIFs when pagination fails.

An append failure sets the global error, which replaces the existing grid and hides “Load more”; users cannot retry without changing the query or reopening the picker. Track pagination errors separately and retain the grid/button.

Proposed fix
 const [error, setError] = useState(null);
+const [loadMoreError, setLoadMoreError] = useState(null);

 // successful response
+setLoadMoreError(null);

 } catch (err) {
   if (requestId !== requestIdRef.current) return;

+  if (append) {
+    setLoadMoreError("Couldn't load more GIFs. Please try again.");
+    return;
+  }
+
   const status = err?.response?.status;
   // existing initial/search error handling
 }
🤖 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 `@frontend/src/components/GifPicker.jsx` around lines 54 - 62, Update the GIF
loading error handling in the component’s pagination flow to track append
failures separately from the initial-load error. Preserve the existing GIF list
and “Load more” control when append is true, while retaining the current
behavior of clearing the grid and showing the global error for non-append
failures; ensure the separate pagination error is cleared on a successful retry.

@Akash504-ai Akash504-ai added enhancement New feature or request frontend backend Backend issue ECSoC26 Required before merging an ECSoC PR. good-pr Bonus +15 XP (excellent overall PR). labels Jul 27, 2026

@Akash504-ai Akash504-ai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reviewed and tested the implementation locally, and everything functions as expected.

@Akash504-ai
Akash504-ai merged commit 4e69e71 into CodePlaygroundHub:main Jul 27, 2026
4 of 5 checks passed
@Komal290106
Komal290106 deleted the feat/gif-picker branch July 27, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend issue ECSoC26-L3 ECSoC26 Required before merging an ECSoC PR. enhancement New feature or request frontend good-pr Bonus +15 XP (excellent overall PR).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Integrate GIF Picker for Chat Messages

3 participants