feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
apps/web/src/modules/pdf/hooks/index.ts
Normal file
7
apps/web/src/modules/pdf/hooks/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// PDF Hooks
|
||||
|
||||
export { usePdfViewer, useCanGoBack, useCanGoForward } from "../context";
|
||||
export { usePdfNavigation } from "./use-pdf-navigation";
|
||||
export { useEmbedding } from "./use-embedding";
|
||||
export { useCitationUnit } from "./use-citation-unit";
|
||||
export type { CitationUnitDetail, BoundingBox } from "./use-citation-unit";
|
||||
73
apps/web/src/modules/pdf/hooks/use-citation-unit.ts
Normal file
73
apps/web/src/modules/pdf/hooks/use-citation-unit.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { api } from "~/lib/api/client";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Bounding box for pixel-perfect highlighting
|
||||
*/
|
||||
export interface BoundingBox {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Citation unit with precise location for highlighting (WF-0028)
|
||||
*/
|
||||
export interface CitationUnitDetail {
|
||||
id: string;
|
||||
content: string;
|
||||
pageNumber: number;
|
||||
paragraphIndex: number;
|
||||
charStart: number;
|
||||
charEnd: number;
|
||||
bbox: BoundingBox | null;
|
||||
sectionTitle: string | null;
|
||||
unitType: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hook
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch citation unit details by ID for bounding box-based highlighting
|
||||
*
|
||||
* Falls back to legacy embedding endpoint if citation unit not found
|
||||
*/
|
||||
export function useCitationUnit(unitId: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ["pdf", "citation-unit", unitId],
|
||||
queryFn: async (): Promise<CitationUnitDetail | null> => {
|
||||
if (!unitId) return null;
|
||||
|
||||
// Try citation unit endpoint first (WF-0028 dual-resolution)
|
||||
const response = await api.ai.pdf.search["citation-units"].single[":id"].$get({
|
||||
param: { id: unitId },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
return (result as { data: CitationUnitDetail }).data;
|
||||
}
|
||||
|
||||
// If not found in citation units, this might be a legacy embedding ID
|
||||
// Return null - the highlight layer will fall back to word overlap
|
||||
if (response.status === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Error("Failed to fetch citation unit");
|
||||
},
|
||||
enabled: Boolean(unitId),
|
||||
staleTime: Infinity, // Citation units don't change
|
||||
gcTime: 1000 * 60 * 30, // Keep in cache for 30 minutes
|
||||
});
|
||||
}
|
||||
48
apps/web/src/modules/pdf/hooks/use-embedding.ts
Normal file
48
apps/web/src/modules/pdf/hooks/use-embedding.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { api } from "~/lib/api/client";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface EmbeddingDetail {
|
||||
id: string;
|
||||
content: string;
|
||||
pageNumber: number;
|
||||
charStart?: number;
|
||||
charEnd?: number;
|
||||
sectionTitle?: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hook
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch embedding details by ID for citation highlighting
|
||||
*/
|
||||
export function useEmbedding(embeddingId: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ["pdf", "embedding", embeddingId],
|
||||
queryFn: async (): Promise<EmbeddingDetail | null> => {
|
||||
if (!embeddingId) return null;
|
||||
|
||||
const response = await api.ai.pdf.embeddings[":id"].$get({
|
||||
param: { id: embeddingId },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) return null;
|
||||
throw new Error("Failed to fetch embedding");
|
||||
}
|
||||
|
||||
return response.json() as Promise<EmbeddingDetail>;
|
||||
},
|
||||
enabled: Boolean(embeddingId),
|
||||
staleTime: Infinity, // Embeddings don't change
|
||||
gcTime: 1000 * 60 * 30, // Keep in cache for 30 minutes
|
||||
});
|
||||
}
|
||||
26
apps/web/src/modules/pdf/hooks/use-pdf-navigation.ts
Normal file
26
apps/web/src/modules/pdf/hooks/use-pdf-navigation.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { useCanGoBack, useCanGoForward, usePdfViewer } from "../context";
|
||||
|
||||
/**
|
||||
* Convenience hook for PDF navigation controls.
|
||||
* Combines navigation state and actions in one place.
|
||||
*/
|
||||
export function usePdfNavigation() {
|
||||
const { goBack, goForward, navigateTo, history, historyIndex } =
|
||||
usePdfViewer();
|
||||
const canGoBack = useCanGoBack();
|
||||
const canGoForward = useCanGoForward();
|
||||
|
||||
return {
|
||||
// Actions
|
||||
goBack,
|
||||
goForward,
|
||||
navigateTo,
|
||||
// State
|
||||
canGoBack,
|
||||
canGoForward,
|
||||
historyLength: history.length,
|
||||
currentIndex: historyIndex,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user