Skip to content

feat(web): AI Search Assist#951

Merged
brendan-kellam merged 9 commits intomainfrom
bkellam/search-assist
Feb 27, 2026
Merged

feat(web): AI Search Assist#951
brendan-kellam merged 9 commits intomainfrom
bkellam/search-assist

Conversation

@brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Feb 27, 2026

search-assist.mp4

Summary

  • Adds an AI Search Assist panel to the search bar, allowing users to describe what they're looking for in natural language and generate a Sourcebot search query automatically
  • Implements a translateSearchQuery server action backed by a configured language model using structured output (generateObject) to ensure clean query results
  • Adds a SEARCH_SYNTAX_DESCRIPTION to the query language package used as the LLM system prompt
  • Wires isSearchAssistSupported through the component tree — the wand button is disabled with a tooltip when no language model is configured
  • Adds PostHog instrumentation for the adoption funnel (wa_search_assist_opened, wa_search_assist_query_generated, wa_search_assist_generate_failed, wa_search_assist_example_clicked)
  • Adds docs page at docs/features/search/ai-search-assist.mdx with demo video

Test plan

  • Configure a language model and verify the wand button is enabled in the search bar
  • Open the AI Search Assist panel and type a natural language description, confirm a query is generated and inserted into the search bar
  • Press Enter on an example pill and confirm it submits directly
  • Remove language model config and verify the wand button is disabled with a tooltip linking to docs
  • Verify PostHog events fire correctly for open, generate, failure, and example click

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • AI Search Assist: describe what you want in natural language and have the app generate a code search query; integrated into the search bar with examples, keyboard handling, and a floating “Generate a query” UI.
  • Documentation

    • Added a full guide with video demo, usage steps, and configuration notes; navigation entry added to docs.
  • Chores

    • Added a news item and new analytics events for assist usage.

@github-actions

This comment has been minimized.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19d8ad9 and 7659353.

📒 Files selected for processing (1)
  • packages/web/src/app/[domain]/components/searchBar/searchAssistBox.tsx

Walkthrough

Adds an AI Search Assist feature: docs and changelog, a server action to translate natural-language prompts to Sourcebot queries, a new SearchAssistBox UI and SearchBar integration, query-language export for syntax description, analytics events, and plumbing for feature availability across pages and layout.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, docs/docs.json, docs/docs/features/search/ai-search-assist.mdx
Added changelog entry, docs navigation entry, and a new MDX page with demo, usage, and requirements.
Query Language Exports
packages/queryLanguage/src/index.ts, packages/queryLanguage/src/syntaxDescription.ts
Added SEARCH_SYNTAX_DESCRIPTION constant (long raw string) and re-exported it from the package index.
Search UI Components
packages/web/src/app/[domain]/components/searchBar/searchAssistBox.tsx, packages/web/src/app/[domain]/components/searchBar/searchBar.tsx, packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx
New client component SearchAssistBox (input, generate button, examples, loading/error handling, analytics); SearchBar refactored to panel state and integrates SearchAssistBox; SearchSuggestionsBox accepts optional className.
Search Pages
packages/web/src/app/[domain]/search/page.tsx, .../search/components/searchLandingPage.tsx, .../search/components/searchResultsPage.tsx
Added language-model detection and threaded isSearchAssistSupported prop through landing/results pages to SearchBar.
Browse Layout
packages/web/src/app/[domain]/browse/layout.tsx, .../browse/layoutClient.tsx
Detect configured language models and pass isSearchAssistSupported to layout client and downstream components.
Server Action
packages/web/src/features/searchAssist/actions.ts
New server-side translateSearchQuery({ prompt }) action: validates configured models, selects model, calls AI SDK generateObject with system prompt + SEARCH_SYNTAX_DESCRIPTION, returns { query: string }, signals errors via standard ServiceError codes.
Data & Events
packages/web/src/lib/newsData.ts, packages/web/src/lib/posthogEvents.ts
Added news item for AI Search Assist and four PostHog events (opened, query_generated, generate_failed, example_clicked).

Sequence Diagram

sequenceDiagram
    participant User
    participant SearchAssistBox as SearchAssistBox (Client)
    participant Server as translateSearchQuery (Server)
    participant LM as LanguageModel (AI SDK)
    participant SearchBar as SearchBar (Client)

    User->>SearchAssistBox: Open panel / enter prompt
    SearchAssistBox->>SearchAssistBox: Capture prompt
    User->>SearchAssistBox: Press Enter / Click Generate
    SearchAssistBox->>Server: POST translateSearchQuery({ prompt })
    activate Server
    Server->>Server: getConfiguredLanguageModelsInfo()
    Server->>LM: generateObject(systemPrompt + SEARCH_SYNTAX_DESCRIPTION, schema)
    activate LM
    LM-->>Server: { query }
    deactivate LM
    Server-->>SearchAssistBox: { query }
    deactivate Server
    SearchAssistBox->>SearchBar: onQueryGenerated(query)
    SearchBar->>SearchBar: Insert query, focus editor, set flags
    SearchBar->>User: Display generated query in search input
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #951: Implements overlapping AI Search Assist changes (same symbols, components, and docs).
  • PR #424: Adds OpenAI-compatible provider / language-model plumbing used by translateSearchQuery.
  • PR #623: Related updates to the query-language package and exports referenced by SEARCH_SYNTAX_DESCRIPTION.

