Skip to content

Conversation

ahmedriad1
Copy link
Member

@ahmedriad1 ahmedriad1 commented Sep 30, 2025

Note

Adds a new Chunk Explorer with semantic/keyword modes and advanced filters, updates Playground navigation/UI, and introduces a backend endpoint to power chunk exploration.

  • UI/UX:
    • Playground: Rename page title to Chat and remove in-page tabs.
    • Chunk Explorer: New search page (playground/search) with advanced filters (mode, topK, minScore, rerank, metadata, relationships), improved loading/error states, and detailed results header.
    • Navigation: Sidebar Playground now has nested items for Chat and Chunk Explorer.
  • Frontend Logic:
    • Query updated to trpc.search.exploreChunks with filter params; analytics event renamed to playground_chunk_exploration.
  • Backend:
    • New search.exploreChunks TRPC endpoint with validated input, supports semantic (vector store + optional rerank) and keyword search, enforces keywordEnabled, returns results with parameters/metadata, and tracks usage.

Written by Cursor Bugbot for commit a1b4b17. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features
    • Introduced an advanced Chunk Explorer with filters: topK, rerank toggle, model selection, and rerank limit.
    • Enhanced results area with counts, loading/error/empty states, and improved item rendering.
  • UI/UX
    • Split Playground into dedicated Chat and Search pages; updated page titles.
    • Updated sidebar to group Playground into Chat and Search for quicker access.
    • Simplified navigation by removing in-page tabs; actions are now focused per page.
  • Refactor
    • Configuration and settings persistence streamlined for improved reliability.

@vercel
Copy link

vercel bot commented Sep 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
app-agentset-ai Ready Ready Preview Comment Oct 15, 2025 7:03am

@coderabbitai
Copy link

coderabbitai bot commented Sep 30, 2025

Walkthrough

Refactors the Playground into separate Chat and Search experiences, overhauls the Search client into a Chunk Explorer with reranking controls, updates the server search router to use vector-store querying with rerank options, restructures the Next.js config to an async factory using jiti, updates the sidebar links, and tweaks chat settings persistence.

Changes

Cohort / File(s) Summary
Playground navigation removal
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/page.tsx, apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx
Removed Tabs/Link-based navigation and useParams; pages now render a single view each. Chat page title set to "Chat". Search page now renders ChunkExplorerPageClient directly. No public signature changes.
Chunk Explorer UI overhaul
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
Renamed default export to ChunkExplorerPageClient. Added topK, rerank, rerankModel, rerankLimit controls and payload. Introduced richer loading/error/empty states and results rendering via DataWrapper. Updated analytics event name/payload.
Sidebar links update
apps/web/src/components/app-sidebar/links.ts
Replaced single Playground link with grouped items: Chat and Search (/playground/search). Added SearchIcon import. Public type remains SidebarItemType[].
Search API refactor
apps/web/src/server/api/routers/search.ts
Replaced agenticSearch with queryVectorStore. Switched input to chunkExplorerInputSchema (fields: namespaceId, query, topK, rerank, rerankModel, rerankLimit, filter). Returns vector-store results directly; usage increment simplified. Public router input shape changed.
Next.js config to async factory
apps/web/next.config.ts, apps/web/package.json
Converted static export to export default makeConfig() with async env loading via jiti. Preserves/adjusts images, transpilePackages, webpack (PrismaPlugin on server), rewrites, and lint/TS ignores. Added jiti dependency. Default export is now Promise-based.
Chat settings persistence tweak
apps/web/src/components/chat/chat-settings.store.ts
In updateNamespace, spreads defaultState before existing state/update. Persist initializer signature changed from (set, get) to (set). Public factory signature updated accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant C as ChunkExplorerPageClient
  participant API as searchRouter
  participant E as embeddingModel
  participant VS as vectorStore
  participant R as reranker (optional)

  U->>C: Enter query + options (topK, rerank, model, limit, filter)
  C->>API: search({ namespaceId, query, topK, rerank, rerankModel, rerankLimit, filter })
  par Resolve dependencies
    API->>E: getEmbeddingModel()
    API->>VS: getVectorStore()
  end
  API->>VS: queryVectorStore({ query, topK, includeMetadata, filter })
  alt rerank == true
    API->>R: rerank(results, model=rerankModel, limit=rerankLimit)
    R-->>API: reranked results
  else
    Note over API,VS: Use raw vector-store results
  end
  API-->>C: results[]
  C-->>U: Render results, counts, states
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A hop, a skip, I tweak the search,
With reranks ready, I make my lurch.
Chat goes left, Search goes right—
Tabs hop off into the night.
Vectors hum, results align,
A bunny ships—on carrot time. 🥕✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 “New playground search experience” succinctly summarizes the primary feature added by this PR—the Chunk Explorer search interface—without extraneous detail, and it aligns with the main user-facing change in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/new-playground-search

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

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

