├── .gitignore ├── LICENSE ├── README.md ├── app ├── api │ └── gemini │ │ └── route.ts ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── api-key-config.tsx ├── footer.tsx ├── rag-eval-demo.tsx ├── response-details-modal.tsx ├── theme-provider.tsx └── ui │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── breadcrumb.tsx │ ├── button.tsx │ ├── calendar.tsx │ ├── card.tsx │ ├── carousel.tsx │ ├── chart.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── hover-card.tsx │ ├── input-otp.tsx │ ├── input.tsx │ ├── label.tsx │ ├── menubar.tsx │ ├── navigation-menu.tsx │ ├── pagination.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── sidebar.tsx │ ├── skeleton.tsx │ ├── slider.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ ├── tooltip.tsx │ ├── use-mobile.tsx │ └── use-toast.ts ├── hooks ├── use-mobile.tsx └── use-toast.ts ├── lib └── utils.ts ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── placeholder-logo.png ├── placeholder-logo.svg ├── placeholder-user.jpg ├── placeholder.jpg └── placeholder.svg ├── rag-evals-app.tsx ├── styles └── globals.css ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | # .env.production 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | .trigger -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Anubhav Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RAG Evals App 2 | 3 | A Next.js application for evaluating and visualizing RAG (Retrieval-Augmented Generation) systems. This application provides a user-friendly interface to test and analyze the performance of RAG implementations through various metrics and visualizations. 4 | 5 | This repo goes along with the Python Notebooks at https://github.com/xprilion/gemini-as-a-judge-for-rag-evals 6 | 7 | ## Features 8 | 9 | - Interactive chat interface for testing RAG systems 10 | - Real-time visualization of vector embeddings 11 | - Cosine similarity scoring and ranking 12 | - Comprehensive evaluation tables for: 13 | - Sentence analysis 14 | - Vector representations 15 | - Query information 16 | - Similarity scores 17 | 18 | ## Tech Stack 19 | 20 | - **Framework**: Next.js 15.2.4 21 | - **Language**: TypeScript 22 | - **UI Components**: Radix UI 23 | - **Styling**: Tailwind CSS 24 | - **State Management**: React Hooks 25 | - **Form Handling**: React Hook Form 26 | - **Data Visualization**: Recharts 27 | - **Date Handling**: date-fns 28 | - **AI Integration**: Gemini 2.0 29 | 30 | ## Getting Started 31 | 32 | ### Prerequisites 33 | 34 | - Node.js (v18 or higher) 35 | - pnpm (recommended package manager) 36 | 37 | ### Installation 38 | 39 | 1. Clone the repository: 40 | 41 | ```bash 42 | git clone https://github.com/xprilion/gemini-rag-evals-visualizer-app.git 43 | cd gemini-rag-evals-visualizer-app 44 | ``` 45 | 46 | 2. Install dependencies: 47 | 48 | ```bash 49 | pnpm install 50 | ``` 51 | 52 | 3. Start the development server: 53 | 54 | ```bash 55 | pnpm dev 56 | ``` 57 | 58 | The application will be available at `http://localhost:3000`. 59 | 60 | ## Project Structure 61 | 62 | ``` 63 | gemini-rag-evals-visualizer-app/ 64 | ├── app/ # Next.js app directory 65 | │ ├── api/ # API routes 66 | │ ├── page.tsx # Main page component 67 | │ └── layout.tsx # Root layout 68 | ├── components/ # Reusable UI components 69 | ├── hooks/ # Custom React hooks 70 | ├── lib/ # Utility functions and configurations 71 | ├── public/ # Static assets 72 | └── styles/ # Global styles 73 | ``` 74 | 75 | ## Development 76 | 77 | - `pnpm dev` - Start development server 78 | - `pnpm build` - Build for production 79 | - `pnpm start` - Start production server 80 | - `pnpm lint` - Run ESLint 81 | 82 | ## Contributing 83 | 84 | Contributions are welcome! Please feel free to submit a Pull Request. 85 | 86 | ## License 87 | 88 | This project is licensed under the MIT License - see the LICENSE file for details. 89 | -------------------------------------------------------------------------------- /app/api/gemini/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest, NextResponse } from "next/server"; 2 | 3 | export async function POST(req: NextRequest) { 4 | try { 5 | const { prompt, context, apiKey } = await req.json(); 6 | 7 | if (!apiKey) { 8 | return NextResponse.json( 9 | { error: "API key is required" }, 10 | { status: 400 } 11 | ); 12 | } 13 | 14 | // Prepare the API request 15 | const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; 16 | 17 | // Create the request payload 18 | const payload = { 19 | contents: [ 20 | { 21 | parts: [ 22 | { 23 | text: `Context: ${context}\n\nUser query: ${prompt}`, 24 | }, 25 | ], 26 | }, 27 | ], 28 | generationConfig: { 29 | temperature: 0.7, 30 | maxOutputTokens: 800, 31 | }, 32 | }; 33 | 34 | // Make the direct API call 35 | const response = await fetch(url, { 36 | method: "POST", 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | body: JSON.stringify(payload), 41 | }); 42 | 43 | if (!response.ok) { 44 | throw new Error(`API request failed with status ${response.status}`); 45 | } 46 | 47 | const data = await response.json(); 48 | const text = data.candidates[0].content.parts[0].text; 49 | 50 | return NextResponse.json({ text }); 51 | } catch (error) { 52 | console.error("Error in Gemini API:", error); 53 | return NextResponse.json( 54 | { error: "Failed to generate response" }, 55 | { status: 500 } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 222.2 84% 4.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 222.2 84% 4.9%; 13 | --primary: 221.2 83.2% 53.3%; 14 | --primary-foreground: 210 40% 98%; 15 | --secondary: 210 40% 96.1%; 16 | --secondary-foreground: 222.2 47.4% 11.2%; 17 | --muted: 210 40% 96.1%; 18 | --muted-foreground: 215.4 16.3% 46.9%; 19 | --accent: 210 40% 96.1%; 20 | --accent-foreground: 222.2 47.4% 11.2%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 210 40% 98%; 23 | --border: 214.3 31.8% 91.4%; 24 | --input: 214.3 31.8% 91.4%; 25 | --ring: 221.2 83.2% 53.3%; 26 | --radius: 0.5rem; 27 | } 28 | 29 | .dark { 30 | --background: 222.2 84% 4.9%; 31 | --foreground: 210 40% 98%; 32 | --card: 222.2 84% 4.9%; 33 | --card-foreground: 210 40% 98%; 34 | --popover: 222.2 84% 4.9%; 35 | --popover-foreground: 210 40% 98%; 36 | --primary: 217.2 91.2% 59.8%; 37 | --primary-foreground: 222.2 47.4% 11.2%; 38 | --secondary: 217.2 32.6% 17.5%; 39 | --secondary-foreground: 210 40% 98%; 40 | --muted: 217.2 32.6% 17.5%; 41 | --muted-foreground: 215 20.2% 65.1%; 42 | --accent: 217.2 32.6% 17.5%; 43 | --accent-foreground: 210 40% 98%; 44 | --destructive: 0 62.8% 30.6%; 45 | --destructive-foreground: 210 40% 98%; 46 | --border: 217.2 32.6% 17.5%; 47 | --input: 217.2 32.6% 17.5%; 48 | --ring: 224.3 76.3% 48%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply bg-background text-foreground; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type React from "react" 2 | import "@/app/globals.css" 3 | import { Inter } from "next/font/google" 4 | import { ThemeProvider } from "@/components/theme-provider" 5 | 6 | const inter = Inter({ subsets: ["latin"] }) 7 | 8 | export const metadata = { 9 | title: "RAG Evaluation Demo", 10 | description: "Interactive tool for demonstrating Retrieval Augmented Generation", 11 | generator: 'v0.dev' 12 | } 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | 31 | 32 | import './globals.css' -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import RagEvalDemo from "@/components/rag-eval-demo"; 2 | import { ThemeToggle } from "@/components/theme-provider"; 3 | import { Footer } from "@/components/footer"; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 |
9 |
10 |

