diff --git a/apps/webapp/app/components/logs/logs-filters.tsx b/apps/webapp/app/components/logs/logs-filters.tsx new file mode 100644 index 0000000..0263a09 --- /dev/null +++ b/apps/webapp/app/components/logs/logs-filters.tsx @@ -0,0 +1,228 @@ +import { useState } from "react"; +import { Check, ChevronsUpDown, Filter, X } from "lucide-react"; +import { Button } from "~/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "~/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "~/components/ui/popover"; +import { Badge } from "~/components/ui/badge"; +import { cn } from "~/lib/utils"; + +interface LogsFiltersProps { + availableSources: Array<{ name: string; slug: string }>; + selectedSource?: string; + selectedStatus?: string; + onSourceChange: (source?: string) => void; + onStatusChange: (status?: string) => void; +} + +const statusOptions = [ + { value: "PENDING", label: "Pending" }, + { value: "PROCESSING", label: "Processing" }, + { value: "COMPLETED", label: "Completed" }, + { value: "FAILED", label: "Failed" }, + { value: "CANCELLED", label: "Cancelled" }, +]; + +export function LogsFilters({ + availableSources, + selectedSource, + selectedStatus, + onSourceChange, + onStatusChange, +}: LogsFiltersProps) { + const [sourceOpen, setSourceOpen] = useState(false); + const [statusOpen, setStatusOpen] = useState(false); + + const selectedSourceName = availableSources.find( + (s) => s.slug === selectedSource, + )?.name; + const selectedStatusLabel = statusOptions.find( + (s) => s.value === selectedStatus, + )?.label; + + const clearFilters = () => { + onSourceChange(undefined); + onStatusChange(undefined); + }; + + const hasFilters = selectedSource || selectedStatus; + + return ( +
+
+ + Filters: +
+ + {/* Source Filter */} + + + + + + + + + No sources found. + + { + onSourceChange(undefined); + setSourceOpen(false); + }} + > + + All sources + + {availableSources.map((source) => ( + { + onSourceChange( + source.slug === selectedSource + ? undefined + : source.slug, + ); + setSourceOpen(false); + }} + > + + {source.name} + + ))} + + + + + + + {/* Status Filter */} + + + + + + + + + No status found. + + { + onStatusChange(undefined); + setStatusOpen(false); + }} + > + + All statuses + + {statusOptions.map((status) => ( + { + onStatusChange( + status.value === selectedStatus + ? undefined + : status.value, + ); + setStatusOpen(false); + }} + > + + {status.label} + + ))} + + + + + + + {/* Active Filters */} + {hasFilters && ( +
+ {selectedSource && ( + + {selectedSourceName} + onSourceChange(undefined)} + /> + + )} + {selectedStatus && ( + + {selectedStatusLabel} + onStatusChange(undefined)} + /> + + )} + +
+ )} +
+ ); +} diff --git a/apps/webapp/app/components/logs/virtual-logs-list.tsx b/apps/webapp/app/components/logs/virtual-logs-list.tsx new file mode 100644 index 0000000..2878281 --- /dev/null +++ b/apps/webapp/app/components/logs/virtual-logs-list.tsx @@ -0,0 +1,198 @@ +import { useEffect, useRef, useState } from "react"; +import { List, InfiniteLoader, WindowScroller } from "react-virtualized"; +import { LogItem } from "~/hooks/use-logs"; +import { Badge } from "~/components/ui/badge"; +import { Card, CardContent } from "~/components/ui/card"; +import { AlertCircle, CheckCircle, Clock, XCircle } from "lucide-react"; +import { cn } from "~/lib/utils"; + +interface VirtualLogsListProps { + logs: LogItem[]; + hasMore: boolean; + loadMore: () => void; + isLoading: boolean; + height?: number; +} + +const ITEM_HEIGHT = 120; + +interface LogItemRendererProps { + index: number; + key: string; + style: React.CSSProperties; +} + +function LogItemRenderer(props: LogItemRendererProps, logs: LogItem[]) { + const { index, key, style } = props; + const log = logs[index]; + + if (!log) { + return ( +
+
+
+ ); + } + + const getStatusIcon = (status: string) => { + switch (status) { + case "PROCESSING": + return ; + case "PENDING": + return ; + case "COMPLETED": + return ; + case "FAILED": + return ; + case "CANCELLED": + return ; + default: + return ; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "PROCESSING": + return "bg-blue-100 text-blue-800"; + case "PENDING": + return "bg-yellow-100 text-yellow-800"; + case "COMPLETED": + return "bg-green-100 text-green-800"; + case "FAILED": + return "bg-red-100 text-red-800"; + case "CANCELLED": + return "bg-gray-100 text-gray-800"; + default: + return "bg-gray-100 text-gray-800"; + } + }; + + return ( +
+ + +
+
+ + {log.source} + +
+ {getStatusIcon(log.status)} + + {log.status.toLowerCase()} + +
+
+
+ {new Date(log.time).toLocaleString()} +
+
+ +
+

+ {log.ingestText} +

+
+ +
+
+ {log.sourceURL && ( + + Source URL + + )} + {log.processedAt && ( + + Processed: {new Date(log.processedAt).toLocaleString()} + + )} +
+ + {log.error && ( +
+ + + {log.error} + +
+ )} +
+
+
+
+ ); +} + +export function VirtualLogsList({ + logs, + hasMore, + loadMore, + isLoading, + height = 600, +}: VirtualLogsListProps) { + const [containerHeight, setContainerHeight] = useState(height); + + useEffect(() => { + const updateHeight = () => { + const availableHeight = window.innerHeight - 300; // Account for header, filters, etc. + setContainerHeight(Math.min(availableHeight, height)); + }; + + updateHeight(); + window.addEventListener("resize", updateHeight); + return () => window.removeEventListener("resize", updateHeight); + }, [height]); + + const isRowLoaded = ({ index }: { index: number }) => { + return !!logs[index]; + }; + + const loadMoreRows = async () => { + if (hasMore) { + return loadMore(); + } + + return false; + }; + + const rowRenderer = (props: LogItemRendererProps) => { + return LogItemRenderer(props, logs); + }; + + const itemCount = hasMore ? logs.length + 1 : logs.length; + + return ( +
+ + {({ onRowsRendered, registerChild }) => ( + + )} + + + {isLoading && ( +
+ Loading more logs... +
+ )} +
+ ); +} diff --git a/apps/webapp/app/components/sidebar/app-sidebar.tsx b/apps/webapp/app/components/sidebar/app-sidebar.tsx index 62400b9..426ae5e 100644 --- a/apps/webapp/app/components/sidebar/app-sidebar.tsx +++ b/apps/webapp/app/components/sidebar/app-sidebar.tsx @@ -28,8 +28,8 @@ const data = { icon: Network, }, { - title: "Activity", - url: "/home/activity", + title: "Logs", + url: "/home/logs/all", icon: Activity, }, { diff --git a/apps/webapp/app/components/ui/command.tsx b/apps/webapp/app/components/ui/command.tsx new file mode 100644 index 0000000..307f965 --- /dev/null +++ b/apps/webapp/app/components/ui/command.tsx @@ -0,0 +1,179 @@ +import { type DialogProps } from "@radix-ui/react-dialog"; +import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { Command as CommandPrimitive } from "cmdk"; +import React from "react"; + +import { Dialog, DialogContent } from "./dialog"; +import { cn } from "../../lib/utils"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps { + commandProps?: React.ComponentPropsWithoutRef; +} + +interface CommandInputProps + extends React.ComponentPropsWithoutRef { + icon?: boolean; + containerClassName?: string; +} + +const CommandDialog = ({ + children, + commandProps, + ...props +}: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + CommandInputProps +>(({ className, icon, containerClassName, ...props }, ref) => ( +
+ {icon && ( + + )} + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/apps/webapp/app/components/ui/header.tsx b/apps/webapp/app/components/ui/header.tsx index a45252a..b991f86 100644 --- a/apps/webapp/app/components/ui/header.tsx +++ b/apps/webapp/app/components/ui/header.tsx @@ -7,7 +7,7 @@ const PAGE_TITLES: Record = { "/home/dashboard": "Memory graph", "/home/conversation": "Conversation", "/home/integrations": "Integrations", - "/home/activity": "Activity", + "/home/logs": "Logs", }; function getHeaderTitle(pathname: string): string { @@ -26,12 +26,17 @@ function isConversationDetail(pathname: string): boolean { return /^\/home\/conversation\/[^/]+$/.test(pathname); } +function isIntegrationsPage(pathname: string): boolean { + return pathname === "/home/integrations"; +} + export function SiteHeader() { const location = useLocation(); const navigate = useNavigate(); const title = getHeaderTitle(location.pathname); const showNewConversationButton = isConversationDetail(location.pathname); + const showRequestIntegrationButton = isIntegrationsPage(location.pathname); return (
@@ -51,6 +56,16 @@ export function SiteHeader() { New conversation )} + {showRequestIntegrationButton && ( + + )}
diff --git a/apps/webapp/app/components/ui/sidebar.tsx b/apps/webapp/app/components/ui/sidebar.tsx index b2df014..6cbd3c0 100644 --- a/apps/webapp/app/components/ui/sidebar.tsx +++ b/apps/webapp/app/components/ui/sidebar.tsx @@ -263,7 +263,8 @@ function SidebarTrigger({ data-sidebar="trigger" data-slot="sidebar-trigger" variant="ghost" - className={cn("size-8", className)} + size="xs" + className={cn("size-6 rounded-md", className)} onClick={(event) => { onClick?.(event); toggleSidebar(); diff --git a/apps/webapp/app/hooks/use-ingestion-status.tsx b/apps/webapp/app/hooks/use-ingestion-status.tsx index 597a6ab..a55c6d9 100644 --- a/apps/webapp/app/hooks/use-ingestion-status.tsx +++ b/apps/webapp/app/hooks/use-ingestion-status.tsx @@ -36,7 +36,7 @@ export function useIngestionStatus() { clearInterval(interval); setIsPolling(false); }; - }, [fetcher]); + }, []); // Remove fetcher from dependencies to prevent infinite loop return { data: fetcher.data, diff --git a/apps/webapp/app/hooks/use-logs.tsx b/apps/webapp/app/hooks/use-logs.tsx new file mode 100644 index 0000000..fb934b0 --- /dev/null +++ b/apps/webapp/app/hooks/use-logs.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState, useCallback } from "react"; +import { useFetcher } from "@remix-run/react"; + +export interface LogItem { + id: string; + source: string; + ingestText: string; + time: string; + processedAt?: string; + status: "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED" | "CANCELLED"; + error?: string; + sourceURL?: string; + integrationSlug?: string; + activityId?: string; +} + +export interface LogsResponse { + logs: LogItem[]; + totalCount: number; + page: number; + limit: number; + hasMore: boolean; + availableSources: Array<{ name: string; slug: string }>; +} + +export interface UseLogsOptions { + endpoint: string; // '/api/v1/logs/all' or '/api/v1/logs/activity' + source?: string; + status?: string; +} + +export function useLogs({ endpoint, source, status }: UseLogsOptions) { + const fetcher = useFetcher(); + const [logs, setLogs] = useState([]); + const [page, setPage] = useState(1); + const [hasMore, setHasMore] = useState(true); + const [availableSources, setAvailableSources] = useState>([]); + const [isInitialLoad, setIsInitialLoad] = useState(true); + + const buildUrl = useCallback((pageNum: number) => { + const params = new URLSearchParams(); + params.set('page', pageNum.toString()); + params.set('limit', '20'); + if (source) params.set('source', source); + if (status) params.set('status', status); + return `${endpoint}?${params.toString()}`; + }, [endpoint, source, status]); + + const loadMore = useCallback(() => { + if (fetcher.state === 'idle' && hasMore) { + fetcher.load(buildUrl(page + 1)); + } + }, [hasMore, page, buildUrl]); + + const reset = useCallback(() => { + setLogs([]); + setPage(1); + setHasMore(true); + setIsInitialLoad(true); + fetcher.load(buildUrl(1)); + }, [buildUrl]); + + // Effect to handle fetcher data + useEffect(() => { + if (fetcher.data) { + const { logs: newLogs, hasMore: newHasMore, page: currentPage, availableSources: sources } = fetcher.data; + + if (currentPage === 1) { + // First page or reset + setLogs(newLogs); + setIsInitialLoad(false); + } else { + // Append to existing logs + setLogs(prev => [...prev, ...newLogs]); + } + + setHasMore(newHasMore); + setPage(currentPage); + setAvailableSources(sources); + } + }, [fetcher.data]); + + // Effect to reset when filters change + useEffect(() => { + setLogs([]); + setPage(1); + setHasMore(true); + setIsInitialLoad(true); + fetcher.load(buildUrl(1)); + }, [source, status, buildUrl]); // Inline reset logic to avoid dependency issues + + // Initial load + useEffect(() => { + if (isInitialLoad) { + fetcher.load(buildUrl(1)); + } + }, [isInitialLoad, buildUrl]); + + return { + logs, + hasMore, + loadMore, + reset, + availableSources, + isLoading: fetcher.state === 'loading', + isInitialLoad, + }; +} diff --git a/apps/webapp/app/lib/ingest.server.ts b/apps/webapp/app/lib/ingest.server.ts index 3c97ddb..03f130c 100644 --- a/apps/webapp/app/lib/ingest.server.ts +++ b/apps/webapp/app/lib/ingest.server.ts @@ -16,6 +16,7 @@ export const IngestBodyRequest = z.object({ export const addToQueue = async ( body: z.infer, userId: string, + activityId?: string, ) => { const user = await prisma.user.findFirst({ where: { @@ -39,6 +40,7 @@ export const addToQueue = async ( status: IngestionStatus.PENDING, priority: 1, workspaceId: user.Workspace.id, + activityId, }, }); diff --git a/apps/webapp/app/routes/api.v1.activity.tsx b/apps/webapp/app/routes/api.v1.activity.tsx index 18e5357..7cadb22 100644 --- a/apps/webapp/app/routes/api.v1.activity.tsx +++ b/apps/webapp/app/routes/api.v1.activity.tsx @@ -40,7 +40,6 @@ const { action, loader } = createActionApiRoute( throw new Error("User not found"); } - // Create the activity record const activity = await prisma.activity.create({ data: { @@ -64,7 +63,11 @@ const { action, loader } = createActionApiRoute( }, }; - const queueResponse = await addToQueue(ingestData, authentication.userId); + const queueResponse = await addToQueue( + ingestData, + authentication.userId, + activity.id, + ); logger.log("Activity created and queued for ingestion", { activityId: activity.id, @@ -90,4 +93,4 @@ const { action, loader } = createActionApiRoute( }, ); -export { action, loader }; \ No newline at end of file +export { action, loader }; diff --git a/apps/webapp/app/routes/api.v1.logs.activity.tsx b/apps/webapp/app/routes/api.v1.logs.activity.tsx new file mode 100644 index 0000000..79cca5f --- /dev/null +++ b/apps/webapp/app/routes/api.v1.logs.activity.tsx @@ -0,0 +1,130 @@ +import { LoaderFunctionArgs, json } from "@remix-run/node"; +import { prisma } from "~/db.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 = parseInt(url.searchParams.get("page") || "1"); + const limit = parseInt(url.searchParams.get("limit") || "20"); + const source = url.searchParams.get("source"); + const status = url.searchParams.get("status"); + const skip = (page - 1) * limit; + + const user = await prisma.user.findUnique({ + where: { id: userId }, + include: { Workspace: true }, + }); + + if (!user?.Workspace) { + throw new Response("Workspace not found", { status: 404 }); + } + + // Build where clause for filtering - only items with activityId + const whereClause: any = { + workspaceId: user.Workspace.id, + activityId: { + not: null, + }, + }; + + if (status) { + whereClause.status = status; + } + + // If source filter is provided, we need to filter by integration source + if (source) { + whereClause.activity = { + integrationAccount: { + integrationDefinition: { + slug: source, + }, + }, + }; + } + + const [logs, totalCount] = await Promise.all([ + prisma.ingestionQueue.findMany({ + where: whereClause, + include: { + activity: { + include: { + integrationAccount: { + include: { + integrationDefinition: { + select: { + name: true, + slug: true, + }, + }, + }, + }, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take: limit, + }), + prisma.ingestionQueue.count({ + where: whereClause, + }), + ]); + + // Get available sources for filtering (only those with activities) + const availableSources = await prisma.integrationDefinitionV2.findMany({ + where: { + IntegrationAccount: { + some: { + workspaceId: user.Workspace.id, + Activity: { + some: { + IngestionQueue: { + some: { + activityId: { + not: null, + }, + }, + }, + }, + }, + }, + }, + }, + select: { + name: true, + slug: true, + }, + }); + + // Format the response + const formattedLogs = logs.map((log) => ({ + id: log.id, + source: log.activity?.integrationAccount?.integrationDefinition?.name || + (log.data as any)?.source || + 'Unknown', + ingestText: log.activity?.text || + (log.data as any)?.episodeBody || + (log.data as any)?.text || + 'No content', + time: log.createdAt, + processedAt: log.processedAt, + status: log.status, + error: log.error, + sourceURL: log.activity?.sourceURL, + integrationSlug: log.activity?.integrationAccount?.integrationDefinition?.slug, + activityId: log.activityId, + })); + + return json({ + logs: formattedLogs, + totalCount, + page, + limit, + hasMore: skip + logs.length < totalCount, + availableSources, + }); +} diff --git a/apps/webapp/app/routes/api.v1.logs.all.tsx b/apps/webapp/app/routes/api.v1.logs.all.tsx new file mode 100644 index 0000000..71aeb89 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.logs.all.tsx @@ -0,0 +1,137 @@ +import { type LoaderFunctionArgs, json } from "@remix-run/node"; +import { prisma } from "~/db.server"; +import { requireUserId } from "~/services/session.server"; + +/** + * Optimizations: + * - Use `findMany` with `select` instead of `include` to fetch only required fields. + * - Use `count` with the same where clause, but only after fetching logs (to avoid unnecessary count if no logs). + * - Use a single query for availableSources with minimal fields. + * - Avoid unnecessary object spreading and type casting. + * - Minimize nested object traversal in mapping. + */ +export async function loader({ request }: LoaderFunctionArgs) { + const userId = await requireUserId(request); + const url = new URL(request.url); + + const page = parseInt(url.searchParams.get("page") || "1"); + const limit = parseInt(url.searchParams.get("limit") || "20"); + const source = url.searchParams.get("source"); + const status = url.searchParams.get("status"); + const skip = (page - 1) * limit; + + // Get user and workspace in one query + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { Workspace: { select: { id: true } } }, + }); + + if (!user?.Workspace) { + throw new Response("Workspace not found", { status: 404 }); + } + + // Build where clause for filtering + const whereClause: any = { + workspaceId: user.Workspace.id, + }; + + if (status) { + whereClause.status = status; + } + + // If source filter is provided, filter by integration source + if (source) { + whereClause.activity = { + integrationAccount: { + integrationDefinition: { + slug: source, + }, + }, + }; + } + + // Use select to fetch only required fields for logs + const [logs, totalCount, availableSources] = await Promise.all([ + prisma.ingestionQueue.findMany({ + where: whereClause, + select: { + id: true, + createdAt: true, + processedAt: true, + status: true, + error: true, + data: true, + activity: { + select: { + text: true, + sourceURL: true, + integrationAccount: { + select: { + integrationDefinition: { + select: { + name: true, + slug: true, + }, + }, + }, + }, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + skip, + take: limit, + }), + + prisma.ingestionQueue.count({ + where: whereClause, + }), + + prisma.integrationDefinitionV2.findMany({ + where: { + IntegrationAccount: { + some: { + workspaceId: user.Workspace.id, + }, + }, + }, + select: { + name: true, + slug: true, + }, + }), + ]); + + // Format the response + const formattedLogs = logs.map((log) => { + const integrationDef = + log.activity?.integrationAccount?.integrationDefinition; + const logData = log.data as any; + return { + id: log.id, + source: integrationDef?.name || logData?.source || "Unknown", + ingestText: + log.activity?.text || + logData?.episodeBody || + logData?.text || + "No content", + time: log.createdAt, + processedAt: log.processedAt, + status: log.status, + error: log.error, + sourceURL: log.activity?.sourceURL, + integrationSlug: integrationDef?.slug, + }; + }); + + return json({ + logs: formattedLogs, + totalCount, + page, + limit, + hasMore: skip + logs.length < totalCount, + availableSources, + }); +} diff --git a/apps/webapp/app/routes/home.integrations.tsx b/apps/webapp/app/routes/home.integrations.tsx index f509e8a..335b098 100644 --- a/apps/webapp/app/routes/home.integrations.tsx +++ b/apps/webapp/app/routes/home.integrations.tsx @@ -6,19 +6,33 @@ import { requireUserId, requireWorkpace } from "~/services/session.server"; import { getIntegrationDefinitions } from "~/services/integrationDefinition.server"; import { getIntegrationAccounts } from "~/services/integrationAccount.server"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card"; import { Button } from "~/components/ui/button"; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "~/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "~/components/ui/dialog"; import { Input } from "~/components/ui/input"; import { FormButtons } from "~/components/ui/FormButtons"; import { Plus, Search } from "lucide-react"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select"; // Loader to fetch integration definitions and existing accounts export async function loader({ request }: LoaderFunctionArgs) { const userId = await requireUserId(request); const workspace = await requireWorkpace(request); - + const [integrationDefinitions, integrationAccounts] = await Promise.all([ getIntegrationDefinitions(workspace.id), getIntegrationAccounts(userId), @@ -32,46 +46,26 @@ export async function loader({ request }: LoaderFunctionArgs) { } export default function Integrations() { - const { integrationDefinitions, integrationAccounts, userId } = useLoaderData(); - const [selectedCategory, setSelectedCategory] = useState("all"); + const { integrationDefinitions, integrationAccounts, userId } = + useLoaderData(); const [selectedIntegration, setSelectedIntegration] = useState(null); const [apiKey, setApiKey] = useState(""); const [isLoading, setIsLoading] = useState(false); const [isConnecting, setIsConnecting] = useState(false); - // Extract categories from integration definitions - const categories = Array.from( - new Set(integrationDefinitions.map((integration) => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; - return specData?.category || "Uncategorized"; - })) - ); - - // Filter integrations by selected category - const filteredIntegrations = selectedCategory === "all" - ? integrationDefinitions - : integrationDefinitions.filter( - (integration) => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; - return specData?.category === selectedCategory; - } - ); - // Check if user has an active account for an integration const hasActiveAccount = (integrationDefinitionId: string) => { return integrationAccounts.some( - (account) => account.integrationDefinitionId === integrationDefinitionId && account.isActive + (account) => + account.integrationDefinitionId === integrationDefinitionId && + account.isActive, ); }; // Handle connection with API key const handleApiKeyConnect = async () => { if (!selectedIntegration || !apiKey.trim()) return; - + setIsLoading(true); try { const response = await fetch("/api/v1/integration_account", { @@ -84,12 +78,12 @@ export default function Integrations() { apiKey, }), }); - + if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to connect integration"); } - + // Refresh the page to show the new integration account window.location.reload(); } catch (error) { @@ -103,7 +97,7 @@ export default function Integrations() { // Handle OAuth connection const handleOAuthConnect = async () => { if (!selectedIntegration) return; - + setIsConnecting(true); try { const response = await fetch("/api/v1/oauth", { @@ -116,12 +110,12 @@ export default function Integrations() { userId, }), }); - + if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Failed to start OAuth flow"); } - + const { url } = await response.json(); // Redirect to OAuth authorization URL window.location.href = url; @@ -134,117 +128,93 @@ export default function Integrations() { }; return ( -
-
-
-

Integrations

-

Connect your tools and services

-
-
- {/* Category filter */} - - - {/* Add integration button */} - -
+
+
+

Connect your tools and services

- + {/* Integration cards grid */}
- {filteredIntegrations.map((integration) => { + {integrationDefinitions.map((integration) => { const isConnected = hasActiveAccount(integration.id); - const authType = integration.spec?.auth?.type || "unknown"; - + return ( - { - if (open) { - setSelectedIntegration(integration); - setApiKey(""); - } else { - setSelectedIntegration(null); - } - }}> + { + if (open) { + setSelectedIntegration(integration); + setApiKey(""); + } else { + setSelectedIntegration(null); + } + }} + > -
+
{integration.icon ? ( - {integration.name} ) : (
)}
- {integration.name} + + {integration.name} + - {integration.description || "Connect to " + integration.name} + {integration.description || + "Connect to " + integration.name} -
- - {(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; - return specData?.category || "Uncategorized"; - })()} - +
{isConnected ? ( Connected ) : ( - Not connected + + Not connected + )}
- + Connect to {integration.name} - {integration.description || `Connect your ${integration.name} account to enable integration.`} + {integration.description || + `Connect your ${integration.name} account to enable integration.`} - + {/* API Key Authentication */} {(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; + const specData = + typeof integration.spec === "string" + ? JSON.parse(integration.spec) + : integration.spec; return specData?.auth?.api_key; })() && (
setApiKey(e.target.value)} /> {(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; + const specData = + typeof integration.spec === "string" + ? JSON.parse(integration.spec) + : integration.spec; return specData?.auth?.api_key?.description; })() && ( -

+

{(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; + const specData = + typeof integration.spec === "string" + ? JSON.parse(integration.spec) + : integration.spec; return specData?.auth?.api_key?.description; })()}

)}
- - - - + + + {isLoading ? "Connecting..." : "Connect"} + + } + >
)} - + {/* OAuth Authentication */} {(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; + const specData = + typeof integration.spec === "string" + ? JSON.parse(integration.spec) + : integration.spec; return specData?.auth?.oauth2; })() && (
@@ -298,26 +273,30 @@ export default function Integrations() { disabled={isConnecting} onClick={handleOAuthConnect} > - {isConnecting ? "Connecting..." : `Connect to ${integration.name}`} + {isConnecting + ? "Connecting..." + : `Connect to ${integration.name}`}
)} - + {/* No authentication method found */} {(() => { - const specData = typeof integration.spec === 'string' - ? JSON.parse(integration.spec) - : integration.spec; + const specData = + typeof integration.spec === "string" + ? JSON.parse(integration.spec) + : integration.spec; return !specData?.auth?.api_key && !specData?.auth?.oauth2; })() && ( -
+
This integration doesn't specify an authentication method.
)} - + -
- By connecting, you agree to the {integration.name} terms of service. +
+ By connecting, you agree to the {integration.name} terms of + service.
@@ -325,19 +304,14 @@ export default function Integrations() { ); })}
- + {/* Empty state */} - {filteredIntegrations.length === 0 && ( + {integrationDefinitions.length === 0 && (
- +

No integrations found

-

- {selectedCategory === "all" - ? "No integrations are available at this time." - : `No integrations found in the "${selectedCategory}" category.`} -

)}
); -} \ No newline at end of file +} diff --git a/apps/webapp/app/routes/home.logs.activity.tsx b/apps/webapp/app/routes/home.logs.activity.tsx new file mode 100644 index 0000000..fc7c7ca --- /dev/null +++ b/apps/webapp/app/routes/home.logs.activity.tsx @@ -0,0 +1,116 @@ +import { useState } from "react"; +import { useLogs } from "~/hooks/use-logs"; +import { LogsFilters } from "~/components/logs/logs-filters"; +import { VirtualLogsList } from "~/components/logs/virtual-logs-list"; +import { AppContainer, PageContainer, PageBody } from "~/components/layout/app-layout"; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Badge } from "~/components/ui/badge"; +import { Activity } from "lucide-react"; + +export default function LogsActivity() { + const [selectedSource, setSelectedSource] = useState(); + const [selectedStatus, setSelectedStatus] = useState(); + + const { + logs, + hasMore, + loadMore, + availableSources, + isLoading, + isInitialLoad + } = useLogs({ + endpoint: '/api/v1/logs/activity', + source: selectedSource, + status: selectedStatus + }); + + if (isInitialLoad) { + return ( + + +
+
+
+
+
+ ); + } + + return ( + + + +
+ {/* Header */} +
+
+ +
+

Activity Ingestion Logs

+

+ View ingestion logs for activities from connected integrations +

+
+
+ + {logs.length} activity logs loaded + +
+ + {/* Filters */} + + + Filters + + + + + + + {/* Logs List */} +
+
+

Activity Ingestion Queue

+ {hasMore && ( + + Scroll to load more... + + )} +
+ + {logs.length === 0 ? ( + + +
+ +

No activity logs found

+

+ {selectedSource || selectedStatus + ? 'Try adjusting your filters to see more results.' + : 'No activity ingestion logs are available yet.'} +

+
+
+
+ ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/apps/webapp/app/routes/home.logs.all.tsx b/apps/webapp/app/routes/home.logs.all.tsx new file mode 100644 index 0000000..6d872e1 --- /dev/null +++ b/apps/webapp/app/routes/home.logs.all.tsx @@ -0,0 +1,102 @@ +import { useState } from "react"; +import { useLogs } from "~/hooks/use-logs"; +import { LogsFilters } from "~/components/logs/logs-filters"; +import { VirtualLogsList } from "~/components/logs/virtual-logs-list"; +import { + AppContainer, + PageContainer, + PageBody, +} from "~/components/layout/app-layout"; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { Badge } from "~/components/ui/badge"; +import { Database } from "lucide-react"; + +export default function LogsAll() { + const [selectedSource, setSelectedSource] = useState(); + const [selectedStatus, setSelectedStatus] = useState(); + + const { + logs, + hasMore, + loadMore, + availableSources, + isLoading, + isInitialLoad, + } = useLogs({ + endpoint: "/api/v1/logs/all", + source: selectedSource, + status: selectedStatus, + }); + + if (isInitialLoad) { + return ( + + +
+
+
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+

+ View all ingestion queue items and their processing status +

+
+
+
+ + {/* Filters */} + + + {/* Logs List */} +
+
+

Ingestion Queue

+ {hasMore && ( + + Scroll to load more... + + )} +
+ + {logs.length === 0 ? ( + + +
+ +

No logs found

+

+ {selectedSource || selectedStatus + ? "Try adjusting your filters to see more results." + : "No ingestion logs are available yet."} +

+
+
+
+ ) : ( + + )} +
+
+ ); +} diff --git a/apps/webapp/package.json b/apps/webapp/package.json index dbb1363..7c08e8e 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -80,6 +80,7 @@ "class-validator": "0.14.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^0.2.1", "compression": "^1.7.4", "cross-env": "^7.0.3", "d3": "^7.9.0", diff --git a/packages/database/prisma/migrations/20250715055759_add_activity_to_ingestion/migration.sql b/packages/database/prisma/migrations/20250715055759_add_activity_to_ingestion/migration.sql new file mode 100644 index 0000000..a514030 --- /dev/null +++ b/packages/database/prisma/migrations/20250715055759_add_activity_to_ingestion/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "IngestionQueue" ADD COLUMN "activityId" TEXT; + +-- AddForeignKey +ALTER TABLE "IngestionQueue" ADD CONSTRAINT "IngestionQueue_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 996ed8c..621128e 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -31,6 +31,7 @@ model Activity { WebhookDeliveryLog WebhookDeliveryLog[] ConversationHistory ConversationHistory[] + IngestionQueue IngestionQueue[] } model AuthorizationCode { @@ -136,6 +137,9 @@ model IngestionQueue { workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id]) + activity Activity? @relation(fields: [activityId], references: [id]) + activityId String? + // Error handling error String? retryCount Int @default(0) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ebb9ff..cc383f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^0.2.1 + version: 0.2.1(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) compression: specifier: ^1.7.4 version: 1.8.0 @@ -378,7 +381,7 @@ importers: devDependencies: '@remix-run/dev': specifier: 2.16.7 - version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0) + version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0) '@remix-run/eslint-config': specifier: 2.16.7 version: 2.16.7(eslint@8.57.1)(react@18.3.1)(typescript@5.8.3) @@ -393,7 +396,7 @@ importers: version: 0.5.16(tailwindcss@4.1.7) '@tailwindcss/vite': specifier: ^4.1.7 - version: 4.1.9(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)) + version: 4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)) '@trigger.dev/build': specifier: ^4.0.0-v4-beta.22 version: 4.0.0-v4-beta.22(typescript@5.8.3) @@ -489,10 +492,10 @@ importers: version: 5.8.3 vite: specifier: ^6.0.0 - version: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + version: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) vite-tsconfig-paths: specifier: ^4.2.1 - version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)) + version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)) packages/database: dependencies: @@ -547,6 +550,40 @@ importers: specifier: 18.2.69 version: 18.2.69 + packages/mcp: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 1.0.1 + version: 1.0.1 + '@types/node': + specifier: ^22.14.1 + version: 22.16.0 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + axios: + specifier: ^1.8.4 + version: 1.10.0 + dotenv: + specifier: ^16.5.0 + version: 16.5.0 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + zod: + specifier: ^3.24.3 + version: 3.25.76 + zod-to-json-schema: + specifier: ^3.24.5 + version: 3.24.5(zod@3.25.76) + devDependencies: + shx: + specifier: ^0.3.4 + version: 0.3.4 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + packages/mcp-proxy: dependencies: '@modelcontextprotocol/sdk': @@ -1790,6 +1827,9 @@ packages: '@mjackson/headers@0.9.0': resolution: {integrity: sha512-1WFCu2iRaqbez9hcYYI611vcH1V25R+fDfOge/CyKc8sdbzniGfy/FRhNd3DgvFF4ZEEX2ayBrvFHLtOpfvadw==} + '@modelcontextprotocol/sdk@1.0.1': + resolution: {integrity: sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==} + '@modelcontextprotocol/sdk@1.13.2': resolution: {integrity: sha512-Vx7qOcmoKkR3qhaQ9qf3GxiVKCEu+zfJddHv6x3dY/9P6+uIwJnmuAur5aB+4FDXf41rRrDnOEGkviX5oYZ67w==} engines: {node: '>=18'} @@ -2139,6 +2179,9 @@ packages: '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@radix-ui/primitive@1.0.0': + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} + '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} @@ -2275,6 +2318,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-compose-refs@1.0.0': + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-compose-refs@1.0.1': resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -2302,6 +2350,11 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.0.0': + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -2320,6 +2373,12 @@ packages: '@types/react': optional: true + '@radix-ui/react-dialog@1.0.0': + resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-dialog@1.1.14': resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} peerDependencies: @@ -2351,6 +2410,12 @@ packages: '@types/react': optional: true + '@radix-ui/react-dismissable-layer@1.0.0': + resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-dismissable-layer@1.1.0': resolution: {integrity: sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==} peerDependencies: @@ -2390,6 +2455,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-guards@1.0.0': + resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-focus-guards@1.1.0': resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} peerDependencies: @@ -2408,6 +2478,12 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-scope@1.0.0': + resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-focus-scope@1.1.0': resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} peerDependencies: @@ -2439,6 +2515,11 @@ packages: peerDependencies: react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.0.0': + resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-id@1.1.0': resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} peerDependencies: @@ -2535,6 +2616,12 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.0.0': + resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-portal@1.1.1': resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} peerDependencies: @@ -2561,6 +2648,12 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.0.0': + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-presence@1.1.0': resolution: {integrity: sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==} peerDependencies: @@ -2587,6 +2680,12 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@1.0.0': + resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -2678,6 +2777,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slot@1.0.0': + resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-slot@1.0.2': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -2796,6 +2900,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-use-callback-ref@1.0.0': + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -2814,6 +2923,11 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.0.0': + resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: @@ -2841,6 +2955,11 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-escape-keydown@1.0.0': + resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-use-escape-keydown@1.1.0': resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} peerDependencies: @@ -2868,6 +2987,11 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-layout-effect@1.0.0': + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + '@radix-ui/react-use-layout-effect@1.1.0': resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} peerDependencies: @@ -4310,6 +4434,9 @@ packages: '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/uuid@9.0.8': resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} @@ -5077,6 +5204,12 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + cmdk@0.2.1: + resolution: {integrity: sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + cmdk@1.1.1: resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: @@ -6576,6 +6709,10 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + ioredis@5.6.1: resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==} engines: {node: '>=12.22.0'} @@ -8544,6 +8681,16 @@ packages: '@types/react': optional: true + react-remove-scroll@2.5.4: + resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-remove-scroll@2.5.7: resolution: {integrity: sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==} engines: {node: '>=10'} @@ -8650,6 +8797,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -8968,9 +9119,19 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + shx@0.3.4: + resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==} + engines: {node: '>=6'} + hasBin: true + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -9753,6 +9914,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -10089,6 +10254,9 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@4.5.7: resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} engines: {node: '>=12.7.0'} @@ -11470,6 +11638,12 @@ snapshots: '@mjackson/headers@0.9.0': {} + '@modelcontextprotocol/sdk@1.0.1': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.0 + zod: 3.25.76 + '@modelcontextprotocol/sdk@1.13.2': dependencies: ajv: 6.12.6 @@ -11832,6 +12006,10 @@ snapshots: '@radix-ui/number@1.1.1': {} + '@radix-ui/primitive@1.0.0': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/primitive@1.1.0': {} '@radix-ui/primitive@1.1.2': {} @@ -11970,6 +12148,11 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.69)(react@18.3.1)': dependencies: '@babel/runtime': 7.27.6 @@ -11989,6 +12172,11 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-context@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + '@radix-ui/react-context@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: react: 18.3.1 @@ -12001,6 +12189,28 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-dialog@1.0.0(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.0.0(react@18.3.1) + '@radix-ui/react-focus-scope': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.0(react@18.3.1) + '@radix-ui/react-portal': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.0(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.5.4(@types/react@18.2.69)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + '@radix-ui/react-dialog@1.1.14(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -12035,6 +12245,17 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-dismissable-layer@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-dismissable-layer@1.1.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -12076,6 +12297,11 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-focus-guards@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + '@radix-ui/react-focus-guards@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: react: 18.3.1 @@ -12088,6 +12314,15 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-focus-scope@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12114,6 +12349,12 @@ snapshots: dependencies: react: 18.3.1 + '@radix-ui/react-id@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + '@radix-ui/react-id@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12245,6 +12486,13 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-portal@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-primitive': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-portal@1.1.1(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -12265,6 +12513,14 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-presence@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-presence@1.1.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12285,6 +12541,13 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-primitive@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-slot': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12392,6 +12655,12 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-slot@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + react: 18.3.1 + '@radix-ui/react-slot@1.0.2(@types/react@18.2.69)(react@18.3.1)': dependencies: '@babel/runtime': 7.27.6 @@ -12531,6 +12800,11 @@ snapshots: '@types/react': 18.2.69 '@types/react-dom': 18.3.7(@types/react@18.2.69) + '@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: react: 18.3.1 @@ -12543,6 +12817,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-use-controllable-state@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12565,6 +12845,12 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-use-escape-keydown@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.47)(react@18.3.1) @@ -12586,6 +12872,11 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + '@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.47)(react@18.3.1)': dependencies: react: 18.3.1 @@ -12791,7 +13082,7 @@ snapshots: transitivePeerDependencies: - encoding - '@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)': + '@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)': dependencies: '@babel/core': 7.27.4 '@babel/generator': 7.27.5 @@ -12808,7 +13099,7 @@ snapshots: '@remix-run/router': 1.23.0 '@remix-run/server-runtime': 2.16.7(typescript@5.8.3) '@types/mdx': 2.0.13 - '@vanilla-extract/integration': 6.5.0(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0) + '@vanilla-extract/integration': 6.5.0(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0) arg: 5.0.2 cacache: 17.1.4 chalk: 4.1.2 @@ -12848,12 +13139,12 @@ snapshots: tar-fs: 2.1.3 tsconfig-paths: 4.2.0 valibot: 0.41.0(typescript@5.8.3) - vite-node: 3.2.3(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + vite-node: 3.2.3(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) ws: 7.5.10 optionalDependencies: '@remix-run/serve': 2.16.7(typescript@5.8.3) typescript: 5.8.3 - vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -13592,12 +13883,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.1.7 - '@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))': + '@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))': dependencies: '@tailwindcss/node': 4.1.9 '@tailwindcss/oxide': 4.1.9 tailwindcss: 4.1.9 - vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -13947,7 +14238,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/compression@1.8.1': dependencies: @@ -13958,7 +14249,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/cookie@0.4.1': {} @@ -13966,7 +14257,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/d3-array@3.2.1': {} @@ -14113,7 +14404,7 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 0.17.5 @@ -14234,12 +14525,12 @@ snapshots: '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/serve-static@1.15.8': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 18.19.115 + '@types/node': 22.16.0 '@types/send': 0.17.5 '@types/shimmer@1.2.0': {} @@ -14254,13 +14545,15 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} + '@types/uuid@10.0.0': {} + '@types/uuid@9.0.8': {} '@types/validator@13.15.2': {} '@types/webpack@5.28.5(@swc/core@1.3.101(@swc/helpers@0.5.17))(esbuild@0.19.11)': dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 tapable: 2.2.2 webpack: 5.99.9(@swc/core@1.3.101(@swc/helpers@0.5.17))(esbuild@0.19.11) transitivePeerDependencies: @@ -14523,7 +14816,7 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - '@vanilla-extract/integration@6.5.0(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0)': + '@vanilla-extract/integration@6.5.0(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)': dependencies: '@babel/core': 7.27.4 '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) @@ -14536,8 +14829,8 @@ snapshots: lodash: 4.17.21 mlly: 1.7.4 outdent: 0.8.0 - vite: 5.4.19(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0) - vite-node: 1.6.1(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0) + vite: 5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0) + vite-node: 1.6.1(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -15174,6 +15467,14 @@ snapshots: cluster-key-slot@1.1.2: {} + cmdk@0.2.1(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-dialog': 1.0.0(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.2.69)(react@18.3.1) @@ -15794,7 +16095,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.19 - '@types/node': 18.19.115 + '@types/node': 22.16.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -16407,7 +16708,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 require-like: 0.1.2 event-target-shim@5.0.1: {} @@ -17092,6 +17393,8 @@ snapshots: internmap@2.0.3: {} + interpret@1.4.0: {} + ioredis@5.6.1: dependencies: '@ioredis/commands': 1.2.0 @@ -17326,7 +17629,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 18.19.115 + '@types/node': 22.16.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -19209,7 +19512,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 18.19.115 + '@types/node': 22.16.0 long: 5.3.2 proxy-addr@2.0.7: @@ -19416,6 +19719,17 @@ snapshots: optionalDependencies: '@types/react': 18.2.69 + react-remove-scroll@2.5.4(@types/react@18.2.69)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.2.69)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.2.69)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.2.69)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.2.69)(react@18.3.1) + optionalDependencies: + '@types/react': 18.2.69 + react-remove-scroll@2.5.7(@types/react@18.2.47)(react@18.3.1): dependencies: react: 18.3.1 @@ -19554,6 +19868,10 @@ snapshots: readdirp@4.1.2: {} + rechoir@0.6.2: + dependencies: + resolve: 1.22.10 + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -19956,8 +20274,19 @@ snapshots: shell-quote@1.8.3: {} + shelljs@0.8.5: + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + shimmer@1.2.1: {} + shx@0.3.4: + dependencies: + minimist: 1.2.8 + shelljs: 0.8.5 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -20920,6 +21249,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@11.1.0: {} + uuid@9.0.1: {} uvu@0.5.6: @@ -20966,13 +21297,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@1.6.1(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0): + vite-node@1.6.1(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0): dependencies: cac: 6.7.14 debug: 4.4.1 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.19(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0) + vite: 5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0) transitivePeerDependencies: - '@types/node' - less @@ -20984,13 +21315,13 @@ snapshots: - supports-color - terser - vite-node@3.2.3(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0): + vite-node@3.2.3(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -21005,29 +21336,29 @@ snapshots: - tsx - yaml - vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)): + vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.8.3) optionalDependencies: - vite: 6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) + vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.19(@types/node@20.19.7)(lightningcss@1.30.1)(terser@5.42.0): + vite@5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0): dependencies: esbuild: 0.21.5 postcss: 8.5.5 rollup: 4.43.0 optionalDependencies: - '@types/node': 20.19.7 + '@types/node': 22.16.0 fsevents: 2.3.3 lightningcss: 1.30.1 terser: 5.42.0 - vite@6.3.5(@types/node@20.19.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0): + vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -21036,7 +21367,7 @@ snapshots: rollup: 4.43.0 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 20.19.7 + '@types/node': 22.16.0 fsevents: 2.3.3 jiti: 2.4.2 lightningcss: 1.30.1 @@ -21293,12 +21624,18 @@ snapshots: dependencies: zod: 3.23.8 + zod-to-json-schema@3.24.5(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod-validation-error@1.5.0(zod@3.23.8): dependencies: zod: 3.23.8 zod@3.23.8: {} + zod@3.25.76: {} + zustand@4.5.7(@types/react@18.2.69)(react@18.3.1): dependencies: use-sync-external-store: 1.5.0(react@18.3.1)