feat(chat): add GIF picker with Giphy integration - #127
Conversation
- 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
|
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. |
📝 WalkthroughWalkthroughAdds 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. ChangesGIF Picker 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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
backend/src/services/gif.service.js (1)
6-13: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider a smaller GIF variant for the sent message, not the original.
urlmaps toimages.original, which for many Giphy GIFs is multiple MB. Since this URL becomesMessage.imageand goes through the existing upload pipeline, sending a smaller rendition (e.g.fixed_heightordownsized) 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 winPage-size
24is duplicated from the backend'sDEFAULT_LIMIT.
offset + 24assumes the backend's page size (gif.service.jsDEFAULT_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
📒 Files selected for processing (8)
backend/.env.examplebackend/src/controllers/gif.controller.jsbackend/src/index.jsbackend/src/middleware/rateLimiter.jsbackend/src/routes/gif.routes.jsbackend/src/services/gif.service.jsfrontend/src/components/GifPicker.jsxfrontend/src/components/MessageInput.jsx
|
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. |
There was a problem hiding this comment.
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 winLabel 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
📒 Files selected for processing (3)
backend/.env.examplefrontend/src/components/GifPicker.jsxfrontend/src/components/MessageInput.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/MessageInput.jsx
| 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([]); |
There was a problem hiding this comment.
🩺 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
left a comment
There was a problem hiding this comment.
I reviewed and tested the implementation locally, and everything functions as expected.
What changed
Integrates a GIF picker into the message composer, backed by Giphy, as requested in #118.
/api/gif/trending,/api/gif/search) — keeps the Giphy API key server-side only, never exposed to the client. Protected byprotectRouteand a dedicated rate limiter (30 req/min per IP) to gracefully handle abuse/rate-limit scenarios.GifPickercomponent — trending GIFs load on open, debounced keyword search (400ms), paginated "Load more," and distinct loading/empty/error states.MessageInput, hidden for AI chats just like those.imagePreviewstate and send pipeline — the GIF URL flows throughsendMessage/sendGroupMessageexactly like an uploaded photo, gets uploaded to Cloudinary server-side, and is stored on the existingMessage.imagefield. This means DMs, groups, reactions, replies, and message rendering all work with zero changes tomessage.controller.jsor theMessagemodel.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
199 passed, 2 skipped, 0 failed)vite build)npm run lintclean on all new/modified files (no new errors; pre-existing repo-wide warnings unaffected)Screenshots:




Trending
Cat Search
Sent in chat
Sent in group
Known limitations
GIPHY_API_KEYenv var to be configured; without it, the picker shows a graceful "not available" message rather than crashing (documented in.env.example).Closes #118
Summary by CodeRabbit
GIPHY_API_KEYto the environment configuration example.