diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
index e03582f350..cca311b4f3 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
@@ -6,43 +6,48 @@ import { SidebarProvider } from "@/context/sidebar-context";
import { cookies } from "next/headers";
import dynamic from "next/dynamic";
import { redirect } from "next/navigation";
+import { UserProvider } from "@/store/user/provider";
+import { AssistantSheet } from "@/components/sheets/assistant-sheet";
const HotKeys = dynamic(
- () => import("@/components/hot-keys").then((mod) => mod.HotKeys),
- {
- ssr: true,
- },
+ () => import("@/components/hot-keys").then((mod) => mod.HotKeys),
+ {
+ ssr: true,
+ },
);
export default async function Layout({
- children,
- params,
+ children,
+ params,
}: {
- children: React.ReactNode;
- params: Promise<{ orgId: string }>;
+ children: React.ReactNode;
+ params: Promise<{ orgId: string }>;
}) {
- const session = await auth();
- const orgId = (await params).orgId;
- const cookieStore = await cookies();
- const isCollapsed = cookieStore.get("sidebar-collapsed")?.value === "true";
+ const session = await auth();
+ const orgId = (await params).orgId;
+ const cookieStore = await cookies();
+ const isCollapsed = cookieStore.get("sidebar-collapsed")?.value === "true";
- if (!session) {
- redirect("/auth");
- }
+ if (!session) {
+ redirect("/auth");
+ }
- if (!orgId) {
- redirect("/");
- }
+ if (!orgId) {
+ redirect("/");
+ }
- return (
-
- } isCollapsed={isCollapsed}>
-
-
- {children}
-
-
-
-
- );
+ return (
+
+
+ } isCollapsed={isCollapsed}>
+
+
+ {children}
+
+
+
+
+
+
+ );
}
diff --git a/apps/app/src/app/[locale]/layout.tsx b/apps/app/src/app/[locale]/layout.tsx
index f0d874cb71..1eefc9897d 100644
--- a/apps/app/src/app/[locale]/layout.tsx
+++ b/apps/app/src/app/[locale]/layout.tsx
@@ -10,104 +10,101 @@ import localFont from "next/font/local";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import { Toaster } from "sonner";
import { Providers } from "./providers";
-import { ChatProvider } from "@/components/chat/ChatProvider";
export const metadata: Metadata = {
- metadataBase: new URL("https://app.trycomp.ai"),
- title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- twitter: {
- title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- images: [
- {
- url: "https://cdn.trycomp.ai/opengraph-image.jpg",
- width: 800,
- height: 600,
- },
- {
- url: "https://cdn.trycomp.ai/opengraph-image.jpg",
- width: 1800,
- height: 1600,
- },
- ],
- },
- openGraph: {
- title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
- url: "https://app.trycomp.ai",
- siteName: "Comp AI",
- images: [
- {
- url: "https://cdn.trycomp.ai/opengraph-image.jpg",
- width: 800,
- height: 600,
- },
- {
- url: "https://cdn.trycomp.ai/opengraph-image.jpg",
- width: 1800,
- height: 1600,
- },
- ],
- locale: "en_US",
- type: "website",
- },
+ metadataBase: new URL("https://app.trycomp.ai"),
+ title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ twitter: {
+ title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ images: [
+ {
+ url: "https://cdn.trycomp.ai/opengraph-image.jpg",
+ width: 800,
+ height: 600,
+ },
+ {
+ url: "https://cdn.trycomp.ai/opengraph-image.jpg",
+ width: 1800,
+ height: 1600,
+ },
+ ],
+ },
+ openGraph: {
+ title: "Comp AI | Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ description: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.",
+ url: "https://app.trycomp.ai",
+ siteName: "Comp AI",
+ images: [
+ {
+ url: "https://cdn.trycomp.ai/opengraph-image.jpg",
+ width: 800,
+ height: 600,
+ },
+ {
+ url: "https://cdn.trycomp.ai/opengraph-image.jpg",
+ width: 1800,
+ height: 1600,
+ },
+ ],
+ locale: "en_US",
+ type: "website",
+ },
};
export const viewport = {
- width: "device-width",
- initialScale: 1,
- maximumScale: 1,
- userScalable: false,
- themeColor: [
- { media: "(prefers-color-scheme: light)" },
- { media: "(prefers-color-scheme: dark)" },
- ],
+ width: "device-width",
+ initialScale: 1,
+ maximumScale: 1,
+ userScalable: false,
+ themeColor: [
+ { media: "(prefers-color-scheme: light)" },
+ { media: "(prefers-color-scheme: dark)" },
+ ],
};
const font = localFont({
- src: "/../../../public/fonts/GeneralSans-Variable.ttf",
- display: "swap",
- variable: "--font-general-sans",
+ src: "/../../../public/fonts/GeneralSans-Variable.ttf",
+ display: "swap",
+ variable: "--font-general-sans",
});
export const preferredRegion = ["auto"];
export const maxDuration = 5;
if (env.NEXT_PUBLIC_POSTHOG_KEY && env.NEXT_PUBLIC_POSTHOG_HOST) {
- initializeServer({
- apiKey: env.NEXT_PUBLIC_POSTHOG_KEY,
- apiHost: env.NEXT_PUBLIC_POSTHOG_HOST,
- });
+ initializeServer({
+ apiKey: env.NEXT_PUBLIC_POSTHOG_KEY,
+ apiHost: env.NEXT_PUBLIC_POSTHOG_HOST,
+ });
}
export default async function Layout(props: {
- children: React.ReactNode;
- params: Promise<{ locale: string }>;
+ children: React.ReactNode;
+ params: Promise<{ locale: string }>;
}) {
- const params = await props.params;
- const { locale } = params;
- const { children } = props;
+ const params = await props.params;
+ const { locale } = params;
+ const { children } = props;
- return (
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ );
}
diff --git a/apps/app/src/app/api/chat/route.ts b/apps/app/src/app/api/chat/route.ts
index 232fac3edb..f4a0f85739 100644
--- a/apps/app/src/app/api/chat/route.ts
+++ b/apps/app/src/app/api/chat/route.ts
@@ -1,33 +1,73 @@
-import { openai } from "@ai-sdk/openai";
-import { streamText } from "ai";
-import { env } from "@/env.mjs";
+import { auth } from "@/auth";
+import { model, type modelID } from "@/hooks/ai/providers";
+import {
+ getOrgTool,
+ getVendorsTool,
+ getPoliciesTool,
+ getRisksTool,
+ getCloudTestsTool,
+ getFrameworkTool,
+ getVendorDetailsTool,
+ getPolicyDetails,
+} from "@/hooks/ai/tools";
+import { db } from "@bubba/db";
+import { streamText, type UIMessage } from "ai";
+
+export const maxDuration = 30;
export async function POST(req: Request) {
- const { messages } = await req.json();
+ const {
+ messages,
+ selectedModel,
+ }: { messages: UIMessage[]; selectedModel: modelID } = await req.json();
+
+ const session = await auth();
- if (!env.OPENAI_API_KEY) {
- return new Response(
- JSON.stringify({ error: "OpenAI API key is not configured" }),
- { status: 500 },
- );
+ if (!session?.user.organizationId) {
+ return new Response("Unauthorized", { status: 401 });
}
- try {
- const result = await streamText({
- model: openai("gpt-4o-mini"),
- system:
- "You are a helpful assistant. Provide concise, accurate, and helpful responses to user queries.",
- messages,
- });
-
- return result.toDataStreamResponse();
- } catch (error) {
- console.error("Error in chat API:", error);
- return new Response(
- JSON.stringify({
- error: "An error occurred while processing your request",
- }),
- { status: 500 },
- );
+ const org = await db.organization.findUnique({
+ where: {
+ id: session.user.organizationId,
+ },
+ });
+
+ if (!org) {
+ return new Response("Organization not found", { status: 404 });
}
+
+ const systemPrompt = `
+ You are an expert in governance, risk, and compliance.
+
+ You're a helpful assistant in Comp AI, a platform that helps companies
+ get compliant with frameworks like SOC 2, ISO 27001 and GDPR - similar to Vanta and Drata.
+
+ You have access to various tools to help gather information. You can:
+ 1. Use multiple tools in sequence to gather comprehensive information
+ 2. Chain tool calls together to get more detailed information
+ 3. Use tools to verify or expand upon information from previous tool calls
+
+ When using tools:
+ - Start with broader queries to get an overview
+ - Then use specific queries to get detailed information
+`;
+
+ const result = streamText({
+ model: model.languageModel(selectedModel),
+ system: systemPrompt,
+ messages,
+ tools: {
+ getOrgTool,
+ getVendorsTool,
+ getPoliciesTool,
+ getRisksTool,
+ getCloudTestsTool,
+ getFrameworkTool,
+ getVendorDetailsTool,
+ getPolicyDetails,
+ },
+ });
+
+ return result.toDataStreamResponse({ sendReasoning: true });
}
diff --git a/apps/app/src/components/ai/chat-empty.tsx b/apps/app/src/components/ai/chat-empty.tsx
new file mode 100644
index 0000000000..57a048247f
--- /dev/null
+++ b/apps/app/src/components/ai/chat-empty.tsx
@@ -0,0 +1,17 @@
+import { LogoSpinner } from "../logo-spinner";
+
+type Props = {
+ firstName: string;
+};
+
+export function ChatEmpty({ firstName }: Props) {
+ return (
+
+
+
+ Hi {firstName}, how can I help
+ you today?
+
+
+ );
+}
diff --git a/apps/app/src/components/ai/chat-text-area.tsx b/apps/app/src/components/ai/chat-text-area.tsx
new file mode 100644
index 0000000000..d80423ee0b
--- /dev/null
+++ b/apps/app/src/components/ai/chat-text-area.tsx
@@ -0,0 +1,117 @@
+import type { modelID } from "@/hooks/ai/providers";
+import { Icons } from "@bubba/ui/icons";
+import { Popover, PopoverTrigger, PopoverContent } from "@bubba/ui/popover";
+import { Textarea as ShadcnTextarea } from "@bubba/ui/textarea";
+import { ArrowUp } from "lucide-react";
+import app from "next/app";
+import { useRouter } from "next/navigation";
+
+interface InputProps {
+ input: string;
+ handleInputChange: (event: React.ChangeEvent) => void;
+ isLoading: boolean;
+ status: string;
+ stop: () => void;
+ selectedModel: modelID;
+ setSelectedModel: (model: modelID) => void;
+}
+
+export const ChatTextarea = ({
+ input,
+ handleInputChange,
+ isLoading,
+ status,
+ stop,
+ selectedModel,
+ setSelectedModel,
+}: InputProps) => {
+ const router = useRouter();
+
+ const handleOpenUrl = (url: string) => {
+ router.push(url);
+ };
+ return (
+
+
{
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ if (input.trim() && !isLoading) {
+ // @ts-expect-error err
+ const form = e.target.closest("form");
+ if (form) form.requestSubmit();
+ }
+ }
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/apps/app/src/components/ai/chat.tsx b/apps/app/src/components/ai/chat.tsx
new file mode 100644
index 0000000000..4f8f16a6c3
--- /dev/null
+++ b/apps/app/src/components/ai/chat.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import type { modelID } from "@/hooks/ai/providers";
+import { useChat } from "@ai-sdk/react";
+import { useState } from "react";
+import { Messages } from "./messages";
+import { ChatEmpty } from "./chat-empty";
+import { useSession } from "next-auth/react";
+import { ChatTextarea } from "./chat-text-area";
+import { ScrollArea } from "@bubba/ui/scroll-area";
+
+export default function Chat() {
+ const { data: session } = useSession();
+
+ const [selectedModel, setSelectedModel] = useState(
+ "deepseek-r1-distill-llama-70b",
+ );
+
+ const {
+ messages,
+ input,
+ handleInputChange,
+ handleSubmit,
+ error,
+ status,
+ stop,
+ } = useChat({
+ maxSteps: 5,
+ body: {
+ selectedModel,
+ },
+ });
+
+ const isLoading = status === "streaming" || status === "submitted";
+
+ if (error) return {error.message}
;
+
+ return (
+
+
+ {messages.length === 0 ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/app/src/components/ai/icons.tsx b/apps/app/src/components/ai/icons.tsx
new file mode 100644
index 0000000000..89efe5e9e0
--- /dev/null
+++ b/apps/app/src/components/ai/icons.tsx
@@ -0,0 +1,158 @@
+import Link from "next/link";
+import * as React from "react";
+import type { SVGProps } from "react";
+
+export const VercelIcon = ({ size = 17 }) => {
+ return (
+
+ );
+};
+
+export const SpinnerIcon = ({ size = 16 }: { size?: number }) => (
+
+);
+
+export const Github = (props: SVGProps) => (
+
+);
+
+export function StarButton() {
+ return (
+
+
+ Star on GitHub
+
+ );
+}
+
+export const GroqIcon = ({ size = 16 }) => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/apps/app/src/components/ai/markdown.tsx b/apps/app/src/components/ai/markdown.tsx
new file mode 100644
index 0000000000..c561c16f1b
--- /dev/null
+++ b/apps/app/src/components/ai/markdown.tsx
@@ -0,0 +1,112 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import React, { memo } from "react";
+import ReactMarkdown, { type Components } from "react-markdown";
+import remarkGfm from "remark-gfm";
+
+const components: Partial = {
+ pre: ({ children }) => <>{children}>,
+ ol: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ li: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ ul: ({ node, children, ...props }) => {
+ return (
+
+ );
+ },
+ p: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ strong: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ a: ({ children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h1: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h2: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h3: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h4: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h5: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+ h6: ({ node, children, ...props }) => {
+ return (
+
+ {children}
+
+ );
+ },
+};
+
+const remarkPlugins = [remarkGfm];
+
+const NonMemoizedMarkdown = ({ children }: { children: string }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const Markdown = memo(
+ NonMemoizedMarkdown,
+ (prevProps, nextProps) => prevProps.children === nextProps.children,
+);
\ No newline at end of file
diff --git a/apps/app/src/components/ai/message.tsx b/apps/app/src/components/ai/message.tsx
new file mode 100644
index 0000000000..4023d407c3
--- /dev/null
+++ b/apps/app/src/components/ai/message.tsx
@@ -0,0 +1,245 @@
+"use client";
+
+import type { Message as TMessage } from "ai";
+import { AnimatePresence, motion } from "motion/react";
+import { memo, useCallback, useEffect, useState } from "react";
+import equal from "fast-deep-equal";
+
+import { Markdown } from "./markdown";
+import { cn } from "@bubba/ui/cn";
+import {
+ CheckCircle,
+ ChevronDownIcon,
+ ChevronUpIcon,
+ Loader2,
+ PocketKnife,
+ SparklesIcon,
+ StopCircle,
+} from "lucide-react";
+import { SpinnerIcon } from "./icons";
+
+interface ReasoningPart {
+ type: "reasoning";
+ reasoning: string;
+ details: Array<{ type: "text"; text: string }>;
+}
+
+interface ReasoningMessagePartProps {
+ part: ReasoningPart;
+ isReasoning: boolean;
+}
+
+export function ReasoningMessagePart({
+ part,
+ isReasoning,
+}: ReasoningMessagePartProps) {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const variants = {
+ collapsed: {
+ height: 0,
+ opacity: 0,
+ marginTop: 0,
+ marginBottom: 0,
+ },
+ expanded: {
+ height: "auto",
+ opacity: 1,
+ marginTop: "1rem",
+ marginBottom: 0,
+ },
+ };
+
+ const memoizedSetIsExpanded = useCallback((value: boolean) => {
+ setIsExpanded(value);
+ }, []);
+
+ useEffect(() => {
+ memoizedSetIsExpanded(isReasoning);
+ }, [isReasoning, memoizedSetIsExpanded]);
+
+ return (
+
+ {isReasoning ? (
+
+ ) : (
+
+
Reasoned for a few seconds
+
+
+ )}
+
+
+ {isExpanded && (
+
+ {part.details.map((detail, detailIndex) =>
+ detail.type === "text" ? (
+ {detail.text}
+ ) : (
+ ""
+ ),
+ )}
+
+ )}
+
+
+ );
+}
+
+const PurePreviewMessage = ({
+ message,
+ isLatestMessage,
+ status,
+}: {
+ message: TMessage;
+ isLoading: boolean;
+ status: "error" | "submitted" | "streaming" | "ready";
+ isLatestMessage: boolean;
+}) => {
+ return (
+
+
+
+ {message.role === "assistant" && (
+
+ )}
+
+
+ {message.parts?.map((part, i) => {
+ switch (part.type) {
+ case "text":
+ return (
+
+
+ {part.text}
+
+
+ );
+ case "tool-invocation": {
+ const { toolName, state } = part.toolInvocation;
+
+ return (
+
+
+
+
+
+ {state === "call" ? "Calling" : "Called"}{" "}
+
+ {toolName}
+
+
+
+
+ {state === "call" ? (
+ isLatestMessage && status !== "ready" ? (
+
+ ) : (
+
+ )
+ ) : state === "result" ? (
+
+ ) : null}
+
+
+
+ );
+ }
+ case "reasoning": {
+ return (
+
+ );
+ }
+ default:
+ return null;
+ }
+ })}
+
+
+
+
+ );
+};
+
+export const Message = memo(PurePreviewMessage, (prevProps, nextProps) => {
+ if (prevProps.status !== nextProps.status) return false;
+
+ if (prevProps.message.annotations !== nextProps.message.annotations)
+ return false;
+
+ if (!equal(prevProps.message.parts, nextProps.message.parts)) return false;
+
+ return true;
+});
\ No newline at end of file
diff --git a/apps/app/src/components/ai/messages.tsx b/apps/app/src/components/ai/messages.tsx
new file mode 100644
index 0000000000..ab8476bb81
--- /dev/null
+++ b/apps/app/src/components/ai/messages.tsx
@@ -0,0 +1,34 @@
+import type { Message as TMessage } from "ai";
+import { Message } from "./message";
+import { useScrollToBottom } from "@/hooks/use-scroll-to-bottom";
+
+export const Messages = ({
+ messages,
+ isLoading,
+ status,
+}: {
+ messages: TMessage[];
+ isLoading: boolean;
+ status: "error" | "submitted" | "streaming" | "ready";
+}) => {
+ const [containerRef, endRef] = useScrollToBottom();
+ return (
+
+
+ {messages.map((m, i) => (
+
+ ))}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/apps/app/src/components/ai/model-picker.tsx b/apps/app/src/components/ai/model-picker.tsx
new file mode 100644
index 0000000000..4de16e4497
--- /dev/null
+++ b/apps/app/src/components/ai/model-picker.tsx
@@ -0,0 +1,43 @@
+"use client";
+import type { modelID } from "@/hooks/ai/providers";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@bubba/ui/select";
+
+interface ModelPickerProps {
+ selectedModel: modelID;
+ setSelectedModel: (model: modelID) => void;
+}
+
+const MODELS: Record = {
+ "deepseek-r1-distill-llama-70b": "A reasoning model",
+};
+
+export const ModelPicker = ({
+ selectedModel,
+ setSelectedModel,
+}: ModelPickerProps) => {
+ return (
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/apps/app/src/components/animated-layout.tsx b/apps/app/src/components/animated-layout.tsx
index 559d0cfe3b..08c4419c47 100644
--- a/apps/app/src/components/animated-layout.tsx
+++ b/apps/app/src/components/animated-layout.tsx
@@ -1,27 +1,27 @@
-import { cn } from "../../../../packages/ui/src/utils";
+import { cn } from "@bubba/ui/cn";
interface AnimatedLayoutProps {
- children: React.ReactNode;
- sidebar: React.ReactNode;
- isCollapsed: boolean;
+ children: React.ReactNode;
+ sidebar: React.ReactNode;
+ isCollapsed: boolean;
}
export function AnimatedLayout({
- children,
- sidebar,
- isCollapsed,
+ children,
+ sidebar,
+ isCollapsed,
}: AnimatedLayoutProps) {
- return (
-
-
- {sidebar}
-
-
{children}
-
- );
+ return (
+
+
+ {sidebar}
+
+
{children}
+
+ );
}
diff --git a/apps/app/src/components/chat/ChatButton.tsx b/apps/app/src/components/chat/ChatButton.tsx
deleted file mode 100644
index fb6ef0ec51..0000000000
--- a/apps/app/src/components/chat/ChatButton.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { ChatBubbleIcon } from "@radix-ui/react-icons";
-import { ChatInterface } from "./ChatInterface";
-import { Button } from "@bubba/ui/button";
-import { cn } from "@bubba/ui/cn";
-
-interface ChatButtonProps {
- className?: string;
-}
-
-export function ChatButton({ className }: ChatButtonProps) {
- const [isOpen, setIsOpen] = useState(false);
-
- const toggleChat = () => {
- setIsOpen(!isOpen);
- };
-
- return (
-
- {isOpen && setIsOpen(false)} />}
-
-
- );
-}
diff --git a/apps/app/src/components/chat/ChatInterface.tsx b/apps/app/src/components/chat/ChatInterface.tsx
deleted file mode 100644
index 92d93f9be9..0000000000
--- a/apps/app/src/components/chat/ChatInterface.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-"use client";
-
-import { useChat } from "@/hooks/chat/useChat";
-import type { ChatMessage } from "@/hooks/chat/useChat";
-import { useRef, useEffect } from "react";
-import { Cross2Icon, PaperPlaneIcon, PersonIcon } from "@radix-ui/react-icons";
-import { Button } from "@bubba/ui/button";
-import { Input } from "@bubba/ui/input";
-import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card";
-import { cn } from "@bubba/ui/cn";
-import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar";
-
-interface ChatInterfaceProps {
- onClose: () => void;
-}
-
-export function ChatInterface({ onClose }: ChatInterfaceProps) {
- const { messages, input, handleInputChange, handleSubmit, isLoading } =
- useChat();
- const messagesEndRef = useRef(null);
-
- // Scroll to bottom when messages change
- useEffect(() => {
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
- }, [messages]);
-
- // Helper to determine if a specific message is currently being generated
- const isGeneratingMessage = (message: ChatMessage, index: number) => {
- return (
- isLoading &&
- index === messages.length - 1 &&
- message.role === "assistant" &&
- message.content === ""
- );
- };
-
- // Format timestamp
- const formatTime = () => {
- const now = new Date();
- return now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
- };
-
- return (
-
-
-
- Chat Assistant
-
-
-
-
-
- {messages.length === 0 ? (
-
-
How can I help you today?
-
- ) : (
- messages.map((message: ChatMessage, index) => (
-
-
- {message.role === "user" ? (
-
-
- U
-
-
- ) : (
-
-
- AI
-
-
- )}
-
-
-
-
- {message.role === "user" ? "User" : "Assistant"}
-
-
- {formatTime()}
-
-
-
- {isGeneratingMessage(message, index) ? (
-
-
-
- Generating response...
-
-
- ) : (
-
- {message.content}
-
- )}
-
-
-
- ))
- )}
-
-
-
-
-
- );
-}
diff --git a/apps/app/src/components/chat/ChatProvider.tsx b/apps/app/src/components/chat/ChatProvider.tsx
deleted file mode 100644
index bb4d2eda7e..0000000000
--- a/apps/app/src/components/chat/ChatProvider.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-"use client";
-
-import type { ReactNode } from "react";
-import { ChatButton } from "./ChatButton";
-
-interface ChatProviderProps {
- children: ReactNode;
-}
-
-export function ChatProvider({ children }: ChatProviderProps) {
- return (
- <>
- {children}
-
- >
- );
-}
diff --git a/apps/app/src/components/hot-keys.tsx b/apps/app/src/components/hot-keys.tsx
index 23ac6fcc02..19d0a162b4 100644
--- a/apps/app/src/components/hot-keys.tsx
+++ b/apps/app/src/components/hot-keys.tsx
@@ -1,13 +1,17 @@
"use client";
-import { useAssistantStore } from "@/store/assistant";
import { signOut } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useHotkeys } from "react-hotkeys-hook";
+import { useQueryState } from "nuqs";
export function HotKeys() {
const router = useRouter();
- const { setOpen } = useAssistantStore();
+ const [, setAssistantOpen] = useQueryState("assistant", {
+ history: "push",
+ parse: (value) => value === "true",
+ serialize: (value) => value.toString(),
+ });
const handleSignOut = async () => {
await signOut();
@@ -56,7 +60,7 @@ export function HotKeys() {
useHotkeys("meta+k", (evt) => {
evt.preventDefault();
- setOpen(true);
+ setAssistantOpen(true);
});
return null;
diff --git a/apps/app/src/components/sheets/assistant-sheet.tsx b/apps/app/src/components/sheets/assistant-sheet.tsx
new file mode 100644
index 0000000000..1bf3e7cec6
--- /dev/null
+++ b/apps/app/src/components/sheets/assistant-sheet.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { Sheet, SheetContent } from "@bubba/ui/sheet";
+import { useQueryState } from "nuqs";
+import Chat from "../ai/chat";
+
+export function AssistantSheet() {
+ const [isOpen, setIsOpen] = useQueryState("assistant", {
+ history: "push",
+ parse: (value) => value === "true",
+ serialize: (value) => value.toString(),
+ });
+
+ return (
+ setIsOpen(open)}>
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/app/src/env.mjs b/apps/app/src/env.mjs
index 7a8f2848a5..9d5e041b92 100644
--- a/apps/app/src/env.mjs
+++ b/apps/app/src/env.mjs
@@ -26,6 +26,7 @@ export const env = createEnv({
AWS_SECRET_ACCESS_KEY: z.string(),
AWS_REGION: z.string(),
AWS_BUCKET_NAME: z.string(),
+ GROQ_API_KEY: z.string(),
},
client: {
@@ -63,6 +64,7 @@ export const env = createEnv({
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_REGION: process.env.AWS_REGION,
AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME,
+ GROQ_API_KEY: process.env.GROQ_API_KEY,
},
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION,
diff --git a/apps/app/src/hooks/ai/providers.ts b/apps/app/src/hooks/ai/providers.ts
new file mode 100644
index 0000000000..f8d742fa42
--- /dev/null
+++ b/apps/app/src/hooks/ai/providers.ts
@@ -0,0 +1,19 @@
+import { groq } from "@ai-sdk/groq";
+import {
+ customProvider,
+ extractReasoningMiddleware,
+ wrapLanguageModel,
+} from "ai";
+
+export const model = customProvider({
+ languageModels: {
+ "deepseek-r1-distill-llama-70b": wrapLanguageModel({
+ middleware: extractReasoningMiddleware({
+ tagName: "think",
+ }),
+ model: groq("deepseek-r1-distill-llama-70b"),
+ }),
+ },
+});
+
+export type modelID = Parameters<(typeof model)["languageModel"]>["0"];
diff --git a/apps/app/src/hooks/ai/tools.ts b/apps/app/src/hooks/ai/tools.ts
new file mode 100644
index 0000000000..5940c6a206
--- /dev/null
+++ b/apps/app/src/hooks/ai/tools.ts
@@ -0,0 +1,361 @@
+import { auth } from "@/auth";
+import { db } from "@bubba/db";
+import { tool } from "ai";
+import { z } from "zod";
+
+// Organization Tools
+export const getOrgTool = tool({
+ description: "Get the organization details",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const org = await db.organization.findUnique({
+ where: { id: session.user.organizationId },
+ select: {
+ id: true,
+ name: true,
+ website: true,
+ tier: true,
+ setup: true,
+ frameworkId: true,
+ },
+ });
+
+ if (!org) {
+ return { error: "Organization not found" };
+ }
+
+ return org;
+ },
+});
+
+// Vendor Tools
+export const getVendorsTool = tool({
+ description: "Get all vendors for the organization",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const vendors = await db.vendor.findMany({
+ where: { organizationId: session.user.organizationId },
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ category: true,
+ status: true,
+ inherentRisk: true,
+ residualRisk: true,
+ createdAt: true,
+ updatedAt: true,
+ owner: {
+ select: {
+ name: true,
+ email: true,
+ },
+ },
+ contacts: {
+ select: {
+ name: true,
+ email: true,
+ phone: true,
+ },
+ },
+ },
+ });
+
+ return vendors;
+ },
+});
+
+export const getVendorDetailsTool = tool({
+ description: "Get detailed information about a specific vendor",
+ parameters: z.object({
+ vendorId: z.string(),
+ }),
+ execute: async ({ vendorId }) => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const vendor = await db.vendor.findFirst({
+ where: {
+ id: vendorId,
+ organizationId: session.user.organizationId,
+ },
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ category: true,
+ status: true,
+ inherentRisk: true,
+ residualRisk: true,
+ createdAt: true,
+ updatedAt: true,
+ owner: {
+ select: {
+ name: true,
+ email: true,
+ },
+ },
+ contacts: {
+ select: {
+ name: true,
+ email: true,
+ phone: true,
+ },
+ },
+ tasks: {
+ select: {
+ id: true,
+ title: true,
+ description: true,
+ status: true,
+ dueDate: true,
+ completedAt: true,
+ owner: {
+ select: {
+ name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ comments: {
+ select: {
+ content: true,
+ createdAt: true,
+ owner: {
+ select: {
+ name: true,
+ email: true,
+ },
+ },
+ },
+ },
+ },
+ });
+
+ if (!vendor) {
+ return { error: "Vendor not found" };
+ }
+
+ return vendor;
+ },
+});
+
+// Risk Tools
+export const getRisksTool = tool({
+ description: "Get all risks for the organization",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const risks = await db.risk.findMany({
+ where: { organizationId: session.user.organizationId },
+ select: {
+ id: true,
+ title: true,
+ description: true,
+ category: true,
+ department: true,
+ status: true,
+ probability: true,
+ impact: true,
+ residual_probability: true,
+ residual_impact: true,
+ createdAt: true,
+ updatedAt: true,
+ owner: {
+ select: {
+ name: true,
+ email: true,
+ },
+ },
+ mitigationTasks: {
+ select: {
+ id: true,
+ title: true,
+ status: true,
+ dueDate: true,
+ completedAt: true,
+ },
+ },
+ },
+ });
+
+ return risks;
+ },
+});
+
+// Policy Tools
+export const getPoliciesTool = tool({
+ description: "Get all policies for the organization",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const policies = await db.organizationPolicy.findMany({
+ where: { organizationId: session.user.organizationId },
+ select: {
+ id: true,
+ policy: {
+ select: {
+ name: true,
+ },
+ },
+ },
+ });
+
+ return policies;
+ },
+});
+
+export const getPolicyDetails = tool({
+ description: "Get detailed information about a specific policy",
+ parameters: z.object({
+ policyName: z.string(),
+ }),
+ execute: async ({ policyName }) => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const policy = await db.organizationPolicy.findFirst({
+ where: {
+ organizationId: session.user.organizationId,
+ policy: {
+ name: policyName,
+ },
+ isArchived: false,
+ },
+ select: {
+ id: true,
+ status: true,
+ createdAt: true,
+ updatedAt: true,
+ policy: {
+ select: {
+ id: true,
+ name: true,
+ description: true,
+ content: true,
+ version: true,
+ frequency: true,
+ department: true,
+ isRequiredToSign: true,
+ },
+ },
+ },
+ });
+
+ if (!policy) {
+ return { error: "Policy not found" };
+ }
+
+ return policy;
+ },
+});
+
+// Framework Tools
+export const getFrameworkTool = tool({
+ description: "Get the organization's framework details",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const orgFramework = await db.organizationFramework.findFirst({
+ where: { organizationId: session.user.organizationId },
+ select: {
+ id: true,
+ status: true,
+ adoptedAt: true,
+ lastAssessed: true,
+ nextAssessment: true,
+ framework: {
+ select: {
+ name: true,
+ description: true,
+ version: true,
+ categories: {
+ select: {
+ name: true,
+ description: true,
+ controls: {
+ select: {
+ code: true,
+ name: true,
+ description: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ if (!orgFramework) {
+ return { error: "Framework not found" };
+ }
+
+ return orgFramework;
+ },
+});
+
+// Cloud Test Tools
+export const getCloudTestsTool = tool({
+ description: "Get all cloud tests for the organization",
+ parameters: z.object({}),
+ execute: async () => {
+ const session = await auth();
+
+ if (!session?.user.organizationId) {
+ return { error: "Unauthorized" };
+ }
+
+ const tests = await db.organizationIntegrationResults.findMany({
+ where: { organizationId: session.user.organizationId },
+ select: {
+ id: true,
+ title: true,
+ status: true,
+ severity: true,
+ remediation: true,
+ resultDetails: true,
+ completedAt: true,
+ organizationIntegrationId: true,
+ organizationId: true,
+ assignedUserId: true,
+ },
+ });
+
+ return tests;
+ },
+});
diff --git a/apps/app/src/hooks/chat/useChat.ts b/apps/app/src/hooks/chat/useChat.ts
deleted file mode 100644
index a7821cdbc9..0000000000
--- a/apps/app/src/hooks/chat/useChat.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-"use client";
-
-import { useChat as useAIChat } from "@ai-sdk/react";
-import { v4 as uuid } from "uuid";
-
-export interface ChatMessage {
- id: string;
- role: "user" | "assistant";
- content: string;
-}
-
-export function useChat() {
- const {
- messages: aiMessages,
- input,
- handleInputChange,
- handleSubmit,
- isLoading,
- } = useAIChat({
- api: "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/api/chat",
- });
-
- // Convert AI SDK messages to our ChatMessage format
- const messages: ChatMessage[] = aiMessages.map((msg) => ({
- id: msg.id || uuid(),
- role: msg.role as "user" | "assistant",
- content: msg.content,
- }));
-
- return {
- messages,
- input,
- handleInputChange,
- handleSubmit,
- isLoading,
- };
-}
diff --git a/apps/app/src/hooks/use-scroll-to-bottom.ts b/apps/app/src/hooks/use-scroll-to-bottom.ts
new file mode 100644
index 0000000000..9710001855
--- /dev/null
+++ b/apps/app/src/hooks/use-scroll-to-bottom.ts
@@ -0,0 +1,32 @@
+import { useEffect, useRef, type RefObject } from "react";
+
+export function useScrollToBottom(): [
+ RefObject,
+ RefObject,
+] {
+ const containerRef = useRef(null);
+ const endRef = useRef(null);
+
+ useEffect(() => {
+ const container = containerRef.current;
+ const end = endRef.current;
+
+ if (container && end) {
+ const observer = new MutationObserver(() => {
+ end.scrollIntoView({ behavior: "instant", block: "end" });
+ });
+
+ observer.observe(container, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ characterData: true,
+ });
+
+ return () => observer.disconnect();
+ }
+ }, []);
+
+ // @ts-expect-error error
+ return [containerRef, endRef];
+}
diff --git a/apps/app/src/store/assistant.ts b/apps/app/src/store/assistant.ts
index a9abfe9ce3..f20af4f564 100644
--- a/apps/app/src/store/assistant.ts
+++ b/apps/app/src/store/assistant.ts
@@ -1,19 +1,13 @@
import { create } from "zustand";
interface AssistantState {
- isOpen: boolean;
- message?: string;
- setOpen: (value: boolean | string | undefined) => void;
- setClose: () => void;
+ isOpen: boolean;
+ message?: string;
+ setOpen: (message?: string) => void;
}
export const useAssistantStore = create()((set) => ({
- isOpen: false,
- message: undefined,
- setOpen: (value) =>
- set({
- isOpen: typeof value === "boolean" ? value : true,
- message: typeof value === "string" ? value : undefined,
- }),
- setClose: () => set({ isOpen: false, message: undefined }),
+ isOpen: false,
+ message: undefined,
+ setOpen: (message) => set((state) => ({ isOpen: !state.isOpen, message })),
}));
diff --git a/apps/app/src/store/user/store.ts b/apps/app/src/store/user/store.ts
index ae4f96ab8a..c3ab9e69af 100644
--- a/apps/app/src/store/user/store.ts
+++ b/apps/app/src/store/user/store.ts
@@ -1,27 +1,20 @@
-import type { User as NextAuthUser } from "next-auth";
+import type { User } from "next-auth";
import { createContext } from "react";
import { createStore } from "zustand";
-type User = {
- id: string;
- organizationId?: string;
- full_name?: string;
- avatar_url?: string;
-} & NextAuthUser;
-
export interface UserProps {
- data: User | null;
+ data: User | null;
}
export interface UserState extends UserProps {
- setUser: (user: User) => void;
+ setUser: (user: User) => void;
}
export const createUserStore = (initProps: UserProps) => {
- return createStore()((set) => ({
- data: initProps?.data,
- setUser: (user: User) => set({ data: user }),
- }));
+ return createStore()((set) => ({
+ data: initProps?.data,
+ setUser: (user: User) => set({ data: user }),
+ }));
};
export type UserStore = ReturnType;
diff --git a/biome.json b/biome.json
index 902a681eb6..9572599eea 100644
--- a/biome.json
+++ b/biome.json
@@ -15,6 +15,7 @@
"noExplicitAny": "off"
},
"a11y": {
+ "useValidAriaRole": "off",
"noSvgWithoutTitle": "off",
"useKeyWithClickEvents": "off"
},
diff --git a/bun.lock b/bun.lock
index 0559d10c5b..e3e926a4e7 100644
--- a/bun.lock
+++ b/bun.lock
@@ -4,6 +4,7 @@
"": {
"name": "comp",
"dependencies": {
+ "@ai-sdk/groq": "^1.2.1",
"@aws-sdk/client-s3": "^3.758.0",
"@aws-sdk/client-securityhub": "^3.758.0",
"@azure/core-http": "^3.0.5",
@@ -17,6 +18,7 @@
"d3": "^7.9.0",
"gitmoji": "^1.1.1",
"gray-matter": "^4.0.3",
+ "motion": "^12.6.2",
"pg": "^8.14.1",
"sharp": "^0.33.5",
"use-debounce": "^10.0.4",
@@ -310,11 +312,13 @@
},
},
"packages": {
+ "@ai-sdk/groq": ["@ai-sdk/groq@1.2.1", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "@ai-sdk/provider-utils": "2.2.1" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-e9Vn6sE6u+pm97YSK9+xiTgQ2ScRdipE5gAwXj/9HdgMnUyp3mDpWjFsmDM6bzyeb2iKOGv6f3eiRsLxOAPv4A=="],
+
"@ai-sdk/openai": ["@ai-sdk/openai@1.2.6", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "@ai-sdk/provider-utils": "2.1.14" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-0HxWjE3NDABAxu6u33Q253a4N+xxOCD/M2HeZuALC1O2gjmzLeytxjSbDA6QU0PnVN0uaYLOc2YT/keFF5eGrg=="],
"@ai-sdk/provider": ["@ai-sdk/provider@1.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-88Uu1zJIE1UUOVJWfE2ybJXgiH8JJ97QY9fbmplErEbfa/k/1kF+tWMVAAJolF2aOGmazQGyQLhv4I9CCuVACw=="],
- "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+ "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.1", "", { "dependencies": { "@ai-sdk/provider": "1.1.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-BuExLp+NcpwsAVj1F4bgJuQkSqO/+roV9wM7RdIO+NVrcT8RBUTdXzf5arHt5T58VpK7bZyB2V9qigjaPHE+Dg=="],
"@ai-sdk/react": ["@ai-sdk/react@1.1.24", "", { "dependencies": { "@ai-sdk/provider-utils": "2.1.14", "@ai-sdk/ui-utils": "1.1.20", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.0.0" }, "optionalPeers": ["react", "zod"] }, "sha512-20rVK34QCaEl/4u87/Y+JXt7eCzF60UwoJOjHJhlCdmFrbk6AfRsijv0ludA9ELArE+ulxkSf21L3FokkZ2Gmg=="],
@@ -2694,6 +2698,8 @@
"module-details-from-path": ["module-details-from-path@1.0.3", "", {}, "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="],
+ "motion": ["motion@12.6.2", "", { "dependencies": { "framer-motion": "^12.6.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-8OBjjuC59WuWHKmPzVWT5M0t5kDxtkfMfHF1M7Iey6F/nvd0AI15YlPnpGlcagW/eOfkdWDO90U/K5LF/k55Yw=="],
+
"motion-dom": ["motion-dom@12.5.0", "", { "dependencies": { "motion-utils": "^12.5.0" } }, "sha512-uH2PETDh7m+Hjd1UQQ56yHqwn83SAwNjimNPE/kC+Kds0t4Yh7+29rfo5wezVFpPOv57U4IuWved5d1x0kNhbQ=="],
"motion-utils": ["motion-utils@12.5.0", "", {}, "sha512-+hFFzvimn0sBMP9iPxBa9OtRX35ZQ3py0UHnb8U29VD+d8lQ8zH3dTygJWqK7av2v6yhg7scj9iZuvTS0f4+SA=="],
@@ -3638,9 +3644,11 @@
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+ "@ai-sdk/groq/@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
+
"@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.1.14", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "eventsource-parser": "^3.0.0", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-0fNQwstwqHP+cE9bP9nQXeEhY/qJRGW5Y+AQrvwzd0EI2+gH1GmwI1IC/gIOksmz/gRy4G+kXXD+Fbq4T/jCbA=="],
- "@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+ "@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@1.1.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew=="],
"@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
@@ -3648,8 +3656,16 @@
"@ai-sdk/react/@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.1.20", "", { "dependencies": { "@ai-sdk/provider": "1.0.12", "@ai-sdk/provider-utils": "2.1.14", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-k+qQh1YA8/kH12d8KQFWjr0UxyS9nLibzbLr2OY6fzpfZg8xIAKlZeH952JIp2XwAp72tesgbsAmA15cDldtyw=="],
+ "@ai-sdk/solid/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+
+ "@ai-sdk/svelte/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+
"@ai-sdk/ui-utils/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+ "@ai-sdk/ui-utils/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+
+ "@ai-sdk/vue/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+
"@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"@auth/core/preact": ["preact@10.11.3", "", {}, "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="],
@@ -3796,6 +3812,8 @@
"ai/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+ "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@1.0.22", "", { "dependencies": { "@ai-sdk/provider": "0.0.26", "eventsource-parser": "^1.1.2", "nanoid": "^3.3.7", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.0.0" }, "optionalPeers": ["zod"] }, "sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ=="],
+
"ai/@ai-sdk/react": ["@ai-sdk/react@0.0.70", "", { "dependencies": { "@ai-sdk/provider-utils": "1.0.22", "@ai-sdk/ui-utils": "0.0.50", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.0.0" }, "optionalPeers": ["react", "zod"] }, "sha512-GnwbtjW4/4z7MleLiW+TOZC2M29eCg1tOUpuEiYFMmFNZK8mkrqM0PFZMo6UsYeUYMWqEOOcPOU9OQVJMJh7IQ=="],
"ansi-escapes/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="],
@@ -3942,6 +3960,8 @@
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "motion/framer-motion": ["framer-motion@12.6.2", "", { "dependencies": { "motion-dom": "^12.6.1", "motion-utils": "^12.5.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-7LgPRlPs5aG8UxeZiMCMZz8firC53+2+9TnWV22tuSi38D3IFRxHRUqOREKckAkt6ztX+Dn6weLcatQilJTMcg=="],
+
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"next-auth/next": ["next@15.2.3", "", { "dependencies": { "@next/env": "15.2.3", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.3", "@next/swc-darwin-x64": "15.2.3", "@next/swc-linux-arm64-gnu": "15.2.3", "@next/swc-linux-arm64-musl": "15.2.3", "@next/swc-linux-x64-gnu": "15.2.3", "@next/swc-linux-x64-musl": "15.2.3", "@next/swc-win32-arm64-msvc": "15.2.3", "@next/swc-win32-x64-msvc": "15.2.3", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w=="],
@@ -4074,6 +4094,20 @@
"@ai-sdk/react/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+ "@ai-sdk/solid/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+
+ "@ai-sdk/solid/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "@ai-sdk/svelte/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+
+ "@ai-sdk/svelte/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "@ai-sdk/ui-utils/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+ "@ai-sdk/vue/@ai-sdk/provider-utils/@ai-sdk/provider": ["@ai-sdk/provider@0.0.26", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg=="],
+
+ "@ai-sdk/vue/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
"@auth/prisma-adapter/@auth/core/jose": ["jose@6.0.10", "", {}, "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw=="],
"@auth/prisma-adapter/@auth/core/preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="],
@@ -4164,6 +4198,8 @@
"@uploadthing/react/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
+ "ai/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
"are-we-there-yet/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"are-we-there-yet/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
@@ -4252,6 +4288,8 @@
"languine/@trigger.dev/sdk/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
+ "motion/framer-motion/motion-dom": ["motion-dom@12.6.1", "", { "dependencies": { "motion-utils": "^12.5.0" } }, "sha512-8XVsriTUEVOepoIDgE/LDGdg7qaKXWdt+wQA/8z0p8YzJDLYL8gbimZ3YkCLlj7bB2i/4UBD/g+VO7y9ZY0zHQ=="],
+
"next-auth/next/@next/env": ["@next/env@15.2.3", "", {}, "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw=="],
"next-auth/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw=="],
diff --git a/package.json b/package.json
index 08bdb4863e..a0743d5abe 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"packages/*"
],
"dependencies": {
+ "@ai-sdk/groq": "^1.2.1",
"@aws-sdk/client-s3": "^3.758.0",
"@aws-sdk/client-securityhub": "^3.758.0",
"@azure/core-http": "^3.0.5",
@@ -40,6 +41,7 @@
"d3": "^7.9.0",
"gitmoji": "^1.1.1",
"gray-matter": "^4.0.3",
+ "motion": "^12.6.2",
"pg": "^8.14.1",
"sharp": "^0.33.5",
"use-debounce": "^10.0.4"
diff --git a/turbo.json b/turbo.json
index ce35a37bd6..34f7398d2b 100644
--- a/turbo.json
+++ b/turbo.json
@@ -37,7 +37,8 @@
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"ANTHROPIC_API_KEY",
- "REVALIDATION_SECRET"
+ "REVALIDATION_SECRET",
+ "GROQ_API_KEY"
],
"inputs": ["$TURBO_DEFAULT$", ".env"],
"dependsOn": ["^build", "^db:generate", "clean-react"],
diff --git a/yarn.lock b/yarn.lock
index 99668788fc..d7c6121273 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3,6 +3,14 @@
# bun ./bun.lockb --hash: 0000000000000000-0000000000000000-0000000000000000-0000000000000000
+"@ai-sdk/groq@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/@ai-sdk/groq/-/groq-1.2.1.tgz"
+ integrity sha512-e9Vn6sE6u+pm97YSK9+xiTgQ2ScRdipE5gAwXj/9HdgMnUyp3mDpWjFsmDM6bzyeb2iKOGv6f3eiRsLxOAPv4A==
+ dependencies:
+ "@ai-sdk/provider" "1.1.0"
+ "@ai-sdk/provider-utils" "2.2.1"
+
"@ai-sdk/openai@^0.0.66":
version "0.0.66"
resolved "https://registry.npmjs.org/@ai-sdk/openai/-/openai-0.0.66.tgz"
@@ -40,6 +48,13 @@
dependencies:
json-schema "^0.4.0"
+"@ai-sdk/provider@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz"
+ integrity sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==
+ dependencies:
+ json-schema "^0.4.0"
+
"@ai-sdk/provider-utils@1.0.20":
version "1.0.20"
resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.20.tgz"
@@ -70,6 +85,15 @@
nanoid "^3.3.8"
secure-json-parse "^2.7.0"
+"@ai-sdk/provider-utils@2.2.1":
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.1.tgz"
+ integrity sha512-BuExLp+NcpwsAVj1F4bgJuQkSqO/+roV9wM7RdIO+NVrcT8RBUTdXzf5arHt5T58VpK7bZyB2V9qigjaPHE+Dg==
+ dependencies:
+ "@ai-sdk/provider" "1.1.0"
+ nanoid "^3.3.8"
+ secure-json-parse "^2.7.0"
+
"@ai-sdk/react@0.0.70":
version "0.0.70"
resolved "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.70.tgz"
@@ -1223,14 +1247,14 @@
formdata-node "^4.3.2"
node-fetch "^2.6.7"
-"@bubba/analytics@^workspace:packages/analytics":
+"@bubba/analytics@packages/analytics":
version "workspace:packages/analytics"
resolved "workspace:packages/analytics"
dependencies:
posthog-js "^1.215.3"
posthog-node "^4.4.1"
-"@bubba/app@^workspace:apps/app", "@bubba/app@workspace:*":
+"@bubba/app@apps/app", "@bubba/app@workspace:*":
version "workspace:apps/app"
resolved "workspace:apps/app"
devDependencies:
@@ -1321,11 +1345,11 @@
yarn "^1.22.22"
zustand "^5.0.3"
-"@bubba/data@^workspace:packages/data":
+"@bubba/data@packages/data":
version "workspace:packages/data"
resolved "workspace:packages/data"
-"@bubba/db@^workspace:packages/db", "@bubba/db@workspace:*":
+"@bubba/db@packages/db", "@bubba/db@workspace:*":
version "workspace:packages/db"
resolved "workspace:packages/db"
devDependencies:
@@ -1340,7 +1364,7 @@
"@prisma/extension-accelerate" "^1.2.2"
prisma "^6.5.0"
-"@bubba/email@^workspace:packages/email":
+"@bubba/email@packages/email":
version "workspace:packages/email"
resolved "workspace:packages/email"
devDependencies:
@@ -1360,7 +1384,7 @@
react-email "3.0.4"
responsive-react-email "^0.0.5"
-"@bubba/integrations@^workspace:packages/integrations":
+"@bubba/integrations@packages/integrations":
version "workspace:packages/integrations"
resolved "workspace:packages/integrations"
devDependencies:
@@ -1383,14 +1407,14 @@
sharp "^0.33.5"
zod "^3.24.2"
-"@bubba/kv@^workspace:packages/kv":
+"@bubba/kv@packages/kv":
version "workspace:packages/kv"
resolved "workspace:packages/kv"
dependencies:
"@upstash/redis" "^1.34.2"
server-only "0.0.1"
-"@bubba/notifications@^workspace:packages/notifications", "@bubba/notifications@workspace:*":
+"@bubba/notifications@packages/notifications", "@bubba/notifications@workspace:*":
version "workspace:packages/notifications"
resolved "workspace:packages/notifications"
devDependencies:
@@ -1399,11 +1423,11 @@
"@novu/node" "^2.0.1"
nanoid "5.1.0"
-"@bubba/tsconfig@^workspace:packages/tsconfig", "@bubba/tsconfig@workspace:*":
+"@bubba/tsconfig@packages/tsconfig", "@bubba/tsconfig@workspace:*":
version "workspace:packages/tsconfig"
resolved "workspace:packages/tsconfig"
-"@bubba/ui@^workspace:packages/ui", "@bubba/ui@workspace:*":
+"@bubba/ui@packages/ui", "@bubba/ui@workspace:*":
version "workspace:packages/ui"
resolved "workspace:packages/ui"
devDependencies:
@@ -1467,7 +1491,7 @@
use-callback-ref "^1.3.3"
vaul "^1.0.0"
-"@bubba/utils@^workspace:packages/utils", "@bubba/utils@workspace:*":
+"@bubba/utils@packages/utils", "@bubba/utils@workspace:*":
version "workspace:packages/utils"
resolved "workspace:packages/utils"
devDependencies:
@@ -8190,6 +8214,15 @@ framer-motion@^12.5.0:
motion-utils "^12.5.0"
tslib "^2.4.0"
+framer-motion@^12.6.2:
+ version "12.6.2"
+ resolved "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.2.tgz"
+ integrity sha512-7LgPRlPs5aG8UxeZiMCMZz8firC53+2+9TnWV22tuSi38D3IFRxHRUqOREKckAkt6ztX+Dn6weLcatQilJTMcg==
+ dependencies:
+ tslib "^2.4.0"
+ motion-dom "^12.6.1"
+ motion-utils "^12.5.0"
+
framework-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/framework-utils/-/framework-utils-1.1.0.tgz"
@@ -10471,6 +10504,14 @@ module-details-from-path@^1.0.3:
resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz"
integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==
+motion@^12.6.2:
+ version "12.6.2"
+ resolved "https://registry.npmjs.org/motion/-/motion-12.6.2.tgz"
+ integrity sha512-8OBjjuC59WuWHKmPzVWT5M0t5kDxtkfMfHF1M7Iey6F/nvd0AI15YlPnpGlcagW/eOfkdWDO90U/K5LF/k55Yw==
+ dependencies:
+ tslib "^2.4.0"
+ framer-motion "^12.6.2"
+
motion-dom@^12.5.0:
version "12.5.0"
resolved "https://registry.npmjs.org/motion-dom/-/motion-dom-12.5.0.tgz"
@@ -10478,6 +10519,13 @@ motion-dom@^12.5.0:
dependencies:
motion-utils "^12.5.0"
+motion-dom@^12.6.1:
+ version "12.6.1"
+ resolved "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.1.tgz"
+ integrity sha512-8XVsriTUEVOepoIDgE/LDGdg7qaKXWdt+wQA/8z0p8YzJDLYL8gbimZ3YkCLlj7bB2i/4UBD/g+VO7y9ZY0zHQ==
+ dependencies:
+ motion-utils "^12.5.0"
+
motion-utils@^12.5.0:
version "12.5.0"
resolved "https://registry.npmjs.org/motion-utils/-/motion-utils-12.5.0.tgz"
@@ -11326,7 +11374,7 @@ plist@^3.1.0:
base64-js "^1.5.1"
xmlbuilder "^15.1.1"
-portal@^workspace:apps/portal:
+"portal@apps/portal":
version "workspace:apps/portal"
resolved "workspace:apps/portal"
devDependencies:
@@ -14432,7 +14480,7 @@ zod@3.23.8:
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
-"zod@>= 3", zod@^3.0.0, zod@^3.18.0, zod@^3.20.2, zod@^3.24.0, zod@^3.24.1, zod@^3.24.2:
+"zod@>= 3", zod@^3.0.0, zod@^3.18.0, zod@^3.20.2, zod@^3.23.8, zod@^3.24.0, zod@^3.24.1, zod@^3.24.2:
version "3.24.2"
resolved "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz"
integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==