Skip to content

feat(ai): AI-powered recommendations and natural-language search#3258

Open
rholmboe wants to merge 13 commits into
seerr-team:developfrom
rholmboe:feat/ai-recommendations
Open

feat(ai): AI-powered recommendations and natural-language search#3258
rholmboe wants to merge 13 commits into
seerr-team:developfrom
rholmboe:feat/ai-recommendations

Conversation

@rholmboe

@rholmboe rholmboe commented Jul 18, 2026

Copy link
Copy Markdown

Description

This adds an optional, fully opt-in AI layer to Seerr: personalized recommendations and natural-language search powered by any OpenAI-compatible LLM provider. Everything is off by default — Seerr behaves exactly as before until an administrator enables it.

Personalized recommendations — a "Recommended for You" discover slider and a dedicated page, generated per user from their requests, watchlist, and available library via a two-phase pipeline (taste profile → recommendations). Titles returned by the model are resolved to real TMDB entries by title/year search (model-supplied tmdbId values are deliberately not trusted). Recommendations persist between runs and age out via a configurable TTL rather than being regenerated wholesale.

Natural-language search — an "AI Search" toggle on the search page. Regular keyword search stays the default (fast, no LLM); in AI mode the query is interpreted into TMDB discover parameters plus suggested titles, resolved to results, and shown with an "AI interpretation" badge describing how the query was parsed.

Feedback loop — 👍 / 👁️ / 👎 on recommendation cards. Likes bias future generation toward similar content; "already watched" and "not interested" remove the card and exclude the title going forward.

Provider-agnostic and self-hostable — works with OpenAI, OpenRouter, Ollama, LM Studio, LiteLLM, etc., so it can run entirely locally. Configured under a new Settings → AI Settings tab (provider, model, max results, minimum TMDB rating, recommendation TTL).

Inspired by the community projects SuggestArr and Recomendarr; reuses Seerr's existing discover-slider, settings, and scheduled-job infrastructure.

AI assistance disclosure: This feature was developed with AI assistance (Claude, via Claude Code) and the commits are co-authored accordingly. AI was used for substantial code generation across the feature. All design decisions, scope, and trade-offs were driven and reviewed by me as the contributor, every change was reviewed before commit, and the feature was manually validated end-to-end (see below). This is AI-assisted work under human review — not autonomous generation.

How Has This Been Tested?

Manual testing in a Docker dev container against an OpenAI-compatible provider (OpenRouter, tencent/hy3:free):

  • Recommendations: generated 11+ personalized titles per user, ~100% resolved to real TMDB entries; like-biasing, dislike/seen exclusion, and TTL refresh all verified.
  • AI Search: natural-language queries returned ~20 results, sorted by relevance, with the AI interpretation badge rendering correctly.
  • Feedback: like / already-watched / not-interested all behaved as intended (dislike/seen remove the card immediately; like is recorded quietly).
  • Settings: provider config saves, API-key masking with re-test without re-entry works, and min-rating / max-results / TTL validation is enforced.

Automated checks: pnpm build (next build + server tsc) passes, pnpm lint (0 errors), tsc --noEmit (clean), pnpm format:check (clean), pnpm i18n:extract (run; 58 keys added, none removed).

Screenshots / Logs (if applicable)

The "Recommended for You" page is included in the new docs page at gen-docs/static/img/seerr-ai-recommend.png.

Checklist:

  • I have read and followed the contribution guidelines.
  • Disclosed any use of AI (see our policy) — extent described in the Description above.
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • Successful build pnpm build
  • Translation keys pnpm i18n:extract
  • Database migration (if required)

Summary by CodeRabbit

  • New Features
    • Added AI-powered personalized recommendations (“Recommended for You”) with rationale, like/dislike/seen feedback, and manual regeneration.
    • Added natural-language AI Search with an “AI interpretation” panel and AI-mode results.
    • Added an AI Settings area (provider configuration, recommendations + AI search toggles, and provider connection testing).
    • Added scheduled AI recommendations sync and persisted AI feedback.
    • Added optional local AI backend setup via Docker Compose (Ollama).
  • Documentation
    • Documented AI settings, recommendations, and AI search behavior.
  • UI Improvements
    • Added AI recommendation cards/listing, AI search mode UI, and AI settings/admin pages.

