diff --git a/.env.example b/.env.example index 350d740..9761590 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -VERSION=0.1.23 +VERSION=0.1.24 # Nest run in docker, change host to database container name DB_HOST=localhost diff --git a/apps/init/src/utils/env.ts b/apps/init/src/utils/env.ts index b9e9acf..458ec7b 100644 --- a/apps/init/src/utils/env.ts +++ b/apps/init/src/utils/env.ts @@ -2,7 +2,7 @@ import { z } from "zod"; const EnvironmentSchema = z.object({ // Version - VERSION: z.string().default("0.1.14"), + VERSION: z.string().default("0.1.24"), // Database DB_HOST: z.string().default("localhost"), diff --git a/apps/webapp/app/components/command-bar/add-memory-command.tsx b/apps/webapp/app/components/command-bar/add-memory-command.tsx new file mode 100644 index 0000000..b8fa9c2 --- /dev/null +++ b/apps/webapp/app/components/command-bar/add-memory-command.tsx @@ -0,0 +1,71 @@ +import { useState } from "react"; +import { FileText, Plus } from "lucide-react"; +import { + CommandDialog, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "../ui/command"; +import { AddMemoryDialog } from "./memory-dialog.client"; +import { AddDocumentDialog } from "./document-dialog"; + +interface AddMemoryCommandProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AddMemoryCommand({ + open, + onOpenChange, +}: AddMemoryCommandProps) { + const [showAddMemory, setShowAddMemory] = useState(false); + const [showAddDocument, setShowAddDocument] = useState(false); + + const handleAddMemory = () => { + onOpenChange(false); + setShowAddMemory(true); + }; + + const handleAddDocument = () => { + onOpenChange(false); + setShowAddDocument(true); + }; + + return ( + <> + {/* Main Command Dialog */} + + + + + + + Add Memory + + + + Add Document + + + + + + {showAddMemory && ( + + )} + + {/* Add Document Dialog */} + + + ); +} diff --git a/apps/webapp/app/components/command-bar/document-dialog.tsx b/apps/webapp/app/components/command-bar/document-dialog.tsx new file mode 100644 index 0000000..b766f07 --- /dev/null +++ b/apps/webapp/app/components/command-bar/document-dialog.tsx @@ -0,0 +1,27 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; + +interface AddDocumentDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AddDocumentDialog({ + open, + onOpenChange, +}: AddDocumentDialogProps) { + return ( + + + + Add Document + + {/* TODO: Add document content here */} +
+

+ Document upload content goes here... +

