chore: remove the new folder and fix imports (#740)

This commit is contained in:
Mahesh Sanikommu 2026-02-17 03:00:29 +05:30 committed by GitHub
parent 15613c2421
commit 1b1b34fb66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
106 changed files with 442 additions and 163 deletions

View file

@ -0,0 +1,273 @@
"use client"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useCallback, useMemo, useState, useRef, useEffect } from "react"
import { $fetch } from "@repo/lib/api"
import type {
GraphViewportResponse,
GraphBoundsResponse,
GraphStatsResponse,
} from "../types"
interface ViewportParams {
minX: number
maxX: number
minY: number
maxY: number
}
interface UseGraphApiOptions {
containerTags?: string[]
limit?: number
enabled?: boolean
documentIds?: string[]
}
export function useGraphApi(options: UseGraphApiOptions = {}) {
const { containerTags, documentIds, limit = 200, enabled = true } = options
const queryClient = useQueryClient()
const [viewport, setViewport] = useState<ViewportParams>({
minX: 0,
maxX: 1000,
minY: 0,
maxY: 1000,
})
// Debounce viewport changes
const viewportTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const pendingViewportRef = useRef<ViewportParams | null>(null)
const updateViewport = useCallback((newViewport: ViewportParams) => {
pendingViewportRef.current = newViewport
if (viewportTimeoutRef.current) {
clearTimeout(viewportTimeoutRef.current)
}
viewportTimeoutRef.current = setTimeout(() => {
if (pendingViewportRef.current) {
setViewport(pendingViewportRef.current)
pendingViewportRef.current = null
}
}, 150)
}, [])
useEffect(() => {
return () => {
if (viewportTimeoutRef.current) {
clearTimeout(viewportTimeoutRef.current)
}
}
}, [])
const boundsQuery = useQuery({
queryKey: ["graph-bounds", containerTags?.join(",")],
queryFn: async (): Promise<GraphBoundsResponse> => {
const params = new URLSearchParams()
if (containerTags?.length) {
params.set("containerTags", JSON.stringify(containerTags))
}
const response = await $fetch("@get/graph/bounds", {
query: Object.fromEntries(params),
disableValidation: true,
})
if (response.error) {
throw new Error(
response.error?.message || "Failed to fetch graph bounds",
)
}
return response.data as GraphBoundsResponse
},
staleTime: 5 * 60 * 1000,
enabled,
})
const statsQuery = useQuery({
queryKey: ["graph-stats", containerTags?.join(",")],
queryFn: async (): Promise<GraphStatsResponse> => {
const params = new URLSearchParams()
if (containerTags?.length) {
params.set("containerTags", JSON.stringify(containerTags))
}
const response = await $fetch("@get/graph/stats", {
query: Object.fromEntries(params),
disableValidation: true,
})
if (response.error) {
throw new Error(
response.error?.message || "Failed to fetch graph stats",
)
}
return response.data as GraphStatsResponse
},
staleTime: 5 * 60 * 1000,
enabled,
})
const viewportQuery = useQuery({
queryKey: [
"graph-viewport",
viewport.minX,
viewport.maxX,
viewport.minY,
viewport.maxY,
containerTags?.join(","),
documentIds?.join(","),
limit,
],
queryFn: async (): Promise<GraphViewportResponse> => {
const response = await $fetch("@post/graph/viewport", {
body: {
viewport: {
minX: viewport.minX,
maxX: viewport.maxX,
minY: viewport.minY,
maxY: viewport.maxY,
},
containerTags,
documentIds,
limit,
},
disableValidation: true,
})
if (response.error) {
throw new Error(
response.error?.message || "Failed to fetch graph viewport",
)
}
return response.data as GraphViewportResponse
},
staleTime: 30 * 1000,
enabled,
})
// Prefetch adjacent viewports for smoother panning
const prefetchAdjacentViewports = useCallback(
(currentViewport: ViewportParams) => {
const viewportWidth = currentViewport.maxX - currentViewport.minX
const viewportHeight = currentViewport.maxY - currentViewport.minY
const offsets = [
{ dx: viewportWidth * 0.5, dy: 0 },
{ dx: -viewportWidth * 0.5, dy: 0 },
{ dx: 0, dy: viewportHeight * 0.5 },
{ dx: 0, dy: -viewportHeight * 0.5 },
]
offsets.forEach(({ dx, dy }) => {
const prefetchViewport = {
minX: Math.max(0, currentViewport.minX + dx),
maxX: Math.max(0, currentViewport.maxX + dx),
minY: Math.max(0, currentViewport.minY + dy),
maxY: Math.max(0, currentViewport.maxY + dy),
}
queryClient.prefetchQuery({
queryKey: [
"graph-viewport",
prefetchViewport.minX,
prefetchViewport.maxX,
prefetchViewport.minY,
prefetchViewport.maxY,
containerTags?.join(","),
limit,
],
queryFn: async () => {
const response = await $fetch("@post/graph/viewport", {
body: {
viewport: prefetchViewport,
containerTags,
limit,
},
disableValidation: true,
})
if (response.error) {
throw new Error(
response.error?.message || "Failed to fetch graph viewport",
)
}
return response.data
},
staleTime: 30 * 1000,
})
})
},
[queryClient, containerTags, limit],
)
const data = useMemo(() => {
return {
documents: viewportQuery.data?.documents ?? [],
edges: viewportQuery.data?.edges ?? [],
totalCount: viewportQuery.data?.totalCount ?? 0,
bounds: boundsQuery.data?.bounds ?? null,
stats: statsQuery.data ?? null,
}
}, [viewportQuery.data, boundsQuery.data, statsQuery.data])
const isLoading = viewportQuery.isPending || boundsQuery.isPending
const isRefetching = viewportQuery.isRefetching
const error =
viewportQuery.error || boundsQuery.error || statsQuery.error || null
return {
data,
isLoading,
isRefetching,
error,
viewport,
updateViewport,
prefetchAdjacentViewports,
refetch: viewportQuery.refetch,
}
}
/**
* Scales backend coordinates (0-1000) to graph canvas coordinates
*/
export function scaleBackendToCanvas(
x: number,
y: number,
canvasWidth: number,
canvasHeight: number,
): { x: number; y: number } {
const scale = Math.min(canvasWidth, canvasHeight) / 1000
const offsetX = (canvasWidth - 1000 * scale) / 2
const offsetY = (canvasHeight - 1000 * scale) / 2
return {
x: x * scale + offsetX,
y: y * scale + offsetY,
}
}
/**
* Scales canvas coordinates to backend coordinates (0-1000)
*/
export function scaleCanvasToBackend(
x: number,
y: number,
canvasWidth: number,
canvasHeight: number,
): { x: number; y: number } {
const scale = Math.min(canvasWidth, canvasHeight) / 1000
const offsetX = (canvasWidth - 1000 * scale) / 2
const offsetY = (canvasHeight - 1000 * scale) / 2
return {
x: (x - offsetX) / scale,
y: (y - offsetY) / scale,
}
}

