feat(24-02): API client methods and React Query hooks for search, bookmarks, branches

- Add searchMessages, toggleBookmark, getBookmarks, branchConversation, listBranches, exportConversation to chatApi
- Create useChatSearch hook with debounced FTS, placeholderData, 30s staleTime
- Create useChatBookmarks and useToggleBookmark with cache invalidation for bookmarks and search queries
This commit is contained in:
Nexus Dev 2026-04-01 22:31:05 +00:00
parent d78bdad0a9
commit 1b80631b66
3 changed files with 90 additions and 0 deletions

View file

@ -4,6 +4,9 @@ import type {
ChatConversationListResponse,
ChatMessage,
ChatMessageListResponse,
ChatMessageSearchResponse,
ChatBookmarkToggleResponse,
ChatBookmarkListResponse,
} from "@paperclipai/shared";
export const chatApi = {
@ -166,4 +169,46 @@ export const chatApi = {
) {
return api.post<{ id: string }>(`/conversations/${conversationId}/status-update`, data);
},
searchMessages(companyId: string, q: string, limit?: number) {
const params = new URLSearchParams({ q });
if (limit) params.set("limit", String(limit));
return api.get<ChatMessageSearchResponse>(
`/companies/${companyId}/messages/search?${params}`,
);
},
toggleBookmark(conversationId: string, messageId: string) {
return api.post<ChatBookmarkToggleResponse>(
`/conversations/${conversationId}/bookmarks`,
{ messageId },
);
},
getBookmarks(companyId: string, conversationId?: string) {
const params = new URLSearchParams();
if (conversationId) params.set("conversationId", conversationId);
const qs = params.toString();
return api.get<ChatBookmarkListResponse>(
`/companies/${companyId}/bookmarks${qs ? `?${qs}` : ""}`,
);
},
branchConversation(conversationId: string, branchFromMessageId: string) {
return api.post<ChatConversation>(
`/conversations/${conversationId}/branch`,
{ branchFromMessageId },
);
},
listBranches(conversationId: string) {
return api.get<{ items: ChatConversation[] }>(
`/conversations/${conversationId}/branches`,
);
},
exportConversation(conversationId: string, format: "markdown" | "json") {
// Returns a download URL — use window.location.href to trigger
return `/api/conversations/${conversationId}/export?format=${format}`;
},
};

View file

@ -0,0 +1,33 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { chatApi } from "../api/chat";
export function useChatBookmarks(companyId: string | null, conversationId?: string) {
const query = useQuery({
queryKey: ["chat", "bookmarks", companyId, conversationId],
queryFn: () => chatApi.getBookmarks(companyId!, conversationId),
enabled: !!companyId,
});
return {
data: query.data,
isLoading: query.isLoading,
};
}
export function useToggleBookmark() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: ({ conversationId, messageId }: { conversationId: string; messageId: string }) =>
chatApi.toggleBookmark(conversationId, messageId),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["chat", "bookmarks"] });
void queryClient.invalidateQueries({ queryKey: ["chat", "search"] });
},
});
return {
toggleBookmark: mutation.mutate,
isPending: mutation.isPending,
};
}

View file

@ -0,0 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { chatApi } from "../api/chat";
export function useChatSearch(companyId: string | null, query: string) {
return useQuery({
queryKey: ["chat", "search", companyId, query],
queryFn: () => chatApi.searchMessages(companyId!, query),
enabled: !!companyId && query.trim().length >= 2,
placeholderData: (prev) => prev,
staleTime: 30_000,
});
}