Skip to content

Conversation

Chinmay67
Copy link
Contributor

@Chinmay67 Chinmay67 commented Oct 22, 2025

Description

  1. added admin overview to view chats along with filtering

Testing

  1. tested locally

Summary by CodeRabbit

  • New Features

    • Admin chat overview page: browsing, search, filters, sorting, pagination and page-size controls.
    • Admin chats table: per-chat metrics, client-side controls and a detailed chat viewer (messages, user/agent info, cost/tokens, likes/dislikes, bookmarks).
    • Dashboard: "All Chats" and per-user "View Chats" actions linking into the chat overview.
    • Server/API: paginated admin chats with server-side filtering/sorting and a new endpoint to fetch chat query messages; validated chat-id parameter.
  • UX

    • Improved message-loading experience with loading, error/retry and empty states.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Chinmay67, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the administrative capabilities of the application by providing a comprehensive overview of all chat interactions. Administrators can now easily monitor, search, filter, and sort chat conversations, and delve into the details of individual chats, improving system oversight and debugging processes.

Highlights

  • Admin Chat Overview Page: Introduced a new dedicated page for administrators to view all chat conversations across the platform, accessible via /admin/chat-overview.
  • Interactive Chat Table: Developed a new AdminChatsTable component that displays chat data with robust filtering, searching (by chat title, user name, email, agent), and sorting capabilities (by date, messages, cost, tokens).
  • Detailed Chat View: Added a ChatViewDialog that allows administrators to open any chat from the table and view its individual user messages, along with user and agent information.
  • Dashboard Integration: Integrated a summary of the AdminChatsTable into the main dashboard, providing quick links to view all chats or specific user's chats directly from the user leaderboard.
  • Backend API Enhancements: Updated the backend API to support pagination, user-specific filtering, and search functionality for chat data, and added a new endpoint to fetch individual chat messages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds an admin chat management UI and route, server-side pagination/filtering/sorting for admin chats, a new API endpoint to fetch user queries for a chat, and a DB helper to return user-sent messages for a chat.

Changes

Cohort / File(s) Summary
Frontend: Admin Chat UI
frontend/src/components/AdminChatsTable.tsx
New exported AdminChatsTable component; introduces AdminChat and AdminUser interfaces, ChatViewDialog (fetches/display messages), client-side controls (search/filter/user/sort), loading/error/empty states, memoized display list, and per-chat metrics/actions.
Frontend: Dashboard Integration
frontend/src/components/Dashboard.tsx
Extends AdminUsersLeaderboard signature with onAllChatsClick? and onUserChatsClick?; imports AdminChat type; wires navigation handlers and maps fetched admin data into AdminChat objects for AdminChatsTable.
Frontend: Admin Route
frontend/src/routes/_authenticated/admin/chat-overview.tsx
New route export via createFileRoute("/_authenticated/admin/chat-overview"); ChatOverviewPage with admin guard, Zod-validated search params, parallel fetch of admin chats/agents, client-side filters/sort/pagination, renders AdminChatsTable, and provides pagination and navigation controls.
Frontend: Route Manifest
frontend/src/routeTree.gen.ts
Adds AuthenticatedAdminChatOverviewRoute and associated imports, route tree entries, and typing/manifest updates to expose the new admin chat-overview route.
Backend: Admin API
server/api/admin.ts
Extends adminQuerySchema (page, offset, search, filterType, sortBy); enhances GetAdminChats with cross-workspace filtering, SQL pagination, dynamic ordering and aggregations (messageCount, likes, dislikes, totalCost, totalTokens); adds and exports GetChatQueriesApi.
Backend: DB layer
server/db/message.ts
Adds export const fetchUserQueriesForChat(trx, chatExternalId, workspaceExternalId?) returning non-deleted user-sent message contents for a chat ordered by creation time.
Backend: Routes & Types
server/server.ts, server/types.ts
Registers GET /chat/queries/:chatId (public and admin routes) wired to GetChatQueriesApi; adds chatIdParamSchema and AdminChatsPaginationResponseSchema/type; formatting/type adjustments.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Admin
    participant Frontend
    participant API
    participant DB

    Admin->>Frontend: Open Chat Overview / Dashboard → All/User Chats
    Frontend->>API: GET /admin/chats?page&offset&search&filterType&sortBy
    API->>DB: Query chats with filters, aggregates, pagination
    DB-->>API: Paginated AdminChat rows + aggregations
    API-->>Frontend: AdminChat objects + pagination meta
    Frontend->>Frontend: Render AdminChatsTable

    Admin->>Frontend: Click "View" on a chat
    Frontend->>API: GET /chat/queries/:chatId
    API->>DB: fetchUserQueriesForChat(chatId[, workspace])
    DB-->>API: Array<string> (user queries)
    API-->>Frontend: Messages
    Frontend->>Frontend: Open ChatViewDialog (display messages)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • zereraz
  • shivamashtikar
  • kalpadhwaryu
  • junaid-shirur
  • devesh-juspay

Poem

🐇 I hopped through lists of chats at night,

Fetched tiny queries by soft moonlight.
Filters, pages, dialogs all in play,
I nudged the view so admins find their way.
A twitch, a nibble — a carrot-coded day!

Pre-merge checks and finishing touches

✅ 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 "feat: XYNE-206 added a chats overview for admins" directly corresponds to the main change in this pull request, which introduces a comprehensive admin chats overview feature. The changeset consistently implements this feature across multiple layers: a new AdminChatsTable UI component, a new admin chat overview route with filtering and pagination, backend support for fetching and filtering chats, and integration with the Dashboard. The title is concise, specific, and avoids vague terminology or noise, making it clear to teammates that the PR adds an admin-facing chats overview capability.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ 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/admin-chat-overview

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
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new feature for administrators to view and filter chats. The implementation includes a new AdminChatsTable component, a dedicated route for the chat overview, and backend API enhancements for fetching chat data. My review focuses on improving performance, fixing a few bugs related to filtering and pagination, and enhancing code maintainability. Key suggestions include implementing server-side pagination and sorting correctly, memoizing expensive computations on the frontend, and addressing code duplication. Overall, this is a great addition, and with these changes, it will be more robust and performant.

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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
frontend/src/components/Dashboard.tsx (2)

1629-1633: Bug: mutating the users prop while computing originalRank.

users.sort(...) mutates the prop array on every render; this can cause subtle UI bugs and unnecessary re-renders.

Apply this non‑mutating, cached rank computation:

@@
-  const sortedUsers = filteredUsers.sort(
-    (a, b) => b.messageCount - a.messageCount,
-  )
+  const sortedUsers = [...filteredUsers].sort(
+    (a, b) => b.messageCount - a.messageCount,
+  )
+
+  // Precompute original ranks once per `users` change
+  const rankByUserId = React.useMemo(() => {
+    const arr = [...users].sort((a, b) => b.messageCount - a.messageCount)
+    return new Map(arr.map((u, idx) => [u.userId, idx + 1]))
+  }, [users])
@@
-              const originalRank =
-                users
-                  .sort((a, b) => b.messageCount - a.messageCount)
-                  .findIndex((u) => u.userId === user.userId) + 1
+              const originalRank = rankByUserId.get(user.userId) ?? index + 1

3340-3368: Inconsistent time bucketing for “3m”.

processChatsData uses weekly buckets for "3m", but processAdminChatsData uses daily buckets. This yields mismatched charts across tabs.

Unify “3m” to weekly buckets in processAdminChatsData:

 case "3m":
-  d.setHours(0, 0, 0, 0)
-  return {
-    key: d.toISOString(),
-    display: d.toLocaleDateString("en-US", {
-      month: "short",
-      day: "numeric",
-    }),
-  }
+  const startOfWeek = new Date(d)
+  const day = startOfWeek.getDay()
+  startOfWeek.setDate(startOfWeek.getDate() - day)
+  startOfWeek.setHours(0, 0, 0, 0)
+  return {
+    key: startOfWeek.toISOString(),
+    display: `Week of ${startOfWeek.toLocaleDateString("en-US", {
+      month: "short",
+      day: "numeric",
+    })}`,
+  }

And increment by weeks in the filler:

 case "3m":
-  current.setDate(current.getDate() + 1)
+  current.setDate(current.getDate() + 7)

Also applies to: 3017-3030

🧹 Nitpick comments (13)
server/db/message.ts (1)

345-363: Add optional limit and workspace scoping to prevent large responses and cross-tenant leaks.

Current query returns all user-authored messages for a chat without any cap. Consider:

  • Accepting an optional limit (e.g., default 50/100).
  • Optionally scoping by workspaceExternalId when the caller can supply it.

Apply this diff to make the API safer and more flexible:

-export const fetchUserQueriesForChat = async (
-  trx: TxnOrClient,
-  chatExternalId: string,
-): Promise<string[]> => {
-  const queries = await trx
+export const fetchUserQueriesForChat = async (
+  trx: TxnOrClient,
+  chatExternalId: string,
+  opts?: { limit?: number; workspaceExternalId?: string },
+): Promise<string[]> => {
+  const q = trx
     .select({
       content: messages.message,
     })
     .from(messages)
-    .where(
+    .where(
       and(
         eq(messages.chatExternalId, chatExternalId),
         eq(messages.messageRole, MessageRole.User),
         isNull(messages.deletedAt),
-      ),
-    )
-    .orderBy(asc(messages.createdAt))
-  return queries.map((q) => q.content)
+        ...(opts?.workspaceExternalId
+          ? [eq(messages.workspaceExternalId, opts.workspaceExternalId)]
+          : []),
+      ),
+    )
+    .orderBy(asc(messages.createdAt))
+
+  const queries = opts?.limit ? await q.limit(opts.limit) : await q
+  return queries.map((q) => q.content)
 }
server/server.ts (2)

1321-1321: Validate route params with zod to harden the admin endpoint.

Add a param validator to ensure chatId is present/non-empty and block malformed requests.

-  .get("/chat/queries/:chatId", GetChatQueriesApi)
+  .get(
+    "/chat/queries/:chatId",
+    zValidator(
+      "param",
+      z.object({ chatId: z.string().min(1, "chatId is required") }),
+    ),
+    GetChatQueriesApi,
+  )

1443-1471: Minor: use Object.entries for clarity and to avoid repeated lookups.

This is optional, but simplifies the loop.

