+
Facts
diff --git a/apps/webapp/app/components/logs/log-options.tsx b/apps/webapp/app/components/logs/log-options.tsx
index f5d9b2f..be7bead 100644
--- a/apps/webapp/app/components/logs/log-options.tsx
+++ b/apps/webapp/app/components/logs/log-options.tsx
@@ -1,4 +1,4 @@
-import { EllipsisVertical, Trash } from "lucide-react";
+import { EllipsisVertical, Trash, Copy } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -18,6 +18,7 @@ import {
} from "../ui/alert-dialog";
import { useState, useEffect } from "react";
import { useFetcher, useNavigate } from "@remix-run/react";
+import { toast } from "~/hooks/use-toast";
interface LogOptionsProps {
id: string;
@@ -40,8 +41,24 @@ export const LogOptions = ({ id }: LogOptionsProps) => {
setDeleteDialogOpen(false);
};
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(id);
+ toast({
+ title: "Copied",
+ description: "Episode ID copied to clipboard",
+ });
+ } catch (err) {
+ console.error("Failed to copy:", err);
+ toast({
+ title: "Error",
+ description: "Failed to copy ID",
+ variant: "destructive",
+ });
+ }
+ };
+
useEffect(() => {
- console.log(deleteFetcher.state, deleteFetcher.data);
if (deleteFetcher.state === "idle" && deleteFetcher.data?.success) {
navigate(`/home/inbox`);
}
@@ -49,16 +66,26 @@ export const LogOptions = ({ id }: LogOptionsProps) => {
return (
<>
-
+
+
+
+
diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx
index 0bc0139..6c7c61f 100644
--- a/apps/webapp/app/components/logs/log-text-collapse.tsx
+++ b/apps/webapp/app/components/logs/log-text-collapse.tsx
@@ -4,6 +4,7 @@ import { type LogItem } from "~/hooks/use-logs";
import { getIconForAuthorise } from "../icon-utils";
import { useNavigate, useParams } from "@remix-run/react";
import { getStatusColor, getStatusValue } from "./utils";
+import { File, MessageSquare } from "lucide-react";
interface LogTextCollapseProps {
text?: string;
@@ -49,9 +50,13 @@ export function LogTextCollapse({ text, log }: LogTextCollapseProps) {
};
const getIngestType = (log: LogItem) => {
- const type = log.type ?? log.data.type ?? "Conversation";
+ const type = log.type ?? log.data.type ?? "CONVERSATION";
- return type[0].toUpperCase();
+ return type === "CONVERSATION" ? (
+
+ ) : (
+
+ );
};
return (
@@ -100,7 +105,7 @@ export function LogTextCollapse({ text, log }: LogTextCollapseProps) {
{getIngestType(log)}
diff --git a/apps/webapp/app/components/logs/utils.ts b/apps/webapp/app/components/logs/utils.ts
index 71b6b8d..40df970 100644
--- a/apps/webapp/app/components/logs/utils.ts
+++ b/apps/webapp/app/components/logs/utils.ts
@@ -22,5 +22,5 @@ export function getStatusValue(status: string) {
return formatString("In Queue");
}
- return status;
+ return formatString(status);
}
diff --git a/apps/webapp/app/components/logs/virtual-logs-list.tsx b/apps/webapp/app/components/logs/virtual-logs-list.tsx
index 4fde4eb..70544cc 100644
--- a/apps/webapp/app/components/logs/virtual-logs-list.tsx
+++ b/apps/webapp/app/components/logs/virtual-logs-list.tsx
@@ -10,6 +10,7 @@ import {
import { type LogItem } from "~/hooks/use-logs";
import { ScrollManagedList } from "../virtualized-list";
import { LogTextCollapse } from "./log-text-collapse";
+import { LoaderCircle } from "lucide-react";
interface VirtualLogsListProps {
logs: LogItem[];
@@ -139,7 +140,7 @@ export function VirtualLogsList({
{isLoading && (
- Loading more logs...
+
)}
diff --git a/apps/webapp/app/components/onboarding/onboarding-question.tsx b/apps/webapp/app/components/onboarding/onboarding-question.tsx
index 85c35a6..ccfac4e 100644
--- a/apps/webapp/app/components/onboarding/onboarding-question.tsx
+++ b/apps/webapp/app/components/onboarding/onboarding-question.tsx
@@ -139,6 +139,7 @@ export default function OnboardingQuestionComponent({
variant="ghost"
size="xl"
onClick={onPrevious}
+ disabled={loading}
className="rounded-lg px-4 py-2"
>
Previous
@@ -151,7 +152,7 @@ export default function OnboardingQuestionComponent({
size="xl"
onClick={onNext}
isLoading={!!loading}
- disabled={!isValid()}
+ disabled={!isValid() || loading}
className="rounded-lg px-4 py-2"
>
{isLast ? "Complete Profile" : "Continue"}
diff --git a/apps/webapp/app/components/sidebar/app-sidebar.tsx b/apps/webapp/app/components/sidebar/app-sidebar.tsx
index 8226609..c1b6ae3 100644
--- a/apps/webapp/app/components/sidebar/app-sidebar.tsx
+++ b/apps/webapp/app/components/sidebar/app-sidebar.tsx
@@ -1,4 +1,5 @@
import * as React from "react";
+import { useHotkeys } from "react-hotkeys-hook";
import {
Sidebar,
@@ -12,14 +13,20 @@ import {
Columns3,
Inbox,
LayoutGrid,
+ LoaderCircle,
MessageSquare,
Network,
+ Plus,
} from "lucide-react";
import { NavMain } from "./nav-main";
import { useUser } from "~/hooks/useUser";
import { NavUser } from "./nav-user";
import Logo from "../logo/logo";
import { ConversationList } from "../conversation";
+import { Button } from "../ui";
+import { Project } from "../icons/project";
+import { AddMemoryCommand } from "../command-bar/add-memory-command";
+import { AddMemoryDialog } from "../command-bar/memory-dialog.client";
const data = {
navMain: [
@@ -41,7 +48,7 @@ const data = {
{
title: "Spaces",
url: "/home/space",
- icon: Columns3,
+ icon: Project,
},
{
title: "Integrations",
@@ -54,33 +61,57 @@ const data = {
export function AppSidebar({ ...props }: React.ComponentProps) {
const user = useUser();
- return (
-
-
-
-
-
-
- C.O.R.E.
-
-
-
-
-
-
-
-
History
-
-
-
+ const [showAddMemory, setShowAddMemory] = React.useState(false);
-
-
-
-
+ // Open command bar with Meta+K (Cmd+K on Mac, Ctrl+K on Windows/Linux)
+ useHotkeys("meta+k", (e) => {
+ e.preventDefault();
+ setShowAddMemory(true);
+ });
+
+ return (
+ <>
+
+
+
+
+
+
+ C.O.R.E.
+
+
+
+
+
+
+
+
+
+
History
+
+
+
+
+
+
+
+
+
+ {showAddMemory && (
+
+ )}
+ >
);
}
diff --git a/apps/webapp/app/components/sidebar/nav-user.tsx b/apps/webapp/app/components/sidebar/nav-user.tsx
index cb162f9..a346003 100644
--- a/apps/webapp/app/components/sidebar/nav-user.tsx
+++ b/apps/webapp/app/components/sidebar/nav-user.tsx
@@ -67,6 +67,15 @@ export function NavUser({ user }: { user: ExtendedUser }) {
+
+
);
diff --git a/apps/webapp/app/components/spaces/space-card.tsx b/apps/webapp/app/components/spaces/space-card.tsx
index 3753bb8..4882e32 100644
--- a/apps/webapp/app/components/spaces/space-card.tsx
+++ b/apps/webapp/app/components/spaces/space-card.tsx
@@ -17,8 +17,8 @@ interface SpaceCardProps {
createdAt: string;
updatedAt: string;
autoMode: boolean;
- statementCount: number | null;
summary: string | null;
+ contextCount?: number | null;
themes?: string[];
};
}
@@ -46,13 +46,17 @@ export function SpaceCard({ space }: SpaceCardProps) {
{space.name}
- {space.description || space.summary || "Knowledge space"}
+
- {space.statementCount && space.statementCount > 0 && (
+ {space.contextCount && space.contextCount > 0 && (
- {space.statementCount} fact
- {space.statementCount !== 1 ? "s" : ""}
+ {space.contextCount} episode
+ {space.contextCount !== 1 ? "s" : ""}
)}
diff --git a/apps/webapp/app/components/spaces/space-dropdown.tsx b/apps/webapp/app/components/spaces/space-dropdown.tsx
new file mode 100644
index 0000000..99f06f8
--- /dev/null
+++ b/apps/webapp/app/components/spaces/space-dropdown.tsx
@@ -0,0 +1,167 @@
+import { useState, useEffect } from "react";
+import { Check, Plus, X } from "lucide-react";
+import { Button } from "~/components/ui/button";
+import {
+ Popover,
+ PopoverContent,
+ PopoverPortal,
+ PopoverTrigger,
+} from "~/components/ui/popover";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "~/components/ui/command";
+import { Badge } from "~/components/ui/badge";
+import { cn } from "~/lib/utils";
+import { useFetcher } from "@remix-run/react";
+import { Project } from "../icons/project";
+
+interface Space {
+ id: string;
+ name: string;
+ description?: string;
+}
+
+interface SpaceDropdownProps {
+ episodeIds: string[];
+ selectedSpaceIds?: string[];
+ onSpaceChange?: (spaceIds: string[]) => void;
+ className?: string;
+}
+
+export function SpaceDropdown({
+ episodeIds,
+ selectedSpaceIds = [],
+ onSpaceChange,
+ className,
+}: SpaceDropdownProps) {
+ const [open, setOpen] = useState(false);
+ const [selectedSpaces, setSelectedSpaces] =
+ useState
(selectedSpaceIds);
+ const [spaces, setSpaces] = useState([]);
+ const spacesFetcher = useFetcher<{ spaces: Space[] }>();
+ const assignFetcher = useFetcher();
+
+ // Fetch all spaces
+ useEffect(() => {
+ spacesFetcher.load("/api/v1/spaces");
+ }, []);
+
+ // Update spaces when data is fetched
+ useEffect(() => {
+ if (spacesFetcher.data?.spaces) {
+ setSpaces(spacesFetcher.data.spaces);
+ }
+ }, [spacesFetcher.data]);
+
+ const handleSpaceToggle = (spaceId: string) => {
+ const newSelectedSpaces = selectedSpaces.includes(spaceId)
+ ? selectedSpaces.filter((id) => id !== spaceId)
+ : [...selectedSpaces, spaceId];
+
+ setSelectedSpaces(newSelectedSpaces);
+ if (episodeIds) {
+ assignFetcher.submit(
+ {
+ episodeIds: JSON.stringify(episodeIds),
+ spaceId,
+ action: selectedSpaces.includes(spaceId) ? "remove" : "assign",
+ },
+ {
+ method: "post",
+ action: "/api/v1/episodes/assign-space",
+ encType: "application/json",
+ },
+ );
+ }
+
+ // Call the callback if provided
+ if (onSpaceChange) {
+ onSpaceChange(newSelectedSpaces);
+ }
+ };
+
+ const selectedSpaceObjects = spaces.filter((space) =>
+ selectedSpaces.includes(space.id),
+ );
+
+ const getTrigger = () => {
+ if (selectedSpaceObjects?.length === 1) {
+ return (
+ <>
+ {selectedSpaceObjects[0].name}
+ >
+ );
+ }
+
+ if (selectedSpaceObjects?.length > 1) {
+ return (
+ <>
+ {selectedSpaceObjects.length} Spaces
+ >
+ );
+ }
+
+ return (
+ <>
+ {" "}
+
+ Spaces
+ >
+ );
+ };
+
+ return (
+
+ {/* + button to add more spaces */}
+
+
+
+
+
+
+
+
+
+ No spaces found.
+
+ {spaces.map((space) => (
+ handleSpaceToggle(space.id)}
+ >
+
+
+ {space.name}
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/webapp/app/components/spaces/space-fact-card.tsx b/apps/webapp/app/components/spaces/space-episode-card.tsx
similarity index 61%
rename from apps/webapp/app/components/spaces/space-fact-card.tsx
rename to apps/webapp/app/components/spaces/space-episode-card.tsx
index bb003df..5a0bfbf 100644
--- a/apps/webapp/app/components/spaces/space-fact-card.tsx
+++ b/apps/webapp/app/components/spaces/space-episode-card.tsx
@@ -2,12 +2,27 @@ import { Calendar } from "lucide-react";
import { Badge } from "~/components/ui/badge";
import type { StatementNode } from "@core/types";
import { cn } from "~/lib/utils";
+import { useNavigate } from "@remix-run/react";
+import Markdown from "react-markdown";
-interface SpaceFactCardProps {
- fact: StatementNode;
+export interface Episode {
+ uuid: string;
+ content: string;
+ originalContent: string;
+ source: any;
+ createdAt: Date;
+ validAt: Date;
+ metadata: any;
+ sessionId: any;
+ logId?: any;
}
-export function SpaceFactCard({ fact }: SpaceFactCardProps) {
+interface SpaceFactCardProps {
+ episode: Episode;
+}
+
+export function SpaceEpisodeCard({ episode }: SpaceFactCardProps) {
+ const navigate = useNavigate();
const formatDate = (date: Date | string) => {
const d = new Date(date);
return d.toLocaleDateString("en-US", {
@@ -17,18 +32,20 @@ export function SpaceFactCard({ fact }: SpaceFactCardProps) {
});
};
- const displayText = fact.fact;
+ const displayText = episode.originalContent;
- const recallCount =
- (fact.recallCount?.high ?? 0) + (fact.recallCount?.low ?? 0);
+ const onClick = () => {
+ navigate(`/home/inbox/${episode.logId}`);
+ };
return (
<>
-
+
-
{displayText}
+
{displayText}
- {!!recallCount && Recalled: {recallCount} times}
- {formatDate(fact.validAt)}
+ {formatDate(episode.validAt)}
- {fact.invalidAt && (
-
- Invalid since {formatDate(fact.invalidAt)}
-
- )}
diff --git a/apps/webapp/app/components/spaces/space-facts-filters.tsx b/apps/webapp/app/components/spaces/space-episode-filters.tsx
similarity index 61%
rename from apps/webapp/app/components/spaces/space-facts-filters.tsx
rename to apps/webapp/app/components/spaces/space-episode-filters.tsx
index 584e244..325d8d8 100644
--- a/apps/webapp/app/components/spaces/space-facts-filters.tsx
+++ b/apps/webapp/app/components/spaces/space-episode-filters.tsx
@@ -9,7 +9,7 @@ import {
} from "~/components/ui/popover";
import { Badge } from "~/components/ui/badge";
-interface SpaceFactsFiltersProps {
+interface SpaceEpisodesFiltersProps {
selectedValidDate?: string;
selectedSpaceFilter?: string;
onValidDateChange: (date?: string) => void;
@@ -22,34 +22,24 @@ const validDateOptions = [
{ value: "last_6_months", label: "Last 6 Months" },
];
-const spaceFilterOptions = [
- { value: "active", label: "Active Facts" },
- { value: "archived", label: "Archived Facts" },
- { value: "all", label: "All Facts" },
-];
+type FilterStep = "main" | "validDate";
-type FilterStep = "main" | "validDate" | "spaceFilter";
-
-export function SpaceFactsFilters({
+export function SpaceEpisodesFilters({
selectedValidDate,
selectedSpaceFilter,
onValidDateChange,
- onSpaceFilterChange,
-}: SpaceFactsFiltersProps) {
+}: SpaceEpisodesFiltersProps) {
const [popoverOpen, setPopoverOpen] = useState(false);
const [step, setStep] = useState
("main");
const selectedValidDateLabel = validDateOptions.find(
(d) => d.value === selectedValidDate,
)?.label;
- const selectedSpaceFilterLabel = spaceFilterOptions.find(
- (f) => f.value === selectedSpaceFilter,
- )?.label;
const hasFilters = selectedValidDate || selectedSpaceFilter;
return (
-
+ <>
{
@@ -79,13 +69,6 @@ export function SpaceFactsFilters({
>
Valid Date
-
)}
@@ -122,40 +105,6 @@ export function SpaceFactsFilters({
))}
)}
-
- {step === "spaceFilter" && (
-
-
- {spaceFilterOptions.map((option) => (
-
- ))}
-
- )}
@@ -172,17 +121,8 @@ export function SpaceFactsFilters({
/>
)}
- {selectedSpaceFilter && (
-
- {selectedSpaceFilterLabel}
- onSpaceFilterChange(undefined)}
- />
-
- )}
)}
-
+ >
);
}
diff --git a/apps/webapp/app/components/spaces/space-facts-list.tsx b/apps/webapp/app/components/spaces/space-episodes-list.tsx
similarity index 78%
rename from apps/webapp/app/components/spaces/space-facts-list.tsx
rename to apps/webapp/app/components/spaces/space-episodes-list.tsx
index a3476cc..c26265f 100644
--- a/apps/webapp/app/components/spaces/space-facts-list.tsx
+++ b/apps/webapp/app/components/spaces/space-episodes-list.tsx
@@ -9,25 +9,24 @@ import {
} from "react-virtualized";
import { Database } from "lucide-react";
import { Card, CardContent } from "~/components/ui/card";
-import type { StatementNode } from "@core/types";
import { ScrollManagedList } from "../virtualized-list";
-import { SpaceFactCard } from "./space-fact-card";
+import { type Episode, SpaceEpisodeCard } from "./space-episode-card";
-interface SpaceFactsListProps {
- facts: any[];
+interface SpaceEpisodesListProps {
+ episodes: any[];
hasMore: boolean;
loadMore: () => void;
isLoading: boolean;
height?: number;
}
-function FactItemRenderer(
+function EpisodeItemRenderer(
props: ListRowProps,
- facts: StatementNode[],
+ episodes: Episode[],
cache: CellMeasurerCache,
) {
const { index, key, style, parent } = props;
- const fact = facts[index];
+ const episode = episodes[index];
return (
-
+
);
}
-export function SpaceFactsList({
- facts,
+export function SpaceEpisodesList({
+ episodes,
hasMore,
loadMore,
isLoading,
-}: SpaceFactsListProps) {
+}: SpaceEpisodesListProps) {
// Create a CellMeasurerCache instance using useRef to prevent recreation
const cacheRef = useRef(null);
if (!cacheRef.current) {
cacheRef.current = new CellMeasurerCache({
- defaultHeight: 200, // Default row height for fact cards
+ defaultHeight: 200, // Default row height for episode cards
fixedWidth: true, // Rows have fixed width but dynamic height
});
}
@@ -62,17 +61,17 @@ export function SpaceFactsList({
useEffect(() => {
cache.clearAll();
- }, [facts, cache]);
+ }, [episodes, cache]);
- if (facts.length === 0 && !isLoading) {
+ if (episodes.length === 0 && !isLoading) {
return (
-
No facts found
+
No Episodes found
- This space doesn't contain any facts yet.
+ This space doesn't contain any episodes yet.
@@ -81,7 +80,7 @@ export function SpaceFactsList({
}
const isRowLoaded = ({ index }: { index: number }) => {
- return !!facts[index];
+ return !!episodes[index];
};
const loadMoreRows = async () => {
@@ -92,14 +91,14 @@ export function SpaceFactsList({
};
const rowRenderer = (props: ListRowProps) => {
- return FactItemRenderer(props, facts, cache);
+ return EpisodeItemRenderer(props, episodes, cache);
};
const rowHeight = ({ index }: Index) => {
return cache.getHeight(index, 0);
};
- const itemCount = hasMore ? facts.length + 1 : facts.length;
+ const itemCount = hasMore ? episodes.length + 1 : episodes.length;
return (
@@ -131,7 +130,7 @@ export function SpaceFactsList({
{isLoading && (
- Loading more facts...
+ Loading more episodes...
)}
diff --git a/apps/webapp/app/components/spaces/space-options.tsx b/apps/webapp/app/components/spaces/space-options.tsx
index d2170c3..95fc0ba 100644
--- a/apps/webapp/app/components/spaces/space-options.tsx
+++ b/apps/webapp/app/components/spaces/space-options.tsx
@@ -1,4 +1,4 @@
-import { EllipsisVertical, RefreshCcw, Trash, Edit } from "lucide-react";
+import { EllipsisVertical, RefreshCcw, Trash, Edit, Copy } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -19,6 +19,7 @@ import {
import { useEffect, useState } from "react";
import { useFetcher, useNavigate } from "@remix-run/react";
import { EditSpaceDialog } from "./edit-space-dialog.client";
+import { toast } from "~/hooks/use-toast";
interface SpaceOptionsProps {
id: string;
@@ -64,6 +65,23 @@ export const SpaceOptions = ({ id, name, description }: SpaceOptionsProps) => {
// revalidator.revalidate();
};
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(id);
+ toast({
+ title: "Copied",
+ description: "Space ID copied to clipboard",
+ });
+ } catch (err) {
+ console.error("Failed to copy:", err);
+ toast({
+ title: "Error",
+ description: "Failed to copy ID",
+ variant: "destructive",
+ });
+ }
+ };
+
return (
<>
@@ -79,6 +97,11 @@ export const SpaceOptions = ({ id, name, description }: SpaceOptionsProps) => {
+
+
+
setEditDialogOpen(true)}>