Suggested labels

sourcebot-team

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(web): AI Search Assist' clearly and concisely describes the main feature addition—an AI-powered search assistance panel for the web application.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bkellam/search-assist

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.

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

🧹 Nitpick comments (7)
docs/docs/features/search/ai-search-assist.mdx (1)

7-14: Consider wrapping the video in a <Frame> component for consistent styling.

The coding guidelines specify that images should be wrapped in <Frame>. While this video is technically not an image, wrapping it in <Frame> may provide consistent visual presentation with other media in the docs.

💡 Optional: Wrap video in Frame
-<video
-  autoPlay
-  muted
-  loop
-  playsInline
-  className="w-full aspect-video"
-  src="/images/search-assist.mp4"
-></video>
+<Frame>
+  <video
+    autoPlay
+    muted
+    loop
+    playsInline
+    className="w-full aspect-video"
+    src="/images/search-assist.mp4"
+  ></video>
+</Frame>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/features/search/ai-search-assist.mdx` around lines 7 - 14, Wrap the
video JSX in the shared Frame component to ensure consistent styling with other
media (replace the bare <video ...> element with a <Frame> that contains the
<video>), updating the file's JSX where the video element appears; ensure the
Frame import (Frame) is present at the top of the module if not already and
preserve all existing video props (autoPlay, muted, loop, playsInline,
className, src).
packages/web/src/lib/posthogEvents.ts (1)

238-244: Consider adding error details to the failure event.

The wa_search_assist_generate_failed event has an empty payload. Adding an errorCode field (consistent with other _fail events in this file) would help with debugging and analytics.

💡 Suggested improvement
     wa_search_assist_opened: {},
     wa_search_assist_query_generated: {},
-    wa_search_assist_generate_failed: {},
+    wa_search_assist_generate_failed: {
+        errorCode: string,
+    },
     wa_search_assist_example_clicked: {
         example: string,
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/lib/posthogEvents.ts` around lines 238 - 244, Add an
errorCode field to the wa_search_assist_generate_failed event payload so
failures report useful error metadata; update the event definition named
wa_search_assist_generate_failed to include errorCode: string (matching other
*_fail events), and ensure any places emitting this event supply the errorCode
when dispatching so analytics/debugging can surface the failure reason.
packages/web/src/features/searchAssist/actions.ts (3)

27-28: Consider validating prompt length to prevent abuse.

There's no validation on the input prompt length. Extremely long prompts could:

  1. Increase API costs
  2. Hit model token limits
  3. Be used for abuse
