From 134f9577b9d2355f8852cfee43be1f392cd29242 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 15 Jan 2026 19:57:25 +0200 Subject: [PATCH] feat(web): add chat comments atoms and hooks --- .../chat-comments/comments-mutation.atoms.ts | 110 ++++++++++++++++++ .../chat-comments/comments-query.atoms.ts | 34 ++++++ surfsense_web/hooks/use-comments.ts | 19 +++ 3 files changed, 163 insertions(+) create mode 100644 surfsense_web/atoms/chat-comments/comments-mutation.atoms.ts create mode 100644 surfsense_web/atoms/chat-comments/comments-query.atoms.ts create mode 100644 surfsense_web/hooks/use-comments.ts diff --git a/surfsense_web/atoms/chat-comments/comments-mutation.atoms.ts b/surfsense_web/atoms/chat-comments/comments-mutation.atoms.ts new file mode 100644 index 000000000..a0cabe1a9 --- /dev/null +++ b/surfsense_web/atoms/chat-comments/comments-mutation.atoms.ts @@ -0,0 +1,110 @@ +import { atomWithMutation } from "jotai-tanstack-query"; +import { toast } from "sonner"; +import type { + CreateCommentRequest, + CreateReplyRequest, + DeleteCommentRequest, + MarkMentionReadRequest, + UpdateCommentRequest, +} from "@/contracts/types/chat-comments.types"; +import { chatCommentsApiService } from "@/lib/apis/chat-comments-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { queryClient } from "@/lib/query-client/client"; + +export const createCommentMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: CreateCommentRequest) => { + return chatCommentsApiService.createComment(request); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.comments.byMessage(variables.message_id), + }); + }, + onError: (error: Error) => { + console.error("Error creating comment:", error); + toast.error("Failed to create comment"); + }, +})); + +export const createReplyMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: CreateReplyRequest & { message_id: number }) => { + return chatCommentsApiService.createReply(request); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.comments.byMessage(variables.message_id), + }); + }, + onError: (error: Error) => { + console.error("Error creating reply:", error); + toast.error("Failed to create reply"); + }, +})); + +export const updateCommentMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: UpdateCommentRequest & { message_id: number }) => { + return chatCommentsApiService.updateComment(request); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.comments.byMessage(variables.message_id), + }); + }, + onError: (error: Error) => { + console.error("Error updating comment:", error); + toast.error("Failed to update comment"); + }, +})); + +export const deleteCommentMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: DeleteCommentRequest & { message_id: number }) => { + return chatCommentsApiService.deleteComment(request); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.comments.byMessage(variables.message_id), + }); + toast.success("Comment deleted"); + }, + onError: (error: Error) => { + console.error("Error deleting comment:", error); + toast.error("Failed to delete comment"); + }, +})); + +export const markMentionReadMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: MarkMentionReadRequest & { search_space_id?: number }) => { + return chatCommentsApiService.markMentionRead(request); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.mentions.all(variables.search_space_id), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.mentions.unreadOnly(variables.search_space_id), + }); + }, + onError: (error: Error) => { + console.error("Error marking mention as read:", error); + }, +})); + +export const markAllMentionsReadMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: { search_space_id?: number }) => { + return chatCommentsApiService.markAllMentionsRead(); + }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: cacheKeys.mentions.all(variables.search_space_id), + }); + queryClient.invalidateQueries({ + queryKey: cacheKeys.mentions.unreadOnly(variables.search_space_id), + }); + toast.success("All mentions marked as read"); + }, + onError: (error: Error) => { + console.error("Error marking all mentions as read:", error); + toast.error("Failed to mark mentions as read"); + }, +})); + diff --git a/surfsense_web/atoms/chat-comments/comments-query.atoms.ts b/surfsense_web/atoms/chat-comments/comments-query.atoms.ts new file mode 100644 index 000000000..07cf15b78 --- /dev/null +++ b/surfsense_web/atoms/chat-comments/comments-query.atoms.ts @@ -0,0 +1,34 @@ +import { atomWithQuery } from "jotai-tanstack-query"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { chatCommentsApiService } from "@/lib/apis/chat-comments-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +export const mentionsAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.mentions.all(searchSpaceId ? Number(searchSpaceId) : undefined), + staleTime: 60 * 1000, // 1 minute + queryFn: async () => { + return chatCommentsApiService.getMentions({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, + }; +}); + +export const unreadMentionsAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.mentions.unreadOnly(searchSpaceId ? Number(searchSpaceId) : undefined), + staleTime: 30 * 1000, // 30 seconds + queryFn: async () => { + return chatCommentsApiService.getMentions({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + unread_only: true, + }); + }, + }; +}); + diff --git a/surfsense_web/hooks/use-comments.ts b/surfsense_web/hooks/use-comments.ts new file mode 100644 index 000000000..046ca4520 --- /dev/null +++ b/surfsense_web/hooks/use-comments.ts @@ -0,0 +1,19 @@ +import { useQuery } from "@tanstack/react-query"; +import { chatCommentsApiService } from "@/lib/apis/chat-comments-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +interface UseCommentsOptions { + messageId: number; + enabled?: boolean; +} + +export function useComments({ messageId, enabled = true }: UseCommentsOptions) { + return useQuery({ + queryKey: cacheKeys.comments.byMessage(messageId), + queryFn: async () => { + return chatCommentsApiService.getComments({ message_id: messageId }); + }, + enabled: enabled && !!messageId, + }); +} +