diff --git a/surfsense_backend/app/routes/new_chat_routes.py b/surfsense_backend/app/routes/new_chat_routes.py index 14a649923..e6a8db689 100644 --- a/surfsense_backend/app/routes/new_chat_routes.py +++ b/surfsense_backend/app/routes/new_chat_routes.py @@ -45,9 +45,9 @@ from app.schemas.new_chat import ( NewChatThreadUpdate, NewChatThreadVisibilityUpdate, NewChatThreadWithMessages, + PublicChatSnapshotCreateResponse, + PublicChatSnapshotListResponse, RegenerateRequest, - SnapshotCreateResponse, - SnapshotListResponse, ThreadHistoryLoadResponse, ThreadListItem, ThreadListResponse, @@ -736,7 +736,7 @@ async def update_thread_visibility( # ============================================================================= -@router.post("/threads/{thread_id}/snapshots", response_model=SnapshotCreateResponse) +@router.post("/threads/{thread_id}/snapshots", response_model=PublicChatSnapshotCreateResponse) async def create_thread_snapshot( thread_id: int, session: AsyncSession = Depends(get_async_session), @@ -756,7 +756,7 @@ async def create_thread_snapshot( ) -@router.get("/threads/{thread_id}/snapshots", response_model=SnapshotListResponse) +@router.get("/threads/{thread_id}/snapshots", response_model=PublicChatSnapshotListResponse) async def list_thread_snapshots( thread_id: int, session: AsyncSession = Depends(get_async_session), @@ -769,7 +769,7 @@ async def list_thread_snapshots( """ from app.services.public_chat_service import list_snapshots_for_thread - return SnapshotListResponse( + return PublicChatSnapshotListResponse( snapshots=await list_snapshots_for_thread( session=session, thread_id=thread_id, diff --git a/surfsense_backend/app/routes/search_spaces_routes.py b/surfsense_backend/app/routes/search_spaces_routes.py index 2730250ab..a8916f2ea 100644 --- a/surfsense_backend/app/routes/search_spaces_routes.py +++ b/surfsense_backend/app/routes/search_spaces_routes.py @@ -514,7 +514,7 @@ async def list_search_space_snapshots( Requires PUBLIC_SHARING_VIEW permission. """ - from app.schemas.new_chat import SearchSpaceSnapshotListResponse + from app.schemas.new_chat import PublicChatSnapshotsBySpaceResponse from app.services.public_chat_service import list_snapshots_for_search_space snapshots = await list_snapshots_for_search_space( @@ -522,4 +522,4 @@ async def list_search_space_snapshots( search_space_id=search_space_id, user=user, ) - return SearchSpaceSnapshotListResponse(snapshots=snapshots) + return PublicChatSnapshotsBySpaceResponse(snapshots=snapshots) diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py index 51bb584f6..61af0d92c 100644 --- a/surfsense_backend/app/schemas/new_chat.py +++ b/surfsense_backend/app/schemas/new_chat.py @@ -211,17 +211,17 @@ class RegenerateRequest(BaseModel): # ============================================================================= -class SnapshotCreateResponse(BaseModel): - """Response after creating a public snapshot.""" +class PublicChatSnapshotCreateResponse(BaseModel): + """Response after creating a public chat snapshot.""" snapshot_id: int share_token: str public_url: str - is_new: bool # False if existing snapshot returned (same content) + is_new: bool -class SnapshotInfo(BaseModel): - """Info about a single snapshot.""" +class PublicChatSnapshotInfo(BaseModel): + """Info about a single public chat snapshot.""" id: int share_token: str @@ -230,14 +230,14 @@ class SnapshotInfo(BaseModel): message_count: int -class SnapshotListResponse(BaseModel): - """List of snapshots for a thread.""" +class PublicChatSnapshotListResponse(BaseModel): + """List of public chat snapshots for a thread.""" - snapshots: list[SnapshotInfo] + snapshots: list[PublicChatSnapshotInfo] -class SearchSpaceSnapshotInfo(BaseModel): - """Snapshot info with thread context for search space listing.""" +class PublicChatSnapshotDetail(BaseModel): + """Public chat snapshot with thread context.""" id: int share_token: str @@ -248,10 +248,10 @@ class SearchSpaceSnapshotInfo(BaseModel): thread_title: str -class SearchSpaceSnapshotListResponse(BaseModel): - """List of all snapshots in a search space.""" +class PublicChatSnapshotsBySpaceResponse(BaseModel): + """List of public chat snapshots for a search space.""" - snapshots: list[SearchSpaceSnapshotInfo] + snapshots: list[PublicChatSnapshotDetail] # ============================================================================= diff --git a/surfsense_web/atoms/chat/chat-thread-mutation.atoms.ts b/surfsense_web/atoms/chat/chat-thread-mutation.atoms.ts index d8c158fd1..35858171e 100644 --- a/surfsense_web/atoms/chat/chat-thread-mutation.atoms.ts +++ b/surfsense_web/atoms/chat/chat-thread-mutation.atoms.ts @@ -1,17 +1,16 @@ import { atomWithMutation } from "jotai-tanstack-query"; import { toast } from "sonner"; import type { - CreateSnapshotRequest, - CreateSnapshotResponse, + PublicChatSnapshotCreateRequest, + PublicChatSnapshotCreateResponse, } from "@/contracts/types/chat-threads.types"; import { chatThreadsApiService } from "@/lib/apis/chat-threads-api.service"; -export const createSnapshotMutationAtom = atomWithMutation(() => ({ - mutationFn: async (request: CreateSnapshotRequest) => { - return chatThreadsApiService.createSnapshot(request); +export const createPublicChatSnapshotMutationAtom = atomWithMutation(() => ({ + mutationFn: async (request: PublicChatSnapshotCreateRequest) => { + return chatThreadsApiService.createPublicChatSnapshot(request); }, - onSuccess: (response: CreateSnapshotResponse) => { - // Construct URL using frontend origin (backend returns its own URL which differs) + onSuccess: (response: PublicChatSnapshotCreateResponse) => { const publicUrl = `${window.location.origin}/public/${response.share_token}`; navigator.clipboard.writeText(publicUrl); if (response.is_new) { diff --git a/surfsense_web/atoms/snapshots/snapshots-query.atoms.ts b/surfsense_web/atoms/public-chat-snapshots/public-chat-snapshots-query.atoms.ts similarity index 71% rename from surfsense_web/atoms/snapshots/snapshots-query.atoms.ts rename to surfsense_web/atoms/public-chat-snapshots/public-chat-snapshots-query.atoms.ts index 6c29dccc0..9c9eafab4 100644 --- a/surfsense_web/atoms/snapshots/snapshots-query.atoms.ts +++ b/surfsense_web/atoms/public-chat-snapshots/public-chat-snapshots-query.atoms.ts @@ -3,18 +3,18 @@ import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-quer import { chatThreadsApiService } from "@/lib/apis/chat-threads-api.service"; import { cacheKeys } from "@/lib/query-client/cache-keys"; -export const searchSpaceSnapshotsAtom = atomWithQuery((get) => { +export const publicChatSnapshotsAtom = atomWithQuery((get) => { const searchSpaceId = get(activeSearchSpaceIdAtom); return { - queryKey: cacheKeys.snapshots.bySearchSpace(Number(searchSpaceId) || 0), + queryKey: cacheKeys.publicChatSnapshots.bySearchSpace(Number(searchSpaceId) || 0), enabled: !!searchSpaceId, staleTime: 5 * 60 * 1000, queryFn: async () => { if (!searchSpaceId) { return { snapshots: [] }; } - return chatThreadsApiService.listSearchSpaceSnapshots({ + return chatThreadsApiService.listPublicChatSnapshotsForSearchSpace({ search_space_id: Number(searchSpaceId), }); }, diff --git a/surfsense_web/components/new-chat/chat-share-button.tsx b/surfsense_web/components/new-chat/chat-share-button.tsx index 85e76966e..23af0995e 100644 --- a/surfsense_web/components/new-chat/chat-share-button.tsx +++ b/surfsense_web/components/new-chat/chat-share-button.tsx @@ -5,7 +5,7 @@ import { useAtomValue, useSetAtom } from "jotai"; import { Globe, User, Users } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; -import { createSnapshotMutationAtom } from "@/atoms/chat/chat-thread-mutation.atoms"; +import { createPublicChatSnapshotMutationAtom } from "@/atoms/chat/chat-thread-mutation.atoms"; import { currentThreadAtom, setThreadVisibilityAtom } from "@/atoms/chat/current-thread.atom"; import { myAccessAtom } from "@/atoms/members/members-query.atoms"; import { Button } from "@/components/ui/button"; @@ -54,7 +54,7 @@ export function ChatShareButton({ thread, onVisibilityChange, className }: ChatS // Snapshot creation mutation const { mutateAsync: createSnapshot, isPending: isCreatingSnapshot } = useAtomValue( - createSnapshotMutationAtom + createPublicChatSnapshotMutationAtom ); // Permission check for public sharing diff --git a/surfsense_web/contracts/types/chat-threads.types.ts b/surfsense_web/contracts/types/chat-threads.types.ts index 683a72e21..df561092e 100644 --- a/surfsense_web/contracts/types/chat-threads.types.ts +++ b/surfsense_web/contracts/types/chat-threads.types.ts @@ -1,9 +1,9 @@ import { z } from "zod"; /** - * Snapshot info + * Public chat snapshot info */ -export const snapshotInfo = z.object({ +export const publicChatSnapshotInfo = z.object({ id: z.number(), share_token: z.string(), public_url: z.string(), @@ -12,13 +12,13 @@ export const snapshotInfo = z.object({ }); /** - * Create snapshot + * Create public chat snapshot */ -export const createSnapshotRequest = z.object({ +export const publicChatSnapshotCreateRequest = z.object({ thread_id: z.number(), }); -export const createSnapshotResponse = z.object({ +export const publicChatSnapshotCreateResponse = z.object({ snapshot_id: z.number(), share_token: z.string(), public_url: z.string(), @@ -26,28 +26,28 @@ export const createSnapshotResponse = z.object({ }); /** - * List snapshots + * List public chat snapshots for thread */ -export const listSnapshotsRequest = z.object({ +export const publicChatSnapshotListRequest = z.object({ thread_id: z.number(), }); -export const listSnapshotsResponse = z.object({ - snapshots: z.array(snapshotInfo), +export const publicChatSnapshotListResponse = z.object({ + snapshots: z.array(publicChatSnapshotInfo), }); /** - * Delete snapshot + * Delete public chat snapshot */ -export const deleteSnapshotRequest = z.object({ +export const publicChatSnapshotDeleteRequest = z.object({ thread_id: z.number(), snapshot_id: z.number(), }); /** - * Search space snapshot info (includes thread context) + * Public chat snapshot with thread context */ -export const searchSpaceSnapshotInfo = z.object({ +export const publicChatSnapshotDetail = z.object({ id: z.number(), share_token: z.string(), public_url: z.string(), @@ -58,23 +58,23 @@ export const searchSpaceSnapshotInfo = z.object({ }); /** - * List snapshots for search space + * List public chat snapshots for search space */ -export const listSearchSpaceSnapshotsRequest = z.object({ +export const publicChatSnapshotsBySpaceRequest = z.object({ search_space_id: z.number(), }); -export const listSearchSpaceSnapshotsResponse = z.object({ - snapshots: z.array(searchSpaceSnapshotInfo), +export const publicChatSnapshotsBySpaceResponse = z.object({ + snapshots: z.array(publicChatSnapshotDetail), }); // Type exports -export type SnapshotInfo = z.infer; -export type CreateSnapshotRequest = z.infer; -export type CreateSnapshotResponse = z.infer; -export type ListSnapshotsRequest = z.infer; -export type ListSnapshotsResponse = z.infer; -export type DeleteSnapshotRequest = z.infer; -export type SearchSpaceSnapshotInfo = z.infer; -export type ListSearchSpaceSnapshotsRequest = z.infer; -export type ListSearchSpaceSnapshotsResponse = z.infer; +export type PublicChatSnapshotInfo = z.infer; +export type PublicChatSnapshotCreateRequest = z.infer; +export type PublicChatSnapshotCreateResponse = z.infer; +export type PublicChatSnapshotListRequest = z.infer; +export type PublicChatSnapshotListResponse = z.infer; +export type PublicChatSnapshotDeleteRequest = z.infer; +export type PublicChatSnapshotDetail = z.infer; +export type PublicChatSnapshotsBySpaceRequest = z.infer; +export type PublicChatSnapshotsBySpaceResponse = z.infer; diff --git a/surfsense_web/lib/apis/chat-threads-api.service.ts b/surfsense_web/lib/apis/chat-threads-api.service.ts index 985eebc76..9dcd85761 100644 --- a/surfsense_web/lib/apis/chat-threads-api.service.ts +++ b/surfsense_web/lib/apis/chat-threads-api.service.ts @@ -1,28 +1,30 @@ import { - type CreateSnapshotRequest, - type CreateSnapshotResponse, - createSnapshotRequest, - createSnapshotResponse, - type DeleteSnapshotRequest, - deleteSnapshotRequest, - type ListSearchSpaceSnapshotsRequest, - type ListSearchSpaceSnapshotsResponse, - type ListSnapshotsRequest, - type ListSnapshotsResponse, - listSearchSpaceSnapshotsRequest, - listSearchSpaceSnapshotsResponse, - listSnapshotsRequest, - listSnapshotsResponse, + type PublicChatSnapshotCreateRequest, + type PublicChatSnapshotCreateResponse, + type PublicChatSnapshotDeleteRequest, + type PublicChatSnapshotListRequest, + type PublicChatSnapshotListResponse, + type PublicChatSnapshotsBySpaceRequest, + type PublicChatSnapshotsBySpaceResponse, + publicChatSnapshotCreateRequest, + publicChatSnapshotCreateResponse, + publicChatSnapshotDeleteRequest, + publicChatSnapshotListRequest, + publicChatSnapshotListResponse, + publicChatSnapshotsBySpaceRequest, + publicChatSnapshotsBySpaceResponse, } from "@/contracts/types/chat-threads.types"; import { ValidationError } from "../error"; import { baseApiService } from "./base-api.service"; class ChatThreadsApiService { /** - * Create a public snapshot for a thread. + * Create a public chat snapshot for a thread. */ - createSnapshot = async (request: CreateSnapshotRequest): Promise => { - const parsed = createSnapshotRequest.safeParse(request); + createPublicChatSnapshot = async ( + request: PublicChatSnapshotCreateRequest + ): Promise => { + const parsed = publicChatSnapshotCreateRequest.safeParse(request); if (!parsed.success) { const errorMessage = parsed.error.issues.map((issue) => issue.message).join(", "); @@ -31,15 +33,17 @@ class ChatThreadsApiService { return baseApiService.post( `/api/v1/threads/${parsed.data.thread_id}/snapshots`, - createSnapshotResponse + publicChatSnapshotCreateResponse ); }; /** - * List all snapshots for a thread. + * List all public chat snapshots for a thread. */ - listSnapshots = async (request: ListSnapshotsRequest): Promise => { - const parsed = listSnapshotsRequest.safeParse(request); + listPublicChatSnapshots = async ( + request: PublicChatSnapshotListRequest + ): Promise => { + const parsed = publicChatSnapshotListRequest.safeParse(request); if (!parsed.success) { const errorMessage = parsed.error.issues.map((issue) => issue.message).join(", "); @@ -48,15 +52,15 @@ class ChatThreadsApiService { return baseApiService.get( `/api/v1/threads/${parsed.data.thread_id}/snapshots`, - listSnapshotsResponse + publicChatSnapshotListResponse ); }; /** - * Delete a specific snapshot. + * Delete a public chat snapshot. */ - deleteSnapshot = async (request: DeleteSnapshotRequest): Promise => { - const parsed = deleteSnapshotRequest.safeParse(request); + deletePublicChatSnapshot = async (request: PublicChatSnapshotDeleteRequest): Promise => { + const parsed = publicChatSnapshotDeleteRequest.safeParse(request); if (!parsed.success) { const errorMessage = parsed.error.issues.map((issue) => issue.message).join(", "); @@ -69,12 +73,12 @@ class ChatThreadsApiService { }; /** - * List all snapshots for a search space. + * List all public chat snapshots for a search space. */ - listSearchSpaceSnapshots = async ( - request: ListSearchSpaceSnapshotsRequest - ): Promise => { - const parsed = listSearchSpaceSnapshotsRequest.safeParse(request); + listPublicChatSnapshotsForSearchSpace = async ( + request: PublicChatSnapshotsBySpaceRequest + ): Promise => { + const parsed = publicChatSnapshotsBySpaceRequest.safeParse(request); if (!parsed.success) { const errorMessage = parsed.error.issues.map((issue) => issue.message).join(", "); @@ -83,7 +87,7 @@ class ChatThreadsApiService { return baseApiService.get( `/api/v1/searchspaces/${parsed.data.search_space_id}/snapshots`, - listSearchSpaceSnapshotsResponse + publicChatSnapshotsBySpaceResponse ); }; } diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index dcb5288f5..df456eba3 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -82,7 +82,8 @@ export const cacheKeys = { publicChat: { byToken: (shareToken: string) => ["public-chat", shareToken] as const, }, - snapshots: { - bySearchSpace: (searchSpaceId: number) => ["snapshots", "search-space", searchSpaceId] as const, + publicChatSnapshots: { + bySearchSpace: (searchSpaceId: number) => + ["public-chat-snapshots", "search-space", searchSpaceId] as const, }, };