View file

@ -0,0 +1,289 @@
"use client"
import { useMemo, useRef, useEffect } from "react"
import { MEMORY_BORDER, EDGE_COLORS } from "../constants"
import type {
GraphNode,
GraphEdge,
GraphApiDocument,
GraphApiMemory,
GraphApiEdge,
DocumentNodeData,
MemoryNodeData,
} from "../types"
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000
const ONE_DAY_MS = 24 * 60 * 60 * 1000
const MEMORY_CLUSTER_SPREAD = 150
function getMemoryBorderColor(mem: GraphApiMemory): string {
if (mem.isForgotten) return MEMORY_BORDER.forgotten
if (mem.forgetAfter) {
const msLeft = new Date(mem.forgetAfter).getTime() - Date.now()
if (msLeft < SEVEN_DAYS_MS) return MEMORY_BORDER.expiring
}
const age = Date.now() - new Date(mem.createdAt).getTime()
if (age < ONE_DAY_MS) return MEMORY_BORDER.recent
return MEMORY_BORDER.default
}
function getEdgeVisualProps(similarity: number) {
return {
opacity: 0.3 + similarity * 0.5,
thickness: 1 + similarity * 1.5,
}
}
function normalizeDocCoordinates(
documents: GraphApiDocument[],
): GraphApiDocument[] {
if (documents.length <= 1) return documents
let minX = Number.POSITIVE_INFINITY
let maxX = Number.NEGATIVE_INFINITY
let minY = Number.POSITIVE_INFINITY
let maxY = Number.NEGATIVE_INFINITY
for (const doc of documents) {
minX = Math.min(minX, doc.x)
maxX = Math.max(maxX, doc.x)
minY = Math.min(minY, doc.y)
maxY = Math.max(maxY, doc.y)
}
const rangeX = maxX - minX || 1
const rangeY = maxY - minY || 1
const PAD = 100
return documents.map((doc) => ({
...doc,
x: PAD + ((doc.x - minX) / rangeX) * (1000 - 2 * PAD),
y: PAD + ((doc.y - minY) / rangeY) * (1000 - 2 * PAD),
}))
}
export function useGraphData(
documents: GraphApiDocument[],
apiEdges: GraphApiEdge[],
draggingNodeId: string | null,
canvasWidth: number,
canvasHeight: number,
) {
const nodeCache = useRef<Map<string, GraphNode>>(new Map())
useEffect(() => {
if (!documents || documents.length === 0) return
const currentIds = new Set<string>()
for (const doc of documents) {
currentIds.add(doc.id)
for (const mem of doc.memories) currentIds.add(mem.id)
}
for (const [id] of nodeCache.current.entries()) {
if (!currentIds.has(id)) nodeCache.current.delete(id)
}
}, [documents])
const { scale, offsetX, offsetY } = useMemo(() => {
if (canvasWidth === 0 || canvasHeight === 0) {
return { scale: 1, offsetX: 0, offsetY: 0 }
}
const paddingFactor = 0.8
const s = (Math.min(canvasWidth, canvasHeight) * paddingFactor) / 1000
const ox = (canvasWidth - 1000 * s) / 2
const oy = (canvasHeight - 1000 * s) / 2
return { scale: s, offsetX: ox, offsetY: oy }
}, [canvasWidth, canvasHeight])
const normalizedDocs = useMemo(
() => normalizeDocCoordinates(documents),
[documents],
)
const nodes = useMemo(() => {
if (!normalizedDocs || normalizedDocs.length === 0) return []
const result: GraphNode[] = []
for (const doc of normalizedDocs) {
const initialX = doc.x * scale + offsetX
const initialY = doc.y * scale + offsetY
let docNode = nodeCache.current.get(doc.id)
const docData: DocumentNodeData = {
id: doc.id,
title: doc.title,
summary: doc.summary,
type: doc.documentType,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memories,
}
if (docNode) {
docNode.data = docData
docNode.isDragging = draggingNodeId === doc.id
} else {
docNode = {
id: doc.id,
type: "document",
x: initialX,
y: initialY,
data: docData,
size: 50,
borderColor: "#2A2F36",
isHovered: false,
isDragging: false,
}
nodeCache.current.set(doc.id, docNode)
}
result.push(docNode)
const memCount = doc.memories.length
for (let i = 0; i < memCount; i++) {
const mem = doc.memories[i]!
let memNode = nodeCache.current.get(mem.id)
const memData: MemoryNodeData = {
...mem,
documentId: doc.id,
content: mem.memory,
}
if (memNode) {
memNode.data = memData
memNode.borderColor = getMemoryBorderColor(mem)
memNode.isDragging = draggingNodeId === mem.id
} else {
const angle = (i / memCount) * 2 * Math.PI
memNode = {
id: mem.id,
type: "memory",
x: docNode.x + Math.cos(angle) * MEMORY_CLUSTER_SPREAD,
y: docNode.y + Math.sin(angle) * MEMORY_CLUSTER_SPREAD,
data: memData,
size: 36,
borderColor: getMemoryBorderColor(mem),
isHovered: false,
isDragging: false,
}
nodeCache.current.set(mem.id, memNode)
}
result.push(memNode)
}
}
return result
}, [normalizedDocs, scale, offsetX, offsetY, draggingNodeId])
const edges = useMemo(() => {
if (!normalizedDocs || normalizedDocs.length === 0) return []
const result: GraphEdge[] = []
const allNodeIds = new Set(nodes.map((n) => n.id))
for (const doc of normalizedDocs) {
for (const mem of doc.memories) {
result.push({
id: `dm-${doc.id}-${mem.id}`,
source: doc.id,
target: mem.id,
similarity: 1,
visualProps: { opacity: 0.3, thickness: 1.5 },
edgeType: "doc-memory",
})
}
}
for (const doc of normalizedDocs) {
for (const mem of doc.memories) {
if (mem.parentMemoryId && allNodeIds.has(mem.parentMemoryId)) {
result.push({
id: `ver-${mem.parentMemoryId}-${mem.id}`,
source: mem.parentMemoryId,
target: mem.id,
similarity: 1,
visualProps: { opacity: 0.6, thickness: 2 },
edgeType: "version",
})
}
}
}
for (const apiEdge of apiEdges) {
if (!allNodeIds.has(apiEdge.source) || !allNodeIds.has(apiEdge.target)) {
continue
}
result.push({
id: `sim-${apiEdge.source}-${apiEdge.target}`,
source: apiEdge.source,
target: apiEdge.target,
similarity: apiEdge.similarity,
visualProps: getEdgeVisualProps(apiEdge.similarity),
edgeType: "similarity",
})
}
return result
}, [normalizedDocs, apiEdges, nodes])
return { nodes, edges, scale, offsetX, offsetY }
}
export function screenToBackendCoords(
screenX: number,
screenY: number,
panX: number,
panY: number,
zoom: number,
canvasWidth: number,
canvasHeight: number,
): { x: number; y: number } {
const canvasX = (screenX - panX) / zoom
const canvasY = (screenY - panY) / zoom
const paddingFactor = 0.8
const s = (Math.min(canvasWidth, canvasHeight) * paddingFactor) / 1000
const ox = (canvasWidth - 1000 * s) / 2
const oy = (canvasHeight - 1000 * s) / 2
return {
x: (canvasX - ox) / s,
y: (canvasY - oy) / s,
}
}
export function calculateBackendViewport(
panX: number,
panY: number,
zoom: number,
canvasWidth: number,
canvasHeight: number,
): { minX: number; maxX: number; minY: number; maxY: number } {
const topLeft = screenToBackendCoords(
0,
0,
panX,
panY,
zoom,
canvasWidth,
canvasHeight,
)
const bottomRight = screenToBackendCoords(
canvasWidth,
canvasHeight,
panX,
panY,
zoom,
canvasWidth,
canvasHeight,
)
return {
minX: Math.max(0, Math.min(topLeft.x, bottomRight.x)),
maxX: Math.max(topLeft.x, bottomRight.x),
minY: Math.max(0, Math.min(topLeft.y, bottomRight.y)),
maxY: Math.max(topLeft.y, bottomRight.y),
}
}