From d6ff48c0f33ac7d0758c64cfe77f8d0bfbb65a19 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Wed, 1 Apr 2026 17:12:56 +0000 Subject: [PATCH] feat(21-06): add search and agentId filter to listConversations - Add ilike import and search/agentId conditions in chatService.listConversations - Extract search and agentId from req.query in GET /conversations route - Extend chatApi.listConversations opts with search and agentId params - Update useChatConversations to accept opts.search and include in queryKey --- server/src/routes/chat.ts | 4 +++- server/src/services/chat.ts | 12 ++++++++++-- ui/src/api/chat.ts | 4 +++- ui/src/hooks/useChatConversations.ts | 6 +++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/server/src/routes/chat.ts b/server/src/routes/chat.ts index 978400f6..605d1d58 100644 --- a/server/src/routes/chat.ts +++ b/server/src/routes/chat.ts @@ -16,11 +16,13 @@ export function chatRoutes(db: Db): Router { router.get("/companies/:companyId/conversations", async (req, res) => { assertBoard(req); assertCompanyAccess(req, req.params.companyId!); - const { cursor, limit, includeArchived } = req.query; + const { cursor, limit, includeArchived, search, agentId } = req.query; const result = await svc.listConversations(req.params.companyId!, { cursor: cursor as string | undefined, limit: limit ? Number(limit) : undefined, includeArchived: includeArchived === "true", + search: search as string | undefined, + agentId: agentId as string | undefined, }); res.json(result); }); diff --git a/server/src/services/chat.ts b/server/src/services/chat.ts index 6bc8296a..1580054a 100644 --- a/server/src/services/chat.ts +++ b/server/src/services/chat.ts @@ -1,4 +1,4 @@ -import { and, desc, eq, isNull, lt } from "drizzle-orm"; +import { and, desc, eq, ilike, isNull, lt } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { chatConversations, chatMessages } from "@paperclipai/db"; import { notFound } from "../errors.js"; @@ -7,7 +7,7 @@ export function chatService(db: Db) { return { async listConversations( companyId: string, - opts: { cursor?: string; limit?: number; includeArchived?: boolean }, + opts: { cursor?: string; limit?: number; includeArchived?: boolean; search?: string; agentId?: string }, ) { const limit = Math.min(opts.limit ?? 30, 100); const includeArchived = opts.includeArchived ?? false; @@ -25,6 +25,14 @@ export function chatService(db: Db) { conditions.push(lt(chatConversations.updatedAt, new Date(opts.cursor))); } + if (opts.search) { + conditions.push(ilike(chatConversations.title, `%${opts.search}%`)); + } + + if (opts.agentId) { + conditions.push(eq(chatConversations.agentId, opts.agentId)); + } + const rows = await db .select() .from(chatConversations) diff --git a/ui/src/api/chat.ts b/ui/src/api/chat.ts index f3942e02..35377024 100644 --- a/ui/src/api/chat.ts +++ b/ui/src/api/chat.ts @@ -7,10 +7,12 @@ import type { } from "@paperclipai/shared"; export const chatApi = { - listConversations(companyId: string, opts?: { cursor?: string; limit?: number }) { + listConversations(companyId: string, opts?: { cursor?: string; limit?: number; search?: string; agentId?: string }) { const params = new URLSearchParams(); if (opts?.cursor) params.set("cursor", opts.cursor); if (opts?.limit) params.set("limit", String(opts.limit)); + if (opts?.search) params.set("search", opts.search); + if (opts?.agentId) params.set("agentId", opts.agentId); const qs = params.toString(); return api.get( `/companies/${companyId}/conversations${qs ? `?${qs}` : ""}`, diff --git a/ui/src/hooks/useChatConversations.ts b/ui/src/hooks/useChatConversations.ts index 27875afd..7e75341b 100644 --- a/ui/src/hooks/useChatConversations.ts +++ b/ui/src/hooks/useChatConversations.ts @@ -2,13 +2,13 @@ import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-q import { chatApi } from "../api/chat"; import type { ChatConversationListResponse } from "@paperclipai/shared"; -export function useChatConversations(companyId: string | null) { +export function useChatConversations(companyId: string | null, opts?: { search?: string }) { const queryClient = useQueryClient(); const query = useInfiniteQuery({ - queryKey: ["chat", "conversations", companyId], + queryKey: ["chat", "conversations", companyId, opts?.search ?? ""], queryFn: ({ pageParam }) => - chatApi.listConversations(companyId!, { cursor: pageParam as string | undefined }), + chatApi.listConversations(companyId!, { cursor: pageParam as string | undefined, search: opts?.search || undefined }), initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage: ChatConversationListResponse) => lastPage.hasMore ? lastPage.items.at(-1)?.updatedAt : undefined,