diff --git a/.gitignore b/.gitignore index 96fab4f..28a42c2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ yarn-error.log* # Misc .DS_Store *.pem + +docker-compose.dev.yaml \ No newline at end of file diff --git a/apps/webapp/app/components/dashboard/ingest.tsx b/apps/webapp/app/components/dashboard/ingest.tsx index 8a3fa00..457fb5a 100644 --- a/apps/webapp/app/components/dashboard/ingest.tsx +++ b/apps/webapp/app/components/dashboard/ingest.tsx @@ -53,6 +53,7 @@ export const Ingest = () => { placeholder="Tell what you want to add" onChange={(e) => setText(e.target.value)} disabled={isLoading} + className="max-h-[400px]" />
diff --git a/apps/webapp/app/components/graph/graph.tsx b/apps/webapp/app/components/graph/graph.tsx index bfc984f..04c8809 100644 --- a/apps/webapp/app/components/graph/graph.tsx +++ b/apps/webapp/app/components/graph/graph.tsx @@ -366,7 +366,25 @@ export const Graph = forwardRef( } }); - // Create simulation with custom forces + // Enhanced simulation for improved aesthetics and readability + + // Parameters tuned for clear separation of clusters and minimal overlap, + // as seen in the provided image (distinct, well-separated groups). + const LINK_DISTANCE = 120; // Slightly shorter for tighter clusters + const LINK_STRENGTH = 0.6; // Stronger to keep clusters compact + const CHARGE_ISOLATED = -200; // Less repulsion for isolated nodes (keeps them closer) + const CHARGE_CONNECTED = -1000; // Strong repulsion for connected nodes (prevents crowding) + const COLLIDE_RADIUS = 32; // Smaller collision radius for less overlap + const COLLIDE_STRENGTH = 0.9; // Stronger collision to avoid overlap + const COLLIDE_ITER = 10; // More iterations for better separation + const CENTER_STRENGTH = 0.18; // Pull clusters more to center + const ISOLATED_RADIAL_DIST = 260; // Place isolated nodes further from center + const ISOLATED_RADIAL_STRENGTH = 0.28; // Stronger pull for isolated nodes + const NONISOLATED_RADIAL_STRENGTH = 0.06; // Slight pull for non-isolated + const VELOCITY_DECAY = 0.28; // Smoother, more stable layout + const ALPHA_DECAY = 0.035; + const ALPHA_MIN = 0.001; + const simulation = d3 .forceSimulation(nodes as d3.SimulationNodeDatum[]) .force( @@ -374,41 +392,46 @@ export const Graph = forwardRef( d3 .forceLink(links) .id((d: any) => d.id) - .distance(200) - .strength(0.2), + .distance(LINK_DISTANCE) + .strength(LINK_STRENGTH), ) .force( "charge", d3 .forceManyBody() - .strength((d: any) => { - // Use a less negative strength for isolated nodes - // to pull them closer to the center - return isolatedNodeIds.has(d.id) ? -500 : -3000; - }) + .strength((d: any) => + isolatedNodeIds.has(d.id) ? CHARGE_ISOLATED : CHARGE_CONNECTED, + ) .distanceMin(20) - .distanceMax(500) - .theta(0.8), + .distanceMax(600) + .theta(0.9), + ) + .force( + "center", + d3.forceCenter(width / 2, height / 2).strength(CENTER_STRENGTH), ) - .force("center", d3.forceCenter(width / 2, height / 2).strength(0.05)) .force( "collide", - d3.forceCollide().radius(50).strength(0.3).iterations(5), + d3 + .forceCollide() + .radius(COLLIDE_RADIUS) + .strength(COLLIDE_STRENGTH) + .iterations(COLLIDE_ITER), ) - // Add a special gravity force for isolated nodes to pull them toward the center + // Special gravity for isolated nodes to keep them separated and visible .force( "isolatedGravity", d3 - .forceRadial( - 100, // distance from center - width / 2, // center x - height / 2, // center y - ) - .strength((d: any) => (isolatedNodeIds.has(d.id) ? 0.15 : 0.01)), + .forceRadial(ISOLATED_RADIAL_DIST, width / 2, height / 2) + .strength((d: any) => + isolatedNodeIds.has(d.id) + ? ISOLATED_RADIAL_STRENGTH + : NONISOLATED_RADIAL_STRENGTH, + ), ) - .velocityDecay(0.4) - .alphaDecay(0.05) - .alphaMin(0.001); + .velocityDecay(VELOCITY_DECAY) + .alphaDecay(ALPHA_DECAY) + .alphaMin(ALPHA_MIN); simulationRef.current = simulation; diff --git a/apps/webapp/app/lib/ingest.server.ts b/apps/webapp/app/lib/ingest.server.ts index 4aefbbd..a3847af 100644 --- a/apps/webapp/app/lib/ingest.server.ts +++ b/apps/webapp/app/lib/ingest.server.ts @@ -78,7 +78,7 @@ export function getUserQueue(userId: string) { export const IngestBodyRequest = z.object({ episodeBody: z.string(), referenceTime: z.string(), - metadata: z.record(z.union([z.string(), z.number()])), + metadata: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(), source: z.string(), spaceId: z.string().optional(), sessionId: z.string().optional(), diff --git a/apps/webapp/app/routes/home.dashboard.tsx b/apps/webapp/app/routes/home.dashboard.tsx index 794f2d0..dd76859 100644 --- a/apps/webapp/app/routes/home.dashboard.tsx +++ b/apps/webapp/app/routes/home.dashboard.tsx @@ -6,7 +6,7 @@ import { import { parse } from "@conform-to/zod"; import { json } from "@remix-run/node"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Ingest } from "~/components/dashboard/ingest"; import { @@ -23,6 +23,7 @@ import { Search } from "~/components/dashboard"; import { SearchBodyRequest } from "./search"; import { SearchService } from "~/services/search.server"; +// --- Only return userId in loader, fetch nodeLinks on client --- export async function action({ request }: ActionFunctionArgs) { const userId = await requireUserId(request); const formData = await request.formData(); @@ -52,17 +53,46 @@ export async function action({ request }: ActionFunctionArgs) { } export async function loader({ request }: LoaderFunctionArgs) { + // Only return userId, not the heavy nodeLinks const userId = await requireUserId(request); - const nodeLinks = await getNodeLinks(userId); - - return nodeLinks; + return { userId }; } export default function Dashboard() { - const nodeLinks = useTypedLoaderData(); - + const { userId } = useTypedLoaderData(); const [size, setSize] = useState(15); + // State for nodeLinks and loading + const [nodeLinks, setNodeLinks] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + async function fetchNodeLinks() { + setLoading(true); + try { + const res = await fetch( + "/node-links?userId=" + encodeURIComponent(userId), + ); + if (!res.ok) throw new Error("Failed to fetch node links"); + const data = await res.json(); + if (!cancelled) { + setNodeLinks(data); + setLoading(false); + } + } catch (e) { + if (!cancelled) { + setNodeLinks([]); + setLoading(false); + } + } + } + fetchNodeLinks(); + return () => { + cancelled = true; + }; + }, [userId]); + return ( Graph

