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
This commit is contained in:
Nexus Dev 2026-04-01 17:12:56 +00:00
parent 444a3476df
commit d6ff48c0f3
4 changed files with 19 additions and 7 deletions

View file

@ -16,11 +16,13 @@ export function chatRoutes(db: Db): Router {
router.get("/companies/:companyId/conversations", async (req, res) => { router.get("/companies/:companyId/conversations", async (req, res) => {
assertBoard(req); assertBoard(req);
assertCompanyAccess(req, req.params.companyId!); 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!, { const result = await svc.listConversations(req.params.companyId!, {
cursor: cursor as string | undefined, cursor: cursor as string | undefined,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
includeArchived: includeArchived === "true", includeArchived: includeArchived === "true",
search: search as string | undefined,
agentId: agentId as string | undefined,
}); });
res.json(result); res.json(result);
}); });

View file

@ -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 type { Db } from "@paperclipai/db";
import { chatConversations, chatMessages } from "@paperclipai/db"; import { chatConversations, chatMessages } from "@paperclipai/db";
import { notFound } from "../errors.js"; import { notFound } from "../errors.js";
@ -7,7 +7,7 @@ export function chatService(db: Db) {
return { return {
async listConversations( async listConversations(
companyId: string, 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 limit = Math.min(opts.limit ?? 30, 100);
const includeArchived = opts.includeArchived ?? false; const includeArchived = opts.includeArchived ?? false;
@ -25,6 +25,14 @@ export function chatService(db: Db) {
conditions.push(lt(chatConversations.updatedAt, new Date(opts.cursor))); 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 const rows = await db
.select() .select()
.from(chatConversations) .from(chatConversations)

View file

@ -7,10 +7,12 @@ import type {
} from "@paperclipai/shared"; } from "@paperclipai/shared";
export const chatApi = { 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(); const params = new URLSearchParams();
if (opts?.cursor) params.set("cursor", opts.cursor); if (opts?.cursor) params.set("cursor", opts.cursor);
if (opts?.limit) params.set("limit", String(opts.limit)); 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(); const qs = params.toString();
return api.get<ChatConversationListResponse>( return api.get<ChatConversationListResponse>(
`/companies/${companyId}/conversations${qs ? `?${qs}` : ""}`, `/companies/${companyId}/conversations${qs ? `?${qs}` : ""}`,

View file

@ -2,13 +2,13 @@ import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-q
import { chatApi } from "../api/chat"; import { chatApi } from "../api/chat";
import type { ChatConversationListResponse } from "@paperclipai/shared"; 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 queryClient = useQueryClient();
const query = useInfiniteQuery({ const query = useInfiniteQuery({
queryKey: ["chat", "conversations", companyId], queryKey: ["chat", "conversations", companyId, opts?.search ?? ""],
queryFn: ({ pageParam }) => 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, initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage: ChatConversationListResponse) => getNextPageParam: (lastPage: ChatConversationListResponse) =>
lastPage.hasMore ? lastPage.items.at(-1)?.updatedAt : undefined, lastPage.hasMore ? lastPage.items.at(-1)?.updatedAt : undefined,