diff --git a/.eslintignore b/.eslintignore
index 827344f..bd515ce 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,6 @@
*/**.js
*/**.d.ts
packages/*/dist
-packages/*/lib
\ No newline at end of file
+packages/*/lib
+
+build/
\ No newline at end of file
diff --git a/apps/webapp/.eslintignore b/apps/webapp/.eslintignore
new file mode 100644
index 0000000..d298be1
--- /dev/null
+++ b/apps/webapp/.eslintignore
@@ -0,0 +1 @@
+public/
\ No newline at end of file
diff --git a/apps/webapp/.eslintrc b/apps/webapp/.eslintrc
index 9f1bc38..9a70110 100644
--- a/apps/webapp/.eslintrc
+++ b/apps/webapp/.eslintrc
@@ -5,7 +5,6 @@
{
"files": ["*.ts", "*.tsx"],
"rules": {
-
"@typescript-eslint/consistent-type-imports": [
"warn",
{
@@ -13,15 +12,15 @@
// during some autofixes, so easier to just turn it off
"prefer": "type-imports",
"disallowTypeAnnotations": true,
- "fixStyle": "inline-type-imports"
- }
+ "fixStyle": "inline-type-imports",
+ },
],
-
+
"import/no-duplicates": ["warn", { "prefer-inline": true }],
// lots of undeclared vars, enable this rule if you want to clean them up
- "turbo/no-undeclared-env-vars": "off"
- }
- }
+ "turbo/no-undeclared-env-vars": "off",
+ },
+ },
],
- "ignorePatterns": []
+ "ignorePatterns": ["public/"],
}
diff --git a/apps/webapp/app/components/dashboard/index.ts b/apps/webapp/app/components/dashboard/index.ts
new file mode 100644
index 0000000..6e99c11
--- /dev/null
+++ b/apps/webapp/app/components/dashboard/index.ts
@@ -0,0 +1 @@
+export * from "./ingest";
diff --git a/apps/webapp/app/components/dashboard/ingest.tsx b/apps/webapp/app/components/dashboard/ingest.tsx
new file mode 100644
index 0000000..d2ece62
--- /dev/null
+++ b/apps/webapp/app/components/dashboard/ingest.tsx
@@ -0,0 +1,47 @@
+import { PlusIcon } from "lucide-react";
+import { Button } from "../ui";
+import { Textarea } from "../ui/textarea";
+import { useState } from "react";
+import { z } from "zod";
+import { EpisodeType } from "@core/types";
+
+export const IngestBodyRequest = z.object({
+ episodeBody: z.string(),
+ referenceTime: z.string(),
+ type: z.enum([EpisodeType.Conversation, EpisodeType.Text]), // Assuming these are the EpisodeType values
+ source: z.string(),
+ spaceId: z.string().optional(),
+ sessionId: z.string().optional(),
+});
+
+export const Ingest = () => {
+ const [text, setText] = useState("");
+
+ return (
+
+ );
+};
diff --git a/apps/webapp/app/components/graph/graph-popover.tsx b/apps/webapp/app/components/graph/graph-popover.tsx
index e8ce04b..7af0ddf 100644
--- a/apps/webapp/app/components/graph/graph-popover.tsx
+++ b/apps/webapp/app/components/graph/graph-popover.tsx
@@ -75,10 +75,11 @@ export function GraphPopovers({
if (!nodePopupContent) {
return [];
}
+
const entityProperties = Object.fromEntries(
- Object.entries(nodePopupContent.node.attributes || {}).filter(
- ([key]) => key !== "labels",
- ),
+ Object.entries(nodePopupContent.node.attributes || {}).filter(([key]) => {
+ return key !== "labels" && !key.includes("Embedding");
+ }),
);
return Object.entries(entityProperties).map(([key, value]) => ({
@@ -181,24 +182,6 @@ export function GraphPopovers({
)}
-
- {nodePopupContent?.node.labels?.length ? (
-
-
- Labels:
-
-
- {nodePopupContent.node.labels.map((label) => (
-
- {label}
-
- ))}
-
-
- ) : null}
@@ -215,7 +198,7 @@ export function GraphPopovers({
sideOffset={5}
onOpenAutoFocus={(e) => e.preventDefault()}
>
-
+
{edgePopupContent?.source.name || "Unknown"} →{" "}
diff --git a/apps/webapp/app/components/graph/graph-visualization.tsx b/apps/webapp/app/components/graph/graph-visualization.tsx
index bd6118e..f25a5d0 100644
--- a/apps/webapp/app/components/graph/graph-visualization.tsx
+++ b/apps/webapp/app/components/graph/graph-visualization.tsx
@@ -1,18 +1,14 @@
"use client";
import { useState, useMemo, forwardRef } from "react";
-import { Graph, GraphRef } from "./graph";
+import { Graph, type GraphRef } from "./graph";
import { GraphPopovers } from "./graph-popover";
import type { RawTriplet, NodePopupContent, EdgePopupContent } from "./type";
-import { toGraphTriplets } from "./type";
+
import { createLabelColorMap, getNodeColor } from "./node-colors";
-import {
- HoverCard,
- HoverCardContent,
- HoverCardTrigger,
-} from "@/components/ui/hover-card";
import { useTheme } from "remix-themes";
+import { toGraphTriplets } from "./utils";
interface GraphVisualizationProps {
triplets: RawTriplet[];
@@ -29,7 +25,7 @@ export const GraphVisualization = forwardRef(
width = window.innerWidth * 0.85,
height = window.innerHeight * 0.85,
zoomOnMount = true,
- className = "border border-border rounded-md h-[85vh] overflow-hidden relative",
+ className = "rounded-md h-full overflow-hidden relative",
},
ref,
) => {
@@ -120,11 +116,12 @@ export const GraphVisualization = forwardRef(
setShowNodePopup(false);
setShowEdgePopup(false);
};
+
return (
{/* Entity Types Legend Button */}
-
+ {/*
-
+ */}
{triplets.length > 0 ? (
diff --git a/apps/webapp/app/components/graph/graph.tsx b/apps/webapp/app/components/graph/graph.tsx
index cfd81fc..199d07b 100644
--- a/apps/webapp/app/components/graph/graph.tsx
+++ b/apps/webapp/app/components/graph/graph.tsx
@@ -991,7 +991,7 @@ export const Graph = forwardRef
(
const svgElement = d3.select(svgRef.current);
// Update background
- svgElement.style("background-color", theme.background);
+ svgElement.style("background-color", "var(--background-3)");
// Update nodes - use getNodeColor for proper color assignment
svgElement
diff --git a/apps/webapp/app/components/graph/node-colors.ts b/apps/webapp/app/components/graph/node-colors.ts
index e1f8745..1d81634 100644
--- a/apps/webapp/app/components/graph/node-colors.ts
+++ b/apps/webapp/app/components/graph/node-colors.ts
@@ -3,36 +3,32 @@ import colors from "tailwindcss/colors";
// Define a color palette for node coloring
export const nodeColorPalette = {
light: [
- colors.pink[500], // Entity (default)
- colors.blue[500],
- colors.emerald[500],
- colors.amber[500],
- colors.indigo[500],
- colors.orange[500],
- colors.teal[500],
- colors.purple[500],
- colors.cyan[500],
- colors.lime[500],
- colors.rose[500],
- colors.violet[500],
- colors.green[500],
- colors.red[500],
+ "var(--custom-color-1)", // Entity (default)
+ "var(--custom-color-2)",
+ "var(--custom-color-3)",
+ "var(--custom-color-4)",
+ "var(--custom-color-5)",
+ "var(--custom-color-6)",
+ "var(--custom-color-7)",
+ "var(--custom-color-8)",
+ "var(--custom-color-9)",
+ "var(--custom-color-10)",
+ "var(--custom-color-11)",
+ "var(--custom-color-12)",
],
dark: [
- colors.pink[400], // Entity (default)
- colors.blue[400],
- colors.emerald[400],
- colors.amber[400],
- colors.indigo[400],
- colors.orange[400],
- colors.teal[400],
- colors.purple[400],
- colors.cyan[400],
- colors.lime[400],
- colors.rose[400],
- colors.violet[400],
- colors.green[400],
- colors.red[400],
+ "var(--custom-color-1)", // Entity (default)
+ "var(--custom-color-2)",
+ "var(--custom-color-3)",
+ "var(--custom-color-4)",
+ "var(--custom-color-5)",
+ "var(--custom-color-6)",
+ "var(--custom-color-7)",
+ "var(--custom-color-8)",
+ "var(--custom-color-9)",
+ "var(--custom-color-10)",
+ "var(--custom-color-11)",
+ "var(--custom-color-12)",
],
};
diff --git a/apps/webapp/app/components/graph/utils.ts b/apps/webapp/app/components/graph/utils.ts
new file mode 100644
index 0000000..2ee0a83
--- /dev/null
+++ b/apps/webapp/app/components/graph/utils.ts
@@ -0,0 +1,107 @@
+import type {
+ Node,
+ Edge,
+ GraphNode,
+ GraphEdge,
+ RawTriplet,
+ GraphTriplet,
+} from "./type";
+
+export function toGraphNode(node: Node): GraphNode {
+ const primaryLabel =
+ node.labels?.find((label) => label != "Entity") || "Entity";
+
+ return {
+ id: node.uuid,
+ value: node.name,
+ uuid: node.uuid,
+ name: node.name,
+ created_at: node.created_at,
+ updated_at: node.updated_at,
+ attributes: node.attributes,
+ summary: node.summary,
+ labels: node.labels,
+ primaryLabel,
+ };
+}
+
+export function toGraphEdge(edge: Edge): GraphEdge {
+ return {
+ id: edge.uuid,
+ value: edge.name,
+ ...edge,
+ };
+}
+
+export function toGraphTriplet(triplet: RawTriplet): GraphTriplet {
+ return {
+ source: toGraphNode(triplet.sourceNode),
+ relation: toGraphEdge(triplet.edge),
+ target: toGraphNode(triplet.targetNode),
+ };
+}
+
+export function toGraphTriplets(triplets: RawTriplet[]): GraphTriplet[] {
+ return triplets.map(toGraphTriplet);
+}
+
+export function createTriplets(edges: Edge[], nodes: Node[]): RawTriplet[] {
+ // Create a Set of node UUIDs that are connected by edges
+ const connectedNodeIds = new Set();
+
+ // Create triplets from edges
+ const edgeTriplets = edges
+ .map((edge) => {
+ const sourceNode = nodes.find(
+ (node) => node.uuid === edge.source_node_uuid,
+ );
+ const targetNode = nodes.find(
+ (node) => node.uuid === edge.target_node_uuid,
+ );
+
+ if (!sourceNode || !targetNode) return null;
+
+ // Add source and target node IDs to connected set
+ connectedNodeIds.add(sourceNode.uuid);
+ connectedNodeIds.add(targetNode.uuid);
+
+ return {
+ sourceNode,
+ edge,
+ targetNode,
+ };
+ })
+ .filter(
+ (t): t is RawTriplet =>
+ t !== null && t.sourceNode !== undefined && t.targetNode !== undefined,
+ );
+
+ // Find isolated nodes (nodes that don't appear in any edge)
+ const isolatedNodes = nodes.filter(
+ (node) => !connectedNodeIds.has(node.uuid),
+ );
+
+ // For isolated nodes, create special triplets
+ const isolatedTriplets: RawTriplet[] = isolatedNodes.map((node) => {
+ // Create a special marker edge for isolated nodes
+ const virtualEdge: Edge = {
+ uuid: `isolated-node-${node.uuid}`,
+ source_node_uuid: node.uuid,
+ target_node_uuid: node.uuid,
+ // Use a special type that we can filter out in the Graph component
+ type: "_isolated_node_",
+ name: "", // Empty name so it doesn't show a label
+ created_at: node.created_at,
+ updated_at: node.updated_at,
+ };
+
+ return {
+ sourceNode: node,
+ edge: virtualEdge,
+ targetNode: node,
+ };
+ });
+
+ // Combine edge triplets with isolated node triplets
+ return [...edgeTriplets, ...isolatedTriplets];
+}
diff --git a/apps/webapp/app/components/layout/LoginPageLayout.tsx b/apps/webapp/app/components/layout/LoginPageLayout.tsx
index 6462bb1..1619af8 100644
--- a/apps/webapp/app/components/layout/LoginPageLayout.tsx
+++ b/apps/webapp/app/components/layout/LoginPageLayout.tsx
@@ -6,13 +6,14 @@ export function LoginPageLayout({ children }: { children: React.ReactNode }) {
const [, setTheme] = useTheme();
return (
-
-
-
- C.O.R.E
-
-
-
+
diff --git a/apps/webapp/app/components/sidebar/app-sidebar.tsx b/apps/webapp/app/components/sidebar/app-sidebar.tsx
index 20aaac2..7b1252d 100644
--- a/apps/webapp/app/components/sidebar/app-sidebar.tsx
+++ b/apps/webapp/app/components/sidebar/app-sidebar.tsx
@@ -25,12 +25,12 @@ const data = {
navMain: [
{
title: "Dashboard",
- url: "#",
+ url: "/",
icon: DashboardIcon,
},
{
title: "API",
- url: "#",
+ url: "/api",
icon: Code,
},
],
diff --git a/apps/webapp/app/components/sidebar/nav-main.tsx b/apps/webapp/app/components/sidebar/nav-main.tsx
index f7703ff..92bea66 100644
--- a/apps/webapp/app/components/sidebar/nav-main.tsx
+++ b/apps/webapp/app/components/sidebar/nav-main.tsx
@@ -8,6 +8,7 @@ import {
SidebarMenuItem,
} from "../ui/sidebar";
import { NavUser } from "./nav-user";
+import { useLocation } from "@remix-run/react";
export const NavMain = ({
items,
@@ -18,14 +19,18 @@ export const NavMain = ({
icon?: any;
}[];
}) => {
+ const location = useLocation();
+
return (
-
{items.map((item) => (
-
+
{item.icon && }
{item.title}
diff --git a/apps/webapp/app/components/ui/resizable.tsx b/apps/webapp/app/components/ui/resizable.tsx
new file mode 100644
index 0000000..8d3cc4f
--- /dev/null
+++ b/apps/webapp/app/components/ui/resizable.tsx
@@ -0,0 +1,47 @@
+import type { ImperativePanelHandle } from "react-resizable-panels";
+
+import { DragHandleDots2Icon } from "@radix-ui/react-icons";
+import React from "react";
+import * as ResizablePrimitive from "react-resizable-panels";
+
+import { cn } from "../../lib/utils";
+
+const ResizablePanelGroup = ({
+ className,
+ ...props
+}: React.ComponentProps) => (
+
+);
+
+const ResizablePanel = ResizablePrimitive.Panel;
+
+const ResizableHandle = ({
+ withHandle,
+ className,
+ ...props
+}: React.ComponentProps & {
+ withHandle?: boolean;
+}) => (
+ div]:rotate-90",
+ className,
+ )}
+ {...props}
+ >
+ {withHandle && (
+
+
+
+ )}
+
+);
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
+export type { ImperativePanelHandle };
diff --git a/apps/webapp/app/components/ui/tabs.tsx b/apps/webapp/app/components/ui/tabs.tsx
new file mode 100644
index 0000000..7bdf651
--- /dev/null
+++ b/apps/webapp/app/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+import React from "react";
+
+import { cn } from "../../lib/utils";
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/apps/webapp/app/components/ui/textarea.tsx b/apps/webapp/app/components/ui/textarea.tsx
new file mode 100644
index 0000000..bc9e856
--- /dev/null
+++ b/apps/webapp/app/components/ui/textarea.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+import { useAutoSizeTextArea } from "../../hooks/use-autosize-textarea";
+import { cn } from "../../lib/utils";
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, value, ...props }, ref) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const textAreaRef = React.useRef(ref);
+ const id = React.useMemo(() => {
+ return `id${Math.random().toString(16).slice(2)}`;
+ }, []);
+
+ useAutoSizeTextArea(id, textAreaRef.current, value);
+
+ return (
+
+ );
+ },
+);
+Textarea.displayName = "Textarea";
+
+export { Textarea };
diff --git a/apps/webapp/app/hooks/use-autosize-textarea.tsx b/apps/webapp/app/hooks/use-autosize-textarea.tsx
new file mode 100644
index 0000000..efc2f8f
--- /dev/null
+++ b/apps/webapp/app/hooks/use-autosize-textarea.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+// Updates the height of a