import { useMemo } from "react"; import { useQuery, useConvexAuth } from "convex/react"; import type { SidebarInitialData } from "@/components/sidebar/types"; import type { MediaAsset } from "@/types/media"; import { api } from "@/lib/convex/api"; /** * Convex 模式下的侧边栏数据 hook * 使用 Convex 的 useQuery 实现实时订阅,无需手动 refetch * * 注意:此 hook 仅应在 Convex 模式下使用 * 后端从 ctx.auth.getUserIdentity() 获取用户身份,前端无需传入 userId */ export function useConvexSidebarData(workspaceId: string): { data: SidebarInitialData | null; isLoading: boolean; error: Error | null; refetch: () => Promise; } { const { isAuthenticated } = useConvexAuth(); // 显式转为 boolean,确保类型正确 const shouldFetch = Boolean(isAuthenticated && workspaceId); const currentUser = useQuery(api.users.currentUser, shouldFetch ? {} : "skip"); const userId = currentUser && typeof currentUser === "object" && typeof (currentUser as any)._id === "string" ? String((currentUser as any)._id) : ""; const shouldFetchAuthed = Boolean(shouldFetch && userId); // 使用 Convex 的 useQuery,自动订阅实时更新 // 后端从 ctx.auth.getUserIdentity() 获取用户身份,无需前端传入 userId const documents = useQuery( api.documents.listByWorkspace, shouldFetch ? { workspaceId } : "skip" ); const trashedDocuments = useQuery( api.documents.listTrashedByWorkspace, shouldFetch ? { workspaceId } : "skip" ); const mindmaps = useQuery( api.mindmaps.listByWorkspace, shouldFetch ? { workspaceId, includeDeleted: true } : "skip" ); const mediaAssets = useQuery( api.mediaAssets.listByWorkspace, shouldFetchAuthed ? { userId, workspaceId, limit: 200 } : "skip", ); const trashedMediaAssets = useQuery( api.mediaAssets.listDeletedByWorkspace, shouldFetchAuthed ? { userId, workspaceId, limit: 2000 } : "skip", ); const workspacesResult = useQuery( api.workspaces.fetchWorkspaceSummaries, shouldFetch ? undefined : "skip" ); // 组合数据,格式与 SidebarInitialData 一致 const data: SidebarInitialData | null = useMemo(() => { // 当 skip 时,返回值是 undefined if (currentUser === undefined || documents === undefined || trashedDocuments === undefined || mindmaps === undefined || mediaAssets === undefined || trashedMediaAssets === undefined || workspacesResult === undefined) { return null; } const activeMindmaps = (mindmaps ?? []).filter((r) => !r.deleted_at); const trashedMindmaps = (mindmaps ?? []).filter((r) => !!r.deleted_at); const mindmapDocs = Array.from(new Set(activeMindmaps.map((r) => r.document_id))); const mindmapAssets: MediaAsset[] = activeMindmaps.map((r) => { const isLegacy = r.mindmap_id.startsWith("legacy-"); return { id: r.mindmap_id, workspace_id: r.workspace_id ?? workspaceId, document_id: r.document_id, asset_type: "mindmap", file_url: null, thumbnail_url: null, bucket: null, storage_path: null, file_name: isLegacy ? "mindmap.json" : `mindmap-${r.mindmap_id}.json`, file_size: null, mime_type: "application/json", ocr_payload: undefined, ocr_strategy: null, ocr_text: null, ocr_status: null, signed_url: null, created_at: r.created_at ?? "", updated_at: r.updated_at ?? "", }; }); const trashedMindmapAssets: MediaAsset[] = trashedMindmaps.map((r) => { const isLegacy = r.mindmap_id.startsWith("legacy-"); return { id: r.mindmap_id, workspace_id: r.workspace_id ?? workspaceId, document_id: r.document_id, asset_type: "mindmap", file_url: null, thumbnail_url: null, bucket: null, storage_path: null, file_name: isLegacy ? "mindmap.json" : `mindmap-${r.mindmap_id}.json`, file_size: null, mime_type: "application/json", ocr_payload: undefined, ocr_strategy: null, ocr_text: null, ocr_status: null, deleted_at: r.deleted_at ?? null, deleted_by: r.deleted_by ?? null, purged_at: null, signed_url: null, created_at: r.created_at ?? "", updated_at: r.updated_at ?? "", }; }); return { activeWorkspaceId: workspacesResult.activeWorkspaceId || workspaceId, workspaces: workspacesResult.workspaces, documents, trashedDocuments, trashedMediaAssets: (trashedMediaAssets ?? []) as MediaAsset[], trashedMindmapAssets, mindmapDocs, mindmapAssets, mindmapAssetChildren: {}, tableAssets: [], mediaAssets: (mediaAssets ?? []) as MediaAsset[], }; }, [ currentUser, documents, trashedDocuments, mindmaps, mediaAssets, trashedMediaAssets, workspacesResult, workspaceId, ]); // loading 状态:只有当 shouldFetch 为 true 且数据未加载时才算 loading // 使用 === undefined 判断,因为 skip 时返回 undefined const isLoading = shouldFetch && ( currentUser === undefined || documents === undefined || trashedDocuments === undefined || mindmaps === undefined || mediaAssets === undefined || trashedMediaAssets === undefined || workspacesResult === undefined ); const error = null; // Convex 模式下数据自动实时同步,refetch 是空操作 // Convex 的 useQuery 会自动实时同步,无需手动 refetch // 这个方法只是为了保持与 useSidebarData 的接口兼容 const refetch = async () => { // 空操作 - Convex 会自动同步数据 }; return { data, isLoading, error, refetch }; }