-      const queryParams = c.req.query()
-      Object.keys(queryParams).forEach((key) => {
-        if (queryParams[key]) {
-          urlObj.searchParams.set(key, queryParams[key])
-        }
-      })
+      const queryParams = c.req.query()
+      for (const [key, value] of Object.entries(queryParams)) {
+        if (value) urlObj.searchParams.set(key, value)
+      }
server/api/admin.ts (2)

169-187: Use z.coerce.number with defaults; rename “offset” to “limit” for clarity.

z.coerce.number() reduces manual parsing. Also, “offset” currently means page size; renaming avoids confusion.

-  page: z
-    .string()
-    .optional()
-    .refine((val) => !val || (!isNaN(Number(val)) && Number(val) > 0), {
-      message: "Page must be a positive number",
-    })
-    .transform((val) => (val ? Number(val) : 1)),
-  offset: z
-    .string()
-    .optional()
-    .refine((val) => !val || (!isNaN(Number(val)) && Number(val) >= 0), {
-      message: "Offset must be a non-negative number",
-    })
-    .transform((val) => (val ? Number(val) : 20)),
+  page: z.coerce.number().int().positive().default(1),
+  limit: z.coerce.number().int().min(1).default(20),
   search: z
     .string()
     .optional()
     .transform((val) => (val?.trim() ? val.trim() : undefined)),

Follow-up: Adjust downstream code and frontend to send/expect “limit” instead of “offset”.


2363-2394: Admin-only OK, but add param validation and consider a max items cap.

  • Validate chatId with zod (server.ts change suggested separately).
  • Optionally accept a “limit” query param and pass to fetchUserQueriesForChat to avoid very large payloads.
-    const queries = await fetchUserQueriesForChat(db, chatId)
+    const limit =
+      Number(c.req.query("limit")) > 0 ? Number(c.req.query("limit")) : 100
+    const queries = await fetchUserQueriesForChat(db, chatId, { limit })
frontend/src/routes/_authenticated/admin/chat-overview.tsx (3)

69-73: Type search via Zod to avoid “never” and improve resilience; default pageSize/currentPage from search if provided.

Optional, but helps keep URL and state in sync.

-// Reset to page 1 when userId filter changes
+// Reset to page 1 when userId filter changes
 useEffect(() => {
   setCurrentPage(1)
 }, [search.userId])
