├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ └── research │ │ └── route.ts ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── LoadingSteps.tsx └── ui │ ├── button.tsx │ ├── card.tsx │ ├── input.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── scroll-area.tsx │ ├── separator.tsx │ ├── skeleton.tsx │ ├── slider.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toggle-group.tsx │ ├── toggle.tsx │ └── tooltip.tsx ├── flows └── NewsLetter MultiAgent.json ├── hooks └── use-toast.ts ├── lib └── utils.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | /.bolt/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InsightExpress - AI-Powered Newsletter Agent 2 | ### Powered By Langflow 3 | 4 | ## Overview 5 | InsightExpress is a Next.js application that generates AI-powered research reports based on user-provided topics and emails them to users. The application leverages Langflow for its AI capabilities and features a modern, responsive UI built using NextJS. 6 | 7 | 8 | https://github.com/user-attachments/assets/3d364aef-8a1b-4bd2-a782-a9fb8b33b735 9 | 10 | 11 | ## Prerequisites 12 | 13 | Before running the application, ensure you have the following installed: 14 | - Node.js (version 14 or later) 15 | - npm (Node package manager) 16 | - Python (version 3.9 or later) - for running Langflow 17 | - pip (Python package manager) 18 | 19 | ## Installation Steps 20 | 21 | ### 1. Install and Run Langflow 22 | 23 | First, you'll need to set up Langflow locally: 24 | 25 | ```bash 26 | # Install Langflow using pip 27 | pip install langflow 28 | 29 | # Start Langflow server 30 | langflow run 31 | ``` 32 | 33 | Once Langflow is running: 34 | 1. Access the Langflow UI at `http://localhost:7860` 35 | 2. Create a new flow for research generation 36 | 3. Note down the following details: 37 | - Your Langflow URL (typically `http://localhost:7860`) 38 | - Your Flow ID (found in the flow's URL) 39 | - Your API token (found in Settings > API Keys), in case auth enabled in the api settings 40 | 41 | ### 2. Set Up the Next.js Application 42 | 43 | ```bash 44 | # Clone the repository 45 | git clone https://github.com/misbahsy/InsightExpress.git 46 | cd insightexpress 47 | 48 | # Install dependencies 49 | npm install 50 | ``` 51 | 52 | ### 3. Configure Environment Variables 53 | 54 | Create a `.env.local` file in the root directory: 55 | 56 | ```env 57 | LANGFLOW_URL=http://localhost:7860 58 | FLOW_ID=your_flow_id_here 59 | LANGFLOW_TOKEN=your_langflow_api_token_here 60 | ``` 61 | 62 | Replace the values with your actual Langflow configuration. 63 | 64 | ### 4. Run the Application 65 | 66 | ```bash 67 | # Start the development server 68 | npm run dev 69 | ``` 70 | 71 | The application will be available at `http://localhost:3000` 72 | 73 | ## Usage 74 | 75 | 1. Access the application in your browser 76 | 2. Enter a research topic in the provided field 77 | 3. Enter your email address 78 | 4. Click "Generate Research Report" 79 | 5. Wait for the AI to generate your report 80 | 6. The report will be displayed on screen and sent to your email 81 | 82 | ## Project Structure 83 | 84 | ``` 85 | insightexpress/ 86 | ├── app/ # Next.js app directory 87 | │ ├── api/ # API routes 88 | │ ├── page.tsx # Main page component 89 | │ └── layout.tsx # Root layout 90 | ├── components/ # React components 91 | │ ├── ui/ # UI components 92 | │ └── LoadingSteps.tsx # Loading indicator 93 | ├── lib/ # Utility functions 94 | ├── public/ # Static assets 95 | └── styles/ # Global styles 96 | ``` 97 | 98 | ## Features 99 | 100 | - 🎨 Modern, responsive UI with dark mode support 101 | - 🤖 AI-powered research generation 102 | - 📧 Email delivery of reports 103 | - ⚡ Real-time loading indicators 104 | - 🎯 Error handling and notifications 105 | 106 | 107 | ## Troubleshooting 108 | 109 | ### Common Issues 110 | 111 | 1. **Langflow Connection Error** 112 | - Ensure Langflow is running locally 113 | - Verify your environment variables are correct 114 | - Check if your Flow ID is valid 115 | 116 | 2. **Email Delivery Issues** 117 | - Verify the email address format 118 | - Check your Langflow flow configuration esp. Composio connection 119 | 120 | 3. **Build Errors** 121 | - Run `npm clean-install` to refresh dependencies 122 | - Ensure all required dependencies are installed 123 | 124 | ## License 125 | 126 | This project is licensed under the MIT License. See the LICENSE file for details. 127 | 128 | ## Support 129 | 130 | For support, please open an issue in the GitHub repository or contact the maintainers. 131 | -------------------------------------------------------------------------------- /app/api/research/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | interface ResearchRequest { 4 | topic: string; 5 | email: string; 6 | } 7 | 8 | export async function POST(request: Request) { 9 | try { 10 | const { topic, email }: ResearchRequest = await request.json(); 11 | 12 | if (!topic || !email) { 13 | return NextResponse.json( 14 | { error: 'Missing required fields' }, 15 | { status: 400 } 16 | ); 17 | } 18 | 19 | const currentDateTime = new Date().toLocaleString(); 20 | 21 | const response = await fetch( 22 | `${process.env.LANGFLOW_URL}/api/v1/run/${process.env.FLOW_ID}?stream=false`, 23 | { 24 | method: 'POST', 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | // 'Authorization': `Bearer ${process.env.LANGFLOW_TOKEN}`, #could be used if auth enabled 28 | }, 29 | body: JSON.stringify({ 30 | input_value: `${topic}, send it to this email: ${email}. Current date and time: ${currentDateTime}`, 31 | output_type: 'chat', 32 | input_type: 'chat', 33 | tweaks: { 34 | }, 35 | }), 36 | } 37 | ); 38 | 39 | const data = await response.json(); 40 | const result = data.outputs[0].outputs[0].outputs.message.message.text; 41 | 42 | return NextResponse.json({ result }); 43 | } catch (error) { 44 | console.error('Research API error:', error); 45 | return NextResponse.json( 46 | { error: 'Failed to generate research report' }, 47 | { status: 500 } 48 | ); 49 | } 50 | } -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | @layer base { 20 | :root { 21 | --background: 0 0% 100%; 22 | --foreground: 0 0% 3.9%; 23 | --card: 0 0% 100%; 24 | --card-foreground: 0 0% 3.9%; 25 | --popover: 0 0% 100%; 26 | --popover-foreground: 0 0% 3.9%; 27 | --primary: 0 0% 9%; 28 | --primary-foreground: 0 0% 98%; 29 | --secondary: 0 0% 96.1%; 30 | --secondary-foreground: 0 0% 9%; 31 | --muted: 0 0% 96.1%; 32 | --muted-foreground: 0 0% 45.1%; 33 | --accent: 0 0% 96.1%; 34 | --accent-foreground: 0 0% 9%; 35 | --destructive: 0 84.2% 60.2%; 36 | --destructive-foreground: 0 0% 98%; 37 | --border: 0 0% 89.8%; 38 | --input: 0 0% 89.8%; 39 | --ring: 0 0% 3.9%; 40 | --chart-1: 12 76% 61%; 41 | --chart-2: 173 58% 39%; 42 | --chart-3: 197 37% 24%; 43 | --chart-4: 43 74% 66%; 44 | --chart-5: 27 87% 67%; 45 | --radius: 0.5rem; 46 | } 47 | .dark { 48 | --background: 0 0% 3.9%; 49 | --foreground: 0 0% 98%; 50 | --card: 0 0% 3.9%; 51 | --card-foreground: 0 0% 98%; 52 | --popover: 0 0% 3.9%; 53 | --popover-foreground: 0 0% 98%; 54 | --primary: 0 0% 98%; 55 | --primary-foreground: 0 0% 9%; 56 | --secondary: 0 0% 14.9%; 57 | --secondary-foreground: 0 0% 98%; 58 | --muted: 0 0% 14.9%; 59 | --muted-foreground: 0 0% 63.9%; 60 | --accent: 0 0% 14.9%; 61 | --accent-foreground: 0 0% 98%; 62 | --destructive: 0 62.8% 30.6%; 63 | --destructive-foreground: 0 0% 98%; 64 | --border: 0 0% 14.9%; 65 | --input: 0 0% 14.9%; 66 | --ring: 0 0% 83.1%; 67 | --chart-1: 220 70% 50%; 68 | --chart-2: 160 60% 45%; 69 | --chart-3: 30 80% 55%; 70 | --chart-4: 280 65% 60%; 71 | --chart-5: 340 75% 55%; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | body { 80 | @apply bg-background text-foreground; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | import type { Metadata } from 'next'; 3 | import { Inter } from 'next/font/google'; 4 | import { Toaster } from "@/components/ui/toaster"; 5 | 6 | const inter = Inter({ subsets: ['latin'] }); 7 | 8 | export const metadata: Metadata = { 9 | title: 'Multi-Agent Research Demo', 10 | description: 'AI-powered research report generation', 11 | }; 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode; 17 | }) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | 25 | ); 26 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { Card } from '@/components/ui/card'; 5 | import { Input } from '@/components/ui/input'; 6 | import { Button } from '@/components/ui/button'; 7 | import { SearchIcon, SendIcon, BrainCircuit } from 'lucide-react'; 8 | import { useToast } from '@/hooks/use-toast'; 9 | import ReactMarkdown from 'react-markdown'; 10 | import remarkGfm from 'remark-gfm'; 11 | import { LoadingSteps } from '@/components/LoadingSteps'; 12 | 13 | export default function Home() { 14 | const [topic, setTopic] = useState(''); 15 | const [email, setEmail] = useState(''); 16 | const [researchResult, setResearchResult] = useState(''); 17 | const [isLoading, setIsLoading] = useState(false); 18 | const { toast } = useToast(); 19 | 20 | const handleSubmit = async (e: React.FormEvent) => { 21 | e.preventDefault(); 22 | if (!topic || !email) { 23 | toast({ 24 | title: 'Missing Information', 25 | description: 'Please provide both a topic and email address.', 26 | variant: 'destructive', 27 | }); 28 | return; 29 | } 30 | 31 | setIsLoading(true); 32 | 33 | try { 34 | const response = await fetch('/api/research', { 35 | method: 'POST', 36 | headers: { 37 | 'Content-Type': 'application/json', 38 | }, 39 | body: JSON.stringify({ topic, email }), 40 | }); 41 | 42 | const data = await response.json(); 43 | 44 | if (!response.ok) { 45 | throw new Error(data.error || 'Failed to generate research report'); 46 | } 47 | 48 | setResearchResult(data.result); 49 | 50 | toast({ 51 | title: 'Research Complete', 52 | description: 'Your research report has been generated.', 53 | }); 54 | } catch (error) { 55 | toast({ 56 | title: 'Error', 57 | description: 'Failed to generate research report. Please try again.', 58 | variant: 'destructive', 59 | }); 60 | } finally { 61 | setIsLoading(false); 62 | } 63 | }; 64 | 65 | return ( 66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 | 74 |
75 |

