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) => {
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);
});

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 { 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)

View file

@ -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<ChatConversationListResponse>(
`/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 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,