🧹 Nitpick comments (8)
apps/web/src/components/app-sidebar/links.ts (1)

12-12: Nit: Drop unused import (if not planned).

SearchIcon is imported but not used. If there's no immediate use, remove it to keep the bundle and lints clean.

apps/web/src/server/api/routers/search.ts (3)

88-97: Guard rerankLimit against topK.

If rerankLimit > topK, you’ll over-request/rerank more than you retrieve. Clamp it to topK.

-          rerankLimit: input.rerankLimit,
+          rerankLimit:
+            input.rerankLimit !== undefined
+              ? Math.min(input.rerankLimit, input.topK)
+              : undefined,

118-120: Usage accounting parity with agentic search.

You increment by 1 here, while the search route uses results.totalQueries. If billing/quotas depend on actual underlying calls (rerank, pagination), consider incrementing by the real query count returned by the vector/keyword layer.


121-133: Return the applied filter for transparency.

If you convert the filter for keyword mode, consider returning the applied (string) filter rather than the original object to aid debugging and reproducibility.

apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (4)

86-88: Type-safety for handleFilterChange

Avoid any to catch mistakes at compile time.

-const handleFilterChange = (key: keyof ChunkExplorerFilters, value: any) => {
-  setFilters((prev) => ({ ...prev, [key]: value }));
-};
+const handleFilterChange = <K extends keyof ChunkExplorerFilters>(
+  key: K,
+  value: ChunkExplorerFilters[K],
+) => {
+  setFilters((prev) => ({ ...prev, [key]: value }));
+};

160-174: Use functional state update for toggle

Prevents stale state bugs if multiple rapid clicks occur.

-onClick={() => setShowFilters(!showFilters)}
+onClick={() => setShowFilters((v) => !v)}

284-293: Friendlier error handling

TRPC errors can be verbose. Consider a safe fallback while still exposing details in logs.

-<p className="text-muted-foreground mt-1 text-sm">
-  {error.message}
-</p>
+<p className="text-muted-foreground mt-1 text-sm">
+  {(error as { message?: string })?.message ?? "Something went wrong. Please try again."}
+</p>

90-368: Optional: persist query + filters in the URL

Improves shareability and back/forward navigation. Encode state into searchParams and initialize from them on mount.

Happy to draft the useSearchParams + router.replace integration.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81090bd and a1b4b17.

📒 Files selected for processing (5)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/page.tsx (1 hunks)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3 hunks)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx (1 hunks)
  • apps/web/src/components/app-sidebar/links.ts (2 hunks)
  • apps/web/src/server/api/routers/search.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
apps/web/src/server/api/routers/search.ts (4)
apps/web/src/server/api/trpc.ts (1)
  • protectedProcedure (122-133)
apps/web/src/server/api/auth.ts (1)
  • getNamespaceByUser (5-25)
packages/engine/src/keyword-store/index.ts (1)
  • KeywordStore (50-247)
apps/web/src/lib/api/usage.ts (1)
  • incrementSearchUsage (5-23)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx (2)
apps/web/src/components/dashboard-page-wrapper/index.tsx (1)
  • DashboardPageWrapper (21-92)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (1)
  • ChunkExplorerPageClient (45-369)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3)
apps/web/src/hooks/use-namespace.ts (1)
  • useNamespace (9-46)
packages/engine/src/vector-store/pinecone.ts (1)
  • query (97-114)
apps/web/src/lib/analytics.ts (1)
  • logEvent (7-13)
🔇 Additional comments (4)
apps/web/src/components/app-sidebar/links.ts (1)

91-104: Verify active-state behavior for nested links.

"Chat" is marked exact: true; "Chunk Explorer" is not. Confirm the sidebar correctly highlights the active item on /playground/search and does not incorrectly keep the group or "Chat" highlighted.

apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/page.tsx (1)

11-17: LGTM: Chat-only playground page.

Title and actions match the new navigation model. No issues spotted.

If legacy links used ?tab=search, consider a redirect to /playground/search to avoid broken bookmarks.

apps/web/src/server/api/routers/search.ts (1)

6-6: zod/v4 imports are consistent repo-wide. No direct import "zod" entries found; all code imports from "zod/v4". Transitive dependencies still include v3.x, but project code is fully on v4.

apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx (1)

5-9: LGTM: clean page composition

Server page wraps the client with DashboardPageWrapper and requireNamespace=true. No issues spotted.

Comment on lines 96 to 111
<div className="flex gap-2">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Enter your search query to explore chunks..."
className="flex-1"
/>
<Button
type="submit"
disabled={isLoading || !query.trim()}
isLoading={isLoading || isFetching}
>
<SearchIcon className="size-4" />
Explore
</Button>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

A11y: add accessible name for the search input

Input has no programmatic label; placeholders aren’t labels. Add a visually hidden label and an id.

-<div className="flex gap-2">
+<div className="flex gap-2">
+  <Label htmlFor="chunkQuery" className="sr-only">
+    Search chunks
+  </Label>
   <Input
+    id="chunkQuery"
     value={query}
     onChange={(e) => setQuery(e.target.value)}
     placeholder="Enter your search query to explore chunks..."
     className="flex-1"
   />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex gap-2">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Enter your search query to explore chunks..."
className="flex-1"
/>
<Button
type="submit"
disabled={isLoading || !query.trim()}
isLoading={isLoading || isFetching}
>
<SearchIcon className="size-4" />
Explore
</Button>
</div>
<div className="flex gap-2">
<Label htmlFor="chunkQuery" className="sr-only">
Search chunks
</Label>
<Input
id="chunkQuery"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Enter your search query to explore chunks..."
className="flex-1"
/>
<Button
type="submit"
disabled={isLoading || !query.trim()}
isLoading={isLoading || isFetching}
>
<SearchIcon className="size-4" />
Explore
</Button>
</div>
🤖 Prompt for AI Agents
In
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
around lines 96 to 111, the Input has no programmatic label which breaks
accessibility; add an id prop to the Input and render a visually hidden label
element tied to that id (e.g., <label htmlFor="search-query"
className="sr-only">Search query</label>) immediately before the Input so
assistive tech can announce it; ensure the Input component forwards the id to
the underlying input and keep the placeholder for hinting only.

Copy link

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

♻️ Duplicate comments (1)
apps/web/src/server/api/routers/search.ts (1)

16-27: Discriminate schema by mode; fix filter shape for keyword search.

KeywordStore.search expects an OData filter string, but the schema sets filter as an object for all modes. Use a discriminated union so semantic accepts an object and keyword accepts a string. Partially addressed earlier; schema still conflates types.

-const chunkExplorerInputSchema = z.object({
-  namespaceId: z.string(),
-  query: z.string().min(1, "Query is required"),
-  mode: z.enum(["semantic", "keyword"]).default("semantic"),
-  topK: z.number().min(1).max(100).default(20),
-  minScore: z.number().min(0).max(1).optional(),
-  rerank: z.boolean().default(true),
-  rerankLimit: z.number().min(1).max(100).optional(),
-  filter: z.record(z.string(), z.any()).optional(),
-  includeMetadata: z.boolean().default(true),
-  includeRelationships: z.boolean().default(false),
-});
+const semanticSchema = z.object({
+  namespaceId: z.string(),
+  query: z.string().min(1, "Query is required"),
+  mode: z.literal("semantic"),
+  topK: z.number().min(1).max(100).default(20),
+  minScore: z.number().min(0).max(1).optional(),
+  rerank: z.boolean().default(true),
+  rerankLimit: z.number().min(1).max(100).optional(),
+  filter: z.record(z.string(), z.any()).optional(), // structured metadata filter
+  includeMetadata: z.boolean().default(true),
+  includeRelationships: z.boolean().default(false),
+});
+
+const keywordSchema = z.object({
+  namespaceId: z.string(),
+  query: z.string().min(1, "Query is required"),
+  mode: z.literal("keyword"),
+  topK: z.number().min(1).max(100).default(20),
+  minScore: z.number().min(0).max(1).optional(),
+  filter: z.string().optional(), // OData filter string
+  includeMetadata: z.boolean().default(true),
+  includeRelationships: z.boolean().default(false),
+});
+
+const chunkExplorerInputSchema = z.discriminatedUnion("mode", [
+  semanticSchema,
+  keywordSchema,
+]);
🧹 Nitpick comments (3)
apps/web/src/server/api/routers/search.ts (3)

87-90: Tighten types and remove unused var.

results is always assigned to an array; drop | undefined. queryPerformed is redundant (always equals input.query).