76 | InsightExpress 77 |

78 |

79 | Provide a topic of interest, and our intelligent agents will 80 | search the internet to generate a comprehensive custom report 81 | tailored just for you. 82 |

83 |
84 | 85 | 86 |
87 |
88 |
89 | 95 |
96 | 97 | setTopic(e.target.value)} 103 | /> 104 |
105 |
106 | 107 |
108 | 114 |
115 | 116 | setEmail(e.target.value)} 123 | /> 124 |
125 |
126 |
127 | 128 | 137 | 138 | {isLoading && ( 139 |
140 | 141 |
142 | )} 143 |
144 |
145 | 146 | {researchResult && ( 147 | 148 | 208 | 209 | )} 210 |
211 |
212 |
213 | ); 214 | } 215 | -------------------------------------------------------------------------------- /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 | } 21 | -------------------------------------------------------------------------------- /components/LoadingSteps.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Check } from 'lucide-react'; 3 | 4 | interface Step { 5 | text: string; 6 | completed: boolean; 7 | } 8 | 9 | export function LoadingSteps() { 10 | const [steps, setSteps] = useState([ 11 | { text: "AI is researching the topic...", completed: false }, 12 | { text: "Gathering relevant data...", completed: false }, 13 | { text: "Analyzing information...", completed: false }, 14 | { text: "Summarizing findings...", completed: false }, 15 | { text: "Formatting newsletter...", completed: false }, 16 | { text: "Generating final newsletter...", completed: false }, 17 | { text: "Preparing email delivery...", completed: false }, 18 | { text: "Sending newsletter to your inbox...", completed: false } 19 | ]); 20 | 21 | useEffect(() => { 22 | const interval = setInterval(() => { 23 | setSteps(currentSteps => { 24 | const firstIncompleteIndex = currentSteps.findIndex(step => !step.completed); 25 | if (firstIncompleteIndex === -1) return currentSteps; 26 | 27 | return currentSteps.map((step, index) => 28 | index === firstIncompleteIndex ? { ...step, completed: true } : step 29 | ); 30 | }); 31 | }, 8000); // 8 seconds per step = ~64 seconds total 32 | 33 | return () => clearInterval(interval); 34 | }, []); 35 | 36 | return ( 37 |
38 | {steps.map((step, index) => ( 39 |
40 |
47 | {step.completed && } 48 |
49 | 53 | {step.text} 54 | 55 |
56 | ))} 57 |
58 | ); 59 | } -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const buttonVariants = cva( 8 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 15 | outline: 16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 17 | secondary: 18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 19 | ghost: 'hover:bg-accent hover:text-accent-foreground', 20 | link: 'text-primary underline-offset-4 hover:underline', 21 | }, 22 | size: { 23 | default: 'h-10 px-4 py-2', 24 | sm: 'h-9 rounded-md px-3', 25 | lg: 'h-11 rounded-md px-8', 26 | icon: 'h-10 w-10', 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: 'default', 31 | size: 'default', 32 | }, 33 | } 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : 'button'; 45 | return ( 46 | 51 | ); 52 | } 53 | ); 54 | Button.displayName = 'Button'; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )); 18 | Card.displayName = 'Card'; 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )); 30 | CardHeader.displayName = 'CardHeader'; 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )); 45 | CardTitle.displayName = 'CardTitle'; 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )); 57 | CardDescription.displayName = 'CardDescription'; 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )); 65 | CardContent.displayName = 'CardContent'; 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )); 77 | CardFooter.displayName = 'CardFooter'; 78 | 79 | export { 80 | Card, 81 | CardHeader, 82 | CardFooter, 83 | CardTitle, 84 | CardDescription, 85 | CardContent, 86 | }; 87 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ); 21 | } 22 | ); 23 | Input.displayName = 'Input'; 24 | 25 | export { Input }; 26 | -------------------------------------------------------------------------------- /components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as PopoverPrimitive from '@radix-ui/react-popover'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Popover = PopoverPrimitive.Root; 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger; 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )); 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName; 30 | 31 | export { Popover, PopoverTrigger, PopoverContent }; 32 | -------------------------------------------------------------------------------- /components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as ProgressPrimitive from '@radix-ui/react-progress'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )); 26 | Progress.displayName = ProgressPrimitive.Root.displayName; 27 | 28 | export { Progress }; 29 | -------------------------------------------------------------------------------- /components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; 5 | import { Circle } from 'lucide-react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | const RadioGroup = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => { 13 | return ( 14 | 19 | ); 20 | }); 21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; 22 | 23 | const RadioGroupItem = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => { 27 | return ( 28 | 36 | 37 | 38 | 39 | 40 | ); 41 | }); 42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; 43 | 44 | export { RadioGroup, RadioGroupItem }; 45 | -------------------------------------------------------------------------------- /components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { GripVertical } from 'lucide-react'; 4 | import * as ResizablePrimitive from 'react-resizable-panels'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const ResizablePanelGroup = ({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 19 | ); 20 | 21 | const ResizablePanel = ResizablePrimitive.Panel; 22 | 23 | const ResizableHandle = ({ 24 | withHandle, 25 | className, 26 | ...props 27 | }: React.ComponentProps & { 28 | withHandle?: boolean; 29 | }) => ( 30 | div]:rotate-90', 33 | className 34 | )} 35 | {...props} 36 | > 37 | {withHandle && ( 38 |
39 | 40 |
41 | )} 42 |
43 | ); 44 | 45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; 46 | -------------------------------------------------------------------------------- /components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = 'vertical', ...props }, ref) => ( 30 | 43 | 44 | 45 | )); 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 47 | 48 | export { ScrollArea, ScrollBar }; 49 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SeparatorPrimitive from '@radix-ui/react-separator'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = 'horizontal', decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ); 29 | Separator.displayName = SeparatorPrimitive.Root.displayName; 30 | 31 | export { Separator }; 32 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ); 13 | } 14 | 15 | export { Skeleton }; 16 | -------------------------------------------------------------------------------- /components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SliderPrimitive from '@radix-ui/react-slider'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | )); 26 | Slider.displayName = SliderPrimitive.Root.displayName; 27 | 28 | export { Slider }; 29 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useTheme } from 'next-themes'; 4 | import { Toaster as Sonner } from 'sonner'; 5 | 6 | type ToasterProps = React.ComponentProps; 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = 'system' } = useTheme(); 10 | 11 | return ( 12 | 28 | ); 29 | }; 30 | 31 | export { Toaster }; 32 | -------------------------------------------------------------------------------- /components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as SwitchPrimitives from '@radix-ui/react-switch'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )); 27 | Switch.displayName = SwitchPrimitives.Root.displayName; 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |