├── .cursorrules ├── .env.example ├── .eslintrc.json ├── .github └── workflows │ └── ai-agent.yml ├── .gitignore ├── .husky └── pre-commit ├── .repo_ignore ├── README.md ├── __tests__ └── unit │ └── AboutPage.test.tsx ├── app ├── about │ └── page.tsx ├── globals.css ├── layout.tsx └── page.tsx ├── components.json ├── components ├── 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-toast.ts └── utilities │ ├── providers.tsx │ ├── tailwind-indicator.tsx │ └── theme-switcher.tsx ├── constants └── code-rules.ts ├── hooks ├── use-mobile.tsx └── use-toast.ts ├── jest.config.ts ├── lib ├── agents │ ├── code-review.ts │ ├── commit-step-flow.ts │ ├── github-comments.ts │ ├── llm.ts │ ├── partial-pr-context.ts │ ├── planner.ts │ ├── pr-context.ts │ ├── pr-step-flow.ts │ ├── test-fix-proposals.ts │ ├── test-fix.ts │ ├── test-gating.ts │ ├── test-proposals.ts │ ├── test-runner.ts │ └── text-to-feature.ts ├── hooks │ ├── use-copy-to-clipboard.tsx │ ├── use-mobile.tsx │ └── use-toast.ts └── utils.ts ├── license ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prettier.config.cjs ├── public └── hero.png ├── scripts └── master-flow.ts ├── tailwind.config.ts ├── tsconfig.json └── types ├── file-types.ts ├── index.ts ├── server-action-types.ts └── step-types.ts /.env.example: -------------------------------------------------------------------------------- 1 | # OpenAI API Key 2 | OPENAI_API_KEY= 3 | 4 | # Anthropic API Key 5 | ANTHROPIC_API_KEY= 6 | 7 | # LLM Provider 8 | LLM_PROVIDER=openai -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Contains the ESLint configuration for the app. 4 | 5 | */ 6 | 7 | { 8 | "$schema": "https://json.schemastore.org/eslintrc", 9 | "root": true, 10 | "extends": [ 11 | "next/core-web-vitals", 12 | "prettier", 13 | "plugin:tailwindcss/recommended" 14 | ], 15 | "plugins": ["tailwindcss"], 16 | "rules": { 17 | "@next/next/no-img-element": "off", 18 | "jsx-a11y/alt-text": "off", 19 | "react-hooks/exhaustive-deps": "off", 20 | "tailwindcss/enforces-negative-arbitrary-values": "off", 21 | "tailwindcss/no-contradicting-classname": "off", 22 | "tailwindcss/no-custom-classname": "off", 23 | "tailwindcss/no-unnecessary-arbitrary-value": "off", 24 | "react/no-unescaped-entities": "off" 25 | }, 26 | "settings": { 27 | "tailwindcss": { 28 | "callees": ["cn", "cva"], 29 | "config": "tailwind.config.js" 30 | } 31 | }, 32 | "overrides": [ 33 | { 34 | "files": ["*.ts", "*.tsx"], 35 | "parser": "@typescript-eslint/parser" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/ai-agent.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------- 2 | # AI Agent Flow (Manual Trigger) - Detailed Explanation 3 | # ---------------------------------------------------------- 4 | # This GitHub Actions workflow demonstrates how the AI Agent logic runs in a continuous 5 | # integration (CI) context. Instead of listening to pushes or pull requests automatically, 6 | # this workflow is triggered manually via GitHub's "workflow_dispatch" event. 7 | # 8 | # Once triggered, it performs the following tasks: 9 | # 1) Checks out the repository code. 10 | # 2) Sets up a Node.js environment. 11 | # 3) Installs all dependencies via `npm ci`. 12 | # 4) Runs the `scripts/master-flow.ts` script, which: 13 | # - Uses the Planner Agent to break down a "feature request" into multiple steps. 14 | # - Iteratively calls the Text-to-Feature Agent to implement each step in code. 15 | # - Commits each step to a new feature branch. 16 | # - Posts partial AI reviews for each commit (if configured). 17 | # - Once all steps are done, does a final test run, test generation/fix loop, and 18 | # if everything passes, it opens or updates a Pull Request from the agent branch to `main`. 19 | # 20 | # The 'feature_request' input is the key prompt describing what you want the AI to build. 21 | # For example, if a user enters: "Add a new Contact Form with basic validation." 22 | # the AI attempts to plan and implement that feature step by step in the repository code. 23 | # ---------------------------------------------------------- 24 | 25 | name: AI Agent Flow (Manual) 26 | 27 | on: 28 | workflow_dispatch: 29 | inputs: 30 | feature_request: 31 | description: "Describe the feature or change you want the AI to implement." 32 | required: true 33 | type: string 34 | 35 | jobs: 36 | ai-agent: 37 | runs-on: ubuntu-latest 38 | 39 | # ---------------------------------------------------------- 40 | # We need permissions to write to contents/pull-requests/issues 41 | # so that we can commit changes and open PRs from the AI script. 42 | # ---------------------------------------------------------- 43 | permissions: 44 | contents: write 45 | pull-requests: write 46 | issues: write 47 | 48 | steps: 49 | # -------------------------------------------- 50 | # 1) Check out the repository code 51 | # -------------------------------------------- 52 | - name: Check out the repository 53 | uses: actions/checkout@v3 54 | 55 | - name: Set Git Identity 56 | run: | 57 | git config user.email "ai-agent@example.com" 58 | git config user.name "AI Agent" 59 | 60 | # -------------------------------------------- 61 | # 2) Set up Node.js environment 62 | # -------------------------------------------- 63 | - name: Set up Node 64 | uses: actions/setup-node@v3 65 | with: 66 | node-version: 18 67 | 68 | # -------------------------------------------- 69 | # 3) Install dependencies using npm ci 70 | # This ensures a clean installation. 71 | # -------------------------------------------- 72 | - name: Install dependencies 73 | run: npm ci 74 | 75 | # -------------------------------------------- 76 | # 4) Run the Master Flow script 77 | # This script orchestrates the entire AI pipeline: 78 | # - Planning (breaking the feature request into steps) 79 | # - Implementing each step by updating files locally 80 | # - Committing the changes 81 | # - Running partial reviews on each commit 82 | # - Final full test & review pass, then opening/updating a PR 83 | # -------------------------------------------- 84 | - name: Run AI Master Flow 85 | run: | 86 | npx tsx scripts/master-flow.ts 87 | env: 88 | OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} 89 | ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} 90 | LLM_PROVIDER: ${{ secrets.LLM_PROVIDER }} 91 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 92 | FEATURE_REQUEST: ${{ github.event.inputs.feature_request }} 93 | LOCAL_RUN: "1" 94 | -------------------------------------------------------------------------------- /.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 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # testing 39 | reports/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "$(dirname -- "$0")/_/husky.sh" 4 | 5 | npm run lint:fix && npm run format:write && git add . -------------------------------------------------------------------------------- /.repo_ignore: -------------------------------------------------------------------------------- 1 | # Package manager caches 2 | **/node_modules/ 3 | **/.npm/ 4 | **/__pycache__/ 5 | **/.pytest_cache/ 6 | **/.mypy_cache/ 7 | 8 | # Build caches 9 | **/.gradle/ 10 | **/.nuget/ 11 | **/.cargo/ 12 | **/.stack-work/ 13 | **/.ccache/ 14 | 15 | # IDE and Editor caches 16 | **/.idea/ 17 | **/.vscode/ 18 | **/*.swp 19 | **/*~ 20 | 21 | # Temp files 22 | **/*.tmp 23 | **/*.temp 24 | **/*.bak 25 | 26 | **/*.meta 27 | **/package-lock.json 28 | 29 | # AI Specific 30 | .repo_ignore 31 | .cursorrules 32 | 33 | # Project Specific 34 | **/.husky 35 | **/prompts 36 | **/migrations 37 | **/public 38 | **/.next 39 | **/.swc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Level 4 Agent – Text-to-Feature Agent 2 | 3 | This repository demonstrates a **Level 4 AI Agent** that plans and implements a feature in multiple steps, automatically performing code review, test generation, and iterative fixes _per commit_ (partial review), and then a final full review at the end. 4 | 5 | Find a full tutorial [here](https://www.jointakeoff.com/courses/series-5-levels-of-agents-coding-agents) on Takeoff. 6 | 7 | --- 8 | 9 | ## How It Works 10 | 11 | 1. **Manual Trigger** 12 | 13 | - A developer or user triggers a [GitHub Actions workflow](.github/workflows/ai-agent.yml) manually from the **Actions** tab, passing in a `feature_request` input describing what they want to build or change. 14 | - This `feature_request` is the core instruction that the AI uses to plan and implement changes. 15 | 16 | 2. **AI Planning** 17 | 18 | - The **Planner Agent** (`lib/agents/planner.ts`) processes your `feature_request` and breaks it into an ordered list of steps (e.g., Step1, Step2, etc.). 19 | - Each step contains: 20 | - **Name** (for clarity) 21 | - **Description** (what the step aims to achieve) 22 | - **Plan** (specific coding actions needed) 23 | 24 | 3. **Pull Request Initialization** 25 | 26 | - The workflow script (`scripts/master-flow.ts`) checks out the `main` branch locally, creates a new feature branch (e.g., `agent/add-login`) if one doesn’t exist, then opens a pull request from that branch to `main`. 27 | - By creating a dedicated branch, each code change is pushed there, allowing automatic reviews and comments to be posted to a single PR thread. 28 | 29 | 4. **Step-by-Step Implementation** 30 | 31 | For each planned step: 32 | 33 | 1. **Text-to-Feature** (`lib/agents/text-to-feature.ts`) uses an LLM to propose file changes for that step, referencing the codebase context and any accumulated modifications so far. 34 | 2. The script applies these file changes locally, commits, and pushes. 35 | 3. **Partial AI Review** is performed on just the new commit: 36 | - We run `runFlowOnLatestCommit(...)`, which uses local git commands to find only the new diff and do a partial code review/test generation/fix loop: 37 | 1. **AI code review** for the latest commit’s changes. 38 | 2. **Test gating & test generation** if needed for the new/modified code. 39 | 3. **Local test run** (`npm run test`). If failing, the agent attempts iterative fixes (up to 3 times). 40 | - If everything passes, we proceed to the next step. Otherwise, the workflow fails early. 41 | 42 | 5. **Final Full Review** 43 | 44 | - After all steps pass, we do one last **full** AI review/test cycle on the entire PR: 45 | 1. Build a **full PR context** (diff from `main` to the current branch). 46 | 2. The AI agent conducts a **complete** code review, test gating, test generation, and iterative fix cycle for the entire set of changes. 47 | 3. If tests eventually pass, success! Otherwise, the workflow fails. 48 | 49 | 6. **Ready for Review** 50 | 51 | - If the final full review/test cycle is successful, the script marks the PR as “ready for review.” 52 | - At this point, the new feature is available in the PR for final human checks and merging. 53 | 54 | --- 55 | 56 | ## File & Folder Breakdown 57 | 58 | - **`.github/workflows/ai-agent.yml`** 59 | Defines the GitHub Actions workflow. Manually triggered with a `feature_request` describing the desired feature. 60 | 61 | - Installs dependencies and runs `scripts/master-flow.ts`. 62 | 63 | - **`scripts/master-flow.ts`** 64 | The main script orchestrating the entire “plan → partial commit steps → final review” flow. 65 | 66 | 1. Switch/create a feature branch. 67 | 2. Open or find an existing PR. 68 | 3. Run the **Planner Agent** to get a list of steps. 69 | 4. For each step: 70 | - Ask **Text-to-Feature** to propose changes. 71 | - Commit/push them. 72 | - Call **`runFlowOnLatestCommit`** to do a partial code review/test generation/fix loop on just that commit. 73 | 5. After all steps, calls **`runFlowOnPR`** for the final, all-inclusive review/test pass. 74 | 6. If successful, updates the PR to signal that it’s ready for final human review. 75 | 76 | - **`lib/agents/planner.ts`** 77 | 78 | - **Planner Agent** that reads the `feature_request` and splits it into a multi-step plan. 79 | 80 | - **`lib/agents/text-to-feature.ts`** 81 | 82 | - Takes a single step’s instructions plus any previous modifications and returns new or updated file contents. 83 | - Commits these changes to the feature branch locally. 84 | 85 | - **`lib/agents/commit-step-flow.ts`** 86 | 87 | - Exposes `runFlowOnLatestCommit(...)` for partial code reviews and potential fixes of only the latest commit’s changes. 88 | 89 | - **`lib/agents/pr-step-flow.ts`** 90 | 91 | - Exposes `runFlowOnPR(...)`, handling a **full** pass over all changes in the branch (final review stage). 92 | 93 | - **`lib/agents/pr-context.ts`** 94 | 95 | - Defines how we build or parse a “Pull Request Context” from local git diffs (base..HEAD), storing the changed files and commit messages. 96 | - The final pass uses the entire PR diff, while partial passes only handle the latest commit. 97 | 98 | - **`lib/agents/test-*.ts`** 99 | 100 | - Handles test logic: gating (deciding whether new tests are needed), proposals (generating new tests), and fixes (iterating if they fail). 101 | 102 | - **`lib/agents/code-review.ts`** 103 | 104 | - Orchestrates the AI-based code review. Summaries, file analyses, suggestions are posted on the PR. 105 | 106 | - **`lib/agents/test-runner.ts`** 107 | - Runs `npm run test` locally, capturing Jest output to see if tests pass or fail. 108 | 109 | --- 110 | 111 | ## Usage 112 | 113 | 1. **Set up environment variables** 114 | 115 | - Copy `.env.example` to `.env.local`. Provide your `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`. 116 | - (Optional) Define `LLM_PROVIDER` as `"openai"` or `"anthropic"` to choose your model. 117 | 118 | 2. **Trigger the Workflow** 119 | 120 | - Go to **Actions** → **AI Agent Flow (Manual)** → **Run workflow**. 121 | - Provide a `feature_request` describing the feature/change you want. 122 | 123 | 3. **Observe the Workflow** 124 | 125 | - The workflow logs show how the AI planner breaks your request into steps. 126 | - Each step commits partial changes with an AI-based review and test generation/fix cycle. 127 | - After the final step, a full suite test is run, and if it passes, the PR is declared “ready for review.” 128 | 129 | 4. **Check the Pull Request** 130 | 131 | - In GitHub’s Pull Requests tab, you’ll see a new PR from `agent/` to `main`. 132 | - The AI posts comments about code review and test generation for each commit, plus a final summary. 133 | - Merge when you’re satisfied, or continue normal development. 134 | 135 | --- 136 | 137 | ## FAQ 138 | 139 | **Q**: Why partial reviews each step? 140 | **A**: This allows the AI to focus specifically on newly added changes, making it easier to isolate issues incrementally instead of dealing with a large final diff all at once. 141 | 142 | **Q**: Why a final full PR pass? 143 | **A**: Ensures comprehensive coverage and prevents discrepancies where individually passing steps could conflict collectively. 144 | 145 | **Q**: Can I do more than 3 fix iterations? 146 | **A**: Yes. In the code (e.g., `pr-step-flow.ts` or `commit-step-flow.ts`), change `const maxIterations = 3` to any number you prefer. 147 | 148 | **Q**: What if I need the entire file content for partial commits? 149 | **A**: Currently, partial commits rely on local `git diff`. If full file context is needed, you can customize `compareCommitsForPR(...)` or similar logic to retrieve complete files. 150 | 151 | --- 152 | 153 | **Enjoy building your multi-step AI agent workflow!** 154 | -------------------------------------------------------------------------------- /__tests__/unit/AboutPage.test.tsx: -------------------------------------------------------------------------------- 1 | import AboutPage from "@/app/about/page"; 2 | import { render, screen } from "@testing-library/react"; 3 | import "@testing-library/jest-dom"; 4 | 5 | describe("AboutPage Component", () => { 6 | it("renders correctly and displays the updated About page text", async () => { 7 | const content = await AboutPage(); 8 | render(content); 9 | // Updated text check 10 | const headingElement = screen.getByText("About Page"); 11 | expect(headingElement).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/about/page.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This server page shows a basic home page. 4 | 5 | */ 6 | 7 | "use server" 8 | 9 | export default async function AboutPage() { 10 | return
About Page
11 | } 12 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Global styles for the app. 4 | 5 | */ 6 | 7 | @tailwind base; 8 | @tailwind components; 9 | @tailwind utilities; 10 | 11 | @layer base { 12 | :root { 13 | --background: 0 0% 100%; 14 | --foreground: 0 0% 3.9%; 15 | --card: 0 0% 100%; 16 | --card-foreground: 0 0% 3.9%; 17 | --popover: 0 0% 100%; 18 | --popover-foreground: 0 0% 3.9%; 19 | --primary: 0 0% 9%; 20 | --primary-foreground: 0 0% 98%; 21 | --secondary: 0 0% 96.1%; 22 | --secondary-foreground: 0 0% 9%; 23 | --muted: 0 0% 96.1%; 24 | --muted-foreground: 0 0% 45.1%; 25 | --accent: 0 0% 96.1%; 26 | --accent-foreground: 0 0% 9%; 27 | --destructive: 0 84.2% 60.2%; 28 | --destructive-foreground: 0 0% 98%; 29 | --border: 0 0% 89.8%; 30 | --input: 0 0% 89.8%; 31 | --ring: 0 0% 3.9%; 32 | --radius: 0.5rem; 33 | --chart-1: 12 76% 61%; 34 | --chart-2: 173 58% 39%; 35 | --chart-3: 197 37% 24%; 36 | --chart-4: 43 74% 66%; 37 | --chart-5: 27 87% 67%; 38 | --sidebar-background: 0 0% 98%; 39 | --sidebar-foreground: 240 5.3% 26.1%; 40 | --sidebar-primary: 240 5.9% 10%; 41 | --sidebar-primary-foreground: 0 0% 98%; 42 | --sidebar-accent: 240 4.8% 95.9%; 43 | --sidebar-accent-foreground: 240 5.9% 10%; 44 | --sidebar-border: 220 13% 91%; 45 | --sidebar-ring: 217.2 91.2% 59.8%; 46 | } 47 | 48 | .dark { 49 | --background: 0 0% 3.9%; 50 | --foreground: 0 0% 98%; 51 | --card: 0 0% 3.9%; 52 | --card-foreground: 0 0% 98%; 53 | --popover: 0 0% 3.9%; 54 | --popover-foreground: 0 0% 98%; 55 | --primary: 0 0% 98%; 56 | --primary-foreground: 0 0% 9%; 57 | --secondary: 0 0% 14.9%; 58 | --secondary-foreground: 0 0% 98%; 59 | --muted: 0 0% 14.9%; 60 | --muted-foreground: 0 0% 63.9%; 61 | --accent: 0 0% 14.9%; 62 | --accent-foreground: 0 0% 98%; 63 | --destructive: 0 62.8% 30.6%; 64 | --destructive-foreground: 0 0% 98%; 65 | --border: 0 0% 14.9%; 66 | --input: 0 0% 14.9%; 67 | --ring: 0 0% 83.1%; 68 | --chart-1: 220 70% 50%; 69 | --chart-2: 160 60% 45%; 70 | --chart-3: 30 80% 55%; 71 | --chart-4: 280 65% 60%; 72 | --chart-5: 340 75% 55%; 73 | --sidebar-background: 240 5.9% 10%; 74 | --sidebar-foreground: 240 4.8% 95.9%; 75 | --sidebar-primary: 224.3 76.3% 48%; 76 | --sidebar-primary-foreground: 0 0% 100%; 77 | --sidebar-accent: 240 3.7% 15.9%; 78 | --sidebar-accent-foreground: 240 4.8% 95.9%; 79 | --sidebar-border: 240 3.7% 15.9%; 80 | --sidebar-ring: 217.2 91.2% 59.8%; 81 | } 82 | } 83 | 84 | @layer base { 85 | * { 86 | @apply border-border; 87 | } 88 | body { 89 | @apply bg-background text-foreground; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | The root server layout for the app. 4 | 5 | */ 6 | 7 | import { Toaster } from "@/components/ui/toaster" 8 | import { Providers } from "@/components/utilities/providers" 9 | import { TailwindIndicator } from "@/components/utilities/tailwind-indicator" 10 | import { cn } from "@/lib/utils" 11 | import type { Metadata } from "next" 12 | import { Inter } from "next/font/google" 13 | import "./globals.css" 14 | 15 | const inter = Inter({ subsets: ["latin"] }) 16 | 17 | export const metadata: Metadata = { 18 | title: "Level 2 Coding Agent", 19 | description: "A project by Takeoff." 20 | } 21 | 22 | export default async function RootLayout({ 23 | children 24 | }: { 25 | children: React.ReactNode 26 | }) { 27 | return ( 28 | 29 | 35 | 41 | {children} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This server page shows a basic home page. 4 | 5 | */ 6 | 7 | "use server" 8 | 9 | import Link from "next/link" 10 | 11 | export default async function HomePage() { 12 | return ( 13 |
14 |

Home Page

15 | 16 | About 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /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 | } 17 | } -------------------------------------------------------------------------------- /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 | "[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7", 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 | "focus:ring-ring 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-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent", 13 | secondary: 14 | "bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent", 15 | destructive: 16 | "bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent", 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) =>