rholmboe and others added 10 commits July 18, 2026 21:57
Adds an AI recommendation engine that generates personalized movie/TV
suggestions based on a user's request history, watchlist, and available
library content, inspired by SuggestArr and Recomendarr.

Backend:
- Two-phase LLM pipeline: taste profile generation -> recommendations
- Multi-provider support via OpenAI-compatible API (OpenAI, Ollama,
  OpenRouter, custom), with robust JSON repair + retry on parse failure
- LLM titles resolved to real TMDb entries via search (small models
  hallucinate tmdbId values), so only valid titles are stored
- TTL + upsert lifecycle: recommendations persist across runs, are
  refreshed when re-recommended, and age out via configurable TTL cleanup
- Scheduled job (every 6h) generates recommendations for all users
- API endpoints: /discover/ai-recommendations, /ai/settings, /ai/test,
  /ai/search, /ai/feedback, /ai/regenerate
- New entities: AiRecommendation, UserFeedback (+ SQLite/Postgres
  migrations)
- AI settings block (provider, recommendations, search) in global settings

Frontend:
- "Recommended for You" discover slider + dedicated /discover/ai-recommendations page
- AI Settings tab (provider config, feature toggles, maxResults, minScore, TTL)
- AI Recommendations Sync job registered in Jobs & Cache UI
- All AI endpoints declared in the OpenAPI spec (seerr-api.yml)

Composable dev setup via compose.ai.yaml (optional Ollama service).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds an "AI Search" mode to the existing search page so users can describe
what they want to watch in natural language ("90s psychological thrillers"),
alongside the regular keyword search.

Backend:
- aiSearch() now returns { results, interpretation } so the UI can show how
  the query was parsed
- POST /ai/search maps results through mapSearchResults + Media status
  (same shape as the regular /search endpoint) so TitleCard/ListView render
  unchanged in either mode

Frontend:
- useAiSearch hook (SWR, single non-paginated POST via closure-keyed fetcher)
- Search component gains an "AI Search" toggle; in AI mode it shows a
  loading state, an interpretation badge (genres, years, language, min
  rating, keywords), and the result grid
- Same search box as before — toggle is the only entry point, so the common
  title-lookup case stays fast (no LLM call) while NL queries opt in