💡 Optional: Add prompt length validation
 export const translateSearchQuery = async ({ prompt }: { prompt: string }) => sew(() =>
     withOptionalAuthV2(async () => {
+        const MAX_PROMPT_LENGTH = 1000;
+        if (!prompt || prompt.trim().length === 0) {
+            return {
+                statusCode: StatusCodes.BAD_REQUEST,
+                errorCode: ErrorCode.INVALID_REQUEST_BODY,
+                message: 'Prompt cannot be empty.',
+            } satisfies ServiceError;
+        }
+        if (prompt.length > MAX_PROMPT_LENGTH) {
+            return {
+                statusCode: StatusCodes.BAD_REQUEST,
+                errorCode: ErrorCode.INVALID_REQUEST_BODY,
+                message: `Prompt exceeds maximum length of ${MAX_PROMPT_LENGTH} characters.`,
+            } satisfies ServiceError;
+        }
+
         const models = await _getConfiguredLanguageModelsFull();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/searchAssist/actions.ts` around lines 27 - 28,
translateSearchQuery currently accepts an unbounded prompt which can cause
excessive cost, token-limit errors, or abuse; add input validation at the start
of translateSearchQuery (before calling sew/withOptionalAuthV2 logic) to enforce
a sensible max length (e.g., MAX_PROMPT_LENGTH constant) and reject or truncate
prompts that exceed it, returning a clear error (or throwing an Error) for the
caller; reference the translateSearchQuery function and enforce the check on the
prompt parameter so oversized inputs are handled before invoking downstream
model/API calls.

41-48: Consider adding error handling for the AI SDK call.

The generateObject call can throw exceptions (network errors, rate limits, invalid responses, etc.). Without try-catch, these will propagate as unhandled errors with potentially unclear error messages to users.

💡 Suggested error handling
+        try {
             const { object } = await generateObject({
                 model,
                 system: SYSTEM_PROMPT,
                 prompt,
                 schema: z.object({
                     query: z.string().describe("The Sourcebot search query."),
                 }),
             });

             return { query: object.query };
+        } catch (error) {
+            console.error('Failed to generate search query:', error);
+            return {
+                statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
+                errorCode: ErrorCode.INTERNAL_SERVER_ERROR,
+                message: 'Failed to generate search query.',
+            } satisfies ServiceError;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/searchAssist/actions.ts` around lines 41 - 48, Wrap
the generateObject call in a try-catch inside the action where it is invoked
(the generateObject invocation that destructures { object } with model,
SYSTEM_PROMPT, prompt, and z.object schema). In the catch block log the full
error (including error.message/stack) using the existing logger or console,
return or throw a clear, user-friendly error response (or a fallback value) so
the caller can handle it, and ensure you handle the case where object is
undefined after failure before continuing processing.

31-37: Consider using a more appropriate HTTP status code.

BAD_REQUEST (400) implies the client sent an invalid request, but "no language models configured" is a server-side configuration issue. SERVICE_UNAVAILABLE (503) would more accurately indicate the feature is not available due to missing configuration.

💡 Suggested fix
         if (models.length === 0) {
             return {
-                statusCode: StatusCodes.BAD_REQUEST,
-                errorCode: ErrorCode.INVALID_REQUEST_BODY,
+                statusCode: StatusCodes.SERVICE_UNAVAILABLE,
+                errorCode: ErrorCode.INVALID_REQUEST_BODY,
                 message: 'No language models are configured.',
             } satisfies ServiceError;
         }

Also consider adding a more specific error code like NO_LANGUAGE_MODEL_CONFIGURED if the error code enum supports it.

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

In `@packages/web/src/features/searchAssist/actions.ts` around lines 31 - 37,
Replace the client-side 400 response when no language models are found with a
server-side availability error: in the block checking models.length === 0,
change StatusCodes.BAD_REQUEST to StatusCodes.SERVICE_UNAVAILABLE and swap
ErrorCode.INVALID_REQUEST_BODY for a more specific code (e.g.,
ErrorCode.NO_LANGUAGE_MODEL_CONFIGURED or add that enum value if missing) while
keeping the ServiceError shape returned by the function (refer to the
models.length === 0 check, StatusCodes.BAD_REQUEST, and
ErrorCode.INVALID_REQUEST_BODY).
packages/queryLanguage/src/syntaxDescription.ts (1)

91-92: Minor: log4j example references package.json but log4j is a Java dependency.

The example shows searching for log4j versions in package.json, but log4j is a Java logging library typically found in pom.xml or build.gradle. Consider updating to a JavaScript/Node.js package example for consistency, or changing to the appropriate file filter.

💡 Suggested fix
-Input: find log4j versions 2.3.x or lower
-Output: file:package\.json "\"log4j\": \"\^?2\.([0-2]|3)\."
+Input: find lodash versions 4.16.x or lower
+Output: file:package\.json "\"lodash\": \"\^?4\.(1[0-6]|[0-9])\."

Or keep log4j but use Java build files:

-Output: file:package\.json "\"log4j\": \"\^?2\.([0-2]|3)\."
+Output: file:pom\.xml "<version>2\.([0-2]|3)\."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/queryLanguage/src/syntaxDescription.ts` around lines 91 - 92, Update
the example "Input: find log4j versions 2.3.x or lower" / "Output:
file:package\.json ..." in syntaxDescription.ts to avoid suggesting log4j is
found in package.json; either replace the package.json file filter with a Java
build file filter (e.g., pom.xml or build.gradle) when keeping log4j, or swap
the dependency example to a JS/Node package (e.g., "lodash" or "express") so the
file:package\.json regex is correct; locate and edit the example string(s) in
syntaxDescription.ts (the Input/Output example lines) to reflect the chosen fix.
packages/web/src/app/[domain]/components/searchBar/searchAssistBox.tsx (1)

9-10: Consolidate imports from the same module.

The two imports from @/lib/utils can be merged into a single import statement for cleaner code organization.

♻️ Suggested fix
-import { isServiceError } from "@/lib/utils";
-import { cn } from "@/lib/utils";
+import { cn, isServiceError } from "@/lib/utils";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/`[domain]/components/searchBar/searchAssistBox.tsx
around lines 9 - 10, Combine the two separate imports from "@/lib/utils" into a
single import statement that imports both isServiceError and cn together; update
the import declarations so only one import from "@/lib/utils" exists and
references both symbols (isServiceError, cn) to remove the duplicate import.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/docs/features/search/ai-search-assist.mdx`:
- Around line 7-14: Wrap the video JSX in the shared Frame component to ensure
consistent styling with other media (replace the bare <video ...> element with a
<Frame> that contains the <video>), updating the file's JSX where the video
element appears; ensure the Frame import (Frame) is present at the top of the
module if not already and preserve all existing video props (autoPlay, muted,
loop, playsInline, className, src).

In `@packages/queryLanguage/src/syntaxDescription.ts`:
- Around line 91-92: Update the example "Input: find log4j versions 2.3.x or
lower" / "Output: file:package\.json ..." in syntaxDescription.ts to avoid
suggesting log4j is found in package.json; either replace the package.json file
filter with a Java build file filter (e.g., pom.xml or build.gradle) when
keeping log4j, or swap the dependency example to a JS/Node package (e.g.,
"lodash" or "express") so the file:package\.json regex is correct; locate and
edit the example string(s) in syntaxDescription.ts (the Input/Output example
lines) to reflect the chosen fix.

In `@packages/web/src/app/`[domain]/components/searchBar/searchAssistBox.tsx:
- Around line 9-10: Combine the two separate imports from "@/lib/utils" into a
single import statement that imports both isServiceError and cn together; update
the import declarations so only one import from "@/lib/utils" exists and
references both symbols (isServiceError, cn) to remove the duplicate import.

In `@packages/web/src/features/searchAssist/actions.ts`:
- Around line 27-28: translateSearchQuery currently accepts an unbounded prompt
which can cause excessive cost, token-limit errors, or abuse; add input
validation at the start of translateSearchQuery (before calling
sew/withOptionalAuthV2 logic) to enforce a sensible max length (e.g.,
MAX_PROMPT_LENGTH constant) and reject or truncate prompts that exceed it,
returning a clear error (or throwing an Error) for the caller; reference the
translateSearchQuery function and enforce the check on the prompt parameter so
oversized inputs are handled before invoking downstream model/API calls.
- Around line 41-48: Wrap the generateObject call in a try-catch inside the
action where it is invoked (the generateObject invocation that destructures {
object } with model, SYSTEM_PROMPT, prompt, and z.object schema). In the catch
block log the full error (including error.message/stack) using the existing
logger or console, return or throw a clear, user-friendly error response (or a
fallback value) so the caller can handle it, and ensure you handle the case
where object is undefined after failure before continuing processing.
- Around line 31-37: Replace the client-side 400 response when no language
models are found with a server-side availability error: in the block checking
models.length === 0, change StatusCodes.BAD_REQUEST to
StatusCodes.SERVICE_UNAVAILABLE and swap ErrorCode.INVALID_REQUEST_BODY for a
more specific code (e.g., ErrorCode.NO_LANGUAGE_MODEL_CONFIGURED or add that
enum value if missing) while keeping the ServiceError shape returned by the
function (refer to the models.length === 0 check, StatusCodes.BAD_REQUEST, and
ErrorCode.INVALID_REQUEST_BODY).

In `@packages/web/src/lib/posthogEvents.ts`:
- Around line 238-244: Add an errorCode field to the
wa_search_assist_generate_failed event payload so failures report useful error
metadata; update the event definition named wa_search_assist_generate_failed to
include errorCode: string (matching other *_fail events), and ensure any places
emitting this event supply the errorCode when dispatching so analytics/debugging
can surface the failure reason.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e369199 and 19d8ad9.

⛔ Files ignored due to path filters (1)
  • docs/images/search-assist.mp4 is excluded by !**/*.mp4
📒 Files selected for processing (16)
  • CHANGELOG.md
  • docs/docs.json
  • docs/docs/features/search/ai-search-assist.mdx
  • packages/queryLanguage/src/index.ts
  • packages/queryLanguage/src/syntaxDescription.ts
  • packages/web/src/app/[domain]/browse/layout.tsx
  • packages/web/src/app/[domain]/browse/layoutClient.tsx
  • packages/web/src/app/[domain]/components/searchBar/searchAssistBox.tsx
  • packages/web/src/app/[domain]/components/searchBar/searchBar.tsx
  • packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx
  • packages/web/src/app/[domain]/search/components/searchLandingPage.tsx
  • packages/web/src/app/[domain]/search/components/searchResultsPage.tsx
  • packages/web/src/app/[domain]/search/page.tsx
  • packages/web/src/features/searchAssist/actions.ts
  • packages/web/src/lib/newsData.ts
  • packages/web/src/lib/posthogEvents.ts

@brendan-kellam brendan-kellam merged commit 1db9ed6 into main Feb 27, 2026
6 checks passed
@brendan-kellam brendan-kellam deleted the bkellam/search-assist branch February 27, 2026 18:36
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.

2 participants