Your memory graph

-
- {typeof window !== "undefined" && ( - +
+ {loading ? ( +
+
+ Loading graph... +
+ ) : ( + typeof window !== "undefined" && + nodeLinks && )}
@@ -85,7 +121,7 @@ export default function Dashboard() { { - return useCors - ? await apiCors(request, response, { - exposedHeaders: ["x-sol-jwt", "x-sol-jwt-claims"], - }) - : response; + // Prevent double CORS headers by checking if already present + if (useCors && !response.headers.has("access-control-allow-origin")) { + return await apiCors(request, response, { + exposedHeaders: ["x-sol-jwt", "x-sol-jwt-claims"], + }); + } + + return response; } diff --git a/apps/webapp/app/utils.ts b/apps/webapp/app/utils.ts index 0b3dbbf..db1dd86 100644 --- a/apps/webapp/app/utils.ts +++ b/apps/webapp/app/utils.ts @@ -36,10 +36,6 @@ export function useMatchesData( ): UIMatch | undefined { const matchingRoutes = useMatches(); - if (debug) { - console.log("matchingRoutes", matchingRoutes); - } - const paths = Array.isArray(id) ? id : [id]; // Get the first matching route diff --git a/docker-compose.aws.yaml b/docker-compose.aws.yaml deleted file mode 100644 index c793dff..0000000 --- a/docker-compose.aws.yaml +++ /dev/null @@ -1,59 +0,0 @@ -version: "3.8" - -services: - core: - container_name: core-app - image: redplanethq/core:${VERSION} - environment: - - NODE_ENV=${NODE_ENV} - - DATABASE_URL=${DATABASE_URL} - - DIRECT_URL=${DIRECT_URL} - - SESSION_SECRET=${SESSION_SECRET} - - ENCRYPTION_KEY=${ENCRYPTION_KEY} - - MAGIC_LINK_SECRET=${MAGIC_LINK_SECRET} - - LOGIN_ORIGIN=${LOGIN_ORIGIN} - - APP_ORIGIN=${APP_ORIGIN} - - REDIS_HOST=${REDIS_HOST} - - REDIS_PORT=${REDIS_PORT} - - REDIS_TLS_DISABLED=${REDIS_TLS_DISABLED} - - NEO4J_URI=${NEO4J_URI} - - NEO4J_USERNAME=${NEO4J_USERNAME} - - NEO4J_PASSWORD=${NEO4J_PASSWORD} - - OPENAI_API_KEY=${OPENAI_API_KEY} - - AUTH_GOOGLE_CLIENT_ID=${AUTH_GOOGLE_CLIENT_ID} - - AUTH_GOOGLE_CLIENT_SECRET=${AUTH_GOOGLE_CLIENT_SECRET} - - ENABLE_EMAIL_LOGIN=${ENABLE_EMAIL_LOGIN} - ports: - - "3033:3000" - depends_on: - - redis - - neo4j - networks: - - core - - redis: - container_name: core-redis - image: redis:7 - ports: - - "6379:6379" - networks: - - core - - neo4j: - container_name: core-neo4j - image: neo4j:5 - environment: - - NEO4J_AUTH=${NEO4J_AUTH} - ports: - - "7474:7474" - - "7687:7687" - volumes: - - type: bind - source: /efs/neo4j - target: /data - networks: - - core - -networks: - core: - driver: bridge