OpenAPI spec updated for the /ai/search response (incl. interpretation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Search quality:
- Map LLM genre names to TMDb IDs per endpoint type (movie vs TV have
  different IDs), so /discover actually returns results instead of matching
  zero. A query like "dark dystopian sci-fi" now returns ~20 results
  (was ~5, all from suggested titles only).
- Run movie/TV discover with their correct date params and genre IDs.
- Sort merged search results by matchScore so LLM-suggested titles surface
  above discover results.

Provider robustness (helps search + recommendations):
- callLLM falls back to reasoning_content when content is empty, so
  reasoning models (GLM, o-series) work.
- Connection test uses the OpenAI SDK, tolerates reasoning-model replies,
  and surfaces the provider's real error on failure.
- GET /ai/settings returns hasApiKey (never the raw key); the test endpoint
  falls back to the stored key so users can re-test without re-entering it.
  Frontend shows a "key is saved" hint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Feedback loop (frontend + backend):
- AiRecommendationCard wraps TitleCard and overlays 👍/👁️/👎 buttons in
  TitleCard's own visual language (ghost, sm icon buttons, hover-reveal),
  placed just below the MOVIE/blocklist row.
- useAiFeedback hook submits/deletes feedback; dislike and seen remove the
  card optimistically, like marks it; toggle removes; errors roll back.
- /discover/ai-recommendations renders a grid of these cards (replacing the
  generic ListView) so feedback is isolated to the AI page.
- Like-biasing: generateRecommendations resolves liked titles and injects a
  "TITLES THE USER EXPLICITLY LIKED" section into the recommendation prompt,
  so likes now influence future generation (dislike/seen were already filtered).

UI polish:
- Fix "Unknown Slider" in the Discover edit view by mapping AI_RECOMMENDATIONS
  and AI_SEARCH in DiscoverSliderEdit.
- Add a hover tooltip ("Add to Blocklist") to the shared TitleCard blocklist
  button, matching the remove-from-blocklist button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-PR cleanup of the AI feature:

- minScore -> minRating: the setting is wired to TMDb vote_average.gte, so
  relabel and rescale it as a minimum rating (0-10, default 7) instead of a
  0-1 "confidence score" that did nothing at the default. Updated across the
  settings interface, route, UI, and docs. Also fixed the PUT handler to use
  `!== undefined` so a 0 floor actually saves (the old truthiness check was
  falsy for 0).
- i18n: run `pnpm i18n:extract` (the step skipped when the feature landed) so
  all AI strings -- including the AI Recommendations Sync job label -- are in
  en.json and exposed to Weblate. 58 keys added, none removed.
- Lint (52 -> 0 errors): convert relative imports to @server/@app aliases,
  add `import type` where type-only, remove unused imports/vars, drop the
  speculative userId scaffolding from the LLM client, restructure the three
  SettingsAi toggles to the htmlFor+id a11y pattern, and disable
  no-control-regex at the intentional control-char strip in JSON repair.

Verified: pnpm lint (0 errors), tsc --noEmit (clean), pnpm format:check (clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add docs/using-seerr/settings/ai.md (sidebar_position 8) following Seerr's
settings-doc voice, covering provider configuration, recommendations, AI
search, feedback, and a privacy warning. Includes the Recommended for You
screenshot under gen-docs/static/img/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The client typecheck run by `next build` found three errors that the
server-only `tsc --noEmit` didn't cover:

- AiRecommendationCard: `item.title` / `item.releaseDate` only exist on
  MovieResult; TvResult uses `name` / `firstAirDate`. Switched to the
  `mediaType === 'movie'` ternary already used in PersonDetails.
- SettingsAi: PageTitle only accepts `title` (the `message` prop was
  invalid -- the description is already rendered in the section header
  below).
- SettingsAi: the max-results input had a duplicate `type="number"`.

`pnpm build` now passes (next build + server tsc).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The dev container had flipped this auto-generated file to its dev-mode
variant (`.next/dev/types/...`); develop uses the production variant
(`.next/types/...`). Restored to match develop so the PR carries no
spurious diff on this file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per CONTRIBUTING.md "UI Text Style":
- "TMDb" -> "TMDB" in UI strings, docs, and comments (section 4: TMDB has
  a capital B; also the prevailing form across the codebase).
- Form tips no longer end in punctuation (section 6): minRatingTip,
  ttlDaysTip, apiKeySet.
- "..." -> "…" Unicode ellipsis (section 3): testing, testingConnection.

User-facing strings regenerated via pnpm i18n:extract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This dev-setup note was committed by mistake in the initial AI feature
commit. It is an AI-generated artifact and does not belong in the repo
(CONTRIBUTING.md section 4: do not commit AI tool / non-project files).
User-facing documentation lives under docs/using-seerr/settings/ai.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rholmboe
rholmboe requested a review from a team as a code owner July 18, 2026 23:33
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e6cb967-bbe2-49ce-802e-83a343f39bf6

📥 Commits

Reviewing files that changed from the base of the PR and between ef82b9f and ff68a40.

📒 Files selected for processing (2)
  • server/api/ai/index.ts
  • src/components/Search/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/api/ai/index.ts
  • src/components/Search/index.tsx

📝 Walkthrough

Walkthrough

Adds opt-in AI recommendations and natural-language search using OpenAI-compatible providers, persisted feedback and recommendations, scheduled generation, new APIs, settings, Docker Compose support, and corresponding discovery and search interfaces.

Changes

AI recommendation platform

Layer / File(s) Summary
Settings and recommendation storage
package.json, server/lib/settings/index.ts, server/entity/*, server/migration/*, server/constants/discover.ts, compose.ai.yaml, docs/using-seerr/settings/ai.md
Adds AI configuration and scheduling defaults, recommendation and feedback persistence, database migrations, discover slider types, Ollama Compose support, and AI settings documentation.
LLM and recommendation engine
server/api/ai/index.ts, server/lib/aiRecommendations.ts
Adds OpenAI-compatible structured generation, JSON repair and retries, personalized recommendations, AI search interpretation, TMDB resolution, filtering, persistence, and expiration cleanup.
Scheduled generation and API routes
server/job/schedule.ts, server/routes/ai.ts, server/routes/discover.ts, server/routes/index.ts, seerr-api.yml
Adds scheduled recommendation synchronization and authenticated endpoints for settings, provider testing, search, feedback, regeneration, and recommendation retrieval.
AI settings, discovery, and search UI
src/components/Settings/*, src/components/Discover/*, src/components/Search/index.tsx, src/hooks/useAi*.ts, src/pages/*/ai*.tsx, src/i18n/locale/en.json, src/components/TitleCard/index.tsx
Adds AI settings forms, recommendation cards and pages, feedback actions, AI search mode, discover sliders, routes, translations, and a blocklist tooltip.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Search
  participant aiRoutes
  participant aiRecommendations
  participant OpenAICompatibleClient
  participant TMDB
  User->>Search: Enter natural-language query
  Search->>aiRoutes: POST /ai/search
  aiRoutes->>aiRecommendations: aiSearch(userId, query)
  aiRecommendations->>OpenAICompatibleClient: interpretSearchQuery(query)
  OpenAICompatibleClient-->>aiRecommendations: discover parameters and titles
  aiRecommendations->>TMDB: discover and search media
  TMDB-->>aiRecommendations: matching results
  aiRecommendations-->>aiRoutes: results and interpretation
  aiRoutes-->>Search: mapped search response
  Search-->>User: AI results and interpretation
Loading

Suggested reviewers: gauthier-th, m0nsterrr, fallenbagel

Poem

I’m a rabbit with recommendations to share,
From watchlists and wishes, I hop through the air.
Ollama hums softly, the search box gleams,
“Seen,” “like,” and “dislike” now shape movie dreams.
Fresh titles arrive with a carrot-soft glow—
AI finds the next show wherever you go.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes fit the AI feature, but the TitleCard blocklist tooltip is unrelated to the linked issue scope. Move the TitleCard tooltip polish into a separate PR unless it is required for the AI feature.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main AI recommendations and natural-language search change.
Linked Issues check ✅ Passed The PR covers personalized recommendations, AI search, feedback, self-hosted providers, settings, persistence, and TTL as requested in #3257.

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: 20

🧹 Nitpick comments (2)
server/entity/AiRecommendation.ts (1)

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

Index updatedAt, which is the actual TTL lookup column.

Cleanup executes WHERE updatedAt < cutoff; the createdAt index cannot support that query and will cause table scans as recommendations accumulate.

-@Index(['createdAt'])
+@Index(['updatedAt'])

Also applies to: 51-55

🤖 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 `@server/entity/AiRecommendation.ts` at line 15, Update the entity index
declaration in AiRecommendation to index updatedAt instead of createdAt, and
ensure any corresponding index metadata near the referenced lines uses updatedAt
so the cleanup query can use the correct lookup column.
src/components/Discover/AiRecommendationCard/index.tsx (1)

49-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider removing or utilizing SHOW_RATIONALE_BUTTON.

SHOW_RATIONALE_BUTTON is hardcoded to false, making the rationale tooltip code unreachable. If this is a feature flag for future use, consider adding a TODO comment; otherwise, it can be removed to reduce dead code.

🤖 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 `@src/components/Discover/AiRecommendationCard/index.tsx` around lines 49 - 50,
Remove the unused SHOW_RATIONALE_BUTTON constant and the unreachable rationale
tooltip code it gates, unless the feature is intended for future use; in that
case, retain the flag and add a clear TODO describing when it should be enabled.
🤖 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 `@compose.ai.yaml`:
- Around line 29-30: Remove the Ollama port publication from the compose service
so its API remains accessible to Seerr through the internal ollama network
address. If host access is required, change the binding to localhost-only rather
than exposing port 11434 on all interfaces.

In `@docs/using-seerr/settings/ai.md`:
- Line 28: Update the Base URL documentation in the AI settings guide to include
the Docker Compose Ollama endpoint http://ollama:11434/v1, while retaining the
host-based http://localhost:11434/v1 example and clarifying when each URL
applies.

In `@server/api/ai/index.ts`:
- Around line 218-228: Validate the parsed result from extractJSON within
callForJSON before consuming it, using runtime schemas for each operation’s
expected shape. Reject or normalize invalid fields, including ensuring
recommendations is an array before slice() and discover parameters have valid
types before reaching TMDB calls; apply the same validation to the related
parsing paths around the referenced operations.
- Around line 116-122: Update callLLM’s response extraction and testConnection
to use a shared extractor that treats empty content as absent and falls back to
reasoning_content. Replace the current nullish-coalescing logic around
response.choices[0].message, including the corresponding extraction near
testConnection, while preserving the Empty response from LLM error when both
values are empty or unavailable.

In `@server/entity/AiRecommendation.ts`:
- Around line 13-21: Update AiRecommendation so userId is non-null and add a
unique constraint covering userId, tmdbId, and mediaType to prevent duplicate
recommendations. Change the timestamp index from createdAt to updatedAt, and
regenerate both migrations to reflect the schema changes.

In `@server/entity/UserFeedback.ts`:
- Around line 28-29: Update the UserFeedback entity’s feedbackType column
definition to declare an explicit database-level constraint permitting only
'like', 'dislike', and 'seen'. Keep the TypeScript union and ensure the column
metadata generates equivalent validation for both SQLite and PostgreSQL
migrations.

In `@server/job/schedule.ts`:
- Around line 278-347: Add a running guard around the AI Recommendations Sync
scheduled callback so a new invocation exits when a previous run is still
active. Set the guard before asynchronous work begins and reliably clear it
after completion, including failures, while preserving the existing
disabled-settings and per-user error handling; use the established
running-pattern symbol if available, or a distributed lock when multiple
instances can execute the job.

In `@server/lib/aiRecommendations.ts`:
- Around line 514-536: The discovery guard before buildParams must account for
every supported interpretation field, including year_to, original_language, and
sort_by, so queries containing only those values still run discovery. Update the
condition around buildParams, or use the normalized parameter object directly,
while preserving the existing parameter construction.
- Around line 565-579: Update the TMDB resolution logic around the title-search
loop and the positive-ID handling near the related resolution block to use one
shared resolver that validates title, year, and media type before accepting a
match. Validate model-generated IDs instead of trusting any positive ID, and
rank candidates by media type first, then year, so remakes and movie/TV
mismatches are rejected or deprioritized consistently.
- Around line 670-716: Normalize recommendation identity through a shared helper
that extracts tmdbId/id and mediaType/media_type, then keys identities as the
media type plus TMDB ID. Update the merged map in the recommendation merge flow
and the filtering logic in filterExistingContent to use this normalized
identity, preventing movie and TV collisions and ensuring AI search results are
excluded when watched or disliked. Remove the untyped excludedTmdbIds set and
apply the same helper across the additional recommendation paths.

In `@server/routes/ai.ts`:
- Around line 60-68: Update the provider settings logic in the request handler
so changing provider.type or provider.baseUrl clears the existing apiKey when no
replacement key is supplied. Preserve the current key only when the destination
remains unchanged, while continuing to apply a provided apiKey as the
replacement credential.
- Around line 134-143: Bind stored API keys to the provider destination they
were saved with: in server/routes/ai.ts lines 134-143, do not reuse the stored
key when the request provider type or base URL differs; in server/routes/ai.ts
lines 60-68, clear the saved key or require a replacement when saving a changed
destination; in src/components/Settings/SettingsAi/index.tsx lines 316-320, show
key-preservation guidance only when the destination remains unchanged, using the
existing provider and destination comparison symbols.

In `@server/routes/discover.ts`:
- Around line 1022-1055: Limit concurrency in the detailedResults flow around
the Promise.all over stored by processing recommendations in small sequential
batches, with each batch using Promise.all for parallel detail fetches. Preserve
the existing per-item try/catch behavior and result mapping, then combine all
batch results while retaining null entries for failed requests.

In `@server/routes/index.ts`:
- Line 152: Update the AI route mounting around aiRoutes so the global
administration endpoints /ai/settings and /ai/test require both authentication
and Permission.ADMIN authorization, while preserving regular authenticated
access to search, feedback, and regeneration routes. Apply the guard only to the
affected administrative endpoints rather than restricting the entire aiRoutes
mount.

In `@src/components/Discover/index.tsx`:
- Around line 412-423: The AI_SEARCH case in the Discover slider configuration
must use a GET-compatible endpoint and provide the required query parameter
through MediaSlider’s extraParams, or otherwise reuse the established
TMDB_SEARCH configuration if AI results cannot be fetched by MediaSlider’s
useSWRInfinite GET flow. Update the MediaSlider props in the AI_SEARCH branch
without routing it to the POST-only /api/v1/ai/search endpoint.

In `@src/components/Search/index.tsx`:
- Around line 125-135: Update the regular-search rendering branch in the Search
component to check regular.error before rendering ListView and display the
Next.js error page for failed requests. Import or alias that page as ErrorPage
to avoid shadowing the global Error constructor, while preserving the existing
ListView behavior when no error exists.
- Around line 42-48: Route all new user-facing text through react-intl: update
the interpretation field labels in src/components/Search/index.tsx lines 42-48,
the loading-state text in src/components/Settings/SettingsAi/index.tsx lines
143-147, and the provider section heading in
src/components/Settings/SettingsAi/index.tsx lines 216-220. Add or reuse
appropriate message identifiers and render each translated message through the
existing component internationalization pattern.
- Around line 110-120: Render only one empty-state message for AI search
results: update the ListView usage and surrounding conditional in the Search
component so an empty result set does not trigger both ListView’s global message
and the aiNoResults branch. Preserve the AI-specific message by disabling the
ListView empty state for this path or removing the redundant branch, using the
existing symbols ListView, isEmpty, and messages.aiNoResults.

In `@src/components/Settings/SettingsAi/index.tsx`:
- Around line 316-320: Update the provider API-key handling in the SettingsAi
component so changing the provider type or baseUrl clears the preserved
credential or requires a replacement key before submission. Do not display the
apiKeySet message or allow a blank field to reuse the old key when either
destination property changes; preserve the current behavior only when the
destination remains unchanged.

In `@src/hooks/useAiSearch.ts`:
- Around line 46-53: Update the useAiSearch SWR key so it includes the
authenticated user identity alongside the query, ensuring history-aware results
are not shared across accounts; preserve the null key behavior when no query is
provided and use the existing auth/user source available to the hook.

---

Nitpick comments:
In `@server/entity/AiRecommendation.ts`:
- Line 15: Update the entity index declaration in AiRecommendation to index
updatedAt instead of createdAt, and ensure any corresponding index metadata near
the referenced lines uses updatedAt so the cleanup query can use the correct
lookup column.

In `@src/components/Discover/AiRecommendationCard/index.tsx`:
- Around line 49-50: Remove the unused SHOW_RATIONALE_BUTTON constant and the
unreachable rationale tooltip code it gates, unless the feature is intended for
future use; in that case, retain the flag and add a clear TODO describing when
it should be enabled.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8d37c49-a223-401a-a6f0-d45a0fc6d4b9

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae70d0 and 73243a1.

⛔ Files ignored due to path filters (1)
  • gen-docs/static/img/seerr-ai-recommend.png is excluded by !**/*.png
📒 Files selected for processing (31)
  • compose.ai.yaml
  • docs/using-seerr/settings/ai.md
  • package.json
  • seerr-api.yml
  • server/api/ai/index.ts
  • server/constants/discover.ts
  • server/entity/AiRecommendation.ts
  • server/entity/UserFeedback.ts
  • server/job/schedule.ts
  • server/lib/aiRecommendations.ts
  • server/lib/settings/index.ts
  • server/migration/postgres/1784393737543-AddAiRecommendations.ts
  • server/migration/sqlite/1784393737543-AddAiRecommendations.ts
  • server/routes/ai.ts
  • server/routes/discover.ts
  • server/routes/index.ts
  • src/components/Discover/AiRecommendationCard/index.tsx
  • src/components/Discover/AiRecommendations.tsx
  • src/components/Discover/DiscoverSliderEdit/index.tsx
  • src/components/Discover/constants.ts
  • src/components/Discover/index.tsx
  • src/components/Search/index.tsx
  • src/components/Settings/SettingsAi/index.tsx
  • src/components/Settings/SettingsJobsCache/index.tsx
  • src/components/Settings/SettingsLayout.tsx
  • src/components/TitleCard/index.tsx
  • src/hooks/useAiFeedback.ts
  • src/hooks/useAiSearch.ts
  • src/i18n/locale/en.json
  • src/pages/discover/ai-recommendations.tsx
  • src/pages/settings/ai.tsx

Comment thread compose.ai.yaml Outdated
Comment thread docs/using-seerr/settings/ai.md Outdated
Comment thread server/api/ai/index.ts Outdated
Comment thread server/api/ai/index.ts
Comment thread server/entity/AiRecommendation.ts Outdated
Comment thread src/components/Search/index.tsx Outdated
Comment thread src/components/Search/index.tsx
Comment thread src/components/Search/index.tsx
Comment thread src/components/Settings/SettingsAi/index.tsx Outdated
Comment thread src/hooks/useAiSearch.ts
Security:
- Require admin (Permission.ADMIN) on /api/v1/ai/settings and /ai/test.
- Don't reuse a saved API key when the provider type or base URL changes:
  clear it on save, and in /test only fall back to the stored key for the
  same destination. SettingsAi shows the key-preservation hint only when
  the destination is unchanged.

Correctness:
- AI search discovery now runs for any interpretation field (year_to,
  original_language, sort_by), not just genres/year_from/min_rating.
- Stop trusting model-supplied tmdbId values; always resolve by title+yr.
- Key recommendation identity by mediaType+tmdbId so movie/TV no longer
  collide (merge, search-merge, and feedback exclusion).
- Search: single empty state for AI results; render ErrorPage on
  regular-search failure; internationalize the interpretation labels.

Robustness:
- Guard the AI Recommendations Sync job against overlapping runs.
- Batch TMDB detail fetches (chunks of 5) in /discover/ai-recommendations.
- Validate parsed recommendations is an array before slicing.

Schema:
- AiRecommendation: userId NOT NULL, unique(userId,tmdbId,mediaType), and
  index on updatedAt (matches the TTL cleanup query). Both migrations updated.

Misc:
- i18n: SettingsAi loading/Provider Configuration strings; include the
  user id in the useAiSearch SWR key.
- compose.ai.yaml: bind the Ollama port to 127.0.0.1.
- docs: note the Docker-internal Ollama base URL.
- Remove the unused/broken AI_SEARCH discover slider.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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

🤖 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 `@src/components/Search/index.tsx`:
- Around line 91-93: Remove the top-level regular search error return from the
component, and move the regular.error handling into the non-AI rendering branch
that displays ListView. In AI mode, continue rendering the AI results and toggle
even when the background useDiscover request fails; only replace the regular
search view with ErrorPage when that branch is active.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f70e8d0-b435-49fa-899a-d3b16e33dfd2

📥 Commits

Reviewing files that changed from the base of the PR and between 73243a1 and ef82b9f.

📒 Files selected for processing (18)
  • compose.ai.yaml
  • docs/using-seerr/settings/ai.md
  • server/api/ai/index.ts
  • server/constants/discover.ts
  • server/entity/AiRecommendation.ts
  • server/job/schedule.ts
  • server/lib/aiRecommendations.ts
  • server/migration/postgres/1784393737543-AddAiRecommendations.ts
  • server/migration/sqlite/1784393737543-AddAiRecommendations.ts
  • server/routes/ai.ts
  • server/routes/discover.ts
  • src/components/Discover/DiscoverSliderEdit/index.tsx
  • src/components/Discover/constants.ts
  • src/components/Discover/index.tsx
  • src/components/Search/index.tsx
  • src/components/Settings/SettingsAi/index.tsx
  • src/hooks/useAiSearch.ts
  • src/i18n/locale/en.json
💤 Files with no reviewable changes (4)
  • src/components/Discover/DiscoverSliderEdit/index.tsx
  • server/constants/discover.ts
  • src/components/Discover/index.tsx
  • src/components/Discover/constants.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • compose.ai.yaml
  • docs/using-seerr/settings/ai.md
  • src/hooks/useAiSearch.ts
  • server/job/schedule.ts
  • server/routes/discover.ts
  • server/entity/AiRecommendation.ts
  • server/migration/sqlite/1784393737543-AddAiRecommendations.ts
  • server/migration/postgres/1784393737543-AddAiRecommendations.ts
  • src/i18n/locale/en.json
  • server/lib/aiRecommendations.ts
  • server/routes/ai.ts
  • src/components/Settings/SettingsAi/index.tsx
  • server/api/ai/index.ts

Comment thread src/components/Search/index.tsx Outdated
rholmboe and others added 2 commits July 19, 2026 15:33
The top-level regular.error check returned ErrorPage even in AI mode,
where the background keyword search can fail independently of the AI
view. Move the check into the non-AI branch so AI results and the toggle
keep rendering; only the regular-search view swaps to ErrorPage.

Addresses a CodeRabbit follow-up on seerr-team#3258.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dedupe the content/reasoning_content extraction now shared by callLLM and
testConnection. Also fixes testConnection to honor reasoning_content
(reasoning models like GLM/o-series may put the answer there), which the
previous content-only check missed.

Addresses a CodeRabbit review comment on seerr-team#3258.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

AI-powered personalized recommendations and natural-language search

1 participant