+
+
+
+ ); +} diff --git a/apps/webapp/app/components/command-bar/memory-dialog.client.tsx b/apps/webapp/app/components/command-bar/memory-dialog.client.tsx new file mode 100644 index 0000000..91ec4bd --- /dev/null +++ b/apps/webapp/app/components/command-bar/memory-dialog.client.tsx @@ -0,0 +1,95 @@ +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; +import { useEditor, EditorContent } from "@tiptap/react"; +import { + extensionsForConversation, + getPlaceholder, +} from "../conversation/editor-extensions"; +import { Button } from "../ui/button"; +import { SpaceDropdown } from "../spaces/space-dropdown"; +import React from "react"; +import { useFetcher } from "@remix-run/react"; + +interface AddMemoryDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + defaultSpaceId?: string; +} + +export function AddMemoryDialog({ + open, + onOpenChange, + defaultSpaceId, +}: AddMemoryDialogProps) { + const [spaceIds, setSpaceIds] = React.useState( + defaultSpaceId ? [defaultSpaceId] : [], + ); + const fetcher = useFetcher(); + const editor = useEditor({ + extensions: [ + ...extensionsForConversation, + getPlaceholder("Write your memory here..."), + ], + editorProps: { + attributes: { + class: + "prose prose-sm focus:outline-none max-w-full min-h-[200px] p-4 py-0", + }, + }, + }); + + const handleAdd = async () => { + const content = editor?.getText(); + if (!content?.trim()) return; + + const payload = { + episodeBody: content, + referenceTime: new Date().toISOString(), + spaceIds: spaceIds, + source: "core", + }; + + fetcher.submit(payload, { + method: "POST", + action: "/api/v1/add", + encType: "application/json", + }); + + // Clear editor and close dialog + editor?.commands.clearContent(); + setSpaceIds([]); + onOpenChange(false); + }; + + return ( + + +
+ +
+
+
+ { + setSpaceIds(spaceIds); + }} + /> +
+
+ + +
+
+
+
+ ); +} diff --git a/apps/webapp/app/components/conversation/editor-extensions.tsx b/apps/webapp/app/components/conversation/editor-extensions.tsx index 2041de6..19493b8 100644 --- a/apps/webapp/app/components/conversation/editor-extensions.tsx +++ b/apps/webapp/app/components/conversation/editor-extensions.tsx @@ -9,6 +9,7 @@ import TableHeader from "@tiptap/extension-table-header"; import TableRow from "@tiptap/extension-table-row"; import { all, createLowlight } from "lowlight"; import { mergeAttributes, type Extension } from "@tiptap/react"; +import { Markdown } from "tiptap-markdown"; // create a lowlight instance with all languages loaded export const lowlight = createLowlight(all); @@ -136,4 +137,5 @@ export const extensionsForConversation = [ CodeBlockLowlight.configure({ lowlight, }), + Markdown, ]; diff --git a/apps/webapp/app/components/graph/graph-clustering-visualization.tsx b/apps/webapp/app/components/graph/graph-clustering-visualization.tsx index 5ef586a..8fb52df 100644 --- a/apps/webapp/app/components/graph/graph-clustering-visualization.tsx +++ b/apps/webapp/app/components/graph/graph-clustering-visualization.tsx @@ -83,10 +83,14 @@ export const GraphClusteringVisualization = forwardRef< filtered = filtered.filter((triplet) => { const sourceMatches = isEpisodeNode(triplet.sourceNode) && - triplet.sourceNode.attributes?.content?.toLowerCase().includes(query); + triplet.sourceNode.attributes?.content + ?.toLowerCase() + .includes(query); const targetMatches = isEpisodeNode(triplet.targetNode) && - triplet.targetNode.attributes?.content?.toLowerCase().includes(query); + triplet.targetNode.attributes?.content + ?.toLowerCase() + .includes(query); return sourceMatches || targetMatches; }); diff --git a/apps/webapp/app/components/logs/log-details.tsx b/apps/webapp/app/components/logs/log-details.tsx index ff50b57..bc78f40 100644 --- a/apps/webapp/app/components/logs/log-details.tsx +++ b/apps/webapp/app/components/logs/log-details.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, type ReactNode } from "react"; import { useFetcher } from "@remix-run/react"; -import { AlertCircle, Loader2 } from "lucide-react"; +import { AlertCircle, File, Loader2, MessageSquare } from "lucide-react"; import { Badge, BadgeColor } from "../ui/badge"; import { type LogItem } from "~/hooks/use-logs"; import Markdown from "react-markdown"; @@ -8,6 +8,7 @@ import { getIconForAuthorise } from "../icon-utils"; import { cn, formatString } from "~/lib/utils"; import { getStatusColor } from "./utils"; import { format } from "date-fns"; +import { SpaceDropdown } from "../spaces/space-dropdown"; interface LogDetailsProps { log: LogItem; @@ -33,13 +34,13 @@ function PropertyItem({ if (!value) return null; return ( -
- {label} +
+ {label} {variant === "status" ? ( @@ -49,7 +50,13 @@ function PropertyItem({ {value} ) : ( - + {icon} {value} @@ -73,10 +80,10 @@ interface EpisodeFactsResponse { function getStatusValue(status: string) { if (status === "PENDING") { - return "In Queue"; + return formatString("IN QUEUE"); } - return status; + return formatString(status); } export function LogDetails({ log }: LogDetailsProps) { @@ -113,6 +120,9 @@ export function LogDetails({ log }: LogDetailsProps) { } else if (log.episodeUUID) { setFactsLoading(true); fetcher.load(`/api/v1/episodes/${log.episodeUUID}/facts`); + } else { + setFacts([]); + setInvalidFacts([]); } }, [log.episodeUUID, log.data?.type, log.data?.episodes, facts.length]); @@ -129,41 +139,8 @@ export function LogDetails({ log }: LogDetailsProps) { return (
-
-
- Episode Details -
-
- -
+
- {log.data?.type === "DOCUMENT" && log.data?.episodes ? ( - - {log.data.episodes.map( - (episodeId: string, index: number) => ( - - {episodeId} - - ), - )} -
- } - variant="secondary" - /> - ) : ( - - )} + ) : ( + + ) + } variant="secondary" /> + + {/* Space Assignment for CONVERSATION type */} + {log.data.type.toLowerCase() === "conversation" && + log?.episodeUUID && ( +
+ + Spaces + + + +
+ )}
{/* Error Details */} {log.error && (
-
- Error Details -
@@ -212,21 +209,63 @@ export function LogDetails({ log }: LogDetailsProps) {
)} -
-
- Content -
- {/* Log Content */} -
-
- {log.ingestText} + {log.data?.type === "CONVERSATION" && ( +
+ {/* Log Content */} +
+
+ {log.ingestText} +
-
+ )} + + {/* Episodes List for DOCUMENT type */} + {log.data?.type === "DOCUMENT" && log.episodeDetails?.length > 0 && ( +
+
+ Episodes ({log.episodeDetails.length}) +
+
+ {log.episodeDetails.map((episode: any, index: number) => ( +
+
+
+ + Episode {index + 1} + + + {episode.uuid} + +
+
+ +
+
+ {/* Episode Content */} +
+
+ Content +
+
+ {episode.content} +
+
+
+ ))} +
+
+ )} {/* Episode Facts */}
-
+
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)}> + +
)} } />
+ + {showAddMemory && ( + + )}
); diff --git a/apps/webapp/app/routes/onboarding.tsx b/apps/webapp/app/routes/onboarding.tsx index 4219734..448cabe 100644 --- a/apps/webapp/app/routes/onboarding.tsx +++ b/apps/webapp/app/routes/onboarding.tsx @@ -153,6 +153,7 @@ export default function Onboarding() { setCurrentQuestion(currentQuestion + 1); } else { setLoading(true); + // Submit all answers submitAnswers(); } diff --git a/apps/webapp/app/routes/settings.billing.tsx b/apps/webapp/app/routes/settings.billing.tsx index 6c6ac2b..69c913b 100644 --- a/apps/webapp/app/routes/settings.billing.tsx +++ b/apps/webapp/app/routes/settings.billing.tsx @@ -262,6 +262,7 @@ export default function BillingSettings() {

{usageSummary.credits.percentageUsed}% used this period @@ -452,7 +453,7 @@ export default function BillingSettings() { -

+
{/* Free Plan */}
@@ -467,10 +468,10 @@ export default function BillingSettings() {
  • - Memory facts: 3k/mo + Credits: 3k/mo
  • - NO USAGE BASED + No usage based