+>(({ className, ...props }, ref) => (
+ [role=checkbox]]:translate-y-[2px]",
+ className,
+ )}
+ {...props}
+ />
+));
+TableCell.displayName = "TableCell";
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableCaption.displayName = "TableCaption";
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};
diff --git a/apps/webapp/app/lib/ingest.server.ts b/apps/webapp/app/lib/ingest.server.ts
index 04c7d01..6a40f6b 100644
--- a/apps/webapp/app/lib/ingest.server.ts
+++ b/apps/webapp/app/lib/ingest.server.ts
@@ -40,15 +40,17 @@ async function processUserJob(userId: string, job: any) {
await prisma.ingestionQueue.update({
where: { id: job.data.queueId },
data: {
+ output: episodeDetails,
status: IngestionStatus.COMPLETED,
},
});
// your processing logic
- } catch (err) {
+ } catch (err: any) {
await prisma.ingestionQueue.update({
where: { id: job.data.queueId },
data: {
+ error: err.message,
status: IngestionStatus.FAILED,
},
});
@@ -86,12 +88,28 @@ export const addToQueue = async (
body: z.infer,
userId: string,
) => {
+ const user = await prisma.user.findFirst({
+ where: {
+ id: userId,
+ },
+ include: {
+ Workspace: true,
+ },
+ });
+
+ if (!user?.Workspace?.id) {
+ throw new Error(
+ "Workspace ID is required to create an ingestion queue entry.",
+ );
+ }
+
const queuePersist = await prisma.ingestionQueue.create({
data: {
- spaceId: body.spaceId,
+ spaceId: body.spaceId ? body.spaceId : null,
data: body,
status: IngestionStatus.PENDING,
priority: 1,
+ workspaceId: user.Workspace.id,
},
});
diff --git a/apps/webapp/app/lib/neo4j.server.ts b/apps/webapp/app/lib/neo4j.server.ts
index 8f5b8ff..76af75a 100644
--- a/apps/webapp/app/lib/neo4j.server.ts
+++ b/apps/webapp/app/lib/neo4j.server.ts
@@ -78,25 +78,21 @@ export const getNodeLinks = async (userId: string) => {
labels: sourceNode.labels,
attributes: sourceNode.properties,
name: sourceNode.properties.name || "",
- created_at: sourceNode.properties.created_at || "",
- updated_at: sourceNode.properties.updated_at || "",
+ createdAt: sourceNode.properties.createdAt || "",
},
edge: {
uuid: edge.identity.toString(),
type: edge.type,
source_node_uuid: sourceNode.identity.toString(),
target_node_uuid: targetNode.identity.toString(),
- name: edge.properties.name || "",
- created_at: edge.properties.created_at || "",
- updated_at: edge.properties.updated_at || "",
+ createdAt: edge.properties.createdAt || "",
},
targetNode: {
uuid: targetNode.identity.toString(),
labels: targetNode.labels,
attributes: targetNode.properties,
name: targetNode.properties.name || "",
- created_at: targetNode.properties.created_at || "",
- updated_at: targetNode.properties.updated_at || "",
+ createdAt: edge.properties.createdAt || "",
},
});
});
diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx
index 2ba8893..f90ceee 100644
--- a/apps/webapp/app/routes/confirm-basic-details.tsx
+++ b/apps/webapp/app/routes/confirm-basic-details.tsx
@@ -62,7 +62,6 @@ export async function action({ request }: ActionFunctionArgs) {
export default function ConfirmBasicDetails() {
const lastSubmission = useActionData();
- const [selectedApps, setSelectedApps] = useState([]);
const [form, fields] = useForm({
lastSubmission: lastSubmission as any,
diff --git a/apps/webapp/app/routes/home.api.tsx b/apps/webapp/app/routes/home.api.tsx
new file mode 100644
index 0000000..c443988
--- /dev/null
+++ b/apps/webapp/app/routes/home.api.tsx
@@ -0,0 +1,184 @@
+import {
+ type LoaderFunctionArgs,
+ type ActionFunctionArgs,
+} from "@remix-run/server-runtime";
+import { Plus, Copy } from "lucide-react";
+import { Button } from "~/components/ui";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "~/components/ui/dialog";
+import { useFetcher } from "@remix-run/react";
+import { Input } from "~/components/ui/input";
+import { useState } from "react";
+import { parse } from "@conform-to/zod";
+import { json } from "@remix-run/node";
+import { z } from "zod";
+import {
+ createPersonalAccessToken,
+ getValidPersonalAccessTokens,
+ revokePersonalAccessToken,
+} from "~/services/personalAccessToken.server";
+import { requireUserId } from "~/services/session.server";
+import { useTypedLoaderData } from "remix-typedjson";
+import { APITable } from "~/components/api";
+
+export const APIKeyBodyRequest = z.object({
+ name: z.string(),
+});
+
+export const APIKeyDeleteBodyRequest = z.object({
+ id: z.string(),
+});
+
+export async function action({ request }: ActionFunctionArgs) {
+ const userId = await requireUserId(request);
+
+ if (request.method === "DELETE") {
+ const formData = await request.formData();
+ const submission = parse(formData, {
+ schema: APIKeyDeleteBodyRequest,
+ });
+
+ if (!submission.value || submission.intent !== "submit") {
+ return json(submission);
+ }
+
+ const results = await revokePersonalAccessToken(submission.value.id);
+
+ return json(results);
+ }
+
+ const formData = await request.formData();
+
+ const submission = parse(formData, {
+ schema: APIKeyBodyRequest,
+ });
+
+ if (!submission.value || submission.intent !== "submit") {
+ return json(submission);
+ }
+
+ const results = await createPersonalAccessToken({
+ name: submission.value.name,
+ userId,
+ });
+ return json(results);
+}
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ const userId = await requireUserId(request);
+ const personalAccessTokens = await getValidPersonalAccessTokens(userId);
+
+ return personalAccessTokens;
+}
+
+export default function API() {
+ const personalAccessTokens = useTypedLoaderData();
+
+ const [open, setOpen] = useState(false);
+ const [showToken, setShowToken] = useState(false);
+ const fetcher = useFetcher<{ token: string }>();
+ const isSubmitting = fetcher.state !== "idle";
+ const [name, setName] = useState("");
+
+ const onSubmit = async (event: React.FormEvent) => {
+ fetcher.submit({ name }, { method: "POST", action: "/home/api" });
+ setOpen(false);
+ setShowToken(true);
+ };
+
+ const copyToClipboard = (text: string | undefined) => {
+ text && navigator.clipboard.writeText(text);
+ };
+
+ return (
+
+
+
+
API Keys
+
+ Create and manage API keys to access your data programmatically. API
+ keys allow secure access to your workspace's data and functionality
+ through our REST API.
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+ Create API Key
+
+
+
+ setName(e.target.value)}
+ name="name"
+ placeholder="Enter API key name"
+ className="mt-1"
+ required
+ />
+
+
+
+ {isSubmitting ? "Creating..." : "Create API Key"}
+
+
+
+
+
+
+
+
+
+ Your New API Key
+
+
+
+ Make sure to copy your API key now. You won't be able to see
+ it again!
+
+
+
+ {fetcher.data?.token}
+
+ copyToClipboard(fetcher.data?.token)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/webapp/app/routes/home.dashboard.tsx b/apps/webapp/app/routes/home.dashboard.tsx
index 75d915c..794f2d0 100644
--- a/apps/webapp/app/routes/home.dashboard.tsx
+++ b/apps/webapp/app/routes/home.dashboard.tsx
@@ -1,4 +1,3 @@
-import { useLocalCommonState } from "~/hooks/use-local-state";
import {
ResizableHandle,
ResizablePanel,
@@ -15,17 +14,34 @@ import {
type ActionFunctionArgs,
} from "@remix-run/server-runtime";
import { requireUserId } from "~/services/session.server";
-import { useActionData } from "@remix-run/react";
import { addToQueue, IngestBodyRequest } from "~/lib/ingest.server";
import { getNodeLinks } from "~/lib/neo4j.server";
import { useTypedLoaderData } from "remix-typedjson";
import { GraphVisualization } from "~/components/graph/graph-visualization";
+import { Search } from "~/components/dashboard";
+import { SearchBodyRequest } from "./search";
+import { SearchService } from "~/services/search.server";
export async function action({ request }: ActionFunctionArgs) {
const userId = await requireUserId(request);
-
const formData = await request.formData();
+
+ // Check if this is a search request by looking for query parameter
+ if (formData.has("query")) {
+ // Handle ingest request
+ const submission = parse(formData, { schema: SearchBodyRequest });
+ const searchService = new SearchService();
+
+ if (!submission.value || submission.intent !== "submit") {
+ return json(submission);
+ }
+
+ const results = await searchService.search(submission.value.query, userId);
+ return json(results);
+ }
+
+ // Handle ingest request
const submission = parse(formData, { schema: IngestBodyRequest });
if (!submission.value || submission.intent !== "submit") {
@@ -45,8 +61,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
export default function Dashboard() {
const nodeLinks = useTypedLoaderData();
- const actionData = useActionData();
-
const [size, setSize] = useState(15);
return (
@@ -57,8 +71,8 @@ export default function Dashboard() {
order={1}
id="home"
>
-
-
Graph
+
+
Graph
Your memory graph
@@ -86,7 +100,10 @@ export default function Dashboard() {
Retrieve
-
+
+
+
+
diff --git a/apps/webapp/app/routes/home.logs.tsx b/apps/webapp/app/routes/home.logs.tsx
new file mode 100644
index 0000000..b5e9399
--- /dev/null
+++ b/apps/webapp/app/routes/home.logs.tsx
@@ -0,0 +1,52 @@
+import { json } from "@remix-run/node";
+import { Link, useLoaderData } from "@remix-run/react";
+import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
+import { IngestionLogsTable } from "~/components/logs";
+import { getIngestionLogs } from "~/services/ingestionLogs.server";
+import { requireUserId } from "~/services/session.server";
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ const userId = await requireUserId(request);
+ const url = new URL(request.url);
+ const page = Number(url.searchParams.get("page") || 1);
+
+ const { ingestionLogs, pagination } = await getIngestionLogs(userId, page);
+
+ return json({ ingestionLogs, pagination });
+}
+
+export default function Logs() {
+ const { ingestionLogs, pagination } = useLoaderData
();
+
+ return (
+
+
+
+
Logs
+
+ View and monitor your data ingestion logs. These logs show the
+ history of data being loaded into memory, helping you track and
+ debug the ingestion process.
+
+
+
+
+
+
+ {Array.from({ length: pagination.pages }, (_, i) => (
+
+ {i + 1}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/webapp/app/services/ingestionLogs.server.ts b/apps/webapp/app/services/ingestionLogs.server.ts
new file mode 100644
index 0000000..40403f0
--- /dev/null
+++ b/apps/webapp/app/services/ingestionLogs.server.ts
@@ -0,0 +1,46 @@
+import { prisma } from "~/db.server";
+
+export async function getIngestionLogs(
+ userId: string,
+ page: number = 1,
+ limit: number = 10,
+) {
+ const user = await prisma.user.findUnique({
+ where: {
+ id: userId,
+ },
+ include: {
+ Workspace: true,
+ },
+ });
+
+ const skip = (page - 1) * limit;
+
+ const [ingestionLogs, total] = await Promise.all([
+ prisma.ingestionQueue.findMany({
+ where: {
+ workspaceId: user?.Workspace?.id,
+ },
+ skip,
+ take: limit,
+ orderBy: {
+ createdAt: "desc",
+ },
+ }),
+ prisma.ingestionQueue.count({
+ where: {
+ workspaceId: user?.Workspace?.id,
+ },
+ }),
+ ]);
+
+ return {
+ ingestionLogs,
+ pagination: {
+ total,
+ pages: Math.ceil(total / limit),
+ currentPage: page,
+ limit,
+ },
+ };
+}
diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css
index ec7189e..05500ce 100644
--- a/apps/webapp/app/tailwind.css
+++ b/apps/webapp/app/tailwind.css
@@ -324,6 +324,6 @@
@apply border-border outline-ring/50;
}
body {
- @apply bg-background text-foreground;
+ @apply bg-background text-foreground text-base;
}
}
\ No newline at end of file
diff --git a/apps/webapp/package.json b/apps/webapp/package.json
index 430c528..08430ec 100644
--- a/apps/webapp/package.json
+++ b/apps/webapp/package.json
@@ -44,6 +44,7 @@
"@remix-run/server-runtime": "2.16.7",
"@remix-run/v1-meta": "^0.1.3",
"@remixicon/react": "^4.2.0",
+ "@tanstack/react-table": "^8.13.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/postcss": "^4.1.7",
"ai": "4.3.14",
@@ -54,6 +55,7 @@
"cross-env": "^7.0.3",
"d3": "^7.9.0",
"dayjs": "^1.11.10",
+ "date-fns": "^4.1.0",
"express": "^4.18.1",
"ioredis": "^5.6.1",
"isbot": "^4.1.0",
@@ -72,6 +74,7 @@
"remix-typedjson": "0.3.1",
"remix-utils": "^7.7.0",
"react-resizable-panels": "^1.0.9",
+ "react-virtualized": "^9.22.6",
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar-hide": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
diff --git a/packages/database/prisma/migrations/20250611151414_add_workspace_output_to_ingestion/migration.sql b/packages/database/prisma/migrations/20250611151414_add_workspace_output_to_ingestion/migration.sql
new file mode 100644
index 0000000..e4f6908
--- /dev/null
+++ b/packages/database/prisma/migrations/20250611151414_add_workspace_output_to_ingestion/migration.sql
@@ -0,0 +1,12 @@
+/*
+ Warnings:
+
+ - Added the required column `workspaceId` to the `IngestionQueue` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "IngestionQueue" ADD COLUMN "output" JSONB,
+ADD COLUMN "workspaceId" TEXT NOT NULL;
+
+-- AddForeignKey
+ALTER TABLE "IngestionQueue" ADD CONSTRAINT "IngestionQueue_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma
index 513ccd1..386d51a 100644
--- a/packages/database/prisma/schema.prisma
+++ b/packages/database/prisma/schema.prisma
@@ -54,8 +54,9 @@ model Workspace {
integrations String[]
- userId String? @unique
- user User? @relation(fields: [userId], references: [id])
+ userId String? @unique
+ user User? @relation(fields: [userId], references: [id])
+ IngestionQueue IngestionQueue[]
}
enum AuthenticationMethod {
@@ -174,9 +175,13 @@ model IngestionQueue {
// Queue metadata
data Json // The actual data to be processed
+ output Json? // The processed output data
status IngestionStatus
priority Int @default(0)
+ workspaceId String
+ workspace Workspace @relation(fields: [workspaceId], references: [id])
+
// Error handling
error String?
retryCount Int @default(0)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index af5d63a..6341ef3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -135,6 +135,9 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.1.7
version: 4.1.7
+ '@tanstack/react-table':
+ specifier: ^8.13.2
+ version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ai:
specifier: 4.3.14
version: 4.3.14(react@18.3.1)(zod@3.23.8)
@@ -156,6 +159,9 @@ importers:
d3:
specifier: ^7.9.0
version: 7.9.0
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.1.0
dayjs:
specifier: ^1.11.10
version: 1.11.13
@@ -198,6 +204,9 @@ importers:
react-resizable-panels:
specifier: ^1.0.9
version: 1.0.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-virtualized:
+ specifier: ^9.22.6
+ version: 9.22.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
remix-auth:
specifier: ^4.2.0
version: 4.2.0
@@ -2173,6 +2182,17 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6
+ '@tanstack/react-table@8.21.3':
+ resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+
+ '@tanstack/table-core@8.21.3':
+ resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
+ engines: {node: '>=12'}
+
'@testing-library/dom@8.20.1':
resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==}
engines: {node: '>=12'}
@@ -2969,6 +2989,10 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
+ clsx@1.2.1:
+ resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+ engines: {node: '>=6'}
+
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
@@ -3274,6 +3298,9 @@ packages:
dataloader@1.4.0:
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@@ -3406,6 +3433,9 @@ packages:
dom-accessibility-api@0.5.16:
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+ dom-helpers@5.2.1:
+ resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
+
dotenv-cli@7.4.4:
resolution: {integrity: sha512-XkBYCG0tPIes+YZr4SpfFv76SQrV/LeCE8CI7JSEMi3VR9MvTihCGTOtbIexD6i2mXF+6px7trb1imVCXSNMDw==}
hasBin: true
@@ -5635,6 +5665,9 @@ packages:
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+ react-lifecycles-compat@3.0.4:
+ resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
+
react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@@ -5688,6 +5721,12 @@ packages:
'@types/react':
optional: true
+ react-virtualized@9.22.6:
+ resolution: {integrity: sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==}
+ peerDependencies:
+ react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -8662,6 +8701,14 @@ snapshots:
tailwindcss: 4.1.7
vite: 6.3.5(@types/node@22.15.21)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
+ '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/table-core': 8.21.3
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@tanstack/table-core@8.21.3': {}
+
'@testing-library/dom@8.20.1':
dependencies:
'@babel/code-frame': 7.27.1
@@ -9612,6 +9659,8 @@ snapshots:
clone@1.0.4: {}
+ clsx@1.2.1: {}
+
clsx@2.1.1: {}
cluster-key-slot@1.1.2: {}
@@ -9925,6 +9974,8 @@ snapshots:
dataloader@1.4.0: {}
+ date-fns@4.1.0: {}
+
dayjs@1.11.13: {}
debug@2.6.9:
@@ -10039,6 +10090,11 @@ snapshots:
dom-accessibility-api@0.5.16: {}
+ dom-helpers@5.2.1:
+ dependencies:
+ '@babel/runtime': 7.27.3
+ csstype: 3.1.3
+
dotenv-cli@7.4.4:
dependencies:
cross-spawn: 7.0.6
@@ -12617,6 +12673,8 @@ snapshots:
react-is@17.0.2: {}
+ react-lifecycles-compat@3.0.4: {}
+
react-refresh@0.14.2: {}
react-remove-scroll-bar@2.3.8(@types/react@18.3.23)(react@18.3.1):
@@ -12663,6 +12721,17 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.23
+ react-virtualized@9.22.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.27.3
+ clsx: 1.2.1
+ dom-helpers: 5.2.1
+ loose-envify: 1.4.0
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-lifecycles-compat: 3.0.4
+
react@18.3.1:
dependencies:
loose-envify: 1.4.0