RAG Evals Demo

11 | 12 |
13 |

14 | This interactive tool demonstrates how Retrieval Augmented Generation 15 | (RAG) works by visualizing the process of vectorization, similarity 16 | scoring, and response generation. 17 |

18 | 19 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /components/api-key-config.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { Button } from "@/components/ui/button"; 3 | import { Input } from "@/components/ui/input"; 4 | import { Label } from "@/components/ui/label"; 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogHeader, 9 | DialogTitle, 10 | DialogTrigger, 11 | } from "@/components/ui/dialog"; 12 | import { Settings2 } from "lucide-react"; 13 | 14 | interface ApiKeyConfigProps { 15 | children?: React.ReactNode; 16 | } 17 | 18 | export function ApiKeyConfig({ children }: ApiKeyConfigProps) { 19 | const [apiKey, setApiKey] = useState(""); 20 | const [isOpen, setIsOpen] = useState(false); 21 | 22 | useEffect(() => { 23 | const savedKey = localStorage.getItem("geminiApiKey"); 24 | if (savedKey) { 25 | setApiKey(savedKey); 26 | } 27 | }, []); 28 | 29 | const handleSave = () => { 30 | localStorage.setItem("geminiApiKey", apiKey); 31 | setIsOpen(false); 32 | }; 33 | 34 | return ( 35 | 36 | 37 | {children || ( 38 | 41 | )} 42 | 43 | 44 | 45 | Configure Gemini API Key 46 | 47 |
48 |
49 | 50 | setApiKey(e.target.value)} 55 | placeholder="Enter your Gemini API key" 56 | /> 57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Github } from "lucide-react"; 2 | import Link from "next/link"; 3 | 4 | export function Footer() { 5 | return ( 6 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /components/response-details-modal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Dialog, 3 | DialogContent, 4 | DialogHeader, 5 | DialogTitle, 6 | } from "@/components/ui/dialog"; 7 | import { ScrollArea } from "@/components/ui/scroll-area"; 8 | import { Badge } from "@/components/ui/badge"; 9 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 10 | 11 | interface ResponseDetails { 12 | userQuery: string; 13 | matchedSentences: string[]; 14 | matchedIndexes: number[]; 15 | finalPrompt: string; 16 | } 17 | 18 | interface EvaluationComparison { 19 | trueIndexes: number[]; 20 | predictedIndexes: number[]; 21 | intersection: number[]; 22 | precision: number; 23 | recall: number; 24 | f1: number; 25 | } 26 | 27 | interface ResponseDetailsModalProps { 28 | isOpen: boolean; 29 | onClose: () => void; 30 | details: ResponseDetails; 31 | evaluationComparison?: EvaluationComparison; 32 | } 33 | 34 | export function ResponseDetailsModal({ 35 | isOpen, 36 | onClose, 37 | details, 38 | evaluationComparison, 39 | }: ResponseDetailsModalProps) { 40 | return ( 41 | 42 | 43 | 44 | Response Details 45 | 46 | 47 |
48 | 49 | 50 | User Query 51 | 52 | 53 |

{details.userQuery}

54 |
55 |
56 | 57 | 58 | 59 | Matched Sentences 60 | 61 | 62 |
63 | {details.matchedSentences.map((sentence, index) => ( 64 |
65 | 66 | {details.matchedIndexes[index] + 1} 67 | 68 |

{sentence}

69 |
70 | ))} 71 |
72 |
73 |
74 | 75 | {evaluationComparison && ( 76 | 77 | 78 | Evaluation Results 79 | 80 | 81 |
82 |
83 |

True Matches

84 |
85 | {evaluationComparison.trueIndexes.map((index) => ( 86 | 94 | {index} 95 | 96 | ))} 97 |
98 |
99 |
100 |

Predicted Matches

101 |
102 | {evaluationComparison.predictedIndexes.map((index) => ( 103 | 111 | {index + 1} 112 | 113 | ))} 114 |
115 |
116 |
117 |
118 |

Precision

119 |

120 | {(evaluationComparison.precision * 100).toFixed(1)}% 121 |

122 |
123 |
124 |

Recall

125 |

126 | {(evaluationComparison.recall * 100).toFixed(1)}% 127 |

128 |
129 |
130 |

F1 Score

131 |

132 | {(evaluationComparison.f1 * 100).toFixed(1)}% 133 |

134 |
135 |
136 |
137 |
138 |
139 | )} 140 | 141 | 142 | 143 | Final Prompt 144 | 145 | 146 |
147 |                   {details.finalPrompt}
148 |                 
149 |
150 |
151 |
152 |
153 |
154 |
155 | ); 156 | } 157 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { 5 | ThemeProvider as NextThemesProvider, 6 | type ThemeProviderProps, 7 | } from "next-themes"; 8 | import { Moon, Sun } from "lucide-react"; 9 | import { Button } from "@/components/ui/button"; 10 | import { useTheme } from "next-themes"; 11 | 12 | export function ThemeToggle() { 13 | const { theme, setTheme } = useTheme(); 14 | 15 | return ( 16 | 25 | ); 26 | } 27 | 28 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 29 | return {children}; 30 | } 31 | -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { buttonVariants } from "@/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )) 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ) 60 | AlertDialogHeader.displayName = "AlertDialogHeader" 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ) 74 | AlertDialogFooter.displayName = "AlertDialogFooter" 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )) 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )) 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )) 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | } 142 | -------------------------------------------------------------------------------- /components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>