@@
-  }, [search.userId, currentPage, pageSize])
+  }, [search.userId, search.search, currentPage, pageSize])
@@
-  const getPageInfo = () => {
+  const getPageInfo = () => {
     if (search.userId && search.userName) {
       return {
         title: `Chats for ${search.userName}`,
         description: `All chat conversations for user: ${search.userName}`,
       }
     }
     return {
       title: "All Chats Overview",
       description:
         "Complete overview of all chat conversations across the platform",
     }
   }
@@
-          {!loading && (
+          {!loading && (
             <div className="text-xs text-muted-foreground mb-4 p-2 bg-muted/20 rounded">
               Debug: userId={search.userId}, userName={search.userName}, chats=
               {adminChats.length}, page={currentPage}, pageSize={pageSize}
             </div>
           )}

If you want URL-driven pagination later, add page/limit to search and derive state from it.

Also applies to: 138-140, 146-158, 200-205


101-127: Defensive mapping: ensure numbers and booleans are correctly shaped for AdminChatsTable.

Coerce numeric fields and fallback safely.

-        setAdminChats(
-          adminChats.map((chat: any) => ({
+        setAdminChats(
+          adminChats.map((chat: any) => ({
             externalId: chat.externalId,
             title: chat.title || "Untitled Chat",
             createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
+            userId: Number(chat.userId ?? chat.user?.id ?? 0),
             userName: chat.userName || chat.user?.name || "Unknown User",
             userEmail: chat.userEmail || chat.user?.email || "",
             agentId: chat.agentId,
             agentName:
               chat.agentName ||
               (chat.agentId
                 ? adminAgents.find((a: any) => a.externalId === chat.agentId)
                     ?.name
                 : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
+            messageCount: Number(chat.messageCount ?? chat.messages?.length ?? 0),
+            totalCost: Number(chat.totalCost ?? 0),
+            totalTokens: Number(chat.totalTokens ?? 0),
+            likes: Number(chat.likes ?? 0),
+            dislikes: Number(chat.dislikes ?? 0),
+            isBookmarked: Boolean(chat.isBookmarked ?? false),
           })),
         )

217-268: Next page button enablement may mislead when result length equals pageSize.

Consider consuming a totalCount from the API or returning “hasNextPage” to drive the disabled state. As-is, “length < pageSize” may disable Next on exact multiples.

No diff since API changes are required. Suggest API to return { data, page, limit, total }. Then use total to compute last page reliably.

frontend/src/components/Dashboard.tsx (2)

4040-4046: Navigation: avoid full page reload; use router navigation.

Using window.location.href breaks SPA flow and state. Prefer your app router (e.g., react-router useNavigate or TanStack Router).

Example with react-router:

- onAllChatsClick={() => {
-   window.location.href = "/admin/chat-overview"
- }}
+ const navigate = useNavigate()
+ onAllChatsClick={() => navigate("/admin/chat-overview")}
@@
- onUserChatsClick={(userId: number, userName: string) => {
-   window.location.href = `/admin/chat-overview?userId=${userId}&userName=${encodeURIComponent(userName)}`
- }}
+ onUserChatsClick={(userId: number, userName: string) =>
+   navigate(`/admin/chat-overview?userId=${userId}&userName=${encodeURIComponent(userName)}`)
+ }

3569-3583: Inefficient/fragile sort of recentActivity.

Sorting values while recomputing keys per item is O(n²) and brittle. Build a sorted entries list by key once.

- let recentActivity: TimeSeriesData[] = Array.from(
-   timeSeriesMap.values(),
- ).sort((a, b) => {
-   const aTime = timeSeriesMap.keys()
-   const bTime = timeSeriesMap.keys()
-   return (
-     new Date(
-       Array.from(aTime).find((k) => timeSeriesMap.get(k) === a) || "",
-     ).getTime() -
-     new Date(
-       Array.from(bTime).find((k) => timeSeriesMap.get(k) === b) || "",
-     ).getTime()
-   )
- })
+ let recentActivity: TimeSeriesData[] = Array.from(timeSeriesMap.entries())
+   .sort((a, b) => new Date(a[0]).getTime() - new Date(b[0]).getTime())
+   .map(([_, v]) => v)
frontend/src/components/AdminChatsTable.tsx (3)

123-124: Dialog control: don’t close on open.

Passing onClose directly to onOpenChange will also fire when opening, causing flicker. Only handle the “close” transition.

-    <Dialog open={isOpen} onOpenChange={onClose}>
+    <Dialog
+      open={isOpen}
+      onOpenChange={(open) => {
+        if (!open) onClose()
+      }}
+    >

289-311: Data ownership: fetching users inside the table couples UI and data.

Prefer lifting users fetch to the parent (Dashboard) and pass as a prop, so the Admin overview can reuse cached users and avoid duplicate calls when the table re-mounts.


331-365: Perf: repeated filter/sort on every render.

For large datasets, memoize filtered and sorted lists.

Example:

- const filteredChats = chats.filter((chat) => { ... })
- const sortedChats = [...filteredChats].sort((a, b) => { ... })
+ const filteredChats = React.useMemo(() => {
+   return chats.filter((chat) => { /* existing logic */ })
+ }, [chats, searchQuery, filterType, userFilter])
+ const sortedChats = React.useMemo(() => {
+   return [...filteredChats].sort((a, b) => { /* existing logic */ })
+ }, [filteredChats, sortBy])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e50633 and 563c420.

📒 Files selected for processing (6)
  • frontend/src/components/AdminChatsTable.tsx (1 hunks)
  • frontend/src/components/Dashboard.tsx (10 hunks)
  • frontend/src/routes/_authenticated/admin/chat-overview.tsx (1 hunks)
  • server/api/admin.ts (10 hunks)
  • server/db/message.ts (2 hunks)
  • server/server.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
server/server.ts (2)
server/api/ingestion.ts (3)
  • cancelIngestionSchema (32-34)
  • pauseIngestionSchema (41-43)
  • resumeIngestionSchema (36-38)
server/api/admin.ts (1)
  • GetChatQueriesApi (2363-2394)
server/db/message.ts (2)
server/types.ts (1)
  • TxnOrClient (319-321)
server/db/schema/messages.ts (1)
  • messages (31-78)
frontend/src/components/AdminChatsTable.tsx (4)
frontend/src/api.ts (1)
  • api (5-5)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
frontend/src/lib/utils.ts (1)
  • formatCostInINR (90-94)
server/api/admin.ts (6)
server/db/client.ts (1)
  • db (16-16)
server/db/ingestion.ts (2)
  • hasActiveIngestion (127-144)
  • createIngestion (15-24)
server/db/schema/chats.ts (1)
  • chats (37-70)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/message.ts (1)
  • fetchUserQueriesForChat (345-363)
server/utils.ts (1)
  • getErrorMessage (103-106)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (4)
frontend/src/components/error.tsx (1)
  • errorComponent (1-6)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (36-51)
  • AdminChatsTable (260-621)
frontend/src/api.ts (1)
  • api (5-5)
frontend/src/components/Sidebar.tsx (1)
  • Sidebar (40-390)
frontend/src/components/Dashboard.tsx (1)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (36-51)
  • AdminChatsTable (260-621)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/components/AdminChatsTable.tsx

[error] 13-13: TS6133: 'Clock' is declared but its value is never read.


[error] 14-14: TS6133: 'Hash' is declared but its value is never read.


[error] 29-29: TS6133: 'DialogDescription' is declared but its value is never read.

frontend/src/routes/_authenticated/admin/chat-overview.tsx

[error] 26-26: TS2345: Argument of type '"/_authenticated/admin/chat-overview"' is not assignable to parameter of type 'keyof FileRoutesByPath'.


[error] 62-62: TS2322: Type '"/_authenticated/admin/chat-overview"' is not assignable to type 'ConstrainLiteral<"/_authenticated/admin/chat-overview", ...>'.


[error] 72-72: TS2339: Property 'userId' does not exist on type 'never'.


[error] 85-85: TS2339: Property 'userId' does not exist on type 'never'.


[error] 86-86: TS2339: Property 'userId' does not exist on type 'never'.


[error] 88-88: TS2339: Property 'search' does not exist on type 'never'.


[error] 89-89: TS2339: Property 'search' does not exist on type 'never'.


[error] 139-139: TS2339: Property 'userId' does not exist on type 'never'.


[error] 147-147: TS2339: Property 'userId' does not exist on type 'never'.


[error] 147-147: TS2339: Property 'userName' does not exist on type 'never'.


[error] 149-149: TS2339: Property 'userName' does not exist on type 'never'.


[error] 150-150: TS2339: Property 'userName' does not exist on type 'never'.


[error] 202-202: TS2339: Property 'userId' does not exist on type 'never'.


[error] 202-202: TS2339: Property 'userName' does not exist on type 'never'.

🔇 Additional comments (3)
frontend/src/components/Dashboard.tsx (1)

1512-1520: Good addition: “All Chats” and per-user “View Chats” affordances.
The callbacks are optional and guarded; event propagation is handled.

Confirm the new route /admin/chat-overview exists and accepts userId and userName query params (created on 2025-10-22 per PR). If not, I can patch navigation. Based on learnings.

Also applies to: 1715-1733

frontend/src/components/AdminChatsTable.tsx (2)

552-566: Currency display is clear.

formatCostInINR(chat.totalCost) reads well alongside tokens/messages.


83-101: The endpoint path is correctly configured; no changes needed.

Verification confirms that api.admin.chat.queries[chatId].$get() matches the server route at /api/v1/admin/chat/queries/:chatId. The backend applies .basePath("/admin") at line 1207, and the frontend hono-rpc client correctly maps the nested object chain to this path structure. The endpoint path matches exactly as described in the review comment's own example.

Likely an incorrect or invalid review comment.

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.

Actionable comments posted: 6

♻️ Duplicate comments (2)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (2)

258-260: Remove leftover debug log.

Avoid console noise in production.

-            onChatView={(chat: AdminChat) => {
-              console.log("Viewing chat:", chat.externalId)
-              // You can implement chat viewing functionality here if needed
-            }}
+            onChatView={() => {
+              /* no-op: hook available for parent wiring */
+            }}

1-7: Fix TanStack Router typing: use Route.useSearch and silence path typing once.

Build is red due to incorrect useSearch and path typing. Remove useSearch import, switch to Route.useSearch(), and add a one-line ts-expect-error above createFileRoute path literal.

 import {
   createFileRoute,
   redirect,
   useRouterState,
   useNavigate,
-  useSearch,
 } from "@tanstack/react-router"
@@
-export const Route = createFileRoute("/_authenticated/admin/chat-overview")({
+// @ts-expect-error: Path literal is validated by the file-based router plugin at build time.
+export const Route = createFileRoute("/_authenticated/admin/chat-overview")({
@@
-  const search = useSearch({ from: "/_authenticated/admin/chat-overview" })
+  const search = Route.useSearch()

Also applies to: 25-25, 61-61

🧹 Nitpick comments (4)
frontend/src/components/AdminChatsTable.tsx (2)

120-133: Prevent unintended close when Dialog opens; add a11y label.

onOpenChange={onClose} may close on open events. Only close when open becomes false; add aria-label to the close button.

-    <Dialog open={isOpen} onOpenChange={onClose}>
+    <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
...
-            <button
+            <button
+              aria-label="Close dialog"
               onClick={onClose}
               className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
             >

387-406: Add accessible labels to icon-only controls.

Improve keyboard/screen-reader UX for search controls.

-                {searchInput && (
+                {searchInput && (
                   <button
                     type="button"
                     onClick={onClearSearch}
+                    aria-label="Clear search"
                     className="p-1 hover:bg-muted rounded-sm transition-colors"
                     title="Clear search"
                   >
                     <X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
                   </button>
                 )}
                 <button
                   type="button"
                   onClick={onSearch}
+                  aria-label="Search"
                   className="p-1 hover:bg-muted rounded-sm transition-colors"
                   title="Search"
                 >
frontend/src/routes/_authenticated/admin/chat-overview.tsx (1)

269-284: Clarify pagination labels.

“Total: {adminChats.length}” is page count, not total rows. Consider “per page • {adminChats.length} on this page”, or surface a totalCount from API.

frontend/src/components/Dashboard.tsx (1)

2906-2928: Delete duplicated admin chat mapping (now unused).

This block only fed the removed adminChats state and duplicated logic from the route.

-        // Set the raw admin chats data for the table
-        setAdminChats(
-          adminChats.map((chat: any) => ({
-            externalId: chat.externalId,
-            title: chat.title || "Untitled Chat",
-            createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
-            userName: chat.userName || chat.user?.name || "Unknown User",
-            userEmail: chat.userEmail || chat.user?.email || "",
-            agentId: chat.agentId,
-            agentName:
-              chat.agentName ||
-              (chat.agentId
-                ? adminAgents.find((a: any) => a.externalId === chat.agentId)
-                    ?.name
-                : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
-          })),
-        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 563c420 and 8aa031e.

📒 Files selected for processing (4)
  • frontend/src/components/AdminChatsTable.tsx (1 hunks)
  • frontend/src/components/Dashboard.tsx (11 hunks)
  • frontend/src/routes/_authenticated/admin/chat-overview.tsx (1 hunks)
  • server/api/admin.ts (9 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T08:44:08.412Z
Learnt from: MayankBansal2004
PR: xynehq/xyne#719
File: server/api/chat/chat.ts:4023-4029
Timestamp: 2025-08-11T08:44:08.412Z
Learning: In server/api/chat/chat.ts and similar files, MayankBansal2004 prefers not to add explicit type definitions for metadata.usage structures when the receiving array is already typed and default values ensure type safety through the || 0 pattern.

Applied to files:

  • frontend/src/components/Dashboard.tsx
🧬 Code graph analysis (4)
frontend/src/components/AdminChatsTable.tsx (5)
server/db/schema/messages.ts (1)
  • messages (31-78)
frontend/src/api.ts (1)
  • api (5-5)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
frontend/src/lib/utils.ts (1)
  • formatCostInINR (90-94)
server/api/admin.ts (8)
server/db/client.ts (1)
  • db (16-16)
server/db/ingestion.ts (2)
  • hasActiveIngestion (127-144)
  • createIngestion (15-24)
server/db/user.ts (1)
  • getUserByEmail (148-157)
server/db/schema/messages.ts (1)
  • messages (31-78)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
server/db/message.ts (1)
  • fetchUserQueriesForChat (345-363)
server/utils.ts (1)
  • getErrorMessage (103-106)
frontend/src/components/Dashboard.tsx (2)
frontend/src/components/ui/card.tsx (2)
  • CardTitle (76-76)
  • CardDescription (76-76)
frontend/src/components/AdminChatsTable.tsx (1)
  • AdminChat (33-48)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (3)
frontend/src/components/error.tsx (1)
  • errorComponent (1-6)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (33-48)
  • AdminChatsTable (271-635)
frontend/src/api.ts (1)
  • api (5-5)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/components/Dashboard.tsx

[error] 41-41: bunx tsc -b: TS6133: 'AdminChatsTable' is declared but its value is never read.


[error] 2707-2707: bunx tsc -b: TS6133: 'adminChats' is declared but its value is never read.


[error] 4043-4043: bunx tsc -b: TS2322: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4048-4048: bunx tsc -b: TS2322: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4049-4049: bunx tsc -b: TS2353: Object literal may only specify known properties, and 'userName' does not exist in type 'ParamsReducerFn<Router<RootRoute<undefined, {}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, ... 4 more ..., Record<...>>, "SEARCH", string, "/admin/chat-overview">'.

frontend/src/routes/_authenticated/admin/chat-overview.tsx

[error] 25-25: bunx tsc -b: TS2345: Argument of type '"/_authenticated/admin/chat-overview"' is not assignable to parameter of type 'keyof FileRoutesByPath'.


[error] 61-61: bunx tsc -b: TS2322: Type '"/_authenticated/admin/chat-overview"' is not assignable to type 'ConstrainLiteral<"/_authenticated/admin/chat-overview", "root" | "/call" | "/_authenticated" | "/auth" | "/_authenticated/agent" | "/_authenticated/chat" | "/_authenticated/dashboard" | ... 20 more ... | "/_authenticated/admin/integrations/">'.


[error] 108-108: bunx tsc -b: TS2339: Property 'search' does not exist on type 'never'.


[error] 109-109: bunx tsc -b: TS2339: Property 'search' does not exist on type 'never'.


[error] 174-174: bunx tsc -b: TS2339: Property 'search' does not exist on type 'never'.


[error] 189-189: bunx tsc -b: TS2339: Property 'userName' does not exist on type 'never'.


[error] 191-191: bunx tsc -b: TS2339: Property 'userName' does not exist on type 'never'.


[error] 192-192: bunx tsc -b: TS2339: Property 'userName' does not exist on type 'never'.


[error] 256-256: bunx tsc -b: TS2339: Property 'userName' does not exist on type 'never'.

🔇 Additional comments (5)
server/api/admin.ts (3)

144-144: LGTM!

The import is correctly placed and used by the new GetChatQueriesApi endpoint.


1611-1669: LGTM!

The structured metadata approach with websocketData and ingestionState properly supports resumability. The 409 conflict response prevents concurrent ingestions, and all necessary state is captured for resuming interrupted operations.


1767-1804: Excellent implementation of SQL-based pagination and deleted message handling.

The aggregations correctly exclude deleted messages in all calculations (likes, dislikes, cost, tokens), and pagination is now performed in SQL rather than in JavaScript. This addresses the concerns from previous reviews and will perform well with large datasets.

frontend/src/components/Dashboard.tsx (1)

41-42: Remove unused import AdminChatsTable (build break TS6133).

Not referenced in this file.

-import { AdminChatsTable } from "@/components/AdminChatsTable"
-import type { AdminChat } from "@/components/AdminChatsTable"

Likely an incorrect or invalid review comment.

frontend/src/routes/_authenticated/admin/chat-overview.tsx (1)

101-122: No issue found—pagination logic is correct despite confusing parameter naming.

The backend handler (GetAdminChats, lines 1781-1782) correctly calculates pagination:

  • Uses the offset query parameter as the limit (page size)
  • Uses the page parameter with the formula (page - 1) * limit to compute the actual SQL OFFSET

Result: Frontend sends page=2, offset=20 → Backend computes LIMIT 20 OFFSET 20, which correctly returns the second page of 20 items.

The parameter naming is unconventional (sending offset to mean "limit"), but the SQL pagination is mathematically correct.

Likely an incorrect or invalid review comment.

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.

Actionable comments posted: 0

♻️ Duplicate comments (6)
server/api/admin.ts (3)

176-182: Parameter naming remains confusing: offset is actually limit/pageSize.

This concern was previously raised in past reviews. The parameter named offset represents items per page (used as limit on line 1795), while the actual SQL offset is calculated separately on line 1796. This terminology mismatch can confuse developers maintaining pagination logic.


1700-1723: Unused isSuperAdmin variable and disabled SuperAdmin cross-workspace access.

The isSuperAdmin check on line 1707 is computed but never used. Lines 1721-1723 show commented-out logic that would allow SuperAdmins to view chats across all workspaces. Currently, line 1722 enforces workspace filtering for all users, including SuperAdmins.

If SuperAdmin cross-workspace access is not needed, remove the unused variable. Otherwise, uncomment lines 1721 and 1723 to enable the feature.


2394-2458: Verify chat ownership before fetching queries to prevent information disclosure.

While the endpoint correctly checks for Admin/SuperAdmin role (lines 2420-2430) and scopes message queries to the workspace (line 2435), it doesn't verify that the chat itself belongs to currentWorkspaceId. An admin could potentially probe for chats in other workspaces:

  • If the chat exists in another workspace, the query returns [] (leaking that the chatId exists)
  • If the chat doesn't exist, the query also returns [] (same response)

Additionally, SuperAdmin users may need cross-workspace access but are currently restricted.

Consider adding explicit chat ownership verification:

 export const GetChatQueriesApi = async (c: Context) => {
   try {
     const chatId = c.req.param("chatId")
     if (!chatId) {
       return c.json(
         {
           success: false,
           message: "chatId is required",
         },
         400,
       )
     }

     // Get current user and check if they are admin/superadmin
     const { workspaceId: currentWorkspaceId, sub } = c.get(JwtPayloadKey)
     const currentUserRes = await getUserByEmail(db, sub)
     if (!currentUserRes || !currentUserRes.length) {
       loggerWithChild({ email: sub }).error(
         { sub },
         "No user found for sub in GetChatQueriesApi",
       )
       throw new HTTPException(403, { message: "User not found" })
     }
     const currentUser = currentUserRes[0]

     // Check if user is admin or superadmin
     const isAuthorized =
       currentUser.role === "Admin" || currentUser.role === "SuperAdmin"
     if (!isAuthorized) {
       loggerWithChild({ email: sub }).warn(
         { userEmail: sub, userRole: currentUser.role },
         "Unauthorized access attempt to chat queries - not admin",
       )
       throw new HTTPException(403, {
         message: "Access denied. Admin privileges required.",
       })
     }
+
+    // Verify the chat exists and belongs to the workspace
+    const chatResult = await db
+      .select({ 
+        workspaceExternalId: chats.workspaceExternalId,
+        userId: chats.userId 
+      })
+      .from(chats)
+      .where(eq(chats.externalId, chatId))
+      .limit(1)
+
+    if (chatResult.length === 0) {
+      return c.json(
+        {
+          success: false,
+          message: "Chat not found",
+        },
+        404,
+      )
+    }
+
+    const isSuperAdmin = currentUser.role === "SuperAdmin"
+    if (!isSuperAdmin && chatResult[0].workspaceExternalId !== currentWorkspaceId) {
+      loggerWithChild({ email: sub }).warn(
+        { chatId, userWorkspace: currentWorkspaceId, chatWorkspace: chatResult[0].workspaceExternalId },
+        "Unauthorized cross-workspace chat access attempt",
+      )
+      throw new HTTPException(403, {
+        message: "Access denied. Chat not found in your workspace.",
+      })
+    }

-    const queries = await fetchUserQueriesForChat(
-      db,
-      chatId,
-      currentWorkspaceId,
-    )
+    // For SuperAdmin, don't scope to workspace; for Admin, scope to their workspace
+    const queries = await fetchUserQueriesForChat(
+      db,
+      chatId,
+      isSuperAdmin ? undefined : currentWorkspaceId,
+    )

     return c.json(
       {
         success: true,
         data: queries,
       },
       200,
     )
   } catch (error) {
     Logger.error(error, "Error fetching chat queries")
     if (error instanceof HTTPException) {
       throw error
     }
     return c.json(
       {
         success: false,
         message: getErrorMessage(error),
       },
       500,
     )
   }
 }
frontend/src/components/Dashboard.tsx (3)

2706-2706: Remove dead adminChats state (+ unused type import).

State value is discarded and never read; mapping below only wastes CPU and memory. Also drop the now-unused AdminChat import.

-import type { AdminChat } from "@/components/AdminChatsTable"
...
-  const [_, setAdminChats] = useState<AdminChat[]>([])

Also applies to: 41-41


2904-2927: Delete duplicate/unreferenced admin chat mapping.

This duplicates mapping already handled in the admin chat-overview route and sets state that isn't used. Remove to avoid drift and unnecessary work. If you must keep it, coerce types (Number(...) + nullish coalescing).

-        // Set the raw admin chats data for the table
-        setAdminChats(
-          adminChats.map((chat: any) => ({
-            externalId: chat.externalId,
-            title: chat.title || "Untitled Chat",
-            createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
-            userName: chat.userName || chat.user?.name || "Unknown User",
-            userEmail: chat.userEmail || chat.user?.email || "",
-            agentId: chat.agentId,
-            agentName:
-              chat.agentName ||
-              (chat.agentId
-                ? adminAgents.find((a: any) => a.externalId === chat.agentId)
-                    ?.name
-                : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
-          })),
-        )

4041-4050: Fix route path and search type to resolve TS2322/TS2353.

Use the authenticated route and pass only supported search keys (userId as string).

Apply:

-                    onAllChatsClick={() => {
-                      // Navigate to all chats view
-                      navigate({ to: "/admin/chat-overview" })
-                    }}
-                    onUserChatsClick={(userId: number, userName: string) => {
-                      // Navigate to user-specific chats view
-                      navigate({
-                        to: "/admin/chat-overview",
-                        search: { userName: userName },
-                      })
-                    }}
+                    onAllChatsClick={() => {
+                      navigate({ to: "/_authenticated/admin/chat-overview" })
+                    }}
+                    onUserChatsClick={(userId: number, _userName: string) => {
+                      navigate({
+                        to: "/_authenticated/admin/chat-overview",
+                        search: { userId: String(userId) },
+                      })
+                    }}

Run to confirm the route and its search schema:

#!/bin/bash
# Find the chat overview route file and its search schema
fd -a 'chat-overview.tsx' frontend/src/routes | sed -n '1p' | while read f; do
  echo "Route file: $f"
  rg -n 'createFileRoute|search|validateSearch|searchSchema|path' "$f" -n -C2
done
🧹 Nitpick comments (2)
frontend/src/components/Dashboard.tsx (2)

1544-1547: Row “View Chats” handler is fine; minor nit.

Add explicit button type to avoid accidental form submits if nested in a form.

-  const handleViewChatsClick = (e: React.MouseEvent, user: AdminUserUsage) => {
+  const handleViewChatsClick = (e: React.MouseEvent<HTMLButtonElement>, user: AdminUserUsage) => {
     e.stopPropagation()
     onUserChatsClick?.(user.userId, user.userName)
   }

And on the button:

-<button
+<button type="button"

1714-1733: Per-row CTAs LGTM; minor performance note.

Rendering is fine. Consider precomputing a userId→rank map once to avoid sorting per row (Lines 1629–1633).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa031e and 426ac56.

📒 Files selected for processing (3)
  • frontend/src/components/Dashboard.tsx (11 hunks)
  • server/api/admin.ts (9 hunks)
  • server/db/message.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/db/message.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T08:44:08.412Z
Learnt from: MayankBansal2004
PR: xynehq/xyne#719
File: server/api/chat/chat.ts:4023-4029
Timestamp: 2025-08-11T08:44:08.412Z
Learning: In server/api/chat/chat.ts and similar files, MayankBansal2004 prefers not to add explicit type definitions for metadata.usage structures when the receiving array is already typed and default values ensure type safety through the || 0 pattern.

Applied to files:

  • frontend/src/components/Dashboard.tsx
🧬 Code graph analysis (2)
server/api/admin.ts (9)
server/db/client.ts (1)
  • db (16-16)
server/db/ingestion.ts (2)
  • hasActiveIngestion (127-144)
  • createIngestion (15-24)
server/db/user.ts (1)
  • getUserByEmail (148-157)
server/db/schema/messages.ts (1)
  • messages (31-78)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
server/search/vespa.ts (1)
  • GetDocument (22-22)
server/db/message.ts (1)
  • fetchUserQueriesForChat (345-369)
server/utils.ts (1)
  • getErrorMessage (103-106)
frontend/src/components/Dashboard.tsx (1)
frontend/src/components/AdminChatsTable.tsx (1)
  • AdminChat (33-48)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/components/Dashboard.tsx

[error] 4042-4042: bunx tsc -b: TS2322: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4047-4047: bunx tsc -b: TS2322: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4048-4048: bunx tsc -b: TS2353: Object literal may only specify known properties, and 'userName' does not exist in type 'ParamsReducerFn<Router<RootRoute<undefined, {}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, ... 4 more ..., Record<...>>, "SEARCH", string, "/admin/chat-overview">'.

🔇 Additional comments (5)
server/api/admin.ts (2)

1735-1751: Good implementation of server-side filtering with parameterized queries.

The filter logic correctly handles agent/normal chat types (lines 1736-1740) and search functionality (lines 1743-1751) using parameterized SQL queries to prevent SQL injection. This is a solid implementation.


1767-1770: Excellent: SQL-based pagination with proper deleted message exclusion.

The aggregations (lines 1767-1770), sorting (lines 1776-1792), and pagination (lines 1799-1804) are now properly implemented at the SQL level. Deleted messages are correctly excluded from all calculations using CASE WHEN ${messages.deletedAt} IS NULL, which ensures accurate metrics and prevents loading unnecessary data into memory.

Also applies to: 1776-1792, 1799-1804

frontend/src/components/Dashboard.tsx (3)

1512-1520: Prop additions for admin navigation look good.
Wiring onAllChatsClick/onUserChatsClick is clear and optional.


1593-1610: Header CTA integration LGTM.
“All Chats” button is gated on handler presence; good.


2-2: useNavigate import/usage LGTM.
Matches SPA navigation best practices.

Also applies to: 2642-2642

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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
server/api/admin.ts (1)

192-198: Rename offset to limit or pageSize for clarity.

The parameter named offset is actually the page size (items per page), not a positional offset. In standard pagination terminology, "offset" refers to the starting position (e.g., (page - 1) * limit), which can confuse developers. This concern was raised in previous reviews and remains unresolved.

Apply this diff to improve clarity:

-  offset: z
+  limit: z
     .string()
     .optional()
     .refine((val) => !val || (!isNaN(Number(val)) && Number(val) >= 0), {
-      message: "Offset must be a non-negative number",
+      message: "Limit must be a non-negative number",
     })
     .transform((val) => (val ? Number(val) : 20)),

Then update the usage in GetAdminChats at lines 1725, 1811:

-    const offset = query.offset ? Number(query.offset) : 20
+    const limit = query.limit ? Number(query.limit) : 20
     // ... later ...
-    const limit = offset || 20
+    const pageSize = limit || 20
🧹 Nitpick comments (1)
server/api/admin.ts (1)

2410-2450: LGTM! Properly workspace-scoped chat queries endpoint.

The GetChatQueriesApi endpoint correctly:

  • Validates the chatId parameter (lines 2413-2421)
  • Extracts currentWorkspaceId from the JWT payload (line 2422)
  • Passes workspace ID to fetchUserQueriesForChat for workspace scoping (lines 2424-2428)
  • Handles errors appropriately with proper status codes (lines 2437-2449)

The fetchUserQueriesForChat function (from server/db/message.ts) ensures workspace isolation by filtering messages with eq(messages.workspaceExternalId, workspaceExternalId) and also excludes deleted messages with isNull(messages.deletedAt).

Optional enhancement: Consider explicit chat ownership verification.

Currently, if a chatId from another workspace is provided, the query returns an empty array (no messages match). While this is safe, you could improve UX by explicitly checking if the chat exists and belongs to the workspace first:

// After line 2422, before calling fetchUserQueriesForChat:
const chatResult = await db
  .select({ id: chats.id })
  .from(chats)
  .where(
    and(
      eq(chats.externalId, chatId),
      eq(chats.workspaceExternalId, currentWorkspaceId)
    )
  )
  .limit(1)

if (chatResult.length === 0) {
  return c.json(
    {
      success: false,
      message: "Chat not found or access denied",
    },
    404,
  )
}

This provides clearer error feedback when an invalid chatId is provided.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 426ac56 and cd8905c.

📒 Files selected for processing (3)
  • server/api/admin.ts (10 hunks)
  • server/server.ts (6 hunks)
  • server/types.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
server/api/admin.ts (8)
server/db/client.ts (1)
  • db (16-16)
server/db/ingestion.ts (2)
  • hasActiveIngestion (127-144)
  • createIngestion (15-24)
server/db/schema/messages.ts (1)
  • messages (31-78)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
server/search/vespa.ts (1)
  • GetDocument (22-22)
server/db/message.ts (1)
  • fetchUserQueriesForChat (345-369)
server/utils.ts (1)
  • getErrorMessage (103-106)
server/server.ts (3)
server/api/ingestion.ts (3)
  • cancelIngestionSchema (32-34)
  • pauseIngestionSchema (41-43)
  • resumeIngestionSchema (36-38)
server/types.ts (1)
  • chatIdParamSchema (245-247)
server/api/admin.ts (1)
  • GetChatQueriesApi (2410-2450)
🔇 Additional comments (8)
server/types.ts (1)

245-247: LGTM! Clean parameter validation schema.

The chatIdParamSchema provides appropriate validation for the new chat queries endpoint with a clear error message for non-empty string requirements.

server/server.ts (3)

48-48: LGTM! Proper imports for new chat queries endpoint.

The imports correctly support the new /admin/chat/queries/:chatId route added at lines 1322-1326.

Also applies to: 83-83


1322-1326: LGTM! Properly secured admin endpoint.

The new chat queries endpoint correctly:

  • Applies AdminRoleMiddleware for role-based access
  • Validates chatId parameter with zValidator and chatIdParamSchema
  • Delegates to GetChatQueriesApi which includes workspace scoping (confirmed at line 2422 in admin.ts)

1448-1506: LGTM! Improved proxy function with GET request support.

The refactored proxyToSyncServer function correctly:

  • Adds method parameter to support both GET and POST requests
  • Properly handles query parameters for GET requests
  • Conditionally includes request body only for non-GET requests
  • Maintains authentication token forwarding
server/api/admin.ts (4)

29-45: LGTM! Necessary imports for server-side query operations.

The expanded drizzle-orm imports properly support the server-side pagination, filtering, and sorting implementation in GetAdminChats.


160-160: LGTM! Proper import for chat queries functionality.

The fetchUserQueriesForChat import correctly supports the new GetChatQueriesApi endpoint with workspace-scoped query retrieval.


1627-1672: LGTM! Well-structured resumable ingestion implementation.

The changes properly support resumable Slack channel ingestion:

  • Dynamic import of ingestion helpers is appropriate for this code path
  • 409 Conflict status correctly prevents concurrent ingestions
  • Comprehensive metadata structure includes both progress tracking (websocketData) and resumability state (ingestionState)
  • Passing ingestion.id enables proper progress tracking

Also applies to: 1685-1685


1714-1840: Excellent server-side pagination and filtering implementation!

The refactored GetAdminChats correctly implements:

  • SQL-based pagination with LIMIT and OFFSET (lines 1819-1820)
  • Deleted message exclusion in both conditions and aggregations (lines 1734, 1783-1786)
  • Workspace scoping (line 1738)
  • Case-insensitive search using ilike() (lines 1761-1766)
  • Dynamic sorting based on sortBy parameter (lines 1792-1808)
  • Proper aggregation with count() and sum() that excludes deleted messages

This addresses the concerns from previous reviews about JavaScript-based filtering and pagination.

However, clarify the commented-out SuperAdmin check (lines 1736-1739).

The code contains a commented-out block for SuperAdmin cross-workspace access:

// if (!isSuperAdmin) {
  conditions.push(eq(users.workspaceExternalId, currentWorkspaceId))
// }

This means workspace filtering is always applied, preventing SuperAdmins from viewing chats across workspaces. Is this intentional, or should SuperAdmins have cross-workspace visibility?

If cross-workspace access for SuperAdmins is needed, uncomment and implement the check:

+    const { sub } = c.get(JwtPayloadKey)
+    const currentUserRes = await getUserByEmail(db, sub)
+    if (!currentUserRes || !currentUserRes.length) {
+      throw new HTTPException(403, { message: "User not found" })
+    }
+    const currentUser = currentUserRes[0]
+    const isSuperAdmin = currentUser.role === "SuperAdmin"
+
-    // if (!isSuperAdmin) {
+    if (!isSuperAdmin) {
       conditions.push(eq(users.workspaceExternalId, currentWorkspaceId))
-    // }
+    }

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.

Actionable comments posted: 4

♻️ Duplicate comments (3)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (3)

1-7: Fix TanStack Router imports to resolve TypeScript build errors.

The pipeline shows TS2345 and TS2322 errors caused by the route path typing and useSearch usage pattern. Remove the useSearch import here and use Route.useSearch() in the component instead (see also Line 63-65).

Apply this diff:

 import {
   createFileRoute,
   redirect,
   useRouterState,
   useNavigate,
-  useSearch,
 } from "@tanstack/react-router"

Also applies to: 27-27, 63-65


27-27: Add ts-expect-error to silence path inference error.

The pipeline shows a TS2345 error because TypeScript cannot infer the route path in FileRoutesByPath. Add a single-line comment to silence this typing error while preserving runtime behavior.

Apply this diff:

+// @ts-expect-error: Path literal is validated by the file-based router plugin at build time.
 export const Route = createFileRoute("/_authenticated/admin/chat-overview")({

63-65: Replace useSearch with Route.useSearch() to fix TypeScript error.

The pipeline shows a TS2322 error at Line 64. Use Route.useSearch() which provides the correct typing without needing the "from" parameter or type assertion.

Apply this diff:

-  const search = useSearch({
-    from: "/_authenticated/admin/chat-overview",
-  }) as ChatOverviewSearch
+  const search = Route.useSearch()
🧹 Nitpick comments (3)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (3)

51-55: Consider replacing any types with proper interfaces.

The user and workspace props are typed as any, which removes type safety. Consider defining proper interfaces or importing existing types for better type checking and IDE support.


106-109: Type the query object properly instead of using any.

Consider defining a proper type or interface for the query parameters to improve type safety.

-      const query: any = {
+      const query: Record<string, string> = {
         page: currentPage.toString(),
         offset: pageSize.toString(),
       }

141-164: Consider extracting the mapping logic to a helper function.

The inline transformation from API response to AdminChat type is quite large. Extracting it to a named function would improve readability and make the mapping logic reusable and testable.

Example:

const mapToAdminChat = (chat: any, adminAgents: any[]): AdminChat => ({
  externalId: chat.externalId,
  title: chat.title || "Untitled Chat",
  createdAt: chat.createdAt,
  userId: chat.userId ?? chat.user?.id ?? 0,
  userName: (chat.userName ?? chat.user?.name ?? "Unknown User") as string,
  userEmail: (chat.userEmail ?? chat.user?.email ?? "") as string,
  agentId: chat.agentId,
  agentName:
    chat.agentName ||
    (chat.agentId
      ? adminAgents.find((a: any) => a.externalId === chat.agentId)?.name
      : null),
  messageCount: chat.messageCount || chat.messages?.length || 0,
  totalCost: chat.totalCost || 0,
  totalTokens: chat.totalTokens || 0,
  likes: chat.likes || 0,
  dislikes: chat.dislikes || 0,
  isBookmarked: chat.isBookmarked || false,
})

// Then use: setAdminChats(adminChats.map(chat => mapToAdminChat(chat, adminAgents)))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd8905c and c4065b0.

📒 Files selected for processing (1)
  • frontend/src/routes/_authenticated/admin/chat-overview.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (4)
frontend/src/components/error.tsx (1)
  • errorComponent (1-6)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (33-48)
  • AdminChatsTable (271-635)
frontend/src/api.ts (1)
  • api (5-5)
frontend/src/components/Sidebar.tsx (1)
  • Sidebar (40-390)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/routes/_authenticated/admin/chat-overview.tsx

[error] 27-27: bunx tsc -b: TS2345: Argument of type '"/_authenticated/admin/chat-overview"' is not assignable to parameter of type 'keyof FileRoutesByPath'. (src/routes/_authenticated/admin/chat-overview.tsx:27)


[error] 64-64: bunx tsc -b: TS2322: Type '"/_authenticated/admin/chat-overview"' is not assignable to type 'ConstrainLiteral<"/_authenticated/admin/chat-overview", "root" | "/call" | "/_authenticated" | "/auth" | "/_authenticated/agent" | "/_authenticated/chat" | "/_authenticated/dashboard" | ... 20 more ... | "/_authenticated/admin/integrations/">'. (src/routes/_authenticated/admin/chat-overview.tsx:64)

🔇 Additional comments (6)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (6)

66-82: LGTM - well-organized state management.

The state is properly typed and the filter state is correctly lifted to the parent component to support server-side pagination, addressing the previous review feedback.


84-86: LGTM - correct pagination reset logic.

Resetting to page 1 when filters change is the expected behavior for server-side pagination.


89-97: LGTM - clean search handlers.

The separation of searchInput (UI state) and searchQuery (API state) provides good UX by avoiding API calls on every keystroke.


128-135: LGTM - efficient parallel data fetching.

Using Promise.all to fetch admin chats and agents in parallel is a good practice that improves performance.


187-204: LGTM - clean helper functions.

Both navigation and page info helpers are straightforward and provide good UX with context-aware titles.


208-243: LGTM - well-structured layout and error handling.

The UI composition is clean with appropriate error state display and context-aware page titles.

Comment on lines 1782 to 1786
messageCount: count(messages.id),
likes: sql<number>`COUNT(CASE WHEN ${messages.feedback}->>'type' = 'like' AND ${messages.deletedAt} IS NULL THEN 1 END)::int`,
dislikes: sql<number>`COUNT(CASE WHEN ${messages.feedback}->>'type' = 'dislike' AND ${messages.deletedAt} IS NULL THEN 1 END)::int`,
totalCost: sql<number>`COALESCE(SUM(CASE WHEN ${messages.deletedAt} IS NULL THEN ${messages.cost} ELSE 0 END), 0)::numeric`,
totalTokens: sql<number>`COALESCE(SUM(CASE WHEN ${messages.deletedAt} IS NULL THEN ${messages.tokensUsed} ELSE 0 END), 0)::bigint`,
Copy link
Contributor

Choose a reason for hiding this comment

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

use orm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cant be completely converted to orm function because orm functions dont provide access to jsonb fields

Comment on lines 1793 to 1808
let orderByClause
switch (sortBy) {
case "messages":
orderByClause = sql`COUNT(CASE WHEN ${messages.deletedAt} IS NULL THEN ${messages.id} END) DESC`
break
case "cost":
orderByClause = sql`COALESCE(SUM(CASE WHEN ${messages.deletedAt} IS NULL THEN ${messages.cost} ELSE 0 END), 0) DESC`
break
case "tokens":
orderByClause = sql`COALESCE(SUM(CASE WHEN ${messages.deletedAt} IS NULL THEN ${messages.tokensUsed} ELSE 0 END), 0) DESC`
break
case "created":
default:
orderByClause = desc(chats.createdAt)
break
}
Copy link
Contributor

Choose a reason for hiding this comment

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

use orm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

converted to ORM functions wherever possible, used sql only where orm functions did not suffice

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.

Actionable comments posted: 2

♻️ Duplicate comments (3)
frontend/src/components/Dashboard.tsx (2)

2706-2706: Remove dead state and duplicate mapping; centralize mapping in the route.

setAdminChats(...) writes to an unused state (you intentionally dropped the reader). Also, mapping logic duplicates the route’s mapper. Delete the state, its import, and the mapping block.

Apply:

- import type { AdminChat } from "@/components/AdminChatsTable"
+ // (removed) AdminChat no longer needed

@@
-  const [_, setAdminChats] = useState<AdminChat[]>([])
+  // Removed unused adminChats state

@@
-        // Set the raw admin chats data for the table
-        setAdminChats(
-          chatsArray.map((chat: any) => ({
-            externalId: chat.externalId,
-            title: chat.title || "Untitled Chat",
-            createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
-            userName: chat.userName || chat.user?.name || "Unknown User",
-            userEmail: chat.userEmail || chat.user?.email || "",
-            agentId: chat.agentId,
-            agentName:
-              chat.agentName ||
-              (chat.agentId
-                ? adminAgents.find((a: any) => a.externalId === chat.agentId)
-                    ?.name
-                : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
-          })),
-        )
+        // Mapping handled in route; no local state needed

Also applies to: 2921-2944, 41-41


4057-4067: Fix router path for admin chat-overview navigation to use authenticated route.

The route path must include the /_authenticated prefix to match the TanStack Router configuration. Additionally, update the unused parameter naming and simplify the search param syntax.

                    onAllChatsClick={() => {
-                      // Navigate to all chats view
-                      navigate({ to: "/admin/chat-overview" })
+                      navigate({ to: "/_authenticated/admin/chat-overview" })
                    }}
                    onUserChatsClick={(userId: number, userName: string) => {
-                      // Navigate to user-specific chats view
                      navigate({
-                        to: "/admin/chat-overview",
+                        to: "/_authenticated/admin/chat-overview",
-                        search: { userName: userName },
+                        search: { userName },
                      })
                    }}
frontend/src/routes/_authenticated/admin/chat-overview.tsx (1)

299-302: Remove leftover console.log in production.

-            onChatView={(chat: AdminChat) => {
-              console.log("Viewing chat:", chat.externalId)
-              // You can implement chat viewing functionality here if needed
-            }}
+            onChatView={(_chat: AdminChat) => {
+              // Hook up a view handler here if needed
+            }}
🧹 Nitpick comments (5)
server/api/admin.ts (2)

192-198: Rename offset to limit for standard pagination terminology.

The parameter named offset represents the page size (items per page), not a positional offset. In standard pagination, "offset" refers to the starting position (page - 1) * limit, which creates confusion. This was flagged in previous reviews but remains unaddressed.

Apply this diff to improve clarity:

- offset: z
+ limit: z
    .string()
    .optional()
    .refine((val) => !val || (!isNaN(Number(val)) && Number(val) >= 0), {
-     message: "Offset must be a non-negative number",
+     message: "Limit must be a non-negative number",
    })
    .transform((val) => (val ? Number(val) : 20)),

Then update usage throughout GetAdminChats (lines 1725, 1844, 1863, 1872) to use limit instead of offset.


1737-1739: Remove commented-out SuperAdmin check or implement cross-workspace access.

The commented-out lines suggest incomplete implementation of SuperAdmin cross-workspace access. Based on past review comments, this was supposed to be addressed but remains commented out.

Option 1: If cross-workspace access isn't needed, remove the comments:

  // Build the conditions array
  const conditions = []

  // Always exclude deleted messages
  conditions.push(isNull(messages.deletedAt))

- // Add workspace filtering unless user is SuperAdmin
- // if (!isSuperAdmin) {
  conditions.push(eq(users.workspaceExternalId, currentWorkspaceId))
- // }

Option 2: If SuperAdmins should see cross-workspace chats, implement it:

+ // Get current user to check role
+ const { sub } = c.get(JwtPayloadKey)
+ const currentUserRes = await getUserByEmail(db, sub)
+ if (!currentUserRes || !currentUserRes.length) {
+   throw new HTTPException(403, { message: "User not found" })
+ }
+ const isSuperAdmin = currentUserRes[0].role === "SuperAdmin"
+
  // Build the conditions array
  const conditions = []

  // Always exclude deleted messages
  conditions.push(isNull(messages.deletedAt))

  // Add workspace filtering unless user is SuperAdmin
- // if (!isSuperAdmin) {
+ if (!isSuperAdmin) {
    conditions.push(eq(users.workspaceExternalId, currentWorkspaceId))
- // }
+ }
frontend/src/components/Dashboard.tsx (1)

1629-1633: Don’t mutate props when computing rank.

users.sort(...) mutates the prop array. Use a copy.

-              const originalRank =
-                users
-                  .sort((a, b) => b.messageCount - a.messageCount)
-                  .findIndex((u) => u.userId === user.userId) + 1
+              const originalRank =
+                [...users]
+                  .sort((a, b) => b.messageCount - a.messageCount)
+                  .findIndex((u) => u.userId === user.userId) + 1
frontend/src/routes/_authenticated/admin/chat-overview.tsx (2)

73-76: Search state: initialize from URL; prefer single source of truth.

Good initializing from URL. Consider removing the extra search.search check to avoid ambiguity and just rely on searchQuery.

-        if (search.search) {
-          query.search = search.search
-        }
         if (searchQuery.trim()) {
           query.search = searchQuery.trim()
         }

Also applies to: 112-118


178-202: Type normalization in chat mapper (optional hardening).

Coerce numeric fields to numbers to avoid accidental strings from API.

-            userId: chat.userId ?? chat.user?.id ?? 0,
+            userId: Number(chat.userId ?? chat.user?.id ?? 0) || 0,
@@
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
+            messageCount: Number(chat.messageCount ?? chat.messages?.length ?? 0) || 0,
+            totalCost: Number(chat.totalCost ?? 0) || 0,
+            totalTokens: Number(chat.totalTokens ?? 0) || 0,

(Based on learnings)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4065b0 and 7f15808.

📒 Files selected for processing (4)
  • frontend/src/components/Dashboard.tsx (10 hunks)
  • frontend/src/routes/_authenticated/admin/chat-overview.tsx (1 hunks)
  • server/api/admin.ts (11 hunks)
  • server/types.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/types.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T08:44:08.412Z
Learnt from: MayankBansal2004
PR: xynehq/xyne#719
File: server/api/chat/chat.ts:4023-4029
Timestamp: 2025-08-11T08:44:08.412Z
Learning: In server/api/chat/chat.ts and similar files, MayankBansal2004 prefers not to add explicit type definitions for metadata.usage structures when the receiving array is already typed and default values ensure type safety through the || 0 pattern.

Applied to files:

  • frontend/src/components/Dashboard.tsx
🧬 Code graph analysis (3)
server/api/admin.ts (7)
server/db/client.ts (1)
  • db (16-16)
server/db/ingestion.ts (2)
  • hasActiveIngestion (127-144)
  • createIngestion (15-24)
server/db/schema/messages.ts (1)
  • messages (31-78)
server/db/schema/users.ts (1)
  • users (27-57)
server/db/schema/chats.ts (1)
  • chats (37-70)
server/db/message.ts (1)
  • fetchUserQueriesForChat (345-369)
server/utils.ts (1)
  • getErrorMessage (103-106)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (4)
frontend/src/components/error.tsx (1)
  • errorComponent (1-6)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (33-48)
  • AdminChatsTable (271-635)
frontend/src/api.ts (1)
  • api (5-5)
frontend/src/components/Sidebar.tsx (1)
  • Sidebar (40-390)
frontend/src/components/Dashboard.tsx (1)
frontend/src/components/AdminChatsTable.tsx (1)
  • AdminChat (33-48)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/routes/_authenticated/admin/chat-overview.tsx

[error] 23-23: Argument of type '"/_authenticated/admin/chat-overview"' is not assignable to parameter of type 'keyof FileRoutesByPath'.


[error] 74-74: Property 'search' does not exist on the type of the route query options.


[error] 113-113: Property 'search' does not exist on the route query options type.


[error] 114-114: Property 'search' does not exist on the route query options type.


[error] 216-216: Property 'search' does not exist on the route query options type.


[error] 231-231: Property 'userName' does not exist on the route query options type.


[error] 233-233: Property 'userName' does not exist on the route query options type.


[error] 234-234: Property 'userName' does not exist on the route query options type.


[error] 298-298: Property 'userName' does not exist on the route query options type.

frontend/src/components/Dashboard.tsx

[error] 4059-4059: Type '"/admin/chat-overview"' is not assignable to the union of allowed router paths in the component props.


[error] 4064-4064: Type '"/admin/chat-overview"' is not assignable to the union of allowed router paths in the component props.


[error] 4065-4065: Object literal may only specify known properties, and 'userName' does not exist in the expected props type.

🔇 Additional comments (12)
server/api/admin.ts (7)

29-45: LGTM! Import additions support new features.

The added Drizzle ORM operators and fetchUserQueriesForChat import are appropriately used for the new pagination, filtering, sorting, and chat queries functionality.


1627-1685: LGTM! Proper concurrency handling for ingestions.

The transaction-based check prevents race conditions, and the 409 Conflict status appropriately signals concurrent ingestion attempts. The ingestion metadata structure supports resumability.


1716-1729: Consider using validated query parameters.

The query parameters are parsed manually using c.req.query() instead of leveraging the validated query from c.req.valid("query") with the adminQuerySchema. While the schema is defined, it doesn't appear to be used here via validation middleware.

If the adminQuerySchema validation is applied via middleware elsewhere in the routing, the manual parsing here is redundant. Otherwise, consider using:

// @ts-ignore
const { from, to, userId, page, offset, search, filterType, sortBy } = c.req.valid("query")

This ensures consistent validation and leverages the schema transforms (trimming, defaults, etc.).


1770-1780: Note: Count query approach for paginated aggregations.

The count query groups by chats.id and then counts the result length. While this may seem inefficient, it's necessary to get an accurate count when using GROUP BY with joins, as a simple COUNT(*) would count rows before grouping.

This approach is acceptable but be aware that for very large datasets (100k+ chats), performance may degrade. Consider adding an index on commonly filtered columns if not already present.


1796-1807: Excellent handling of deleted messages in aggregations.

The use of CASE statements to exclude deleted messages from counts and cost/token sums is correct and prevents data leakage from soft-deleted records.


2459-2499: LGTM! Authorization handled via workspace validation.

The endpoint properly authorizes access by passing currentWorkspaceId to fetchUserQueriesForChat, which validates that the chat belongs to the user's workspace. This prevents unauthorized cross-workspace access while maintaining a clean implementation.

Error handling appropriately logs errors and returns structured responses.


2408-2408: Function name change verified as correct.

GetDocument is properly exported from server/search/vespa.ts, and the parameters match the call site. This is an internal refactoring within GetKbVespaContent—the public API remains unchanged since GetKbVespaContent is still exported and used by the route handler.

frontend/src/components/Dashboard.tsx (1)

2893-2912: Nice: backward-compat handling for new/old admin chats response.

If the backend settles on metadata shape, add a type to avoid any and narrow keys. I can wire this quickly if you share the final schema.

frontend/src/routes/_authenticated/admin/chat-overview.tsx (4)

325-327: Avoid misleading “Total” when total is unknown.

This already guards on undefined. With the change above (optional totalCount), UI remains correct. No further action if you adopt that change.


129-137: Parallel fetch + guards look good.

Fetching chats and agents together with proper error checks. LGTM.


231-241: Page title from userName query: good UX.

Works once validateSearch typing is fixed.


106-124: Param names verified — no mispagination issue found.

The server correctly expects page and offset parameters (lines 1724–1725 of server/api/admin.ts). The pagination logic properly calculates database offset as (page - 1) * pageSize (line 1845) and applies it correctly (line 1853). The frontend and backend param names align, and pagination functions as intended.

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.

Actionable comments posted: 0

♻️ Duplicate comments (8)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (6)

79-87: URL search params and local state remain out of sync.

This is the same issue flagged in a previous review. Line 79 initializes searchInput from search.search, but searchQuery (which drives the API call) is initialized to an empty string. When a user navigates with ?search=foo in the URL, the input field will show "foo" but the API won't receive the search term until the user manually triggers the search.

The previous review suggested initializing both from URL params:

-  const [searchInput, setSearchInput] = useState<string>(search.search || "")
-  const [searchQuery, setSearchQuery] = useState<string>("")
+  const [searchInput, setSearchInput] = useState<string>(search.search || "")
+  const [searchQuery, setSearchQuery] = useState<string>(search.search || "")

Alternatively, remove the duplicate search.search check at lines 118-120 and rely solely on searchQuery.


118-123: Remove duplicate search filter logic.

Lines 118-120 check search.search and lines 121-123 check searchQuery.trim(), potentially applying the same filter twice or causing confusion about which takes precedence.

As noted in the previous review, this duplication should be removed. Since searchQuery is the canonical state for the search term, remove the search.search check:

         // Add server-side filters
-        if (search.search) {
-          query.search = search.search
-        }
         if (searchQuery.trim()) {
           query.search = searchQuery.trim()
         }

167-175: Fallback pagination metadata is misleading and breaks on exact multiples of pageSize.

The fallback code sets totalCount to adminChatsData.length, which is only the current page count, not the total across all pages. This makes the UI label "Total: X" misleading to users. Additionally, the hasNextPage logic (adminChatsData.length >= pageSize) fails when total items are an exact multiple of pageSize, allowing navigation to an empty page.

This issue was flagged in previous reviews. The recommended fix is to request pageSize + 1 items and use the extra item as a probe for whether more pages exist:

         const query: any = {
           page: currentPage.toString(),
-          offset: pageSize.toString(),
+          offset: (pageSize + 1).toString(),
         }

Then update the fallback logic:

         } else if (Array.isArray(adminChatsData)) {
           // Old format - direct array
-          chatsArray = adminChatsData
-          // Fallback: use length-based logic for hasNextPage
+          const hasExtra = adminChatsData.length > pageSize
+          chatsArray = hasExtra ? adminChatsData.slice(0, pageSize) : adminChatsData
           paginationMeta = {
-            totalCount: adminChatsData.length, // This is just the current page count
-            hasNextPage: adminChatsData.length >= pageSize, // Old logic as fallback
+            // Don't include totalCount when we can't determine it accurately
+            hasNextPage: hasExtra,
             hasPreviousPage: currentPage > 1,
           }

Also update the state type to make totalCount optional:

   const [paginationMetadata, setPaginationMetadata] = useState<{
-    totalCount: number
+    totalCount?: number
     hasNextPage: boolean
     hasPreviousPage: boolean
   } | null>(null)

304-307: Remove debug console.log before merging.

The console.log statement is leftover debug code and should be removed from production.

             onChatView={(chat: AdminChat) => {
-              console.log("Viewing chat:", chat.externalId)
               // You can implement chat viewing functionality here if needed
             }}

328-333: Misleading "Total" count display.

The label shows paginationMetadata.totalCount, which in the fallback mode is just the current page count, not the total across all pages. This is confusing for users.

As noted in previous reviews, either remove the count display when using the fallback, or change the label to clarify:

                 <span className="text-sm text-muted-foreground">
                   per page
-                  {paginationMetadata?.totalCount !== undefined && (
-                    <> (Total: {paginationMetadata.totalCount})</>
+                  {paginationMetadata?.totalCount !== undefined && (
+                    <> (Showing: {adminChats.length} of {paginationMetadata.totalCount})</>
                   )}
                 </span>

Or only show total when it's truly the total count (not in fallback mode):

                 <span className="text-sm text-muted-foreground">
-                  per page
-                  {paginationMetadata?.totalCount !== undefined && (
-                    <> (Total: {paginationMetadata.totalCount})</>
-                  )}
+                  per page (Showing: {adminChats.length})
                 </span>

17-24: Schema definition is correct but route typing integration is broken.

The Zod schema is properly defined, but TanStack Router isn't inferring the search type correctly (as evidenced by the 9 TypeScript errors). The validateSearch function needs to be structured so that Route.useSearch() returns the parsed ChatOverviewSearch type.

The previous review suggested adding ts-expect-error and adjusting validateSearch, but a more robust fix ensures full type safety. Apply this approach:

 const chatOverviewSearchSchema = z.object({
   userName: z.string().optional(),
   page: z.string().optional(),
   offset: z.string().optional(),
   search: z.string().optional(),
 })
 
 type ChatOverviewSearch = z.infer<typeof chatOverviewSearchSchema>
 
+// @ts-expect-error: TanStack Router path literal is validated at build time
 export const Route = createFileRoute("/_authenticated/admin/chat-overview")({
   beforeLoad: ({ context }) => {
     if (
       !(context.user.role === "Admin" || context.user.role === "SuperAdmin")
     ) {
       throw redirect({ to: "/" })
     }
   },
-  validateSearch: (search: Record<string, unknown>): ChatOverviewSearch => {
-    return chatOverviewSearchSchema.parse(search)
-  },
+  validateSearch: chatOverviewSearchSchema.parse,
   component: () => {
     const matches = useRouterState({ select: (s) => s.matches })
     const { user, workspace, agentWhiteList } =
       matches[matches.length - 1].context
     return (
       <ChatOverviewPage
         user={user}
         workspace={workspace}
         agentWhiteList={agentWhiteList}
       />
     )
   },
   errorComponent: errorComponent,
 })

This ensures Route.useSearch() returns the correctly typed ChatOverviewSearch object, eliminating all 9 TypeScript errors.

frontend/src/components/Dashboard.tsx (2)

41-41: Remove unused AdminChat state and duplicate mapping (wasted work + future drift).

State is never read; the heavy mapping runs each admin fetch doing nothing and duplicates logic already in the admin route. Delete the import, state, and mapping block.

Apply:

- import type { AdminChat } from "@/components/AdminChatsTable"
+ // (removed) AdminChat mapping no longer needed here

@@
-  const [_, setAdminChats] = useState<AdminChat[]>([])
+  // removed unused admin chats state

@@
-        // Set the raw admin chats data for the table
-        setAdminChats(
-          chatsArray.map((chat: any) => ({
-            externalId: chat.externalId,
-            title: chat.title || "Untitled Chat",
-            createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
-            userName: chat.userName || chat.user?.name || "Unknown User",
-            userEmail: chat.userEmail || chat.user?.email || "",
-            agentId: chat.agentId,
-            agentName:
-              chat.agentName ||
-              (chat.agentId
-                ? adminAgents.find((a: any) => a.externalId === chat.agentId)
-                    ?.name
-                : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
-          })),
-        )
+        // removed duplicate mapping; route-level overview handles table data

If you must keep it, harden types as below (next comment).

Also applies to: 2706-2706, 2922-2944


2922-2944: Type safety: possible undefined userId and unnormalized fields in AdminChat mapping.

Ensure userId is numeric and name/email are strings; coerce numeric totals.

Apply:

-        setAdminChats(
-          chatsArray.map((chat: any) => ({
+        setAdminChats(
+          chatsArray.map((chat: any) => ({
             externalId: chat.externalId,
             title: chat.title || "Untitled Chat",
             createdAt: chat.createdAt,
-            userId: chat.userId || chat.user?.id,
-            userName: chat.userName || chat.user?.name || "Unknown User",
-            userEmail: chat.userEmail || chat.user?.email || "",
+            userId: Number(chat.userId ?? chat.user?.id ?? 0) || 0,
+            userName: String(chat.userName ?? chat.user?.name ?? "Unknown User"),
+            userEmail: String(chat.userEmail ?? chat.user?.email ?? ""),
             agentId: chat.agentId,
             agentName:
-              chat.agentName ||
-              (chat.agentId
-                ? adminAgents.find((a: any) => a.externalId === chat.agentId)
-                    ?.name
-                : null),
-            messageCount: chat.messageCount || chat.messages?.length || 0,
-            totalCost: chat.totalCost || 0,
-            totalTokens: chat.totalTokens || 0,
-            likes: chat.likes || 0,
-            dislikes: chat.dislikes || 0,
-            isBookmarked: chat.isBookmarked || false,
+              chat.agentName ??
+              (chat.agentId
+                ? adminAgents.find((a: any) => a.externalId === chat.agentId)?.name ?? null
+                : null),
+            messageCount: Number(chat.messageCount ?? chat.messages?.length ?? 0) || 0,
+            totalCost: Number(chat.totalCost ?? 0) || 0,
+            totalTokens: Number(chat.totalTokens ?? 0) || 0,
+            likes: Number(chat.likes ?? 0) || 0,
+            dislikes: Number(chat.dislikes ?? 0) || 0,
+            isBookmarked: Boolean(chat.isBookmarked),
           })),
         )

Based on learnings.

🧹 Nitpick comments (1)
frontend/src/components/Dashboard.tsx (1)

2893-2912: Harden back-compat shape check for adminChats.

Also ensure data is an array before use; otherwise error messages are noisy.

Apply:

-        if (
-          adminChatsData &&
-          typeof adminChatsData === "object" &&
-          "data" in adminChatsData &&
-          "pagination" in adminChatsData
-        ) {
-          // New format with pagination metadata
-          chatsArray = adminChatsData.data
-        } else if (Array.isArray(adminChatsData)) {
+        if (
+          adminChatsData &&
+          typeof adminChatsData === "object" &&
+          "data" in adminChatsData &&
+          Array.isArray((adminChatsData as any).data)
+        ) {
+          chatsArray = (adminChatsData as any).data
+        } else if (Array.isArray(adminChatsData)) {
           // Old format - direct array
           chatsArray = adminChatsData
         } else {
           throw new Error("Invalid response format from admin chats API")
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f15808 and 4c81983.

📒 Files selected for processing (2)
  • frontend/src/components/Dashboard.tsx (10 hunks)
  • frontend/src/routes/_authenticated/admin/chat-overview.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-11T08:44:08.412Z
Learnt from: MayankBansal2004
PR: xynehq/xyne#719
File: server/api/chat/chat.ts:4023-4029
Timestamp: 2025-08-11T08:44:08.412Z
Learning: In server/api/chat/chat.ts and similar files, MayankBansal2004 prefers not to add explicit type definitions for metadata.usage structures when the receiving array is already typed and default values ensure type safety through the || 0 pattern.

Applied to files:

  • frontend/src/components/Dashboard.tsx
🧬 Code graph analysis (2)
frontend/src/routes/_authenticated/admin/chat-overview.tsx (4)
frontend/src/components/error.tsx (1)
  • errorComponent (1-6)
frontend/src/components/AdminChatsTable.tsx (2)
  • AdminChat (33-48)
  • AdminChatsTable (271-635)
frontend/src/api.ts (1)
  • api (5-5)
frontend/src/components/Sidebar.tsx (1)
  • Sidebar (40-390)
frontend/src/components/Dashboard.tsx (1)
frontend/src/components/AdminChatsTable.tsx (1)
  • AdminChat (33-48)
🪛 GitHub Actions: TypeScript Build Check
frontend/src/routes/_authenticated/admin/chat-overview.tsx

[error] 26-26: Argument of type '"/_authenticated/admin/chat-overview"' is not assignable to parameter of type 'keyof FileRoutesByPath'.


[error] 79-79: Property 'search' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 118-118: Property 'search' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 119-119: Property 'search' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 221-221: Property 'search' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 236-236: Property 'userName' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 238-238: Property 'userName' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 239-239: Property 'userName' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] 303-303: Property 'userName' does not exist on type '{ app?: Apps | undefined; entity?: string | undefined; offset?: number | undefined; page?: number | undefined; query?: string | undefined; lastUpdated?: string | undefined; debug?: boolean | undefined; } | { ...; } | {} | { ...; }'.


[error] Process completed with exit code 2.

frontend/src/components/Dashboard.tsx

[error] 4059-4059: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4064-4064: Type '"/admin/chat-overview"' is not assignable to type '"/call" | "/auth" | "/oauth/success" | "/" | "." | ".." | "/agent" | "/chat" | "/dashboard" | "/knowledgeManagement" | "/search" | "/tuning" | "/workflow" | "/admin/userManagement" | ... 13 more ... | "/admin/integrations"'.


[error] 4065-4065: Object literal may only specify known properties, and 'userName' does not exist in type 'ParamsReducerFn<Router<RootRoute<undefined, {}, AnyContext, AnyContext, {}, undefined, RootRouteChildren, FileRouteTypes>, ... 4 more ..., Record<...>>, "SEARCH", string, "/admin/chat-overview">'.


[error] Process completed with exit code 2.

🔇 Additional comments (7)
frontend/src/components/Dashboard.tsx (7)

1512-1520: Props extension is sound.

Optional callbacks are correctly typed and guarded at call sites.


1544-1547: Good event handling.

stopPropagation prevents row click from hijacking per-user actions.


1593-1611: Nice UX: “All Chats” action is conditionally rendered.

Clear affordance and safe optional handling.


1715-1733: Row actions are well-scoped.

Buttons gated by props/feedback presence; concise styling.


2642-2642: useNavigate import/use is appropriate.

Sets up SPA navigation for added actions.


2-2: Import looks good.

Router hook is only used inside component; no issues.


4057-4067: Now let me verify the actual Dashboard.tsx code to see the correct implementation:


The route and search key are already correct. The route /_authenticated/admin/chat-overview exists and its schema explicitly supports userName as an optional search parameter. The suggested diff breaks this by changing userName to userId, which is not in the schema and will cause a runtime validation error. Keep the original code as-is with userName.

Likely an incorrect or invalid review comment.

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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/routeTree.gen.ts (1)

179-184: Route definition is correct, but consider path naming consistency.

The route constant is properly defined and follows the established pattern. However, note that the path uses kebab-case (chat-overview) while other admin routes use camelCase (userManagement). For better consistency across the application, consider aligning route naming conventions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c81983 and a0988dc.

📒 Files selected for processing (1)
  • frontend/src/routeTree.gen.ts (13 hunks)
🔇 Additional comments (3)
frontend/src/routeTree.gen.ts (3)

35-35: LGTM!

The import statement is correctly formatted and follows the established pattern for admin routes.


316-322: LGTM!

All type definitions and interface additions are correctly generated. The route is properly integrated into:

  • FileRoutesByPath
  • AuthenticatedRouteChildren (interface and object)
  • FileRoutesByFullPath
  • FileRoutesByTo
  • FileRoutesById
  • FileRouteTypes union types

The alphabetical ordering is maintained throughout.

Also applies to: 453-453, 479-479, 519-519, 549-549, 581-581, 614-614, 643-643, 673-673


733-733: LGTM!

The route manifest entries are correctly generated and properly positioned. The manifest correctly references the source file at _authenticated/admin/chat-overview.tsx with the appropriate parent route.

Also applies to: 794-797

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