diff --git a/apps/webapp/app/components/graph/graph.tsx b/apps/webapp/app/components/graph/graph.tsx index 20e7ebd..66bd43f 100644 --- a/apps/webapp/app/components/graph/graph.tsx +++ b/apps/webapp/app/components/graph/graph.tsx @@ -284,33 +284,37 @@ export const Graph = forwardRef( // More nodes = need more space to prevent overcrowding let scalingRatio: number; if (nodeCount < 10) { - scalingRatio = 15; // Tight for small graphs + scalingRatio = 20; // Slightly wider for small graphs } else if (nodeCount < 50) { - scalingRatio = 20 + (nodeCount - 10) * 0.5; // Gradual increase + scalingRatio = 30 + (nodeCount - 10) * 1.0; // Faster increase } else if (nodeCount < 200) { - scalingRatio = 40 + (nodeCount - 50) * 0.2; // Slower increase + scalingRatio = 70 + (nodeCount - 50) * 0.5; // More spread + } else if (nodeCount < 500) { + scalingRatio = 145 + (nodeCount - 200) * 0.3; // Continue spreading } else { - scalingRatio = Math.min(80, 70 + (nodeCount - 200) * 0.05); // Cap at 80 + scalingRatio = Math.min(300, 235 + (nodeCount - 500) * 0.1); // Cap at 300 } // Calculate optimal gravity based on density and node count let gravity: number; if (density > 0.3) { // Dense graphs need less gravity to prevent overcrowding - gravity = 1 + density * 2; + gravity = 0.5 + density * 1.5; } else if (density > 0.1) { // Medium density graphs - gravity = 3 + density * 5; + gravity = 2 + density * 3; } else { // Sparse graphs need more gravity to keep components together - gravity = Math.min(8, 5 + (1 - density) * 3); + gravity = Math.min(6, 4 + (1 - density) * 2); } - // Adjust gravity based on node count + // Adjust gravity based on node count - more aggressive reduction for large graphs if (nodeCount < 20) { gravity *= 1.5; // Smaller graphs benefit from stronger gravity } else if (nodeCount > 100) { - gravity *= 0.8; // Larger graphs need gentler gravity + gravity *= 0.5; // Larger graphs need much gentler gravity + } else if (nodeCount > 200) { + gravity *= 0.3; // Very large graphs need very gentle gravity } // Calculate iterations based on complexity @@ -374,10 +378,10 @@ export const Graph = forwardRef( settings: { ...settings, barnesHutOptimize: true, - strongGravityMode: true, + strongGravityMode: false, // Disable strong gravity for more spread gravity: optimalParams.gravity, scalingRatio: optimalParams.scalingRatio, - slowDown: 3, + slowDown: 1.5, // Reduced slowDown for better spreading }, }); diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index 4392768..d5be41d 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -2,12 +2,9 @@ import { Prisma, PrismaClient } from "@core/database"; import invariant from "tiny-invariant"; import { z } from "zod"; import { env } from "./env.server"; -import { logger } from "./services/logger.service"; import { isValidDatabaseUrl } from "./utils/db"; import { singleton } from "./utils/singleton"; -import { type Span } from "@opentelemetry/api"; - export { Prisma }; export const prisma = singleton("prisma", getClient); diff --git a/apps/webapp/app/lib/neo4j.server.ts b/apps/webapp/app/lib/neo4j.server.ts index 6a4b7ca..e924a95 100644 --- a/apps/webapp/app/lib/neo4j.server.ts +++ b/apps/webapp/app/lib/neo4j.server.ts @@ -113,17 +113,25 @@ export const getClusteredGraphData = async (userId: string) => { const session = driver.session(); try { // Get the simplified graph structure: Episode, Subject, Object with Predicate as edge + // Only include entities that are connected to more than 1 episode const result = await session.run( - `// Get all statements with their episode and entity connections - MATCH (e:Episode)-[:HAS_PROVENANCE]->(s:Statement) - WHERE s.userId = $userId + `// Find entities connected to more than 1 episode + MATCH (e:Episode)-[:HAS_PROVENANCE]->(s:Statement {userId: $userId}) + MATCH (s)-[:HAS_SUBJECT|HAS_OBJECT|HAS_PREDICATE]->(ent:Entity) + WITH ent, count(DISTINCT e) as episodeCount + WHERE episodeCount > 1 + WITH collect(ent.uuid) as validEntityUuids - // Get subject and object entities + // Get statements where all entities are in the valid set + MATCH (e:Episode)-[:HAS_PROVENANCE]->(s:Statement {userId: $userId}) MATCH (s)-[:HAS_SUBJECT]->(subj:Entity) + WHERE subj.uuid IN validEntityUuids MATCH (s)-[:HAS_PREDICATE]->(pred:Entity) + WHERE pred.uuid IN validEntityUuids MATCH (s)-[:HAS_OBJECT]->(obj:Entity) + WHERE obj.uuid IN validEntityUuids - // Return Episode, Subject, and Object as nodes with Predicate as edge label + // Build relationships WITH e, s, subj, pred, obj UNWIND [ // Episode -> Subject diff --git a/apps/webapp/app/routes/api.v1.user.delete.tsx b/apps/webapp/app/routes/api.v1.user.delete.tsx index dc21c46..183d053 100644 --- a/apps/webapp/app/routes/api.v1.user.delete.tsx +++ b/apps/webapp/app/routes/api.v1.user.delete.tsx @@ -1,5 +1,4 @@ -import { json, type ActionFunctionArgs } from "@remix-run/node"; -import { requireUser } from "~/services/session.server"; +import { json } from "@remix-run/node"; import { deleteUser, getUserById } from "~/models/user.server"; import { sessionStorage } from "~/services/sessionStorage.server"; import { cancelSubscriptionImmediately } from "~/services/stripe.server"; diff --git a/apps/webapp/app/routes/home.conversation.$conversationId.tsx b/apps/webapp/app/routes/home.conversation.$conversationId.tsx index f29f5ce..71e86cc 100644 --- a/apps/webapp/app/routes/home.conversation.$conversationId.tsx +++ b/apps/webapp/app/routes/home.conversation.$conversationId.tsx @@ -110,7 +110,7 @@ export default function SingleConversation() { } }, [run]); - const getConversations = () => { + const conversations = React.useMemo(() => { const lastConversationHistoryId = conversationResponse?.conversationHistoryId; @@ -124,15 +124,17 @@ export default function SingleConversation() { ); // Filter out any conversation history items that come after the lastConversationHistoryId - const filteredConversationHistory = lastConversationHistoryId + return lastConversationHistoryId ? sortedConversationHistory.filter((_ch, currentIndex: number) => { return currentIndex <= lastIndex; }) : sortedConversationHistory; + }, [conversationResponse, conversationHistory]); + const getConversations = () => { return ( <> - {filteredConversationHistory.map((ch: ConversationHistory) => { + {conversations.map((ch: ConversationHistory) => { return ; })} diff --git a/apps/webapp/app/services/mcp.server.ts b/apps/webapp/app/services/mcp.server.ts index bd3a772..3800f60 100644 --- a/apps/webapp/app/services/mcp.server.ts +++ b/apps/webapp/app/services/mcp.server.ts @@ -266,7 +266,7 @@ export const handleSessionRequest = async ( await transport.handleRequest(req, res); } else { - res.status(400).send("Invalid or missing session ID"); + res.status(401).send("Invalid or missing session ID"); return; } } else { diff --git a/apps/webapp/app/trigger/ingest/ingest.ts b/apps/webapp/app/trigger/ingest/ingest.ts index 79a829d..88b8387 100644 --- a/apps/webapp/app/trigger/ingest/ingest.ts +++ b/apps/webapp/app/trigger/ingest/ingest.ts @@ -153,7 +153,11 @@ export const ingestTask = task({ // Handle space assignment after successful ingestion try { // If spaceIds were explicitly provided, immediately assign the episode to those spaces - if (episodeBody.spaceIds && episodeBody.spaceIds.length > 0 && episodeDetails.episodeUuid) { + if ( + episodeBody.spaceIds && + episodeBody.spaceIds.length > 0 && + episodeDetails.episodeUuid + ) { logger.info(`Assigning episode to explicitly provided spaces`, { userId: payload.userId, episodeId: episodeDetails.episodeUuid, @@ -205,7 +209,10 @@ export const ingestTask = task({ // Auto-trigger session compaction if episode has sessionId try { - if (episodeBody.sessionId && currentStatus === IngestionStatus.COMPLETED) { + if ( + episodeBody.sessionId && + currentStatus === IngestionStatus.COMPLETED + ) { logger.info(`Checking if session compaction should be triggered`, { userId: payload.userId, sessionId: episodeBody.sessionId, diff --git a/apps/webapp/app/utils/mcp/memory.ts b/apps/webapp/app/utils/mcp/memory.ts index af72eee..dafe414 100644 --- a/apps/webapp/app/utils/mcp/memory.ts +++ b/apps/webapp/app/utils/mcp/memory.ts @@ -442,7 +442,7 @@ async function handleGetSpace(args: any) { const spaceDetails = { id: space.id, name: space.name, - summary: space.summary, + description: space.description, }; return { diff --git a/apps/webapp/prisma/schema.prisma b/apps/webapp/prisma/schema.prisma index 5920b88..e6f67e0 100644 --- a/apps/webapp/prisma/schema.prisma +++ b/apps/webapp/prisma/schema.prisma @@ -20,7 +20,7 @@ model Activity { // Used to link the task or activity to external apps sourceURL String? - integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id]) + integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id], onDelete: Cascade) integrationAccountId String? rejectionReason String? @@ -83,7 +83,7 @@ model ConversationExecutionStep { metadata Json? @default("{}") - conversationHistory ConversationHistory @relation(fields: [conversationHistoryId], references: [id]) + conversationHistory ConversationHistory @relation(fields: [conversationHistoryId], references: [id], onDelete: Cascade) conversationHistoryId String } @@ -96,7 +96,7 @@ model ConversationHistory { message String userType UserType - activity Activity? @relation(fields: [activityId], references: [id]) + activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade) activityId String? context Json? @@ -105,7 +105,7 @@ model ConversationHistory { user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? - conversation Conversation @relation(fields: [conversationId], references: [id]) + conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) conversationId String ConversationExecutionStep ConversationExecutionStep[] } @@ -124,7 +124,7 @@ model IngestionQueue { workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) - activity Activity? @relation(fields: [activityId], references: [id]) + activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade) activityId String? // Error handling @@ -168,7 +168,7 @@ model IntegrationAccount { integratedBy User @relation(references: [id], fields: [integratedById], onDelete: Cascade) integratedById String - integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId]) + integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId], onDelete: Cascade) integrationDefinitionId String workspace Workspace @relation(references: [id], fields: [workspaceId], onDelete: Cascade) workspaceId String @@ -304,7 +304,7 @@ model OAuthClient { workspaceId String? // Created by user (for audit trail) - createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) + createdBy User? @relation(fields: [createdById], references: [id], onDelete: Cascade) createdById String? // Relations @@ -594,7 +594,7 @@ model WebhookConfiguration { secret String? isActive Boolean @default(true) eventTypes String[] // List of event types this webhook is interested in, e.g. ["activity.created"] - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) workspaceId String? diff --git a/packages/database/prisma/migrations/20251024163801_add_cascade_to_all_tables/migration.sql b/packages/database/prisma/migrations/20251024163801_add_cascade_to_all_tables/migration.sql new file mode 100644 index 0000000..6877a14 --- /dev/null +++ b/packages/database/prisma/migrations/20251024163801_add_cascade_to_all_tables/migration.sql @@ -0,0 +1,47 @@ +-- DropForeignKey +ALTER TABLE "Activity" DROP CONSTRAINT "Activity_integrationAccountId_fkey"; + +-- DropForeignKey +ALTER TABLE "ConversationExecutionStep" DROP CONSTRAINT "ConversationExecutionStep_conversationHistoryId_fkey"; + +-- DropForeignKey +ALTER TABLE "ConversationHistory" DROP CONSTRAINT "ConversationHistory_activityId_fkey"; + +-- DropForeignKey +ALTER TABLE "ConversationHistory" DROP CONSTRAINT "ConversationHistory_conversationId_fkey"; + +-- DropForeignKey +ALTER TABLE "IngestionQueue" DROP CONSTRAINT "IngestionQueue_activityId_fkey"; + +-- DropForeignKey +ALTER TABLE "IntegrationAccount" DROP CONSTRAINT "IntegrationAccount_integrationDefinitionId_fkey"; + +-- DropForeignKey +ALTER TABLE "OAuthClient" DROP CONSTRAINT "OAuthClient_createdById_fkey"; + +-- DropForeignKey +ALTER TABLE "WebhookConfiguration" DROP CONSTRAINT "WebhookConfiguration_userId_fkey"; + +-- AddForeignKey +ALTER TABLE "Activity" ADD CONSTRAINT "Activity_integrationAccountId_fkey" FOREIGN KEY ("integrationAccountId") REFERENCES "IntegrationAccount"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationExecutionStep" ADD CONSTRAINT "ConversationExecutionStep_conversationHistoryId_fkey" FOREIGN KEY ("conversationHistoryId") REFERENCES "ConversationHistory"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationHistory" ADD CONSTRAINT "ConversationHistory_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ConversationHistory" ADD CONSTRAINT "ConversationHistory_conversationId_fkey" FOREIGN KEY ("conversationId") REFERENCES "Conversation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IngestionQueue" ADD CONSTRAINT "IngestionQueue_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "IntegrationAccount" ADD CONSTRAINT "IntegrationAccount_integrationDefinitionId_fkey" FOREIGN KEY ("integrationDefinitionId") REFERENCES "IntegrationDefinitionV2"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "OAuthClient" ADD CONSTRAINT "OAuthClient_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WebhookConfiguration" ADD CONSTRAINT "WebhookConfiguration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 5920b88..e6f67e0 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -20,7 +20,7 @@ model Activity { // Used to link the task or activity to external apps sourceURL String? - integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id]) + integrationAccount IntegrationAccount? @relation(fields: [integrationAccountId], references: [id], onDelete: Cascade) integrationAccountId String? rejectionReason String? @@ -83,7 +83,7 @@ model ConversationExecutionStep { metadata Json? @default("{}") - conversationHistory ConversationHistory @relation(fields: [conversationHistoryId], references: [id]) + conversationHistory ConversationHistory @relation(fields: [conversationHistoryId], references: [id], onDelete: Cascade) conversationHistoryId String } @@ -96,7 +96,7 @@ model ConversationHistory { message String userType UserType - activity Activity? @relation(fields: [activityId], references: [id]) + activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade) activityId String? context Json? @@ -105,7 +105,7 @@ model ConversationHistory { user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? - conversation Conversation @relation(fields: [conversationId], references: [id]) + conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) conversationId String ConversationExecutionStep ConversationExecutionStep[] } @@ -124,7 +124,7 @@ model IngestionQueue { workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) - activity Activity? @relation(fields: [activityId], references: [id]) + activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade) activityId String? // Error handling @@ -168,7 +168,7 @@ model IntegrationAccount { integratedBy User @relation(references: [id], fields: [integratedById], onDelete: Cascade) integratedById String - integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId]) + integrationDefinition IntegrationDefinitionV2 @relation(references: [id], fields: [integrationDefinitionId], onDelete: Cascade) integrationDefinitionId String workspace Workspace @relation(references: [id], fields: [workspaceId], onDelete: Cascade) workspaceId String @@ -304,7 +304,7 @@ model OAuthClient { workspaceId String? // Created by user (for audit trail) - createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull) + createdBy User? @relation(fields: [createdById], references: [id], onDelete: Cascade) createdById String? // Relations @@ -594,7 +594,7 @@ model WebhookConfiguration { secret String? isActive Boolean @default(true) eventTypes String[] // List of event types this webhook is interested in, e.g. ["activity.created"] - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) workspaceId String?