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 ? (
+
+ ) : (
+ 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