-let results: QueryVectorStoreResult["results"] | undefined = [];
-let queryPerformed = input.query;
+let results: QueryVectorStoreResult["results"] = [];

And in the return payload, use query: input.query.


122-124: Usage tracking nuance.

You always increment by 1. If semantic + rerank triggers additional retrieval/rerank passes you may want to track a more representative counter (e.g., totalQueries if exposed by queryVectorStore). If not available, leave as-is.


125-137: Return applied vs requested filter for observability.

Right now you echo input.filter (object) even when keyword mode applies an OData string. Return both the requested and the applied forms for easier debugging.

-      return {
-        results,
-        query: queryPerformed,
+      return {
+        results,
+        query: input.query,
         mode: input.mode,
         totalResults: results.length,
         parameters: {
           topK: input.topK,
           minScore: input.minScore,
           rerank: input.rerank,
           rerankLimit: input.rerankLimit,
-          filter: input.filter,
+          requestedFilter: input.filter,
+          appliedFilter: typeof odataFilter === "string" ? odataFilter : undefined,
         },
       };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1b4b17 and 7bf5af4.

📒 Files selected for processing (1)
  • apps/web/src/server/api/routers/search.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/server/api/routers/search.ts (5)
apps/web/src/server/api/trpc.ts (1)
  • protectedProcedure (122-133)
apps/web/src/server/api/auth.ts (1)
  • getNamespaceByUser (5-25)
packages/engine/src/vector-store/parse.ts (1)
  • QueryVectorStoreResult (133-135)
packages/engine/src/keyword-store/index.ts (1)
  • KeywordStore (50-247)
apps/web/src/lib/api/usage.ts (1)
  • incrementSearchUsage (5-23)
🔇 Additional comments (2)
apps/web/src/server/api/routers/search.ts (2)

80-85: Good guard: enforce keyword mode availability.

Rejecting keyword queries when namespace.keywordEnabled is false is correct and prevents confusing UX.


109-117: Convert and apply filters for keyword search and pass tenant ID

  • In exploreChunks, map the input.filter object to an OData string and supply it to keywordStore.search(..., { filter: odataFilter }).
  • Extend the KeywordStore constructor call to include the tenant/org identifier (e.g. new KeywordStore(namespace.id, namespace.organizationId ?? ctx.session.org?.id)).
  • Confirm which tenant property is correct and whether you need to support more complex filters (ranges, arrays) in the converter.

Copy link

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

♻️ Duplicate comments (4)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (4)

91-106: A11y: Add accessible label for search input.

The search input lacks a programmatic label; placeholders are not accessible substitutes. This was flagged in a previous review and remains unaddressed.

Apply this diff to add a visually hidden label:

 <div className="flex gap-2">
+  <Label htmlFor="chunkQuery" className="sr-only">
+    Search chunks
+  </Label>
   <Input
+    id="chunkQuery"
     value={query}
     onChange={(e) => setQuery(e.target.value)}
     placeholder="Enter your search query to explore chunks..."
     className="flex-1"
   />

112-129: A11y: Bind "Mode" label to the Select trigger.

The htmlFor="mode" attribute points to no element. This was flagged in a previous review and remains unaddressed.

Apply this diff to properly associate the label:

-<Label htmlFor="mode" className="text-sm">
+<Label id="mode-label" className="text-sm">
   Mode:
 </Label>
 <Select
   value={mode}
   onValueChange={(value: "semantic" | "keyword") =>
     setMode(value)
   }
 >
-  <SelectTrigger className="w-32">
+  <SelectTrigger className="w-32" aria-labelledby="mode-label">
     <SelectValue />
   </SelectTrigger>

115-119: Auto-disable rerank when switching to keyword mode.

When switching to keyword mode, rerank and rerankLimit should be cleared to avoid sending unsupported parameters. This was flagged in a previous review and remains unaddressed.

Apply this diff to reset rerank parameters:

 <Select
   value={mode}
-  onValueChange={(value: "semantic" | "keyword") =>
-    setMode(value)
-  }
+  onValueChange={(value: "semantic" | "keyword") => {
+    setMode(value);
+    if (value === "keyword") {
+      setRerank(false);
+      setRerankLimit(undefined);
+    }
+  }}
 >

134-148: Clamp rerankLimit when topK decreases.

When topK is lowered, rerankLimit can exceed the new topK value, leading to invalid state. This was flagged in a previous review and remains unaddressed.

Apply this diff to clamp rerankLimit:

 <Select
   value={topK.toString()}
-  onValueChange={(value) => setTopK(parseInt(value))}
+  onValueChange={(value) => {
+    const nextTopK = parseInt(value, 10);
+    setTopK(nextTopK);
+    if (rerankLimit != null && rerankLimit > nextTopK) {
+      setRerankLimit(nextTopK);
+    }
+  }}
 >
🧹 Nitpick comments (2)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (2)

266-273: Add explicit type for search result.

The result parameter is typed as any, bypassing type safety. Consider defining a proper type or importing it from the tRPC router output.

If the type is available from tRPC:

+import type { RouterOutputs } from "@/trpc/react";
+
+type SearchResult = RouterOutputs["search"]["exploreChunks"]["results"][number];

Then use it in the map:

-data.results.map((result: any) => (
+data.results.map((result: SearchResult) => (
   <SearchChunk

Alternatively, inline a minimal type:

-data.results.map((result: any) => (
+data.results.map((result: { id: string }) => (
   <SearchChunk

223-233: Consider safer error message extraction.

Direct access to error.message assumes a specific error shape. tRPC errors can have nested structures or missing message properties in edge cases.

Apply this diff for defensive error handling:

 <div className="text-center text-red-600">
   <p className="font-medium">Error occurred</p>
   <p className="text-muted-foreground mt-1 text-sm">
-    {error.message}
+    {error?.message || "An unexpected error occurred"}
   </p>
 </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bf5af4 and f277341.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • apps/web/next.config.ts (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3 hunks)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx (1 hunks)
  • apps/web/src/components/app-sidebar/links.ts (2 hunks)
  • packages/db/prisma/schema/schema.prisma (1 hunks)
  • packages/db/src/client.ts (1 hunks)
  • packages/db/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.tsx
  • apps/web/src/components/app-sidebar/links.ts
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3)
apps/web/src/hooks/use-namespace.ts (1)
  • useNamespace (9-46)
packages/engine/src/vector-store/pinecone.ts (1)
  • query (97-114)
apps/web/src/lib/analytics.ts (1)
  • logEvent (7-13)
apps/web/next.config.ts (1)
apps/web/src/middleware.ts (1)
  • config (9-21)
🔇 Additional comments (2)
packages/db/src/client.ts (1)

6-6: LGTM!

The import path correctly reflects the updated Prisma generator output location.

packages/db/src/index.ts (1)

4-4: LGTM!

The re-export path correctly reflects the updated Prisma generator output location, ensuring consumers access the generated client from the new path.

Copy link

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

♻️ Duplicate comments (3)
apps/web/package.json (1)

52-52: Fix the invalid jiti range.

jiti@^2.6.1 still isn’t published, so installs will fail until a real version ships. Please pin to the latest released build (2.6.0) or otherwise pick a valid range.

-    "jiti": "^2.6.1",
+    "jiti": "^2.6.0",
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (2)

109-122: Clamp rerankLimit when topK changes.

When topK is reduced, rerankLimit could exceed the new topK value, causing inconsistent UI state. The previous review comment on lines 146-166 already flagged this issue.

Apply this diff to clamp rerankLimit:

           <Select
             value={topK.toString()}
-            onValueChange={(value) => setTopK(parseInt(value))}
+            onValueChange={(value) => {
+              const newTopK = parseInt(value, 10);
+              setTopK(newTopK);
+              if (rerankLimit > newTopK) {
+                setRerankLimit(newTopK);
+              }
+            }}
           >

148-165: Guard against out-of-range rerankLimit display.

The displayed value (rerankLimit || topK) can show a value not present in the dropdown options if topK was reduced after rerankLimit was set. A previous review comment already flagged this issue.

Apply this diff to clamp the display value:

             <Select
-              value={(rerankLimit || topK).toString()}
+              value={Math.min(rerankLimit || topK, topK).toString()}
               onValueChange={(value) => setRerankLimit(parseInt(value, 10))}
             >
🧹 Nitpick comments (5)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (4)

33-40: Move interface to end of file per coding guidelines.

The coding guidelines specify: "Place static content (consts) and TypeScript interfaces at the end of the file" for files matching apps/web/**/*.{ts,tsx}. Move the ChunkExplorerFilters interface after the component definition.

Note: A previous review comment already flagged this interface as unused. Consider removing it entirely if it's not needed, or move it to the end if you plan to use it later.

As per coding guidelines.


111-111: Add radix parameter to parseInt.

The parseInt call is missing the radix parameter. Always specify radix 10 to avoid potential parsing issues.

Apply this diff:

-            onValueChange={(value) => setTopK(parseInt(value))}
+            onValueChange={(value) => setTopK(parseInt(value, 10))}

150-150: Add radix parameter to parseInt.

The parseInt call is missing the radix parameter. Always specify radix 10 to avoid potential parsing issues.

Apply this diff:

-                onValueChange={(value) => setRerankLimit(parseInt(value))}
+                onValueChange={(value) => setRerankLimit(parseInt(value, 10))}

211-211: Avoid using any type.

The result parameter uses the any type, which bypasses TypeScript's type checking. Import or define a proper type for the search result.

If a SearchResult type is available from the API response, use it:

-              data.results.map((result: any) => (
+              data.results.map((result) => (

The type should be inferred from the data.results array. If the inference isn't working, define a proper interface for the result type instead of using any.

apps/web/src/server/api/routers/search.ts (1)

57-61: Consider including unorderedIds in the response.

The queryVectorStore function returns unorderedIds (original IDs before reranking), which could be useful for debugging or understanding whether reranking was applied. Consider including it in the response.

Apply this diff:

       return {
         results: queryResult.results,
         query: queryResult.query,
         totalResults: queryResult.results.length,
+        unorderedIds: queryResult.unorderedIds,
       };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f277341 and faea174.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/web/package.json (1 hunks)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3 hunks)
  • apps/web/src/components/chat/chat-settings.store.ts (2 hunks)
  • apps/web/src/server/api/routers/search.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.{ts,tsx}: Place static content (consts) and TypeScript interfaces at the end of the file
Minimize use of useEffect and setState
Use Zod for form validation
Minimize client-side state
Prefer server state when possible

Import UI components from the @agentset/ui alias instead of relative paths

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/components/chat/chat-settings.store.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Use kebab-case for file names (e.g., auth-provider.tsx, user-settings.ts)

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/components/chat/chat-settings.store.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Use package import aliases (e.g., @agentset/ui) and avoid relative paths into other packages

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/components/chat/chat-settings.store.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
apps/web/src/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Organize the web app by feature, then by type under src/{app,components,lib,hooks,types,services}

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/components/chat/chat-settings.store.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
{apps/web,packages/*,tooling/*}/package.json

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

{apps/web,packages/*,tooling/*}/package.json: Reference internal packages with version "workspace:" in package.json dependencies (e.g., @agentset/)
Use "catalog:" references for shared external dependencies
Avoid duplicate external dependencies across packages by using shared catalog entries
Pin AI SDK versions for consistency
Use exact versions for critical dependencies

Files:

  • apps/web/package.json
apps/web/package.json

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

The web app should build using Next.js (e.g., next build in the build script)

Files:

  • apps/web/package.json
🧠 Learnings (1)
📚 Learning: 2025-10-06T12:02:55.630Z
Learnt from: CR
PR: agentset-ai/agentset#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-10-06T12:02:55.630Z
Learning: Applies to {apps/web,packages/*,tooling/*}/package.json : Pin AI SDK versions for consistency

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (2)
apps/web/src/server/api/routers/search.ts (5)
packages/validation/src/re-ranker/index.ts (1)
  • rerankerSchema (5-12)
packages/engine/src/embedding/index.ts (1)
  • getNamespaceEmbeddingModel (9-88)
packages/engine/src/vector-store/index.ts (1)
  • getNamespaceVectorStore (6-75)
packages/engine/src/vector-store/query.ts (1)
  • queryVectorStore (15-55)
apps/web/src/lib/api/usage.ts (1)
  • incrementSearchUsage (5-23)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (4)
packages/validation/src/re-ranker/constants.ts (2)
  • RerankingModel (20-20)
  • DEFAULT_RERANKER (22-22)
apps/web/src/hooks/use-namespace.ts (1)
  • useNamespace (9-46)
apps/web/src/lib/analytics.ts (1)
  • logEvent (7-13)
apps/web/src/components/reranker-selector.tsx (1)
  • RerankerSelector (19-48)
🔇 Additional comments (3)
apps/web/src/components/chat/chat-settings.store.ts (2)

52-52: LGTM! Defensive spread ensures complete default values.

Spreading defaultState first guarantees that all fields are present in the namespace state object, even if the persisted state is missing newly added fields. This prevents runtime errors when the defaultState schema evolves.


61-61: LGTM! Good cleanup removing unused parameter.

The get parameter was unused since both setSettings and reset receive the current state through the set callback. Removing it improves code clarity.

apps/web/src/server/api/routers/search.ts (1)

22-22: Previous filter type issue resolved.

The previous review flagged a critical issue regarding the filter type mismatch between keyword (OData string) and semantic (object) modes. This issue has been resolved by removing keyword mode entirely and using only vector store-based search with an object filter, which is the appropriate type for vector stores like Pinecone and Turbopuffer.

@ahmedriad1 ahmedriad1 merged commit c3b4850 into main Oct 15, 2025
2 of 3 checks passed
@ahmedriad1 ahmedriad1 deleted the feat/new-playground-search branch October 15, 2025 07:04
Copy link

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

♻️ Duplicate comments (5)
apps/web/src/server/api/routers/search.ts (2)

15-23: Previous review comment remains unaddressed: schema placement and missing parameters.

The issues flagged in the previous review have not been resolved:

  • The schema violates the coding guideline requiring static content to be placed at the end of the file
  • Missing optional parameters: minScore, includeMetadata, includeRelationships
  • Missing default values for topK, rerank, and proper optional flag for rerankLimit

As per coding guidelines.


42-52: Previous review comment remains unaddressed: hardcoded values instead of schema parameters.

The includeMetadata is still hardcoded to true, and minScore and includeRelationships are still not passed to queryVectorStore. Once the schema is updated per the previous comment, these should be passed from input.

apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (3)

81-86: Add accessible label for search input.

The search input lacks a programmatic label, which is required for assistive technologies. Placeholders are not substitutes for labels.

This issue was previously flagged. Please refer to the earlier review comment for the suggested fix (adding a visually hidden label with htmlFor binding).


100-113: Clamp rerankLimit when topK decreases.

When users lower topK, the current rerankLimit value can exceed the new topK, leading to invalid UI state and potential backend errors.

This issue was previously flagged. Update the onValueChange handler to clamp rerankLimit to [1, topK] whenever topK changes:

 <Select
   value={topK.toString()}
-  onValueChange={(value) => setTopK(parseInt(value))}
+  onValueChange={(value) => {
+    const nextTopK = parseInt(value, 10);
+    setTopK(nextTopK);
+    if (rerankLimit > nextTopK) {
+      setRerankLimit(nextTopK);
+    }
+  }}
 >

137-157: Bind "Rerank Limit" label to the Select and guard display value.

Two issues:

  1. The label is not programmatically associated with the Select.
  2. The displayed value (rerankLimit || topK).toString() can exceed topK if topK was lowered after rerankLimit was set, causing the Select to show an invalid value.

Apply this fix:

-<Label className="text-sm">Rerank Limit:</Label>
+<Label id="rerank-limit-label" className="text-sm">Rerank Limit:</Label>
 <Select
-  value={(rerankLimit || topK).toString()}
+  value={Math.min(rerankLimit ?? topK, topK).toString()}
   onValueChange={(value) => setRerankLimit(parseInt(value))}
 >
-  <SelectTrigger className="w-20">
+  <SelectTrigger className="w-20" aria-labelledby="rerank-limit-label">
     <SelectValue />
   </SelectTrigger>

The display-value issue was previously flagged in an earlier review.

🧹 Nitpick comments (1)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (1)

102-102: Specify radix in parseInt calls.

Omitting the radix can lead to unexpected parsing behavior in edge cases (e.g., leading zeros interpreted as octal in older environments).

Apply this diff:

-onValueChange={(value) => setTopK(parseInt(value))}
+onValueChange={(value) => setTopK(parseInt(value, 10))}

-onValueChange={(value) => setRerankLimit(parseInt(value))}
+onValueChange={(value) => setRerankLimit(parseInt(value, 10))}

Also applies to: 141-141

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between faea174 and 4b180fe.

📒 Files selected for processing (2)
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (2 hunks)
  • apps/web/src/server/api/routers/search.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.{ts,tsx}: Place static content (consts) and TypeScript interfaces at the end of the file
Minimize use of useEffect and setState
Use Zod for form validation
Minimize client-side state
Prefer server state when possible

Import UI components from the @agentset/ui alias instead of relative paths

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Use kebab-case for file names (e.g., auth-provider.tsx, user-settings.ts)

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Use package import aliases (e.g., @agentset/ui) and avoid relative paths into other packages

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
apps/web/src/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Organize the web app by feature, then by type under src/{app,components,lib,hooks,types,services}

Files:

  • apps/web/src/server/api/routers/search.ts
  • apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
🧬 Code graph analysis (2)
apps/web/src/server/api/routers/search.ts (6)
packages/validation/src/re-ranker/index.ts (1)
  • rerankerSchema (5-12)
apps/web/src/server/api/trpc.ts (1)
  • protectedProcedure (122-133)
packages/engine/src/embedding/index.ts (1)
  • getNamespaceEmbeddingModel (9-88)
packages/engine/src/vector-store/index.ts (1)
  • getNamespaceVectorStore (6-75)
packages/engine/src/vector-store/query.ts (1)
  • queryVectorStore (15-55)
apps/web/src/lib/api/usage.ts (1)
  • incrementSearchUsage (5-23)
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx (5)
apps/web/src/hooks/use-namespace.ts (1)
  • useNamespace (9-46)
packages/validation/src/re-ranker/constants.ts (2)
  • RerankingModel (20-20)
  • DEFAULT_RERANKER (22-22)
apps/web/src/lib/analytics.ts (1)
  • logEvent (7-13)
apps/web/src/components/reranker-selector.tsx (1)
  • RerankerSelector (19-48)
packages/ui/src/components/data-wrapper.tsx (1)
  • DataWrapper (6-31)

Comment on lines +98 to +113
<div className="flex items-center gap-2">
<Label className="text-sm">Results:</Label>
<Select
value={topK.toString()}
onValueChange={(value) => setTopK(parseInt(value))}
>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Bind "Results" label to the Select control.

The label is not programmatically associated with the Select trigger, breaking assistive technology navigation.

Add an id to the Label and link it to the SelectTrigger via aria-labelledby:

-<Label className="text-sm">Results:</Label>
+<Label id="results-label" className="text-sm">Results:</Label>
 <Select
   value={topK.toString()}
   onValueChange={(value) => setTopK(parseInt(value))}
 >
-  <SelectTrigger className="w-20">
+  <SelectTrigger className="w-20" aria-labelledby="results-label">
     <SelectValue />
   </SelectTrigger>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex items-center gap-2">
<Label className="text-sm">Results:</Label>
<Select
value={topK.toString()}
onValueChange={(value) => setTopK(parseInt(value))}
>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center gap-2">
<Label id="results-label" className="text-sm">Results:</Label>
<Select
value={topK.toString()}
onValueChange={(value) => setTopK(parseInt(value))}
>
<SelectTrigger className="w-20" aria-labelledby="results-label">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10</SelectItem>
<SelectItem value="20">20</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
</SelectContent>
</Select>
🤖 Prompt for AI Agents
In
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
around lines 98 to 113, the "Results" Label is not programmatically associated
with the Select control which breaks accessibility; add an id to the Label (e.g.
results-label) and reference that id from the SelectTrigger via aria-labelledby
so screen readers and assistive tech can associate the label with the select
trigger; ensure the id is unique in the component and keep the same string on
both elements.

Comment on lines +129 to +135
<div className="flex items-center gap-2">
<Label className="text-sm">Model:</Label>
<RerankerSelector
value={rerankModel}
onValueChange={setRerankModel}
/>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Bind "Model" label to the RerankerSelector.

The label is not programmatically associated with the selector, which affects assistive technology users.

Add an id to the Label and pass aria-labelledby to RerankerSelector (ensure the component forwards it to its SelectTrigger):

-<Label className="text-sm">Model:</Label>
+<Label id="rerank-model-label" className="text-sm">Model:</Label>
 <RerankerSelector
   value={rerankModel}
   onValueChange={setRerankModel}
+  aria-labelledby="rerank-model-label"
 />

Note: Verify that RerankerSelector forwards aria-labelledby to its underlying SelectTrigger.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex items-center gap-2">
<Label className="text-sm">Model:</Label>
<RerankerSelector
value={rerankModel}
onValueChange={setRerankModel}
/>
</div>
<div className="flex items-center gap-2">
<Label id="rerank-model-label" className="text-sm">Model:</Label>
<RerankerSelector
value={rerankModel}
onValueChange={setRerankModel}
aria-labelledby="rerank-model-label"
/>
</div>
🤖 Prompt for AI Agents
In
apps/web/src/app/app.agentset.ai/(dashboard)/[slug]/[namespaceSlug]/playground/search/page.client.tsx
around lines 129 to 135, the "Model" Label is not programmatically associated
with the RerankerSelector; add an id attribute to the Label (e.g.
id="reranker-label") and pass aria-labelledby="reranker-label" into the
RerankerSelector props so it is announced by assistive tech; also update the
RerankerSelector component to accept and forward the aria-labelledby prop to its
underlying SelectTrigger (or root select element) so the association is
preserved end-to-end.

@coderabbitai coderabbitai bot mentioned this pull request Oct 23, 2025
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.

1 participant